summaryrefslogtreecommitdiff
path: root/pith
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith')
-rw-r--r--pith/IO.ReadMe122
-rw-r--r--pith/Makefile.am47
-rw-r--r--pith/Makefile.in815
-rw-r--r--pith/abdlc.c2066
-rw-r--r--pith/abdlc.h41
-rw-r--r--pith/ablookup.c1629
-rw-r--r--pith/ablookup.h106
-rw-r--r--pith/addrbook.c369
-rw-r--r--pith/addrbook.h30
-rw-r--r--pith/addrstring.c452
-rw-r--r--pith/addrstring.h37
-rw-r--r--pith/adjtime.c61
-rw-r--r--pith/adjtime.h35
-rw-r--r--pith/adrbklib.c6028
-rw-r--r--pith/adrbklib.h857
-rw-r--r--pith/atttype.h46
-rw-r--r--pith/bitmap.h39
-rw-r--r--pith/bldaddr.c1263
-rw-r--r--pith/bldaddr.h122
-rw-r--r--pith/busy.h38
-rw-r--r--pith/charconv/Makefile.am19
-rw-r--r--pith/charconv/Makefile.in527
-rw-r--r--pith/charconv/filesys.c721
-rw-r--r--pith/charconv/filesys.h50
-rw-r--r--pith/charconv/makefile.wnt58
-rw-r--r--pith/charconv/utf8.c2512
-rw-r--r--pith/charconv/utf8.h106
-rw-r--r--pith/charset.c929
-rw-r--r--pith/charset.h56
-rw-r--r--pith/color.c234
-rw-r--r--pith/color.h87
-rw-r--r--pith/conf.c8241
-rw-r--r--pith/conf.h895
-rw-r--r--pith/conftype.h709
-rw-r--r--pith/context.c788
-rw-r--r--pith/context.h52
-rw-r--r--pith/copyaddr.c70
-rw-r--r--pith/copyaddr.h25
-rw-r--r--pith/debug.h58
-rw-r--r--pith/detach.c837
-rw-r--r--pith/detach.h69
-rw-r--r--pith/detoken.c567
-rw-r--r--pith/detoken.h29
-rw-r--r--pith/editorial.c198
-rw-r--r--pith/editorial.h28
-rw-r--r--pith/escapes.c69
-rw-r--r--pith/escapes.h24
-rw-r--r--pith/filter.c11305
-rw-r--r--pith/filter.h222
-rw-r--r--pith/filttype.h75
-rw-r--r--pith/flag.c758
-rw-r--r--pith/flag.h155
-rw-r--r--pith/folder.c2759
-rw-r--r--pith/folder.h134
-rw-r--r--pith/foldertype.h163
-rw-r--r--pith/handle.c169
-rw-r--r--pith/handle.h90
-rw-r--r--pith/headers.h63
-rw-r--r--pith/help.c369
-rw-r--r--pith/help.h61
-rw-r--r--pith/help_c_gen.c291
-rw-r--r--pith/help_h_gen.c92
-rw-r--r--pith/helpindx.c132
-rw-r--r--pith/hist.c218
-rw-r--r--pith/hist.h53
-rw-r--r--pith/icache.c452
-rw-r--r--pith/icache.h56
-rw-r--r--pith/imap.c1111
-rw-r--r--pith/imap.h135
-rw-r--r--pith/indxtype.h249
-rw-r--r--pith/init.c546
-rw-r--r--pith/init.h42
-rw-r--r--pith/keyword.c441
-rw-r--r--pith/keyword.h48
-rw-r--r--pith/ldap.c1798
-rw-r--r--pith/ldap.h186
-rw-r--r--pith/list.c188
-rw-r--r--pith/list.h32
-rw-r--r--pith/mailcap.c976
-rw-r--r--pith/mailcap.h40
-rw-r--r--pith/mailcmd.c2741
-rw-r--r--pith/mailcmd.h77
-rw-r--r--pith/mailindx.c6434
-rw-r--r--pith/mailindx.h60
-rw-r--r--pith/maillist.c210
-rw-r--r--pith/maillist.h57
-rw-r--r--pith/mailpart.h51
-rw-r--r--pith/mailview.c3271
-rw-r--r--pith/mailview.h155
-rw-r--r--pith/makefile.wnt88
-rw-r--r--pith/margin.c138
-rw-r--r--pith/margin.h26
-rw-r--r--pith/mimedesc.c879
-rw-r--r--pith/mimedesc.h37
-rw-r--r--pith/mimetype.c374
-rw-r--r--pith/mimetype.h51
-rw-r--r--pith/msgno.c941
-rw-r--r--pith/msgno.h207
-rw-r--r--pith/newmail.c948
-rw-r--r--pith/newmail.h59
-rw-r--r--pith/news.c459
-rw-r--r--pith/news.h37
-rw-r--r--pith/options.h228
-rw-r--r--pith/osdep/Makefile.am22
-rw-r--r--pith/osdep/Makefile.in558
-rw-r--r--pith/osdep/ReadMe13
-rw-r--r--pith/osdep/bldpath.c190
-rw-r--r--pith/osdep/bldpath.h29
-rw-r--r--pith/osdep/canaccess.c160
-rw-r--r--pith/osdep/canaccess.h51
-rw-r--r--pith/osdep/canonicl.c71
-rw-r--r--pith/osdep/canonicl.h26
-rw-r--r--pith/osdep/collate.c170
-rw-r--r--pith/osdep/collate.h32
-rw-r--r--pith/osdep/color.c93
-rw-r--r--pith/osdep/color.h98
-rw-r--r--pith/osdep/coredump.c31
-rw-r--r--pith/osdep/coredump.h26
-rw-r--r--pith/osdep/creatdir.c55
-rw-r--r--pith/osdep/creatdir.h26
-rw-r--r--pith/osdep/debugtime.c133
-rw-r--r--pith/osdep/debugtime.h31
-rw-r--r--pith/osdep/domnames.c145
-rw-r--r--pith/osdep/domnames.h26
-rw-r--r--pith/osdep/err_desc.c44
-rw-r--r--pith/osdep/err_desc.h26
-rw-r--r--pith/osdep/fgetpos.c50
-rw-r--r--pith/osdep/fgetpos.h27
-rw-r--r--pith/osdep/filesize.c123
-rw-r--r--pith/osdep/filesize.h31
-rw-r--r--pith/osdep/fnexpand.c96
-rw-r--r--pith/osdep/fnexpand.h26
-rw-r--r--pith/osdep/forkwait.h39
-rw-r--r--pith/osdep/hostname.c119
-rw-r--r--pith/osdep/hostname.h26
-rw-r--r--pith/osdep/lstcmpnt.c103
-rw-r--r--pith/osdep/lstcmpnt.h28
-rw-r--r--pith/osdep/makefile.wnt65
-rw-r--r--pith/osdep/mimedisp.c473
-rw-r--r--pith/osdep/mimedisp.h28
-rw-r--r--pith/osdep/pipe.c811
-rw-r--r--pith/osdep/pipe.h117
-rw-r--r--pith/osdep/pithosd.h43
-rw-r--r--pith/osdep/pw_stuff.c207
-rw-r--r--pith/osdep/pw_stuff.h29
-rw-r--r--pith/osdep/rename.c69
-rw-r--r--pith/osdep/rename.h26
-rw-r--r--pith/osdep/temp_nam.c345
-rw-r--r--pith/osdep/temp_nam.h28
-rw-r--r--pith/osdep/tempfile.c55
-rw-r--r--pith/osdep/tempfile.h28
-rw-r--r--pith/osdep/writ_dir.c54
-rw-r--r--pith/osdep/writ_dir.h27
-rw-r--r--pith/pattern.c8226
-rw-r--r--pith/pattern.h417
-rw-r--r--pith/pine.hlp35307
-rw-r--r--pith/pineelt.h60
-rw-r--r--pith/pipe.c102
-rw-r--r--pith/pipe.h33
-rw-r--r--pith/readfile.c71
-rw-r--r--pith/readfile.h35
-rw-r--r--pith/remote.c2820
-rw-r--r--pith/remote.h95
-rw-r--r--pith/remtype.h60
-rw-r--r--pith/repltype.h80
-rw-r--r--pith/reply.c3575
-rw-r--r--pith/reply.h106
-rw-r--r--pith/rfc2231.c313
-rw-r--r--pith/rfc2231.h32
-rw-r--r--pith/save.c1777
-rw-r--r--pith/save.h54
-rw-r--r--pith/savetype.h38
-rw-r--r--pith/search.c70
-rw-r--r--pith/search.h25
-rw-r--r--pith/send.c5901
-rw-r--r--pith/send.h271
-rw-r--r--pith/sequence.c460
-rw-r--r--pith/sequence.h33
-rw-r--r--pith/signal.h29
-rw-r--r--pith/smime.c2377
-rw-r--r--pith/smime.h58
-rw-r--r--pith/smkeys.c925
-rw-r--r--pith/smkeys.h61
-rw-r--r--pith/sort.c710
-rw-r--r--pith/sort.h49
-rw-r--r--pith/sorttype.h32
-rw-r--r--pith/state.c318
-rw-r--r--pith/state.h374
-rw-r--r--pith/status.c163
-rw-r--r--pith/status.h54
-rw-r--r--pith/store.c1021
-rw-r--r--pith/store.h80
-rw-r--r--pith/stream.c3392
-rw-r--r--pith/stream.h471
-rw-r--r--pith/string.c2862
-rw-r--r--pith/string.h151
-rw-r--r--pith/strlst.c51
-rw-r--r--pith/strlst.h25
-rw-r--r--pith/takeaddr.c2228
-rw-r--r--pith/takeaddr.h114
-rw-r--r--pith/tempfile.c87
-rw-r--r--pith/tempfile.h26
-rw-r--r--pith/text.c964
-rw-r--r--pith/text.h58
-rw-r--r--pith/thread.c1561
-rw-r--r--pith/thread.h111
-rw-r--r--pith/url.c542
-rw-r--r--pith/url.h31
-rw-r--r--pith/user.h30
-rw-r--r--pith/util.c40
-rw-r--r--pith/util.h68
211 files changed, 157251 insertions, 0 deletions
diff --git a/pith/IO.ReadMe b/pith/IO.ReadMe
new file mode 100644
index 00000000..55f98067
--- /dev/null
+++ b/pith/IO.ReadMe
@@ -0,0 +1,122 @@
+Here's an attempt at a high-level description of the I/O flow and the
+character set conversions for Alpine from the UNIX point of view. Pico and
+Pine were developed separately so there is a big difference in the way
+that Alpine handles stuff and the way the pico composer handles stuff.
+
+INPUT
+
+There's a low-level function called input_ready() that does a select
+or a poll to see if an octet is ready to be read. Alpine's read_char
+and pico's GetKey call this:
+
+ read_char Alpine
+ check_for_timeout
+ input_ready
+or
+ GetKey Pico
+ ReadyForKey
+ input_ready
+
+Once they've decided an octet is ready to be read they use various versions
+of ttgetc (ttgetc, simple_ttgetc, pine_simple_ttgetc) which all boil down
+to a read of one octet from the keyboard. That incoming stream of
+characters is a stream of multi-byte characters. For example, it might
+be ASCII, UTF-8, ISO-8859-1, or EUC-JP. This would usually be configured
+using the LANG or LC_CTYPE environment variables, or possibly using
+the Alpine Keyboard-Character-Set or Display-Character-Set options.
+The pith routine mbtow is used to convert this stream of bytes into
+UCS-4 characters in the routine kbseq.
+
+ read_char
+ kbseq (uses ttgetc to accumulate bytes)
+ mbtow
+or
+ GetKey
+ kbseq ( " )
+ mbtow
+
+So read_char and GetKey both return UCS-4 characters from the keyboard.
+They actually return a superset of a UCS-4 character (typedef UCS) that
+is 32 bits wide. The superset comes about from the handling of
+escape sequences and error conditions. Besides the 21 bits of UCS-4
+characters other values that might be returned are values like KEY_UP,
+KEY_RESIZE, F1-F12, PF1-PF12, NO_OP_COMMAND, NO_OP_IDLE, BADESC, KEY_JUNK,
+and NODATA. The way that read_char and GetKey work are slightly differnt
+from each other. GetKey has some special bits (CTRL, FUNC, MENU) that
+it might OR together with a character and some of the return values
+are possible in only one or the other function.
+
+Pico is internally UCS-4. A CELL contains one character.
+Alpine, on the other hand, uses UTF-8 internally. The input from read_char
+is read by read_command, radio_buttons, and optionally_enter and is
+then converted to UTF-8 before it is used internally in Alpine.
+
+OUTPUT
+
+All Alpine output to the display funnels through Writechar. The input to
+Writechar is a stream of UTF-8 characters which are then converted to
+the display's multi-byte character stream using c-client's utf8_get
+to convert it to UCS-4 and then wtomb to convert to the multi-byte
+representation.
+The similar function in pico is ttputc (also known as t_putchar) which
+takes an incoming stream of UCS-4 characters and converts it to multi-byte
+characters for the display.
+
+
+
+
+CHARACTER SET CONVERSIONS
+
+Here is a simplified version of the complicated character set conversions
+going on.
+
+ ---------- |--read_char
+ | Keyboard |-ttgetc->-mbtow->-kbseq->| (UCS-4)
+ ---------- (to UCS-4) |--GetKey
+
+
+ |--optionally_enter ----------------
+ read_char->|--read_command ------------> | Alpine internal|
+ |--radio_buttons (to UTF-8) | UTF-8 |
+ ----------------
+or
+ |--pico
+ |--LineEdit ----------
+ GetKey->|--mlyesno ------------> | Composer |
+ |--mlreplyd | UCS-4 |
+ |--FileBrowse ----------
+
+
+ ---------------- ----------
+ | Alpine internal| <-------pico_readc--------- | Composer |
+ | UTF-8 | --------pico_writec-------> | UCS-4 |
+ ---------------- ----------
+
+
+ ---------------- ---------
+ | Alpine internal| -Writechar->-utf8_get->-wtomb-> | Display |
+ | UTF-8 | | (LANG) |
+ ---------------- ---------
+
+ ---------- ---------
+ | Composer | ---pputc-->ttputc->-----wtomb-> | Display |
+ | UCS-4 | | (LANG) |
+ ---------- ---------
+
+
+ ---------------- -----------
+ | Alpine internal| <-decode_text--------<-c-client- | MailStore |
+ | UTF-8 | get_body_part_text | |
+ ---------------- -----------
+
+
+ ---------------- ------------
+ | Alpine internal| <-------------------------- | FileSystem |
+ | UTF-8 | --------------------------> | |
+ ---------------- see flags READ_FROM_LOCALE ------------
+ and WRITE_TO_LOCALE
+
+ ---------- ------------
+ | Composer | <--------ffgetline------------- | FileSystem |
+ | UCS-4 | ---------ffputline------------> | |
+ ---------- ------------
diff --git a/pith/Makefile.am b/pith/Makefile.am
new file mode 100644
index 00000000..ce6c78a3
--- /dev/null
+++ b/pith/Makefile.am
@@ -0,0 +1,47 @@
+## Process this file with automake to produce Makefile.in
+## Use aclocal -I m4; automake
+
+# ========================================================================
+# Copyright 2006-2008 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+SUBDIRS = osdep charconv
+
+noinst_LIBRARIES = libpith.a
+
+BUILT_SOURCES = helptext.h helptext.c
+
+noinst_PROGRAMS = help_h_gen help_c_gen
+
+libpith_a_SOURCES = ablookup.c abdlc.c addrbook.c addrstring.c adrbklib.c bldaddr.c charset.c \
+ color.c conf.c context.c copyaddr.c detoken.c detach.c editorial.c escapes.c \
+ filter.c flag.c folder.c handle.c help.c helpindx.c hist.c icache.c imap.c init.c \
+ keyword.c ldap.c list.c mailcap.c mailcmd.c mailindx.c maillist.c mailview.c \
+ margin.c mimedesc.c mimetype.c msgno.c newmail.c news.c pattern.c pipe.c \
+ readfile.c remote.c reply.c rfc2231.c save.c search.c sequence.c send.c sort.c \
+ state.c status.c store.c stream.c string.c strlst.c takeaddr.c tempfile.c text.c \
+ thread.c adjtime.c url.c util.c helptext.c smkeys.c smime.c
+
+help_c_gen$(EXEEXT): $(help_c_gen_OBJECTS) $(help_c_gen_DEPENDENCIES)
+ @rm -f help_c_gen$(EXEEXT)
+ $(LINK) $(help_c_gen_OBJECTS) $(help_c_gen_LDADD)
+help_h_gen$(EXEEXT): $(help_h_gen_OBJECTS) $(help_h_gen_DEPENDENCIES)
+ @rm -f help_h_gen$(EXEEXT)
+ $(LINK) $(help_h_gen_OBJECTS) $(help_h_gen_LDADD)
+
+helptext.c: help_c_gen pine.hlp
+ ./help_c_gen < pine.hlp > $@
+
+helptext.h: help_h_gen pine.hlp
+ ./help_h_gen < pine.hlp > $@
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
+
+CLEANFILES = helptext.c helptext.h help_h_gen help_c_gen
diff --git a/pith/Makefile.in b/pith/Makefile.in
new file mode 100644
index 00000000..322291b0
--- /dev/null
+++ b/pith/Makefile.in
@@ -0,0 +1,815 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# ========================================================================
+# Copyright 2006-2008 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = help_h_gen$(EXEEXT) help_c_gen$(EXEEXT)
+subdir = pith
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/VERSION \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+libpith_a_AR = $(AR) $(ARFLAGS)
+libpith_a_LIBADD =
+am_libpith_a_OBJECTS = ablookup.$(OBJEXT) abdlc.$(OBJEXT) \
+ addrbook.$(OBJEXT) addrstring.$(OBJEXT) adrbklib.$(OBJEXT) \
+ bldaddr.$(OBJEXT) charset.$(OBJEXT) color.$(OBJEXT) \
+ conf.$(OBJEXT) context.$(OBJEXT) copyaddr.$(OBJEXT) \
+ detoken.$(OBJEXT) detach.$(OBJEXT) editorial.$(OBJEXT) \
+ escapes.$(OBJEXT) filter.$(OBJEXT) flag.$(OBJEXT) \
+ folder.$(OBJEXT) handle.$(OBJEXT) help.$(OBJEXT) \
+ helpindx.$(OBJEXT) hist.$(OBJEXT) icache.$(OBJEXT) \
+ imap.$(OBJEXT) init.$(OBJEXT) keyword.$(OBJEXT) ldap.$(OBJEXT) \
+ list.$(OBJEXT) mailcap.$(OBJEXT) mailcmd.$(OBJEXT) \
+ mailindx.$(OBJEXT) maillist.$(OBJEXT) mailview.$(OBJEXT) \
+ margin.$(OBJEXT) mimedesc.$(OBJEXT) mimetype.$(OBJEXT) \
+ msgno.$(OBJEXT) newmail.$(OBJEXT) news.$(OBJEXT) \
+ pattern.$(OBJEXT) pipe.$(OBJEXT) readfile.$(OBJEXT) \
+ remote.$(OBJEXT) reply.$(OBJEXT) rfc2231.$(OBJEXT) \
+ save.$(OBJEXT) search.$(OBJEXT) sequence.$(OBJEXT) \
+ send.$(OBJEXT) sort.$(OBJEXT) state.$(OBJEXT) status.$(OBJEXT) \
+ store.$(OBJEXT) stream.$(OBJEXT) string.$(OBJEXT) \
+ strlst.$(OBJEXT) takeaddr.$(OBJEXT) tempfile.$(OBJEXT) \
+ text.$(OBJEXT) thread.$(OBJEXT) adjtime.$(OBJEXT) \
+ url.$(OBJEXT) util.$(OBJEXT) helptext.$(OBJEXT) \
+ smkeys.$(OBJEXT) smime.$(OBJEXT)
+libpith_a_OBJECTS = $(am_libpith_a_OBJECTS)
+PROGRAMS = $(noinst_PROGRAMS)
+help_c_gen_SOURCES = help_c_gen.c
+help_c_gen_OBJECTS = help_c_gen.$(OBJEXT)
+help_c_gen_LDADD = $(LDADD)
+help_h_gen_SOURCES = help_h_gen.c
+help_h_gen_OBJECTS = help_h_gen.$(OBJEXT)
+help_h_gen_LDADD = $(LDADD)
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(libpith_a_SOURCES) help_c_gen.c help_h_gen.c
+DIST_SOURCES = $(libpith_a_SOURCES) help_c_gen.c help_h_gen.c
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+ $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+ distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@
+AM_LDFLAGS = @AM_LDFLAGS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CP = @CP@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+C_CLIENT_CFLAGS = @C_CLIENT_CFLAGS@
+C_CLIENT_GCCOPTLEVEL = @C_CLIENT_GCCOPTLEVEL@
+C_CLIENT_LDFLAGS = @C_CLIENT_LDFLAGS@
+C_CLIENT_SPECIALS = @C_CLIENT_SPECIALS@
+C_CLIENT_TARGET = @C_CLIENT_TARGET@
+C_CLIENT_WITH_IPV6 = @C_CLIENT_WITH_IPV6@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISPELLPROG = @ISPELLPROG@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN = @LN@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKE = @MAKE@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NPA_PROG = @NPA_PROG@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PWPROG = @PWPROG@
+RANLIB = @RANLIB@
+REGEX_BUILD = @REGEX_BUILD@
+RM = @RM@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPELLPROG = @SPELLPROG@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEB_BINDIR = @WEB_BINDIR@
+WEB_BUILD = @WEB_BUILD@
+WEB_PUBCOOKIE_BUILD = @WEB_PUBCOOKIE_BUILD@
+WEB_PUBCOOKIE_LIB = @WEB_PUBCOOKIE_LIB@
+WEB_PUBCOOKIE_LINK = @WEB_PUBCOOKIE_LINK@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+acx_pthread_config = @acx_pthread_config@
+alpine_interactive_spellcheck = @alpine_interactive_spellcheck@
+alpine_simple_spellcheck = @alpine_simple_spellcheck@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = osdep charconv
+noinst_LIBRARIES = libpith.a
+BUILT_SOURCES = helptext.h helptext.c
+libpith_a_SOURCES = ablookup.c abdlc.c addrbook.c addrstring.c adrbklib.c bldaddr.c charset.c \
+ color.c conf.c context.c copyaddr.c detoken.c detach.c editorial.c escapes.c \
+ filter.c flag.c folder.c handle.c help.c helpindx.c hist.c icache.c imap.c init.c \
+ keyword.c ldap.c list.c mailcap.c mailcmd.c mailindx.c maillist.c mailview.c \
+ margin.c mimedesc.c mimetype.c msgno.c newmail.c news.c pattern.c pipe.c \
+ readfile.c remote.c reply.c rfc2231.c save.c search.c sequence.c send.c sort.c \
+ state.c status.c store.c stream.c string.c strlst.c takeaddr.c tempfile.c text.c \
+ thread.c adjtime.c url.c util.c helptext.c smkeys.c smime.c
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
+CLEANFILES = helptext.c helptext.h help_h_gen help_c_gen
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign pith/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign pith/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libpith.a: $(libpith_a_OBJECTS) $(libpith_a_DEPENDENCIES)
+ -rm -f libpith.a
+ $(libpith_a_AR) libpith.a $(libpith_a_OBJECTS) $(libpith_a_LIBADD)
+ $(RANLIB) libpith.a
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abdlc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ablookup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/addrbook.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/addrstring.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adjtime.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adrbklib.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bldaddr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charset.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/context.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/copyaddr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/detach.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/detoken.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editorial.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/escapes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flag.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handle.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help_c_gen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help_h_gen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/helpindx.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/helptext.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hist.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/icache.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyword.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailcap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailcmd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailindx.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/maillist.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailview.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/margin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mimedesc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mimetype.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/msgno.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newmail.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/news.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pattern.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pipe.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reply.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rfc2231.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/save.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/send.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sequence.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smime.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smkeys.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sort.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/state.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/status.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/store.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stream.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/string.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strlst.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/takeaddr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tempfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/text.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/thread.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-recursive
+all-am: Makefile $(LIBRARIES) $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all check \
+ ctags-recursive install install-am install-strip \
+ tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am check check-am clean clean-generic clean-libtool \
+ clean-noinstLIBRARIES clean-noinstPROGRAMS ctags \
+ ctags-recursive distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-recursive uninstall uninstall-am
+
+
+help_c_gen$(EXEEXT): $(help_c_gen_OBJECTS) $(help_c_gen_DEPENDENCIES)
+ @rm -f help_c_gen$(EXEEXT)
+ $(LINK) $(help_c_gen_OBJECTS) $(help_c_gen_LDADD)
+help_h_gen$(EXEEXT): $(help_h_gen_OBJECTS) $(help_h_gen_DEPENDENCIES)
+ @rm -f help_h_gen$(EXEEXT)
+ $(LINK) $(help_h_gen_OBJECTS) $(help_h_gen_LDADD)
+
+helptext.c: help_c_gen pine.hlp
+ ./help_c_gen < pine.hlp > $@
+
+helptext.h: help_h_gen pine.hlp
+ ./help_h_gen < pine.hlp > $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pith/abdlc.c b/pith/abdlc.c
new file mode 100644
index 00000000..d332a1b7
--- /dev/null
+++ b/pith/abdlc.c
@@ -0,0 +1,2066 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: abdlc.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h" /* for os-dep and pith defs/includes */
+#include "../pith/abdlc.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/ldap.h"
+#include "../pith/tempfile.h"
+
+
+/*
+ * Internal prototypes
+ */
+void initialize_dlc_cache(void);
+int dlc_siblings(DL_CACHE_S *, DL_CACHE_S *);
+DL_CACHE_S *dlc_mgr(long, DlMgrOps, DL_CACHE_S *);
+void free_cache_array(DL_CACHE_S **, int);
+DL_CACHE_S *dlc_prev(DL_CACHE_S *, DL_CACHE_S *);
+DL_CACHE_S *dlc_next(DL_CACHE_S *, DL_CACHE_S *);
+DL_CACHE_S *get_global_top_dlc(DL_CACHE_S *);
+DL_CACHE_S *get_global_bottom_dlc(DL_CACHE_S *);
+DL_CACHE_S *get_top_dl_of_adrbk(int, DL_CACHE_S *);
+DL_CACHE_S *get_bottom_dl_of_adrbk(int, DL_CACHE_S *);
+
+
+#define NO_PERMISSION _("[ Permission Denied ]")
+/* TRANSLATORS: This is a heading referring to something that is readable
+ but not writeable. */
+#define READONLY _(" (ReadOnly)")
+/* TRANSLATORS: Not readable */
+#define NOACCESS _(" (Un-readable)")
+/* TRANSLATORS: Directories, as in LDAP Directories. A heading. */
+#define OLDSTYLE_DIR_TITLE _("Directories")
+
+
+/* data for the display list cache */
+static DL_CACHE_S *cache_array = (DL_CACHE_S *)NULL;
+static long valid_low,
+ valid_high;
+static int index_of_low,
+ size_of_cache,
+ n_cached;
+
+
+void
+initialize_dlc_cache(void)
+{
+ dprint((11, "- initialize_dlc_cache -\n"));
+
+ (void)dlc_mgr(NO_LINE, Initialize, (DL_CACHE_S *)NULL);
+}
+
+
+void
+done_with_dlc_cache(void)
+{
+ dprint((11, "- done_with_dlc_cache -\n"));
+
+ (void)dlc_mgr(NO_LINE, DoneWithCache, (DL_CACHE_S *)NULL);
+}
+
+
+/*
+ * Returns 1 if the dlc's are related to each other, 0 otherwise.
+ *
+ * The idea is that if you are going to flush one of these dlcs from the
+ * cache you should also flush its partners. For example, if you flush one
+ * Listent from a list you should flush the entire entry including all the
+ * Listents and the ListHead. If you flush a DlcTitle, you should also
+ * flush the SubTitle and the TitleBlankTop.
+ */
+int
+dlc_siblings(DL_CACHE_S *dlc1, DL_CACHE_S *dlc2)
+{
+ if(!dlc1 || !dlc2 || dlc1->adrbk_num != dlc2->adrbk_num)
+ return 0;
+
+ switch(dlc1->type){
+
+ case DlcSimple:
+ case DlcListHead:
+ case DlcListBlankTop:
+ case DlcListBlankBottom:
+ case DlcListEnt:
+ case DlcListClickHere:
+ case DlcListEmpty:
+ switch(dlc2->type){
+ case DlcSimple:
+ case DlcListHead:
+ case DlcListBlankTop:
+ case DlcListBlankBottom:
+ case DlcListEnt:
+ case DlcListClickHere:
+ case DlcListEmpty:
+ return(dlc1->dlcelnum == dlc2->dlcelnum);
+
+ default:
+ return 0;
+ }
+
+ break;
+
+ case DlcDirDelim1:
+ case DlcDirDelim2:
+ switch(dlc2->type){
+ case DlcDirDelim1:
+ case DlcDirDelim2:
+ return 1;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case DlcGlobDelim1:
+ case DlcGlobDelim2:
+ switch(dlc2->type){
+ case DlcGlobDelim1:
+ case DlcGlobDelim2:
+ return 1;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case DlcTitle:
+ case DlcTitleNoPerm:
+ case DlcSubTitle:
+ case DlcTitleBlankTop:
+ switch(dlc2->type){
+ case DlcTitle:
+ case DlcTitleNoPerm:
+ case DlcSubTitle:
+ case DlcTitleBlankTop:
+ return 1;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case DlcDirAccess:
+ case DlcDirSubTitle:
+ case DlcDirBlankTop:
+ switch(dlc2->type){
+ case DlcDirAccess:
+ case DlcDirSubTitle:
+ case DlcDirBlankTop:
+ return 1;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case DlcTitleDashTopCmb:
+ case DlcTitleCmb:
+ case DlcTitleDashBottomCmb:
+ case DlcTitleBlankBottomCmb:
+ case DlcClickHereCmb:
+ case DlcTitleBlankTopCmb:
+ switch(dlc2->type){
+ case DlcTitleDashTopCmb:
+ case DlcTitleCmb:
+ case DlcTitleDashBottomCmb:
+ case DlcTitleBlankBottomCmb:
+ case DlcClickHereCmb:
+ case DlcTitleBlankTopCmb:
+ return 1;
+
+ default:
+ return 0;
+ }
+ break;
+
+ default:
+ return 0;
+ }
+ /*NOTREACHED*/
+}
+
+
+/*
+ * Manage the display list cache.
+ *
+ * The cache is a circular array of DL_CACHE_S elements. It always
+ * contains a contiguous set of display lines.
+ * The lowest numbered line in the cache is
+ * valid_low, and the highest is valid_high. Everything in between is
+ * also valid. Index_of_low is where to look
+ * for the valid_low element in the circular array.
+ *
+ * We make calls to dlc_prev and dlc_next to get new entries for the cache.
+ * We need a starting value before we can do that.
+ *
+ * This returns a pointer to a dlc for the desired row. If you want the
+ * actual display list line you call dlist(row) instead of dlc_mgr.
+ */
+DL_CACHE_S *
+dlc_mgr(long int row, DlMgrOps op, DL_CACHE_S *dlc_start)
+{
+ int new_index, known_index, next_index;
+ DL_CACHE_S *dlc = (DL_CACHE_S *)NULL;
+ long next_row;
+ long prev_row;
+
+
+ if(op == Lookup){
+
+ if(row >= valid_low && row <= valid_high){ /* already cached */
+
+ new_index = ((row - valid_low) + index_of_low) % size_of_cache;
+ dlc = &cache_array[new_index];
+
+ }
+ else if(row > valid_high){ /* row is past where we've looked */
+
+ known_index =
+ ((valid_high - valid_low) + index_of_low) % size_of_cache;
+ next_row = valid_high + 1L;
+
+ /* we'll usually be looking for row = valid_high + 1 */
+ while(next_row <= row){
+
+ new_index = (known_index + 1) % size_of_cache;
+
+ dlc =
+ dlc_next(&cache_array[known_index], &cache_array[new_index]);
+
+ /*
+ * This means somebody changed the file out from underneath
+ * us. This would happen if dlc_next needed to ask for an
+ * abe to figure out what the type of the next row is, but
+ * adrbk_get_ae returned a NULL. I don't think that can
+ * happen, but if it does...
+ */
+ if(dlc->type == DlcNotSet){
+ dprint((1, "dlc_next returned DlcNotSet\n"));
+ goto panic_abook_corrupt;
+ }
+
+ if(n_cached == size_of_cache){ /* replaced low cache entry */
+ valid_low++;
+ index_of_low = (index_of_low + 1) % size_of_cache;
+ }
+ else
+ n_cached++;
+
+ valid_high++;
+
+ next_row++;
+ known_index = new_index; /* for next time through loop */
+ }
+ }
+ else if(row < valid_low){ /* row is back up the screen */
+
+ known_index = index_of_low;
+ prev_row = valid_low - 1L;
+
+ while(prev_row >= row){
+
+ new_index = (known_index - 1 + size_of_cache) % size_of_cache;
+ dlc =
+ dlc_prev(&cache_array[known_index], &cache_array[new_index]);
+
+ if(dlc->type == DlcNotSet){
+ dprint((1, "dlc_prev returned DlcNotSet (1)\n"));
+ goto panic_abook_corrupt;
+ }
+
+ if(n_cached == size_of_cache) /* replaced high cache entry */
+ valid_high--;
+ else
+ n_cached++;
+
+ valid_low--;
+ index_of_low =
+ (index_of_low - 1 + size_of_cache) % size_of_cache;
+
+ prev_row--;
+ known_index = new_index;
+ }
+ }
+ }
+ else if(op == Initialize){
+
+ n_cached = 0;
+
+ if(!cache_array || size_of_cache != 3 * MAX(as.l_p_page,1)){
+ if(cache_array)
+ free_cache_array(&cache_array, size_of_cache);
+
+ size_of_cache = 3 * MAX(as.l_p_page,1);
+ cache_array =
+ (DL_CACHE_S *)fs_get(size_of_cache * sizeof(DL_CACHE_S));
+ memset((void *)cache_array, 0, size_of_cache * sizeof(DL_CACHE_S));
+ }
+
+ /* this will return NULL below and the caller should ignore that */
+ }
+ /*
+ * Flush all rows for a particular addrbook entry from the cache, but
+ * keep the cache alive and anchored in the same place. The particular
+ * entry is the one that dlc_start is one of the rows of.
+ */
+ else if(op == FlushDlcFromCache){
+ long low_entry;
+
+ next_row = dlc_start->global_row - 1;
+ for(; next_row >= valid_low; next_row--){
+ next_index = ((next_row - valid_low) + index_of_low) %
+ size_of_cache;
+ if(!dlc_siblings(dlc_start, &cache_array[next_index]))
+ break;
+ }
+
+ low_entry = next_row + 1L;
+
+ /*
+ * If low_entry now points one past a ListBlankBottom, delete that,
+ * too, since it may not make sense anymore.
+ */
+ if(low_entry > valid_low){
+ next_index = ((low_entry -1L - valid_low) + index_of_low) %
+ size_of_cache;
+ if(cache_array[next_index].type == DlcListBlankBottom)
+ low_entry--;
+ }
+
+ if(low_entry > valid_low){ /* invalidate everything >= this */
+ n_cached -= (valid_high - (low_entry - 1L));
+ valid_high = low_entry - 1L;
+ }
+ else{
+ /*
+ * This is the tough case. That entry was the first thing cached,
+ * so we need to invalidate the whole cache. However, we also
+ * need to keep at least one thing cached for an anchor, so
+ * we need to get the dlc before this one and it should be a
+ * dlc not related to this same addrbook entry.
+ */
+ known_index = index_of_low;
+ prev_row = valid_low - 1L;
+
+ for(;;){
+
+ new_index = (known_index - 1 + size_of_cache) % size_of_cache;
+ dlc =
+ dlc_prev(&cache_array[known_index], &cache_array[new_index]);
+
+ if(dlc->type == DlcNotSet){
+ dprint((1, "dlc_prev returned DlcNotSet (2)\n"));
+ goto panic_abook_corrupt;
+ }
+
+ valid_low--;
+ index_of_low =
+ (index_of_low - 1 + size_of_cache) % size_of_cache;
+
+ if(!dlc_siblings(dlc_start, dlc))
+ break;
+
+ known_index = new_index;
+ }
+
+ n_cached = 1;
+ valid_high = valid_low;
+ }
+ }
+ /*
+ * We have to anchor ourselves at a first element.
+ * Here's how we start at the top.
+ */
+ else if(op == FirstEntry){
+ initialize_dlc_cache();
+ n_cached++;
+ dlc = &cache_array[0];
+ dlc = get_global_top_dlc(dlc);
+ dlc->global_row = row;
+ index_of_low = 0;
+ valid_low = row;
+ valid_high = row;
+ }
+ /* And here's how we start from the bottom. */
+ else if(op == LastEntry){
+ initialize_dlc_cache();
+ n_cached++;
+ dlc = &cache_array[0];
+ dlc = get_global_bottom_dlc(dlc);
+ dlc->global_row = row;
+ index_of_low = 0;
+ valid_low = row;
+ valid_high = row;
+ }
+ /*
+ * And here's how we start from an arbitrary position in the middle.
+ * We root the cache at display line row, so it helps if row is close
+ * to where we're going to be starting so that things are easy to find.
+ * The dl that goes with line row is dl_start from addrbook number
+ * adrbk_num_start.
+ */
+ else if(op == ArbitraryStartingPoint){
+ AddrScrn_Disp dl;
+
+ initialize_dlc_cache();
+ n_cached++;
+ dlc = &cache_array[0];
+ /*
+ * Save this in case fill_in_dl_field needs to free the text
+ * it points to.
+ */
+ dl = dlc->dl;
+ *dlc = *dlc_start;
+ dlc->dl = dl;
+ dlc->global_row = row;
+
+ index_of_low = 0;
+ valid_low = row;
+ valid_high = row;
+ }
+ else if(op == DoneWithCache){
+
+ n_cached = 0;
+ if(cache_array)
+ free_cache_array(&cache_array, size_of_cache);
+ }
+
+ return(dlc);
+
+panic_abook_corrupt:
+ q_status_message(SM_ORDER | SM_DING, 5, 10,
+ _("Addrbook changed unexpectedly, re-syncing..."));
+ dprint((1,
+ _("addrbook changed while we had it open?, re-sync\n")));
+ dprint((2,
+ "valid_low=%ld valid_high=%ld index_of_low=%d size_of_cache=%d\n",
+ valid_low, valid_high, index_of_low, size_of_cache));
+ dprint((2,
+ "n_cached=%d new_index=%d known_index=%d next_index=%d\n",
+ n_cached, new_index, known_index, next_index));
+ dprint((2,
+ "next_row=%ld prev_row=%ld row=%ld\n", next_row, prev_row, row));
+ /* jump back to a safe starting point */
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+}
+
+
+void
+free_cache_array(DL_CACHE_S **c_array, int size)
+{
+ DL_CACHE_S *dlc;
+ int i;
+
+ for(i = 0; i < size; i++){
+ dlc = &(*c_array)[i];
+ /* free any allocated space */
+ switch(dlc->dl.type){
+ case Text:
+ case Title:
+ case TitleCmb:
+ case AskServer:
+ if(dlc->dl.usst)
+ fs_give((void **)&dlc->dl.usst);
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ fs_give((void **)c_array);
+}
+
+
+/*
+ * Get the dlc element that comes before "old". The function that calls this
+ * function is the one that keeps a cache and checks in the cache before
+ * calling here. New is a passed in pointer to a buffer where we fill in
+ * the answer.
+ */
+DL_CACHE_S *
+dlc_prev(DL_CACHE_S *old, DL_CACHE_S *new)
+{
+ PerAddrBook *pab;
+ AdrBk_Entry *abe;
+ adrbk_cntr_t list_count;
+
+ new->adrbk_num = -2;
+ new->dlcelnum = NO_NEXT;
+ new->dlcoffset = NO_NEXT;
+ new->type = DlcNotSet;
+ pab = &as.adrbks[old->adrbk_num];
+
+ switch(old->type){
+ case DlcTitle:
+ case DlcTitleNoPerm:
+ if(old->adrbk_num == 0 && as.config && as.how_many_personals == 0)
+ new->type = DlcGlobDelim2;
+ else if(old->adrbk_num == 0)
+ new->type = DlcOneBeforeBeginning;
+ else if(old->adrbk_num == as.how_many_personals)
+ new->type = DlcGlobDelim2;
+ else
+ new->type = DlcTitleBlankTop;
+
+ break;
+
+ case DlcSubTitle:
+ if(pab->access == NoAccess)
+ new->type = DlcTitleNoPerm;
+ else
+ new->type = DlcTitle;
+
+ break;
+
+ case DlcTitleBlankTop:
+ new->adrbk_num = old->adrbk_num - 1;
+ new->type = DlcSubTitle;
+ break;
+
+ case DlcEmpty:
+ case DlcZoomEmpty:
+ case DlcNoPermission:
+ case DlcNoAbooks:
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(as.n_addrbk == 1 && as.n_serv == 0)
+ new->type = DlcOneBeforeBeginning;
+ else
+ new->type = DlcTitleBlankBottomCmb;
+ }
+ else
+ new->type = DlcOneBeforeBeginning;
+
+ break;
+
+ case DlcSimple:
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ i = old->dlcelnum;
+ i--;
+ el = old->dlcelnum - 1;
+ while(i >= 0L){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el--;
+ i--;
+ }
+
+ if(i >= 0){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(abe && abe->tag == Single)
+ new->type = DlcSimple;
+ else if(abe && abe->tag == List)
+ new->type = DlcListBlankBottom;
+ }
+ else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(as.n_addrbk == 1 && as.n_serv == 0)
+ new->type = DlcOneBeforeBeginning;
+ else
+ new->type = DlcTitleBlankBottomCmb;
+ }
+ else
+ new->type = DlcOneBeforeBeginning;
+ }
+
+ break;
+
+ case DlcListHead:
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ i = old->dlcelnum;
+ i--;
+ el = old->dlcelnum - 1;
+ while(i >= 0L){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el--;
+ i--;
+ }
+
+ if(i >= 0){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(abe && abe->tag == Single){
+ new->type = DlcListBlankTop;
+ new->dlcelnum = old->dlcelnum;
+ }
+ else if(abe && abe->tag == List)
+ new->type = DlcListBlankBottom;
+ }
+ else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(as.n_addrbk == 1 && as.n_serv == 0)
+ new->type = DlcOneBeforeBeginning;
+ else
+ new->type = DlcTitleBlankBottomCmb;
+ }
+ else
+ new->type = DlcOneBeforeBeginning;
+ }
+
+ break;
+
+ case DlcListEnt:
+ if(old->dlcoffset > 0){
+ new->type = DlcListEnt;
+ new->dlcelnum = old->dlcelnum;
+ new->dlcoffset = old->dlcoffset - 1;
+ }
+ else{
+ new->type = DlcListHead;
+ new->dlcelnum = old->dlcelnum;
+ }
+
+ break;
+
+ case DlcListClickHere:
+ case DlcListEmpty:
+ new->type = DlcListHead;
+ new->dlcelnum = old->dlcelnum;
+ break;
+
+ case DlcListBlankTop: /* can only occur between a Simple and a List */
+ new->type = DlcSimple;
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ i = old->dlcelnum;
+ i--;
+ el = old->dlcelnum - 1;
+ while(i >= 0L){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el--;
+ i--;
+ }
+
+ if(i >= 0)
+ new->dlcelnum = el;
+ else{
+ dprint((1, "Bug in addrbook: case ListBlankTop with no selected entry\n"));
+ goto oops;
+ }
+ }
+
+ break;
+
+ case DlcListBlankBottom:
+ new->dlcelnum = old->dlcelnum;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(F_ON(F_EXPANDED_DISTLISTS,ps_global)
+ || exp_is_expanded(pab->address_book->exp, (a_c_arg_t)new->dlcelnum)){
+ list_count = listmem_count_from_abe(abe);
+ if(list_count == 0)
+ new->type = DlcListEmpty;
+ else{
+ new->type = DlcListEnt;
+ new->dlcoffset = list_count - 1;
+ }
+ }
+ else
+ new->type = DlcListClickHere;
+
+ break;
+
+ case DlcGlobDelim1:
+ if(as.how_many_personals == 0)
+ new->type = DlcPersAdd;
+ else{
+ new->adrbk_num = as.how_many_personals - 1;
+ new->type = DlcSubTitle;
+ }
+ break;
+
+ case DlcGlobDelim2:
+ new->type = DlcGlobDelim1;
+ break;
+
+ case DlcPersAdd:
+ new->type = DlcOneBeforeBeginning;
+ break;
+
+ case DlcGlobAdd:
+ new->type = DlcGlobDelim2;
+ break;
+
+ case DlcDirAccess:
+ if(old->adrbk_num == 0 && as.n_addrbk == 0)
+ new->type = DlcOneBeforeBeginning;
+ else if(old->adrbk_num == 0)
+ new->type = DlcDirDelim2;
+ else
+ new->type = DlcDirBlankTop;
+
+ break;
+
+ case DlcDirSubTitle:
+ new->type = DlcDirAccess;
+ break;
+
+ case DlcDirBlankTop:
+ new->adrbk_num = old->adrbk_num -1;
+ new->type = DlcDirSubTitle;
+ break;
+
+ case DlcDirDelim1:
+ new->adrbk_num = as.n_addrbk - 1;
+ if(as.n_addrbk == 0)
+ new->type = DlcNoAbooks;
+ else
+ new = get_bottom_dl_of_adrbk(new->adrbk_num, new);
+
+ break;
+
+ case DlcDirDelim1a:
+ new->type = DlcDirDelim1;
+ break;
+
+ case DlcDirDelim1b:
+ new->type = DlcDirDelim1a;
+ break;
+
+ case DlcDirDelim1c:
+ new->type = DlcDirDelim1b;
+ break;
+
+ case DlcDirDelim2:
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
+ new->type = DlcDirDelim1c;
+ else
+ new->type = DlcDirDelim1;
+
+ break;
+
+ case DlcTitleDashTopCmb:
+ if(old->adrbk_num == 0)
+ new->type = DlcOneBeforeBeginning;
+ else
+ new->type = DlcTitleBlankTopCmb;
+
+ break;
+
+ case DlcTitleCmb:
+ new->type = DlcTitleDashTopCmb;
+ break;
+
+ case DlcTitleDashBottomCmb:
+ new->type = DlcTitleCmb;
+ break;
+
+ case DlcTitleBlankBottomCmb:
+ new->type = DlcTitleDashBottomCmb;
+ break;
+
+ case DlcClickHereCmb:
+ if(as.n_addrbk == 1 && as.n_serv == 0)
+ new->type = DlcOneBeforeBeginning;
+ else
+ new->type = DlcTitleBlankBottomCmb;
+
+ break;
+
+ case DlcTitleBlankTopCmb:
+ new->adrbk_num = old->adrbk_num - 1;
+ new = get_bottom_dl_of_adrbk(new->adrbk_num, new);
+ break;
+
+ case DlcBeginning:
+ case DlcTwoBeforeBeginning:
+ new->type = DlcBeginning;
+ break;
+
+ case DlcOneBeforeBeginning:
+ new->type = DlcTwoBeforeBeginning;
+ break;
+
+oops:
+ default:
+ q_status_message(SM_ORDER | SM_DING, 5, 10,
+ _("Bug in addrbook, not supposed to happen, re-syncing..."));
+ dprint((1, "Bug in addrbook, impossible case (%d) in dlc_prev, re-sync\n",
+ old->type));
+ /* jump back to a safe starting point */
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+
+ new->global_row = old->global_row - 1L;
+ if(new->adrbk_num == -2)
+ new->adrbk_num = old->adrbk_num;
+
+ return(new);
+}
+
+
+/*
+ * Get the dlc element that comes after "old". The function that calls this
+ * function is the one that keeps a cache and checks in the cache before
+ * calling here.
+ */
+DL_CACHE_S *
+dlc_next(DL_CACHE_S *old, DL_CACHE_S *new)
+{
+ PerAddrBook *pab;
+ AdrBk_Entry *abe;
+ adrbk_cntr_t ab_count;
+ adrbk_cntr_t list_count;
+
+ new->adrbk_num = -2;
+ new->dlcelnum = NO_NEXT;
+ new->dlcoffset = NO_NEXT;
+ new->type = DlcNotSet;
+ pab = &as.adrbks[old->adrbk_num];
+
+ switch(old->type){
+ case DlcTitle:
+ case DlcTitleNoPerm:
+ new->type = DlcSubTitle;
+ break;
+
+ case DlcSubTitle:
+ if((old->adrbk_num == as.how_many_personals - 1) &&
+ (as.config || as.n_addrbk > as.how_many_personals))
+ new->type = DlcGlobDelim1;
+ else if(as.n_serv && !as.config &&
+ (old->adrbk_num == as.n_addrbk - 1))
+ new->type = DlcDirDelim1;
+ else if(old->adrbk_num == as.n_addrbk - 1)
+ new->type = DlcEnd;
+ else{
+ new->adrbk_num = old->adrbk_num + 1;
+ new->type = DlcTitleBlankTop;
+ }
+
+ break;
+
+ case DlcTitleBlankTop:
+ if(pab->access == NoAccess)
+ new->type = DlcTitleNoPerm;
+ else
+ new->type = DlcTitle;
+
+ break;
+
+ case DlcEmpty:
+ case DlcZoomEmpty:
+ case DlcNoPermission:
+ case DlcClickHereCmb:
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(old->adrbk_num < as.n_addrbk - 1){
+ new->adrbk_num = old->adrbk_num + 1;
+ new->type = DlcTitleBlankTopCmb;
+ }
+ else if(as.n_serv)
+ new->type = DlcDirDelim1;
+ else
+ new->type = DlcEnd;
+ }
+ else
+ new->type = DlcEnd;
+
+ break;
+
+ case DlcNoAbooks:
+ case DlcGlobAdd:
+ case DlcEnd:
+ new->type = DlcEnd;
+ break;
+
+ case DlcSimple:
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ ab_count = adrbk_count(pab->address_book);
+ i = old->dlcelnum;
+ i++;
+ el = old->dlcelnum + 1;
+ while(i < ab_count){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el++;
+ i++;
+ }
+
+ if(i < ab_count){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(abe && abe->tag == Single)
+ new->type = DlcSimple;
+ else if(abe && abe->tag == List)
+ new->type = DlcListBlankTop;
+ }
+ else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(old->adrbk_num < as.n_addrbk - 1){
+ new->adrbk_num = old->adrbk_num + 1;
+ new->type = DlcTitleBlankTopCmb;
+ }
+ else if(as.n_serv)
+ new->type = DlcDirDelim1;
+ else
+ new->type = DlcEnd;
+ }
+ else
+ new->type = DlcEnd;
+ }
+
+ break;
+
+ case DlcListHead:
+ new->dlcelnum = old->dlcelnum;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(F_ON(F_EXPANDED_DISTLISTS,ps_global)
+ || exp_is_expanded(pab->address_book->exp, (a_c_arg_t)new->dlcelnum)){
+ list_count = listmem_count_from_abe(abe);
+ if(list_count == 0)
+ new->type = DlcListEmpty;
+ else{
+ new->type = DlcListEnt;
+ new->dlcoffset = 0;
+ }
+ }
+ else
+ new->type = DlcListClickHere;
+
+ break;
+
+ case DlcListEnt:
+ new->dlcelnum = old->dlcelnum;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ list_count = listmem_count_from_abe(abe);
+ if(old->dlcoffset == list_count - 1){ /* last member of list */
+ adrbk_cntr_t el;
+ long i;
+
+ ab_count = adrbk_count(pab->address_book);
+ i = old->dlcelnum;
+ i++;
+ el = old->dlcelnum + 1;
+ while(i < ab_count){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el++;
+ i++;
+ }
+
+ if(i < ab_count)
+ new->type = DlcListBlankBottom;
+ else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(old->adrbk_num < as.n_addrbk - 1){
+ new->adrbk_num = old->adrbk_num + 1;
+ new->type = DlcTitleBlankTopCmb;
+ }
+ else if(as.n_serv)
+ new->type = DlcDirDelim1;
+ else
+ new->type = DlcEnd;
+ }
+ else
+ new->type = DlcEnd;
+ }
+ else{
+ new->type = DlcListEnt;
+ new->dlcoffset = old->dlcoffset + 1;
+ }
+
+ break;
+
+ case DlcListClickHere:
+ case DlcListEmpty:
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ new->dlcelnum = old->dlcelnum;
+ ab_count = adrbk_count(pab->address_book);
+ i = old->dlcelnum;
+ i++;
+ el = old->dlcelnum + 1;
+ while(i < ab_count){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el++;
+ i++;
+ }
+
+ if(i < ab_count)
+ new->type = DlcListBlankBottom;
+ else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(old->adrbk_num < as.n_addrbk - 1){
+ new->adrbk_num = old->adrbk_num + 1;
+ new->type = DlcTitleBlankTopCmb;
+ }
+ else if(as.n_serv)
+ new->type = DlcDirDelim1;
+ else
+ new->type = DlcEnd;
+ }
+ else
+ new->type = DlcEnd;
+ }
+
+ break;
+
+ case DlcListBlankTop:
+ new->type = DlcListHead;
+ new->dlcelnum = old->dlcelnum;
+ break;
+
+ case DlcListBlankBottom:
+ {
+ adrbk_cntr_t el;
+ long i;
+
+ ab_count = adrbk_count(pab->address_book);
+ i = old->dlcelnum;
+ i++;
+ el = old->dlcelnum + 1;
+ while(i < ab_count){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el++;
+ i++;
+ }
+
+ if(i < ab_count){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(abe && abe->tag == Single)
+ new->type = DlcSimple;
+ else if(abe && abe->tag == List)
+ new->type = DlcListHead;
+ }
+ else
+ /* can't happen */
+ new->type = DlcEnd;
+ }
+
+ break;
+
+ case DlcGlobDelim1:
+ new->type = DlcGlobDelim2;
+ break;
+
+ case DlcGlobDelim2:
+ if(as.config && as.how_many_personals == as.n_addrbk)
+ new->type = DlcGlobAdd;
+ else{
+ new->adrbk_num = as.how_many_personals;
+ pab = &as.adrbks[new->adrbk_num];
+ if(pab->access == NoAccess)
+ new->type = DlcTitleNoPerm;
+ else
+ new->type = DlcTitle;
+ }
+
+ break;
+
+ case DlcDirDelim1:
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
+ new->type = DlcDirDelim1a;
+ else
+ new->type = DlcDirDelim2;
+
+ new->adrbk_num = 0;
+ break;
+
+ case DlcDirDelim1a:
+ new->type = DlcDirDelim1b;
+ break;
+
+ case DlcDirDelim1b:
+ new->type = DlcDirDelim1c;
+ break;
+
+ case DlcDirDelim1c:
+ new->type = DlcDirDelim2;
+ new->adrbk_num = 0;
+ break;
+
+ case DlcDirDelim2:
+ new->type = DlcDirAccess;
+ new->adrbk_num = 0;
+ break;
+
+ case DlcPersAdd:
+ new->type = DlcGlobDelim1;
+ break;
+
+ case DlcDirAccess:
+ new->type = DlcDirSubTitle;
+ break;
+
+ case DlcDirSubTitle:
+ if(old->adrbk_num == as.n_serv - 1)
+ new->type = DlcEnd;
+ else{
+ new->type = DlcDirBlankTop;
+ new->adrbk_num = old->adrbk_num + 1;
+ }
+
+ break;
+
+ case DlcDirBlankTop:
+ new->type = DlcDirAccess;
+ break;
+
+ case DlcTitleDashTopCmb:
+ new->type = DlcTitleCmb;
+ break;
+
+ case DlcTitleCmb:
+ new->type = DlcTitleDashBottomCmb;
+ break;
+
+ case DlcTitleDashBottomCmb:
+ new->type = DlcTitleBlankBottomCmb;
+ break;
+
+ case DlcTitleBlankBottomCmb:
+ pab = &as.adrbks[old->adrbk_num];
+ if(pab->ostatus != Open && pab->access != NoAccess)
+ new->type = DlcClickHereCmb;
+ else
+ new = get_top_dl_of_adrbk(old->adrbk_num, new);
+
+ break;
+
+ case DlcTitleBlankTopCmb:
+ new->type = DlcTitleDashTopCmb;
+ break;
+
+ case DlcOneBeforeBeginning:
+ new = get_global_top_dlc(new);
+ break;
+
+ case DlcTwoBeforeBeginning:
+ new->type = DlcOneBeforeBeginning;
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 5, 10,
+ _("Bug in addrbook, not supposed to happen, re-syncing..."));
+ dprint((1, "Bug in addrbook, impossible case (%d) in dlc_next, re-sync\n",
+ old->type));
+ /* jump back to a safe starting point */
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+
+ new->global_row = old->global_row + 1L;
+ if(new->adrbk_num == -2)
+ new->adrbk_num = old->adrbk_num;
+
+ return(new);
+}
+
+
+/*
+ * Get the display line at the very top of whole addrbook screen display.
+ */
+DL_CACHE_S *
+get_global_top_dlc(DL_CACHE_S *new)
+ /* fill in answer here */
+{
+ PerAddrBook *pab;
+
+ new->dlcelnum = NO_NEXT;
+ new->dlcoffset = NO_NEXT;
+ new->type = DlcNotSet;
+
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && !as.config){
+ new->adrbk_num = 0;
+ if(as.n_addrbk > 1 || (as.n_serv && as.n_addrbk == 1))
+ new->type = DlcTitleDashTopCmb;
+ else if(as.n_addrbk == 1)
+ new = get_top_dl_of_adrbk(new->adrbk_num, new);
+ else if(as.n_serv > 0){ /* 1st directory */
+ new->type = DlcDirAccess;
+ new->adrbk_num = 0;
+ }
+ else
+ new->type = DlcNoAbooks;
+ }
+ else{
+ new->adrbk_num = 0;
+
+ if(as.config){
+ if(as.how_many_personals == 0) /* no personals */
+ new->type = DlcPersAdd;
+ else{
+ pab = &as.adrbks[new->adrbk_num]; /* 1st personal */
+ if(pab->access == NoAccess)
+ new->type = DlcTitleNoPerm;
+ else
+ new->type = DlcTitle;
+ }
+ }
+ else if(any_ab_open()){
+ new->adrbk_num = as.cur;
+ new = get_top_dl_of_adrbk(new->adrbk_num, new);
+ }
+ else if(as.n_addrbk > 0){ /* 1st addrbook */
+ pab = &as.adrbks[new->adrbk_num];
+ if(pab->access == NoAccess)
+ new->type = DlcTitleNoPerm;
+ else
+ new->type = DlcTitle;
+ }
+ else if(as.n_serv > 0){ /* 1st directory */
+ new->type = DlcDirAccess;
+ new->adrbk_num = 0;
+ }
+ else
+ new->type = DlcNoAbooks;
+ }
+
+ return(new);
+}
+
+
+/*
+ * Get the last display line for the whole address book screen.
+ * This gives us a way to start at the end and move back up.
+ */
+DL_CACHE_S *
+get_global_bottom_dlc(DL_CACHE_S *new)
+ /* fill in answer here */
+{
+ new->dlcelnum = NO_NEXT;
+ new->dlcoffset = NO_NEXT;
+ new->type = DlcNotSet;
+
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && !as.config){
+ if(as.n_serv){
+ new->type = DlcDirSubTitle;
+ new->adrbk_num = as.n_serv - 1;
+ }
+ else if(as.n_addrbk > 0){
+ new->adrbk_num = as.n_addrbk - 1;
+ new = get_bottom_dl_of_adrbk(new->adrbk_num, new);
+ }
+ else
+ new->type = DlcNoAbooks;
+ }
+ else{
+ new->adrbk_num = MAX(as.n_addrbk - 1, 0);
+
+ if(as.config){
+ if(as.how_many_personals == as.n_addrbk) /* no globals */
+ new->type = DlcGlobAdd;
+ else
+ new->type = DlcSubTitle;
+ }
+ else if(any_ab_open()){
+ new->adrbk_num = as.cur;
+ new = get_bottom_dl_of_adrbk(new->adrbk_num, new);
+ }
+ else if(as.n_serv){
+ new->type = DlcDirSubTitle;
+ new->adrbk_num = as.n_serv - 1;
+ }
+ else{ /* !config && !opened && !n_serv */
+ if(as.n_addrbk)
+ new->type = DlcSubTitle;
+ else
+ new->type = DlcNoAbooks;
+ }
+ }
+
+ return(new);
+}
+
+
+/*
+ * First dl in a particular addrbook, not counting title lines.
+ * Assumes as.opened.
+ */
+DL_CACHE_S *
+get_top_dl_of_adrbk(int adrbk_num, DL_CACHE_S *new)
+
+ /* fill in answer here */
+{
+ PerAddrBook *pab;
+ AdrBk_Entry *abe;
+ adrbk_cntr_t ab_count;
+
+ pab = &as.adrbks[adrbk_num];
+ new->adrbk_num = adrbk_num;
+
+ if(pab->access == NoAccess)
+ new->type = DlcNoPermission;
+ else{
+ adrbk_cntr_t el;
+ long i;
+
+ ab_count = adrbk_count(pab->address_book);
+
+ i = 0L;
+ el = 0;
+ /* find first displayed entry */
+ while(i < ab_count){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el++;
+ i++;
+ }
+
+ if(i < ab_count){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum);
+ if(abe && abe->tag == Single)
+ new->type = DlcSimple;
+ else if(abe && abe->tag == List)
+ new->type = DlcListHead;
+ }
+ else if(as.zoomed)
+ new->type = DlcZoomEmpty;
+ else
+ new->type = DlcEmpty;
+ }
+
+ return(new);
+}
+
+
+/*
+ * Find the last display line for addrbook number adrbk_num.
+ * Assumes as.opened (unless OLD_ABOOK_DISP).
+ */
+DL_CACHE_S *
+get_bottom_dl_of_adrbk(int adrbk_num, DL_CACHE_S *new)
+
+ /* fill in answer here */
+{
+ PerAddrBook *pab;
+ AdrBk_Entry *abe;
+ adrbk_cntr_t ab_count;
+ adrbk_cntr_t list_count;
+
+ pab = &as.adrbks[adrbk_num];
+ new->adrbk_num = adrbk_num;
+
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && pab->ostatus != Open){
+ if(pab->access == NoAccess)
+ new->type = DlcNoPermission;
+ else
+ new->type = DlcClickHereCmb;
+ }
+ else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global) && pab->ostatus != Open){
+ new->type = DlcSubTitle;
+ }
+ else{
+ if(pab->access == NoAccess)
+ new->type = DlcNoPermission;
+ else{
+ adrbk_cntr_t el;
+ long i;
+
+ ab_count = adrbk_count(pab->address_book);
+ i = ab_count - 1;
+ el = ab_count - 1;
+ /* find last displayed entry */
+ while(i >= 0L){
+ if(!as.zoomed || entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)el))
+ break;
+
+ el--;
+ i--;
+ }
+
+ if(i >= 0){
+ new->dlcelnum = el;
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum);
+ if(abe && abe->tag == Single)
+ new->type = DlcSimple;
+ else if(abe && abe->tag == List){
+ if(F_ON(F_EXPANDED_DISTLISTS,ps_global)
+ || exp_is_expanded(pab->address_book->exp,
+ (a_c_arg_t)new->dlcelnum)){
+ list_count = listmem_count_from_abe(abe);
+ if(list_count == 0)
+ new->type = DlcListEmpty;
+ else{
+ new->type = DlcListEnt;
+ new->dlcoffset = list_count - 1;
+ }
+ }
+ else
+ new->type = DlcListClickHere;
+ }
+ }
+ else if(as.zoomed)
+ new->type = DlcZoomEmpty;
+ else
+ new->type = DlcEmpty;
+ }
+
+ }
+
+ return(new);
+}
+
+
+/*
+ * This returns the actual dlc instead of the dl within the dlc.
+ */
+DL_CACHE_S *
+get_dlc(long int row)
+{
+ dprint((11, "- get_dlc(%ld) -\n", row));
+
+ return(dlc_mgr(row, Lookup, (DL_CACHE_S *)NULL));
+}
+
+
+/*
+ * Move to new_dlc and give it row number row_number_to_assign_it.
+ * We copy the passed in dlc in case the caller passed us a pointer into
+ * the cache.
+ */
+void
+warp_to_dlc(DL_CACHE_S *new_dlc, long int row_number_to_assign_it)
+{
+ DL_CACHE_S dlc;
+
+ dprint((9, "- warp_to_dlc(%ld) -\n", row_number_to_assign_it));
+
+ dlc = *new_dlc;
+
+ /*
+ * Make sure we can move forward and backward from these
+ * types that we may wish to warp to. The caller may not have known
+ * to set adrbk_num for these types.
+ */
+ switch(dlc.type){
+ case DlcPersAdd:
+ dlc.adrbk_num = 0;
+ break;
+ case DlcGlobAdd:
+ dlc.adrbk_num = as.n_addrbk;
+ break;
+ default:
+ break;
+ }
+
+ (void)dlc_mgr(row_number_to_assign_it, ArbitraryStartingPoint, &dlc);
+}
+
+
+/*
+ * Move to first dlc and give it row number 0.
+ */
+void
+warp_to_beginning(void)
+{
+ dprint((9, "- warp_to_beginning -\n"));
+
+ (void)dlc_mgr(0L, FirstEntry, (DL_CACHE_S *)NULL);
+}
+
+
+/*
+ * Move to first dlc in abook_num and give it row number 0.
+ */
+void
+warp_to_top_of_abook(int abook_num)
+{
+ DL_CACHE_S dlc;
+
+ dprint((9, "- warp_to_top_of_abook(%d) -\n", abook_num));
+
+ (void)get_top_dl_of_adrbk(abook_num, &dlc);
+ warp_to_dlc(&dlc, 0L);
+}
+
+
+/*
+ * Move to last dlc and give it row number 0.
+ */
+void
+warp_to_end(void)
+{
+ dprint((9, "- warp_to_end -\n"));
+
+ (void)dlc_mgr(0L, LastEntry, (DL_CACHE_S *)NULL);
+}
+
+
+/*
+ * This flushes all of the cache that is related to this dlc.
+ */
+void
+flush_dlc_from_cache(DL_CACHE_S *dlc_to_flush)
+{
+ dprint((11, "- flush_dlc_from_cache -\n"));
+
+ (void)dlc_mgr(NO_LINE, FlushDlcFromCache, dlc_to_flush);
+}
+
+
+/*
+ * Returns 1 if the dlc's match, 0 otherwise.
+ */
+int
+matching_dlcs(DL_CACHE_S *dlc1, DL_CACHE_S *dlc2)
+{
+ if(!dlc1 || !dlc2 ||
+ dlc1->type != dlc2->type ||
+ dlc1->adrbk_num != dlc2->adrbk_num)
+ return 0;
+
+ switch(dlc1->type){
+
+ case DlcSimple:
+ case DlcListHead:
+ case DlcListBlankTop:
+ case DlcListBlankBottom:
+ case DlcListClickHere:
+ case DlcListEmpty:
+ return(dlc1->dlcelnum == dlc2->dlcelnum);
+
+ case DlcListEnt:
+ return(dlc1->dlcelnum == dlc2->dlcelnum &&
+ dlc1->dlcoffset == dlc2->dlcoffset);
+
+ case DlcTitle:
+ case DlcSubTitle:
+ case DlcTitleNoPerm:
+ case DlcTitleBlankTop:
+ case DlcEmpty:
+ case DlcZoomEmpty:
+ case DlcNoPermission:
+ case DlcPersAdd:
+ case DlcGlobAdd:
+ case DlcGlobDelim1:
+ case DlcGlobDelim2:
+ case DlcDirAccess:
+ case DlcDirDelim1:
+ case DlcDirDelim2:
+ case DlcTitleDashTopCmb:
+ case DlcTitleCmb:
+ case DlcTitleDashBottomCmb:
+ case DlcTitleBlankBottomCmb:
+ case DlcClickHereCmb:
+ case DlcTitleBlankTopCmb:
+ return 1;
+
+ case DlcNotSet:
+ case DlcBeginning:
+ case DlcOneBeforeBeginning:
+ case DlcTwoBeforeBeginning:
+ case DlcEnd:
+ case DlcNoAbooks:
+ return 0;
+
+ default:
+ break;
+ }
+ /*NOTREACHED*/
+
+ return 0;
+}
+
+
+/*
+ * Uses information in new to fill in new->dl.
+ */
+void
+fill_in_dl_field(DL_CACHE_S *new)
+{
+ AddrScrn_Disp *dl;
+ PerAddrBook *pab;
+ char buf[6*MAX_SCREEN_COLS + 1];
+ char buf2[6*1024];
+ char hostbuf[128];
+ char *folder;
+ char *q;
+ unsigned screen_width = ps_global->ttyo->screen_cols;
+ unsigned got_width, need_width, cellwidth;
+
+ screen_width = MIN(MAX_SCREEN_COLS, screen_width);
+
+ dl = &(new->dl);
+
+ /* free any previously allocated space */
+ switch(dl->type){
+ case Text:
+ case Title:
+ case TitleCmb:
+ case AskServer:
+ if(dl->usst)
+ fs_give((void **)&dl->usst);
+ default:
+ break;
+ }
+
+ /* set up new dl */
+ switch(new->type){
+ case DlcListBlankTop:
+ case DlcListBlankBottom:
+ case DlcGlobDelim1:
+ case DlcGlobDelim2:
+ case DlcDirDelim1:
+ case DlcDirDelim2:
+ case DlcTitleBlankTop:
+ case DlcDirBlankTop:
+ case DlcTitleBlankBottomCmb:
+ case DlcTitleBlankTopCmb:
+ dl->type = Text;
+ dl->usst = cpystr("");
+ break;
+
+ case DlcTitleDashTopCmb:
+ case DlcTitleDashBottomCmb:
+ case DlcDirDelim1a:
+ case DlcDirDelim1c:
+ /* line of dashes in txt field */
+ dl->type = Text;
+ memset((void *)buf, '-', screen_width * sizeof(char));
+ buf[screen_width] = '\0';
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcNoPermission:
+ dl->type = Text;
+ dl->usst = cpystr(NO_PERMISSION);
+ break;
+
+ case DlcTitle:
+ case DlcTitleNoPerm:
+ dl->type = Title;
+ pab = &as.adrbks[new->adrbk_num];
+ /* title for this addrbook */
+ snprintf(buf, sizeof(buf), " %s", pab->abnick ? pab->abnick : pab->filename);
+ cellwidth = utf8_width(buf);
+ if(cellwidth > screen_width)
+ cellwidth = utf8_truncate(buf, screen_width);
+
+ /* add space padding */
+ if(cellwidth < screen_width)
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s",
+ screen_width - cellwidth, screen_width - cellwidth, "");
+
+ buf[sizeof(buf)-1] = '\0';
+
+ if(as.ro_warning && (pab->access == NoAccess || pab->access == ReadOnly)){
+ char *str;
+
+ if(pab->access == NoAccess)
+ str = NOACCESS;
+ else
+ str = READONLY;
+
+ need_width = utf8_width(str);
+
+ if(screen_width > need_width && (q = utf8_count_back_width(buf, buf+strlen(buf), need_width, &got_width)) != NULL)
+
+ (void) utf8_pad_to_width(q, str, sizeof(buf)-(q-buf), need_width, 0);
+ }
+
+ buf[sizeof(buf)-1] = '\0';
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcSubTitle:
+ dl->type = Text;
+ pab = &as.adrbks[new->adrbk_num];
+ /* Find a hostname to put in title */
+ hostbuf[0] = '\0';
+ folder = NULL;
+ if(pab->type & REMOTE_VIA_IMAP && pab->filename){
+ char *start, *end;
+
+ start = strindex(pab->filename, '{');
+ if(start)
+ end = strindex(start+1, '}');
+
+ if(start && end){
+ strncpy(hostbuf, start + 1,
+ MIN(end - start - 1, sizeof(hostbuf)-1));
+ hostbuf[MIN(end - start - 1, sizeof(hostbuf)-1)] = '\0';
+ if(*(end+1))
+ folder = end+1;
+ }
+ }
+
+ if(!folder)
+ folder = pab->filename;
+
+ /*
+ * Just trying to find the name relative to the home directory
+ * to display in an OS-independent way.
+ */
+ if(folder && in_dir(ps_global->home_dir, folder)){
+ char *p, *new_folder = NULL, *savep = NULL;
+ int l, saveval = 0;
+
+ l = strlen(ps_global->home_dir);
+
+ while(!new_folder && (p = last_cmpnt(folder)) != NULL){
+ if(savep){
+ *savep = saveval;
+ savep = NULL;
+ }
+
+ if(folder + l == p || folder + l + 1 == p)
+ new_folder = p;
+ else{
+ savep = --p;
+ saveval = *savep;
+ *savep = '\0';
+ }
+ }
+
+ if(savep)
+ *savep = saveval;
+
+ if(new_folder)
+ folder = new_folder;
+ }
+
+ snprintf(buf, sizeof(buf), " %s AddressBook%s%s in %s",
+ (pab->type & GLOBAL) ? "Global" : "Personal",
+ (pab->type & REMOTE_VIA_IMAP && *hostbuf) ? " on " : "",
+ (pab->type & REMOTE_VIA_IMAP && *hostbuf) ? hostbuf : "",
+ (folder && *folder) ? folder : "<?>");
+ cellwidth = utf8_width(buf);
+ if(cellwidth > screen_width)
+ cellwidth = utf8_truncate(buf, screen_width);
+
+ /* add space padding */
+ if(cellwidth < screen_width)
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s",
+ screen_width - cellwidth, screen_width - cellwidth, "");
+
+ buf[sizeof(buf)-1] = '\0';
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcTitleCmb:
+ dl->type = TitleCmb;
+ pab = &as.adrbks[new->adrbk_num];
+ /* title for this addrbook */
+ snprintf(buf, sizeof(buf), "%s AddressBook <%s>",
+ (new->adrbk_num < as.how_many_personals) ?
+ "Personal" :
+ "Global",
+ pab->abnick ? pab->abnick : pab->filename);
+ cellwidth = utf8_width(buf);
+ if(cellwidth > screen_width)
+ cellwidth = utf8_truncate(buf, screen_width);
+
+ /* add space padding */
+ if(cellwidth < screen_width)
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s",
+ screen_width - cellwidth, screen_width - cellwidth, "");
+
+ buf[sizeof(buf)-1] = '\0';
+
+ if(as.ro_warning && (pab->access == NoAccess || pab->access == ReadOnly)){
+ char *str;
+
+ if(pab->access == NoAccess)
+ str = NOACCESS;
+ else
+ str = READONLY;
+
+ need_width = utf8_width(str);
+
+ if(screen_width > need_width && (q = utf8_count_back_width(buf, buf+strlen(buf), need_width, &got_width)) != NULL)
+
+ (void) utf8_pad_to_width(q, str, sizeof(buf)-(q-buf), need_width, 0);
+ }
+
+ buf[sizeof(buf)-1] = '\0';
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcDirDelim1b:
+ dl->type = Text;
+ utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, OLDSTYLE_DIR_TITLE);
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcClickHereCmb:
+ dl->type = ClickHereCmb;
+ break;
+
+ case DlcListClickHere:
+ dl->type = ListClickHere;
+ dl->elnum = new->dlcelnum;
+ break;
+
+ case DlcListEmpty:
+ dl->type = ListEmpty;
+ dl->elnum = new->dlcelnum;
+ break;
+
+ case DlcEmpty:
+ dl->type = Empty;
+ break;
+
+ case DlcZoomEmpty:
+ dl->type = ZoomEmpty;
+ break;
+
+ case DlcNoAbooks:
+ dl->type = NoAbooks;
+ break;
+
+ case DlcPersAdd:
+ dl->type = AddFirstPers;
+ break;
+
+ case DlcGlobAdd:
+ dl->type = AddFirstGlob;
+ break;
+
+ case DlcDirAccess:
+ dl->type = AskServer;
+
+#ifdef ENABLE_LDAP
+ {
+ LDAP_SERV_S *info;
+
+ info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[new->adrbk_num]);
+ snprintf(buf2, sizeof(buf2), " %s",
+ (info && info->nick && *info->nick) ? info->nick :
+ (info && info->serv && *info->serv) ? info->serv :
+ comatose(new->adrbk_num + 1));
+ if(info)
+ free_ldap_server_info(&info);
+ }
+#else
+ snprintf(buf2, sizeof(buf2), " %s", comatose(new->adrbk_num + 1));
+#endif
+
+ utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, buf2);
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcDirSubTitle:
+ dl->type = Text;
+#ifdef ENABLE_LDAP
+ {
+ LDAP_SERV_S *info;
+
+ info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[new->adrbk_num]);
+ if(info && info->port >= 0)
+ snprintf(buf2, sizeof(buf2), " Directory Server on %s:%d",
+ (info && info->serv && *info->serv) ? info->serv : "<?>",
+ info->port);
+ else
+ snprintf(buf2, sizeof(buf2), " Directory Server on %s",
+ (info && info->serv && *info->serv) ? info->serv : "<?>");
+
+ if(info)
+ free_ldap_server_info(&info);
+ }
+#else
+ snprintf(buf2, sizeof(buf2), " Directory Server %s",
+ comatose(new->adrbk_num + 1));
+#endif
+
+ utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, buf2);
+ dl->usst = cpystr(buf);
+ break;
+
+ case DlcSimple:
+ dl->type = Simple;
+ dl->elnum = new->dlcelnum;
+ break;
+
+ case DlcListHead:
+ dl->type = ListHead;
+ dl->elnum = new->dlcelnum;
+ break;
+
+ case DlcListEnt:
+ dl->type = ListEnt;
+ dl->elnum = new->dlcelnum;
+ dl->l_offset = new->dlcoffset;
+ break;
+
+ case DlcBeginning:
+ case DlcOneBeforeBeginning:
+ case DlcTwoBeforeBeginning:
+ dl->type = Beginning;
+ break;
+
+ case DlcEnd:
+ dl->type = End;
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 5, 10,
+ _("Bug in addrbook, not supposed to happen, re-syncing..."));
+ dprint((1, "Bug in addrbook, impossible dflt in fill_in_dl (%d)\n",
+ new->type));
+ /* jump back to a safe starting point */
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+}
+
+
+/*
+ * Returns a pointer to the member_number'th list member of the list
+ * associated with this display line.
+ */
+char *
+listmem(long int row)
+{
+ PerAddrBook *pab;
+ AddrScrn_Disp *dl;
+
+ dl = dlist(row);
+ if(dl->type != ListEnt)
+ return((char *)NULL);
+
+ pab = &as.adrbks[adrbk_num_from_lineno(row)];
+
+ return(listmem_from_dl(pab->address_book, dl));
+}
+
+
+/*
+ * Returns a pointer to the list member
+ * associated with this display line.
+ */
+char *
+listmem_from_dl(AdrBk *address_book, AddrScrn_Disp *dl)
+{
+ AdrBk_Entry *abe;
+ char **p = (char **)NULL;
+
+ /* This shouldn't happen */
+ if(dl->type != ListEnt)
+ return((char *)NULL);
+
+ abe = adrbk_get_ae(address_book, (a_c_arg_t) dl->elnum);
+
+ /*
+ * If we wanted to be more careful, We'd go through the list making sure
+ * we don't pass the end. We'll count on the caller being careful
+ * instead.
+ */
+ if(abe && abe->tag == List){
+ p = abe->addr.list;
+ p += dl->l_offset;
+ }
+
+ return((p && *p) ? *p : (char *)NULL);
+}
+
+
+/*
+ * How many members in list?
+ */
+adrbk_cntr_t
+listmem_count_from_abe(AdrBk_Entry *abe)
+{
+ char **p;
+
+ if(abe->tag != List)
+ return 0;
+
+ for(p = abe->addr.list; p != NULL && *p != NULL; p++)
+ ;/* do nothing */
+
+ return((adrbk_cntr_t)(p - abe->addr.list));
+}
+
+
+/*
+ * Return a ptr to the row'th line of the global disp_list.
+ * Line numbers count up but you can't count on knowing which line number
+ * goes with the first or the last row. That is, row 0 is not necessarily
+ * special. It could be before the rows that make up the display list, after
+ * them, or anywhere in between. You can't tell what the last row is
+ * numbered, but a dl with type End is returned when you get past the end.
+ * You can't tell what the number of the first row is, but if you go past
+ * the first row a dl of type Beginning will be returned. Row numbers can
+ * be positive or negative. Their values have no meaning other than how
+ * they line up relative to other row numbers.
+ */
+AddrScrn_Disp *
+dlist(long int row)
+{
+ DL_CACHE_S *dlc = (DL_CACHE_S *)NULL;
+
+ dlc = get_dlc(row);
+
+ if(dlc){
+ fill_in_dl_field(dlc);
+ return(&dlc->dl);
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 5, 10,
+ _("Bug in addrbook, not supposed to happen, re-syncing..."));
+ dprint((1, "Bug in addrbook (null dlc in dlist(%ld), not supposed to happen\n", row));
+ /* jump back to a safe starting point */
+
+ dprint((1, "dlist: panic: initialized %d, n_addrbk %d, cur_row %d, top_ent %ld, ro_warning %d, no_op_possbl %d\n",
+ as.initialized, as.n_addrbk, as.cur_row, as.top_ent, as.ro_warning, as.no_op_possbl));
+
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+}
+
+
+/*
+ * Returns the index of the current address book.
+ */
+int
+adrbk_num_from_lineno(long int lineno)
+{
+ DL_CACHE_S *dlc;
+
+ dlc = get_dlc(lineno);
+
+ return(dlc->adrbk_num);
+}
diff --git a/pith/abdlc.h b/pith/abdlc.h
new file mode 100644
index 00000000..cd7ee72b
--- /dev/null
+++ b/pith/abdlc.h
@@ -0,0 +1,41 @@
+/*
+ * $Id: abdlc.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ABDLC_INCLUDED
+#define PITH_ABDLC_INCLUDED
+
+
+#include "../pith/adrbklib.h"
+
+
+/* exported protoypes */
+DL_CACHE_S *get_dlc(long);
+void warp_to_dlc(DL_CACHE_S *, long);
+void warp_to_beginning(void);
+void warp_to_top_of_abook(int);
+void warp_to_end(void);
+void flush_dlc_from_cache(DL_CACHE_S *);
+int matching_dlcs(DL_CACHE_S *, DL_CACHE_S *);
+void fill_in_dl_field(DL_CACHE_S *);
+char *listmem(long);
+char *listmem_from_dl(AdrBk *, AddrScrn_Disp *);
+adrbk_cntr_t listmem_count_from_abe(AdrBk_Entry *);
+AddrScrn_Disp *dlist(long);
+int adrbk_num_from_lineno(long);
+void done_with_dlc_cache(void);
+
+
+#endif /* PITH_ABDLC_INCLUDED */
diff --git a/pith/ablookup.c b/pith/ablookup.c
new file mode 100644
index 00000000..63ff9133
--- /dev/null
+++ b/pith/ablookup.c
@@ -0,0 +1,1629 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: ablookup.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h" /* for os-dep and pith defs/includes */
+#include "../pith/debug.h"
+#include "../pith/util.h"
+#include "../pith/adrbklib.h"
+#include "../pith/copyaddr.h"
+#include "../pith/status.h"
+#include "../pith/conf.h"
+#include "../pith/search.h"
+#include "../pith/abdlc.h"
+#include "../pith/addrstring.h"
+#include "../pith/ablookup.h"
+#include "../pith/options.h"
+#include "../pith/takeaddr.h"
+#ifdef ENABLE_LDAP
+#include "../pith/ldap.h"
+#endif /* ENABLE_LDAP */
+
+
+/*
+ * Internal prototypes
+ */
+int addr_is_in_addrbook(PerAddrBook *, ADDRESS *);
+ABOOK_ENTRY_S *adrbk_list_of_possible_trie_completions(AdrBk_Trie *, AdrBk *, char *, unsigned);
+void gather_abook_entry_list(AdrBk *, AdrBk_Trie *, char *, ABOOK_ENTRY_S **, unsigned);
+void add_addr_to_return_list(ADDRESS *, unsigned, char *, int, COMPLETE_S **);
+
+
+/*
+ * Given an address, try to find the first nickname that goes with it.
+ * Copies that nickname into the passed in buffer, which is assumed to
+ * be at least MAX_NICKNAME+1 in length. Returns NULL if it can't be found,
+ * else it returns a pointer to the buffer.
+ */
+char *
+get_nickname_from_addr(struct mail_address *adr, char *buffer, size_t buflen)
+{
+ AdrBk_Entry *abe;
+ char *ret = NULL;
+ SAVE_STATE_S state;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+ ADDRESS *copied_adr;
+
+ state.savep = NULL;
+ state.stp = NULL;
+ state.dlc_to_warp_to = NULL;
+ copied_adr = copyaddr(adr);
+
+ if(ps_global->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0);
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ ret = NULL;
+ if(state.savep)
+ fs_give((void **)&(state.savep));
+ if(state.stp)
+ fs_give((void **)&(state.stp));
+ if(state.dlc_to_warp_to)
+ fs_give((void **)&(state.dlc_to_warp_to));
+
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... get_nickname_from_addr()!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ abe = address_to_abe(copied_adr);
+
+ if(copied_adr)
+ mail_free_address(&copied_adr);
+
+ if(abe && abe->nickname && abe->nickname[0]){
+ strncpy(buffer, abe->nickname, buflen-1);
+ buffer[buflen-1] = '\0';
+ ret = buffer;
+ }
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ ab_nesting_level--;
+
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(ret);
+}
+
+
+/*
+ * Given an address, try to find the first fcc that goes with it.
+ * Copies that fcc into the passed in buffer.
+ * Returns NULL if it can't be found, else it returns a pointer to the buffer.
+ */
+char *
+get_fcc_from_addr(struct mail_address *adr, char *buffer, size_t buflen)
+{
+ AdrBk_Entry *abe;
+ char *ret = NULL;
+ SAVE_STATE_S state;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+ ADDRESS *copied_adr;
+
+ state.savep = NULL;
+ state.stp = NULL;
+ state.dlc_to_warp_to = NULL;
+ copied_adr = copyaddr(adr);
+
+ if(ps_global->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0);
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ ret = NULL;
+ if(state.savep)
+ fs_give((void **)&(state.savep));
+ if(state.stp)
+ fs_give((void **)&(state.stp));
+ if(state.dlc_to_warp_to)
+ fs_give((void **)&(state.dlc_to_warp_to));
+
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... get_fcc_from_addr()!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ abe = address_to_abe(copied_adr);
+
+ if(copied_adr)
+ mail_free_address(&copied_adr);
+
+ if(abe && abe->fcc && abe->fcc[0]){
+ if(!strcmp(abe->fcc, "\"\""))
+ buffer[0] = '\0';
+ else{
+ strncpy(buffer, abe->fcc, buflen-1);
+ buffer[buflen-1] = '\0';
+ }
+
+ ret = buffer;
+ }
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ ab_nesting_level--;
+
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(ret);
+}
+
+
+/*
+ * Given an address, try to find the first address book entry that
+ * matches it and return all the other fields in the passed in pointers.
+ * Caller needs to free the four fields.
+ * Returns -1 if it can't be found, 0 if it is found.
+ */
+int
+get_contactinfo_from_addr(struct mail_address *adr, char **nick, char **full, char **fcc, char **comment)
+{
+ AdrBk_Entry *abe;
+ int ret = -1;
+ SAVE_STATE_S state;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+ ADDRESS *copied_adr;
+
+ state.savep = NULL;
+ state.stp = NULL;
+ state.dlc_to_warp_to = NULL;
+ copied_adr = copyaddr(adr);
+
+ if(ps_global->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0);
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ ret = -1;
+ if(state.savep)
+ fs_give((void **)&(state.savep));
+ if(state.stp)
+ fs_give((void **)&(state.stp));
+ if(state.dlc_to_warp_to)
+ fs_give((void **)&(state.dlc_to_warp_to));
+
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... get_contactinfo_from_addr()!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ abe = address_to_abe(copied_adr);
+
+ if(copied_adr)
+ mail_free_address(&copied_adr);
+
+ if(abe){
+ if(nick && abe->nickname && abe->nickname[0])
+ *nick = cpystr(abe->nickname);
+
+ if(full && abe->fullname && abe->fullname[0])
+ *full = cpystr(abe->fullname);
+
+ if(fcc && abe->fcc && abe->fcc[0])
+ *fcc = cpystr(abe->fcc);
+
+ if(comment && abe->extra && abe->extra[0])
+ *comment = cpystr(abe->extra);
+
+ ret = 0;
+ }
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ ab_nesting_level--;
+
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(ret);
+}
+
+
+/*
+ * This is a very special-purpose routine.
+ * It implements the From or Reply-To address is in the Address Book
+ * part of Pattern matching.
+ */
+void
+address_in_abook(MAILSTREAM *stream, SEARCHSET *searchset,
+ int inabook, PATTERN_S *abooks)
+{
+ char *savebits;
+ MESSAGECACHE *mc;
+ long i, count = 0L;
+ SEARCHSET *s, *ss;
+ ADDRESS *from, *reply_to, *sender, *to, *cc;
+ int is_there, adrbknum, *abooklist = NULL, positive_match;
+ PATTERN_S *pat;
+ PerAddrBook *pab;
+ ENVELOPE *e;
+ SAVE_STATE_S state;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ if(!stream)
+ return;
+
+ /* everything that matches remains a match */
+ if(inabook == IAB_EITHER)
+ return;
+
+ state.savep = NULL;
+ state.stp = NULL;
+ state.dlc_to_warp_to = NULL;
+
+ /*
+ * This may call build_header_line recursively because we may be in
+ * build_header_line now. So we have to preserve and restore the
+ * sequence bits since we want to use them here.
+ */
+ savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
+
+ for(i = 1L; i <= stream->nmsgs; i++){
+ if((mc = mail_elt(stream, i)) != NULL){
+ savebits[i] = mc->sequence;
+ mc->sequence = 0;
+ }
+ }
+
+ /*
+ * Build a searchset so we can look at all the envelopes
+ * we need to look at but only those we need to look at.
+ * Everything with the searched bit set is still a
+ * possibility, so restrict to that set.
+ */
+
+ for(s = searchset; s; s = s->next)
+ for(i = s->first; i <= s->last; i++)
+ if(i > 0L && i <= stream->nmsgs
+ && (mc=mail_elt(stream, i)) && mc->searched){
+ mc->sequence = 1;
+ count++;
+ }
+
+ ss = build_searchset(stream);
+
+ /*
+ * We save the address book state here so we don't have to do it
+ * each time through the loop below.
+ */
+ if(ss){
+ if(ps_global->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0);
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ if(state.savep)
+ fs_give((void **)&(state.savep));
+ if(state.stp)
+ fs_give((void **)&(state.stp));
+ if(state.dlc_to_warp_to)
+ fs_give((void **)&(state.dlc_to_warp_to));
+
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... address_in_abook()!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ if(as.n_addrbk > 0){
+ abooklist = (int *) fs_get(as.n_addrbk * sizeof(*abooklist));
+ memset((void *) abooklist, 0, as.n_addrbk * sizeof(*abooklist));
+ }
+
+ if(abooklist)
+ switch(inabook & IAB_TYPE_MASK){
+ case IAB_YES:
+ case IAB_NO:
+ for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++)
+ abooklist[adrbknum] = 1;
+
+ break;
+
+ case IAB_SPEC_YES:
+ case IAB_SPEC_NO:
+ /* figure out which address books we're going to look in */
+ for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++){
+ pab = &as.adrbks[adrbknum];
+ /*
+ * For each address book, check all of the address books
+ * in the pattern's list to see if they are it.
+ */
+ for(pat = abooks; pat; pat = pat->next){
+ if(!strcmp(pab->abnick, pat->substring)
+ || !strcmp(pab->filename, pat->substring)){
+ abooklist[adrbknum] = 1;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ switch(inabook & IAB_TYPE_MASK){
+ case IAB_YES:
+ case IAB_SPEC_YES:
+ positive_match = 1;
+ break;
+
+ case IAB_NO:
+ case IAB_SPEC_NO:
+ positive_match = 0;
+ break;
+ }
+ }
+
+ if(count){
+ SEARCHSET **sset;
+
+ mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
+
+ /*
+ * This causes the lookahead to fetch precisely
+ * the messages we want (in the searchset) instead
+ * of just fetching the next 20 sequential
+ * messages. If the searching so far has caused
+ * a sparse searchset in a large mailbox, the
+ * difference can be substantial.
+ * This resets automatically after the first fetch.
+ */
+ sset = (SEARCHSET **) mail_parameters(stream,
+ GET_FETCHLOOKAHEAD,
+ (void *) stream);
+ if(sset)
+ *sset = ss;
+ }
+
+ for(s = ss; s; s = s->next){
+ for(i = s->first; i <= s->last; i++){
+ if(i <= 0L || i > stream->nmsgs)
+ continue;
+
+ e = pine_mail_fetchenvelope(stream, i);
+
+ from = e ? e->from : NULL;
+ reply_to = e ? e->reply_to : NULL;
+ sender = e ? e->sender : NULL;
+ to = e ? e->to : NULL;
+ cc = e ? e->cc : NULL;
+
+ is_there = 0;
+ for(adrbknum = 0; !is_there && adrbknum < as.n_addrbk; adrbknum++){
+ if(!abooklist[adrbknum])
+ continue;
+
+ pab = &as.adrbks[adrbknum];
+ is_there = ((inabook & IAB_FROM) && addr_is_in_addrbook(pab, from))
+ || ((inabook & IAB_REPLYTO) && addr_is_in_addrbook(pab, reply_to))
+ || ((inabook & IAB_SENDER) && addr_is_in_addrbook(pab, sender))
+ || ((inabook & IAB_TO) && addr_is_in_addrbook(pab, to))
+ || ((inabook & IAB_CC) && addr_is_in_addrbook(pab, cc));
+ }
+
+ if(positive_match){
+ /*
+ * We matched up until now. If it isn't there, then it
+ * isn't a match. If it is there, leave the searched bit
+ * set.
+ */
+ if(!is_there && i > 0L && i <= stream->nmsgs && (mc = mail_elt(stream, i)))
+ mc->searched = NIL;
+ }
+ else{
+ if(is_there && i > 0L && i <= stream->nmsgs && (mc = mail_elt(stream, i)))
+ mc->searched = NIL;
+ }
+ }
+ }
+
+ if(ss){
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ ab_nesting_level--;
+
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+ }
+
+ /* restore sequence bits */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = savebits[i];
+
+ fs_give((void **) &savebits);
+
+ if(ss)
+ mail_free_searchset(&ss);
+
+ if(abooklist)
+ fs_give((void **) &abooklist);
+}
+
+
+/*
+ * Given two addresses, check to see if either is in the address book.
+ * Returns 1 if yes, 0 if not found.
+ */
+int
+addr_is_in_addrbook(PerAddrBook *pab, struct mail_address *addr)
+{
+ AdrBk_Entry *abe = NULL;
+ int ret = 0;
+ char abuf[MAX_ADDR_FIELD+1];
+
+ if(!(pab && addr && addr->mailbox))
+ return(ret);
+
+ if(addr){
+ strncpy(abuf, addr->mailbox, sizeof(abuf)-1);
+ abuf[sizeof(abuf)-1] = '\0';
+ if(addr->host && addr->host[0]){
+ strncat(abuf, "@", sizeof(abuf)-strlen(abuf)-1);
+ strncat(abuf, addr->host, sizeof(abuf)-strlen(abuf)-1);
+ }
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ abe = adrbk_lookup_by_addr(pab->address_book, abuf,
+ (adrbk_cntr_t *) NULL);
+ }
+
+ ret = abe ? 1 : 0;
+
+ return(ret);
+}
+
+
+/*
+ * Look through addrbooks for nickname, opening addrbooks first
+ * if necessary. It is assumed that the caller will restore the
+ * state of the addrbooks if desired.
+ *
+ * Args: nickname -- the nickname to lookup
+ * clearrefs -- clear reference bits before lookup
+ * which_addrbook -- If matched, addrbook number it was found in.
+ * not_here -- If non-negative, skip looking in this abook.
+ *
+ * Results: A pointer to an AdrBk_Entry is returned, or NULL if not found.
+ * Stop at first match (so order of addrbooks is important).
+ */
+AdrBk_Entry *
+adrbk_lookup_with_opens_by_nick(char *nickname, int clearrefs, int *which_addrbook, int not_here)
+{
+ AdrBk_Entry *abe = (AdrBk_Entry *)NULL;
+ int i;
+ PerAddrBook *pab;
+
+ dprint((5, "- adrbk_lookup_with_opens_by_nick(%s) -\n",
+ nickname ? nickname : "?"));
+
+ for(i = 0; i < as.n_addrbk; i++){
+
+ if(i == not_here)
+ continue;
+
+ pab = &as.adrbks[i];
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ if(clearrefs)
+ adrbk_clearrefs(pab->address_book);
+
+ abe = adrbk_lookup_by_nick(pab->address_book,
+ nickname,
+ (adrbk_cntr_t *)NULL);
+ if(abe)
+ break;
+ }
+
+ if(abe && which_addrbook)
+ *which_addrbook = i;
+
+ return(abe);
+}
+
+
+/*
+ * Find the addressbook entry that matches the argument address.
+ * Searches through all addressbooks looking for the match.
+ * Opens addressbooks if necessary. It is assumed that the caller
+ * will restore the state of the addrbooks if desired.
+ *
+ * Args: addr -- the address we're trying to match
+ *
+ * Returns: NULL -- no match found
+ * abe -- a pointer to the addrbook entry that matches
+ */
+AdrBk_Entry *
+address_to_abe(struct mail_address *addr)
+{
+ register PerAddrBook *pab;
+ int adrbk_number;
+ AdrBk_Entry *abe = NULL;
+ char *abuf = NULL;
+
+ if(!(addr && addr->mailbox))
+ return (AdrBk_Entry *)NULL;
+
+ abuf = (char *)fs_get((MAX_ADDR_FIELD + 1) * sizeof(char));
+
+ strncpy(abuf, addr->mailbox, MAX_ADDR_FIELD);
+ abuf[MAX_ADDR_FIELD] = '\0';
+ if(addr->host && addr->host[0]){
+ strncat(abuf, "@", MAX_ADDR_FIELD+1-1-strlen(abuf));
+ strncat(abuf, addr->host, MAX_ADDR_FIELD+1-1-strlen(abuf));
+ }
+
+ /* for each addressbook */
+ for(adrbk_number = 0; adrbk_number < as.n_addrbk; adrbk_number++){
+
+ pab = &as.adrbks[adrbk_number];
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ abe = adrbk_lookup_by_addr(pab->address_book,
+ abuf,
+ (adrbk_cntr_t *)NULL);
+ if(abe)
+ break;
+ }
+
+ if(abuf)
+ fs_give((void **)&abuf);
+
+ return(abe);
+}
+
+
+/*
+ * Turn an AdrBk_Entry into an address list
+ *
+ * Args: abe -- the AdrBk_Entry
+ * dl -- the corresponding dl
+ * abook -- which addrbook the abe is in (only used for type ListEnt)
+ * how_many -- The number of addresses is returned here
+ *
+ * Result: allocated address list or NULL
+ */
+ADDRESS *
+abe_to_address(AdrBk_Entry *abe, AddrScrn_Disp *dl, AdrBk *abook, int *how_many)
+{
+ char *fullname, *tmp_a_string;
+ char *list, *l1, **l2;
+ char *fakedomain = "@";
+ ADDRESS *addr = NULL;
+ size_t length;
+ int count = 0;
+
+ if(!dl || !abe)
+ return(NULL);
+
+ fullname = (abe->fullname && abe->fullname[0]) ? abe->fullname : NULL;
+
+ switch(dl->type){
+ case Simple:
+ /* rfc822_parse_adrlist feels free to destroy input so send copy */
+ tmp_a_string = cpystr(abe->addr.addr);
+ rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
+
+ if(tmp_a_string)
+ fs_give((void **)&tmp_a_string);
+
+ if(addr && fullname){
+#ifdef ENABLE_LDAP
+ if(strncmp(addr->mailbox, RUN_LDAP, LEN_RL) != 0)
+#endif /* ENABLE_LDAP */
+ {
+ if(addr->personal)
+ fs_give((void **)&addr->personal);
+
+ addr->personal = adrbk_formatname(fullname, NULL, NULL);
+ }
+ }
+
+ if(addr)
+ count++;
+
+ break;
+
+ case ListEnt:
+ /* rfc822_parse_adrlist feels free to destroy input so send copy */
+ tmp_a_string = cpystr(listmem_from_dl(abook, dl));
+ rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **)&tmp_a_string);
+
+ if(addr)
+ count++;
+
+ break;
+
+ case ListHead:
+ length = 0;
+ for(l2 = abe->addr.list; *l2; l2++)
+ length += (strlen(*l2) + 1);
+
+ list = (char *)fs_get(length + 1);
+ l1 = list;
+ for(l2 = abe->addr.list; *l2; l2++){
+ if(l1 != list && length-(l1-list) > 0)
+ *l1++ = ',';
+
+ strncpy(l1, *l2, length-(l1-list));
+ list[length] = '\0';
+ l1 += strlen(l1);
+ count++;
+ }
+
+ rfc822_parse_adrlist(&addr, list, fakedomain);
+ if(list)
+ fs_give((void **)&list);
+
+ break;
+
+ default:
+ dprint((1, "default case in abe_to_address, shouldn't happen\n"));
+ break;
+ }
+
+ if(how_many)
+ *how_many = count;
+
+ return(addr);
+}
+
+
+/*
+ * Turn an AdrBk_Entry into a nickname (if it has a nickname) or a
+ * formatted addr_string which has been rfc1522 decoded.
+ *
+ * Args: abe -- the AdrBk_Entry
+ * dl -- the corresponding dl
+ * abook -- which addrbook the abe is in (only used for type ListEnt)
+ *
+ * Result: allocated string is returned
+ */
+char *
+abe_to_nick_or_addr_string(AdrBk_Entry *abe, AddrScrn_Disp *dl, int abook_num)
+{
+ ADDRESS *addr;
+ char *a_string;
+
+ if(!dl || !abe)
+ return(cpystr(""));
+
+ if((dl->type == Simple || dl->type == ListHead)
+ && abe->nickname && abe->nickname[0]){
+ char *fname;
+ int which_addrbook;
+
+ /*
+ * We prefer to pass back the nickname since that allows the
+ * caller to keep track of which entry the address came from.
+ * This is useful in build_address so that the fcc line can
+ * be kept correct. However, if the nickname is also present in
+ * another addressbook then we have to be careful. If that other
+ * addressbook comes before this one then passing back the nickname
+ * will cause the wrong entry to get used. So check for that
+ * and pass back the addr_string in that case.
+ */
+ if((fname = addr_lookup(abe->nickname, &which_addrbook, abook_num)) != NULL){
+ fs_give((void **)&fname);
+ if(which_addrbook >= abook_num)
+ return(cpystr(abe->nickname));
+ }
+ else
+ return(cpystr(abe->nickname));
+ }
+
+ addr = abe_to_address(abe, dl, as.adrbks[abook_num].address_book, NULL);
+ a_string = addr_list_string(addr, NULL, 0); /* always returns a string */
+ if(addr)
+ mail_free_address(&addr);
+
+ return(a_string);
+}
+
+
+/*
+ * Check to see if address is that of the current user running pine
+ *
+ * Args: a -- Address to check
+ * ps -- The pine_state structure
+ *
+ * Result: returns 1 if it matches, 0 otherwise.
+ *
+ * The mailbox must match the user name and the hostname must match.
+ * In matching the hostname, matches occur if the hostname in the address
+ * is blank, or if it matches the local hostname, or the full hostname
+ * with qualifying domain, or the qualifying domain without a specific host.
+ * Note, there is a very small chance that we will err on the
+ * non-conservative side here. That is, we may decide two addresses are
+ * the same even though they are different (because we do case-insensitive
+ * compares on the mailbox). That might cause a reply not to be sent to
+ * somebody because they look like they are us. This should be very,
+ * very rare.
+ *
+ * It is also considered a match if any of the addresses in alt-addresses
+ * matches a. The check there is simpler. It parses each address in
+ * the list, adding maildomain if there wasn't a domain, and compares
+ * mailbox and host in the ADDRESS's for equality.
+ */
+int
+address_is_us(struct mail_address *a, struct pine *ps)
+{
+ char **t;
+ int ret;
+ char addrstr[500];
+
+ if(!a || a->mailbox == NULL || !ps)
+ ret = 0;
+
+ /* at least LHS must match, but case-independent */
+ else if(strucmp(a->mailbox, ps->VAR_USER_ID) == 0
+
+ && /* and hostname matches */
+
+ /* hostname matches if it's not there, */
+ (a->host == NULL ||
+ /* or if hostname and userdomain (the one user sets) match exactly, */
+ ((ps->userdomain && a->host && strucmp(a->host,ps->userdomain) == 0) ||
+
+ /*
+ * or if(userdomain is either not set or it is set to be
+ * the same as the localdomain or hostname) and (the hostname
+ * of the address matches either localdomain or hostname)
+ */
+ ((ps->userdomain == NULL ||
+ strucmp(ps->userdomain, ps->localdomain) == 0 ||
+ strucmp(ps->userdomain, ps->hostname) == 0) &&
+ (strucmp(a->host, ps->hostname) == 0 ||
+ strucmp(a->host, ps->localdomain) == 0)))))
+ ret = 1;
+
+ /*
+ * If no match yet, check to see if it matches any of the alternate
+ * addresses the user has specified.
+ */
+ else if(!ps_global->VAR_ALT_ADDRS ||
+ !ps_global->VAR_ALT_ADDRS[0] ||
+ !ps_global->VAR_ALT_ADDRS[0][0])
+ ret = 0; /* none defined */
+
+ else{
+ ret = 0;
+ if(a && a->host && a->mailbox)
+ snprintf(addrstr, sizeof(addrstr), "%s@%s", a->mailbox, a->host);
+
+ for(t = ps_global->VAR_ALT_ADDRS; !ret && t[0] && t[0][0]; t++){
+ char *alt;
+ regex_t reg;
+ int err;
+ char ebuf[200];
+
+ alt = (*t);
+
+ if(F_ON(F_DISABLE_REGEX, ps_global) || !contains_regex_special_chars(alt)){
+ ADDRESS *alt_addr;
+ char *alt2;
+
+ alt2 = cpystr(alt);
+ alt_addr = NULL;
+ rfc822_parse_adrlist(&alt_addr, alt2, ps_global->maildomain);
+ if(alt2)
+ fs_give((void **) &alt2);
+
+ if(address_is_same(a, alt_addr))
+ ret = 1;
+
+ if(alt_addr)
+ mail_free_address(&alt_addr);
+ }
+ else{
+ /* treat alt as a regular expression */
+ ebuf[0] = '\0';
+ if(!(err=regcomp(&reg, alt, REG_ICASE | REG_NOSUB | REG_EXTENDED))){
+ err = regexec(&reg, addrstr, 0, NULL, 0);
+ if(err == 0)
+ ret = 1;
+ else if(err != REG_NOMATCH){
+ regerror(err, &reg, ebuf, sizeof(ebuf));
+ if(ebuf[0])
+ dprint((2, "- address_is_us regexec error: %s (%s)", ebuf, alt));
+ }
+
+ regfree(&reg);
+ }
+ else{
+ regerror(err, &reg, ebuf, sizeof(ebuf));
+ if(ebuf[0])
+ dprint((2, "- address_is_us regcomp error: %s (%s)", ebuf, alt));
+ }
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * In an ad hoc way try to decide if str is meant to be a regular
+ * expression or not. Dot doesn't count * as regex stuff because
+ * we're worried about addresses.
+ *
+ * Returns 0 or 1
+ */
+int
+contains_regex_special_chars(char *str)
+{
+ char special_chars[] = {'*', '|', '+', '?', '{', '[', '^', '$', '\\', '\0'};
+ char *c;
+
+ if(!str)
+ return 0;
+
+ /*
+ * If any of special_chars are in str consider it a regex expression.
+ */
+ for(c = special_chars; *c; c++)
+ if(strindex(str, *c))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Compare the two addresses, and return true if they're the same,
+ * false otherwise
+ *
+ * Args: a -- First address for comparison
+ * b -- Second address for comparison
+ *
+ * Result: returns 1 if it matches, 0 otherwise.
+ */
+int
+address_is_same(struct mail_address *a, struct mail_address *b)
+{
+ return(a && b && a->mailbox && b->mailbox && a->host && b->host
+ && strucmp(a->mailbox, b->mailbox) == 0
+ && strucmp(a->host, b->host) == 0);
+}
+
+
+/*
+ * Returns nonzero if the two address book entries are equal.
+ * Returns zero if not equal.
+ */
+int
+abes_are_equal(AdrBk_Entry *a, AdrBk_Entry *b)
+{
+ int result = 0;
+ char **alist, **blist;
+ char *addra, *addrb;
+
+ if(a && b &&
+ a->tag == b->tag &&
+ ((!a->nickname && !b->nickname) ||
+ (a->nickname && b->nickname && strcmp(a->nickname,b->nickname) == 0)) &&
+ ((!a->fullname && !b->fullname) ||
+ (a->fullname && b->fullname && strcmp(a->fullname,b->fullname) == 0)) &&
+ ((!a->fcc && !b->fcc) ||
+ (a->fcc && b->fcc && strcmp(a->fcc,b->fcc) == 0)) &&
+ ((!a->extra && !b->extra) ||
+ (a->extra && b->extra && strcmp(a->extra,b->extra) == 0))){
+
+ /* If we made it in here, they still might be equal. */
+ if(a->tag == Single)
+ result = strcmp(a->addr.addr,b->addr.addr) == 0;
+ else{
+ alist = a->addr.list;
+ blist = b->addr.list;
+ if(!alist && !blist)
+ result = 1;
+ else{
+ /* compare the whole lists */
+ addra = *alist;
+ addrb = *blist;
+ while(addra && addrb && strcmp(addra,addrb) == 0){
+ alist++;
+ blist++;
+ addra = *alist;
+ addrb = *blist;
+ }
+
+ if(!addra && !addrb)
+ result = 1;
+ }
+ }
+ }
+
+ return(result);
+}
+
+
+/*
+ * Interface to address book lookups for callers outside or inside this file.
+ *
+ * Args: nickname -- The nickname to look up
+ * which_addrbook -- If matched, addrbook number it was found in.
+ * not_here -- If non-negative, skip looking in this abook.
+ *
+ * Result: returns NULL or the corresponding fullname. The fullname is
+ * allocated here so the caller must free it.
+ *
+ * This opens the address books if they haven't been opened and restores
+ * them to the state they were in upon entry.
+ */
+char *
+addr_lookup(char *nickname, int *which_addrbook, int not_here)
+{
+ AdrBk_Entry *abe;
+ SAVE_STATE_S state;
+ char *fullname;
+
+ dprint((9, "- addr_lookup(%s) -\n",nickname ? nickname : "nil"));
+
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ abe = adrbk_lookup_with_opens_by_nick(nickname,0,which_addrbook,not_here);
+
+ fullname = (abe && abe->fullname) ? cpystr(abe->fullname) : NULL;
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ return(fullname);
+}
+
+
+/*
+ * Look in all of the address books for all of the possible entries
+ * that match the query string. The matches can be for the nickname,
+ * for the fullname, or for the address@host part of the address.
+ * All of the matches are at the starts of the strings, not a general
+ * substring match. This is not true anymore. Fullname matches can be
+ * at the start of the fullname or starting after a space in the fullname.
+ * If flags has ALC_INCLUDE_LDAP defined then LDAP
+ * entries are added to the end of the list. The LDAP queries are done
+ * only for those servers that have the 'impl' feature turned on, which
+ * means that lookups should be done implicitly. This feature also
+ * controls whether or not lookups should be done when typing carriage
+ * return (instead of this which is TAB).
+ *
+ * Args query -- What the user has typed so far
+ *
+ * Returns a list of possibilities for the given query string.
+ *
+ * Caller needs to free the answer.
+ */
+COMPLETE_S *
+adrbk_list_of_completions(char *query, MAILSTREAM *stream, imapuid_t uid, int flags)
+{
+ int i;
+ SAVE_STATE_S state;
+ PerAddrBook *pab;
+ ABOOK_ENTRY_S *list, *list2, *biglist = NULL;
+ COMPLETE_S *return_list = NULL, *last_one_added = NULL, *new, *cp, *dp, *dprev;
+ BuildTo toaddr;
+ ADDRESS *addr;
+ char buf[1000];
+ char *newaddr = NULL, *simple_addr = NULL, *fcc = NULL;
+ ENVELOPE *env = NULL;
+ BODY *body = NULL;
+
+ init_ab_if_needed();
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_SAVE, &state);
+
+ for(i = 0; i < as.n_addrbk; i++){
+
+ pab = &as.adrbks[i];
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ list = adrbk_list_of_possible_completions(pab ? pab->address_book : NULL, query);
+ combine_abook_entry_lists(&biglist, list);
+ }
+
+ /*
+ * Eliminate duplicates by NO_NEXTing the entrynums.
+ */
+ for(list = biglist; list; list = list->next)
+ /* eliminate any dups further along in the list */
+ if(list->entrynum != NO_NEXT)
+ for(list2 = list->next; list2; list2 = list2->next)
+ if(list2->entrynum == list->entrynum){
+ list2->entrynum = NO_NEXT;
+ list->matches_bitmap |= list2->matches_bitmap;
+ }
+
+ /* build the return list */
+ for(list = biglist; list; list = list->next)
+ if(list->entrynum != NO_NEXT){
+ fcc = NULL;
+ toaddr.type = Abe;
+ toaddr.arg.abe = adrbk_get_ae(list->ab, list->entrynum);
+ if(our_build_address(toaddr, &newaddr, NULL, &fcc, NULL) == 0){
+ char *reverse_fullname = NULL;
+
+ /*
+ * ALC_FULL is a regular FullName match and that will be
+ * captured in the full_address field. If there was also
+ * an ALC_REVFULL match that means that the user has the
+ * FullName entered in their addrbook as Last, First and
+ * that is where the match was. We want to put that in
+ * the completions structure in the rev_fullname field.
+ */
+ if(list->matches_bitmap & ALC_REVFULL
+ && toaddr.arg.abe
+ && toaddr.arg.abe->fullname && toaddr.arg.abe->fullname[0]
+ && toaddr.arg.abe->fullname[0] != '"'
+ && strindex(toaddr.arg.abe->fullname, ',') != NULL){
+
+ reverse_fullname = toaddr.arg.abe->fullname;
+ }
+
+ if(flags & ALC_INCLUDE_ADDRS){
+ if(toaddr.arg.abe && toaddr.arg.abe->tag == Single
+ && toaddr.arg.abe->addr.addr && toaddr.arg.abe->addr.addr[0]){
+ char *tmp_a_string;
+ char *fakedomain = "@";
+
+ tmp_a_string = cpystr(toaddr.arg.abe->addr.addr);
+ addr = NULL;
+ rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **) &tmp_a_string);
+
+ if(addr){
+ if(addr->mailbox && addr->host
+ && !(addr->host[0] == '@' && addr->host[1] == '\0'))
+ simple_addr = simple_addr_string(addr, buf, sizeof(buf));
+
+ mail_free_address(&addr);
+ }
+ }
+ }
+
+ new = new_complete_s(toaddr.arg.abe ? toaddr.arg.abe->nickname : NULL,
+ newaddr, simple_addr, reverse_fullname, fcc,
+ list->matches_bitmap | ALC_ABOOK);
+
+ /* add to end of list */
+ if(return_list == NULL){
+ return_list = new;
+ last_one_added = new;
+ }
+ else{
+ last_one_added->next = new;
+ last_one_added = new;
+ }
+ }
+
+ if(newaddr)
+ fs_give((void **) &newaddr);
+ }
+
+ if(pith_opt_save_and_restore)
+ (*pith_opt_save_and_restore)(SAR_RESTORE, &state);
+
+ free_abook_entry_s(&biglist);
+
+#ifdef ENABLE_LDAP
+ if(flags & ALC_INCLUDE_LDAP){
+ LDAP_SERV_RES_S *head_of_result_list = NULL, *res;
+ LDAP_CHOOSE_S cs;
+ WP_ERR_S wp_err;
+ LDAPMessage *e;
+
+ memset(&wp_err, 0, sizeof(wp_err));
+
+ /*
+ * This lookup covers all servers with the impl bit set.
+ * It uses the regular LDAP search parameters that the
+ * user has set, not necessarily just a prefix match
+ * like the rest of the address completion above.
+ */
+ head_of_result_list = ldap_lookup_all_work(query, as.n_serv, 0, NULL, &wp_err);
+ for(res = head_of_result_list; res; res = res->next){
+ for(e = ldap_first_entry(res->ld, res->res);
+ e != NULL;
+ e = ldap_next_entry(res->ld, e)){
+ simple_addr = newaddr = NULL;
+ cs.ld = res->ld;
+ cs.selected_entry = e;
+ cs.info_used = res->info_used;
+ cs.serv = res->serv;
+ addr = address_from_ldap(&cs);
+ if(addr){
+ add_addr_to_return_list(addr, ALC_LDAP, query, flags, &return_list);
+ mail_free_address(&addr);
+ }
+ }
+ }
+
+ if(wp_err.error)
+ fs_give((void **) &wp_err.error);
+
+ if(head_of_result_list)
+ free_ldap_result_list(&head_of_result_list);
+ }
+#endif /* ENABLE_LDAP */
+
+ /* add from current message */
+ if(uid > 0 && stream)
+ env = pine_mail_fetch_structure(stream, uid, &body, FT_UID);
+
+ /* from the envelope addresses */
+ if(env){
+ for(addr = env->from; addr; addr = addr->next)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+
+ for(addr = env->reply_to; addr; addr = addr->next)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+
+ for(addr = env->sender; addr; addr = addr->next)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+
+ for(addr = env->to; addr; addr = addr->next)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+
+ for(addr = env->cc; addr; addr = addr->next)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+
+ /*
+ * May as well search the body for addresses.
+ * Use this function written for TakeAddr.
+ */
+ if(body){
+ TA_S *talist = NULL, *tp;
+
+ if(grab_addrs_from_body(stream, mail_msgno(stream,uid), body, &talist) > 0){
+ if(talist){
+
+ /* rewind to start */
+ while(talist->prev)
+ talist = talist->prev;
+
+ for(tp = talist; tp; tp = tp->next){
+ addr = tp->addr;
+ if(addr)
+ add_addr_to_return_list(addr, ALC_CURR, query, flags, &return_list);
+ }
+
+ free_talines(&talist);
+ }
+ }
+ }
+ }
+
+
+ /*
+ * Check for and eliminate some duplicates.
+ * The criteria for deciding what is a duplicate is
+ * kind of ad hoc.
+ */
+ for(cp = return_list; cp; cp = cp->next)
+ for(dprev = cp, dp = cp->next; dp; ){
+ if(cp->full_address && dp->full_address
+ && !strucmp(dp->full_address, cp->full_address)
+ && (((cp->matches_bitmap & ALC_ABOOK)
+ && (dp->matches_bitmap & ALC_ABOOK)
+ && (!(dp->matches_bitmap & ALC_NICK && dp->nickname && dp->nickname[0])
+ || ((!cp->addr && !dp->addr) || (cp->addr && dp->addr && !strucmp(cp->addr, dp->addr)))))
+ ||
+ (dp->matches_bitmap & ALC_CURR)
+ ||
+ (dp->matches_bitmap & ALC_LDAP
+ && dp->matches_bitmap & (ALC_FULL | ALC_ADDR))
+ ||
+ (cp->matches_bitmap == dp->matches_bitmap
+ && (!(dp->matches_bitmap & ALC_NICK && dp->nickname && dp->nickname[0])
+ || (dp->nickname && cp->nickname
+ && !strucmp(cp->nickname, dp->nickname)))))){
+ /*
+ * dp is equivalent to cp so eliminate dp
+ */
+ dprev->next = dp->next;
+ dp->next = NULL;
+ free_complete_s(&dp);
+ dp = dprev;
+ }
+
+ dprev = dp;
+ dp = dp->next;
+ }
+
+ return(return_list);
+}
+
+
+void
+add_addr_to_return_list(ADDRESS *addr, unsigned bitmap, char *query,
+ int flags, COMPLETE_S **return_list)
+{
+ char buf[1000];
+ char *newaddr = NULL;
+ char *simple_addr = NULL;
+ COMPLETE_S *new = NULL, *cp;
+ ADDRESS *savenext;
+
+ if(return_list && query && addr && addr->mailbox && addr->host){
+
+ savenext = addr->next;
+ addr->next = NULL;
+ newaddr = addr_list_string(addr, NULL, 0);
+ addr->next = savenext;
+
+ /*
+ * If the start of the full_address actually matches the query
+ * string then mark this as ALC_FULL. This might be helpful
+ * when deciding on the longest unambiguous match.
+ */
+ if(newaddr && newaddr[0] && !struncmp(newaddr, query, strlen(query)))
+ bitmap |= ALC_FULL;
+
+ if(newaddr && newaddr[0] && flags & ALC_INCLUDE_ADDRS){
+ if(addr->mailbox && addr->host
+ && !(addr->host[0] == '@' && addr->host[1] == '\0'))
+ simple_addr = simple_addr_string(addr, buf, sizeof(buf));
+
+ if(simple_addr && !simple_addr[0])
+ simple_addr = NULL;
+
+ if(simple_addr && !struncmp(simple_addr, query, strlen(query)))
+ bitmap |= ALC_ADDR;
+ }
+
+ /*
+ * We used to require && bitmap & (ALC_FULL | ALC_ADDR) before
+ * we would add a match but we think that we should match
+ * other stuff that matches (like middle names), too, unless we
+ * are adding an address from the current message.
+ */
+ if((newaddr && newaddr[0])
+ && (!(bitmap & ALC_CURR) || bitmap & (ALC_FULL | ALC_ADDR))){
+ new = new_complete_s(NULL, newaddr, simple_addr, NULL, NULL, bitmap);
+
+ /* add to end of list */
+ if(*return_list == NULL){
+ *return_list = new;
+ }
+ else{
+ for(cp = *return_list; cp->next; cp = cp->next)
+ ;
+
+ cp->next = new;
+ }
+ }
+
+ if(newaddr)
+ fs_give((void **) &newaddr);
+ }
+}
+
+
+/*
+ * nick = nickname
+ * full = whole thing, like Some Body <someb@there.org>
+ * addr = address part, like someb@there.org
+ */
+COMPLETE_S *
+new_complete_s(char *nick, char *full, char *addr,
+ char *rev_fullname, char *fcc, unsigned matches_bitmap)
+{
+ COMPLETE_S *new = NULL;
+
+ new = (COMPLETE_S *) fs_get(sizeof(*new));
+ memset((void *) new, 0, sizeof(*new));
+ new->nickname = nick ? cpystr(nick) : NULL;
+ new->full_address = full ? cpystr(full) : NULL;
+ new->addr = addr ? cpystr(addr) : NULL;
+ new->rev_fullname = rev_fullname ? cpystr(rev_fullname) : NULL;
+ new->fcc = fcc ? cpystr(fcc) : NULL;
+ new->matches_bitmap = matches_bitmap;
+
+ return(new);
+}
+
+
+void
+free_complete_s(COMPLETE_S **compptr)
+{
+ if(compptr && *compptr){
+ if((*compptr)->next)
+ free_complete_s(&(*compptr)->next);
+
+ if((*compptr)->nickname)
+ fs_give((void **) &(*compptr)->nickname);
+
+ if((*compptr)->full_address)
+ fs_give((void **) &(*compptr)->full_address);
+
+ if((*compptr)->addr)
+ fs_give((void **) &(*compptr)->addr);
+
+ if((*compptr)->rev_fullname)
+ fs_give((void **) &(*compptr)->rev_fullname);
+
+ if((*compptr)->fcc)
+ fs_give((void **) &(*compptr)->fcc);
+
+ fs_give((void **) compptr);
+ }
+}
+
+
+ABOOK_ENTRY_S *
+new_abook_entry_s(AdrBk *ab, a_c_arg_t numarg, unsigned bit)
+{
+ ABOOK_ENTRY_S *new = NULL;
+ adrbk_cntr_t entrynum;
+
+ entrynum = (adrbk_cntr_t) numarg;
+
+ new = (ABOOK_ENTRY_S *) fs_get(sizeof(*new));
+ memset((void *) new, 0, sizeof(*new));
+ new->ab = ab;
+ new->entrynum = entrynum;
+ new->matches_bitmap = bit;
+
+ return(new);
+}
+
+
+void
+free_abook_entry_s(ABOOK_ENTRY_S **aep)
+{
+ if(aep && *aep){
+ if((*aep)->next)
+ free_abook_entry_s(&(*aep)->next);
+
+ fs_give((void **) aep);
+ }
+}
+
+
+/*
+ * Add the second list to the end of the first.
+ */
+void
+combine_abook_entry_lists(ABOOK_ENTRY_S **first, ABOOK_ENTRY_S *second)
+{
+ ABOOK_ENTRY_S *sl;
+
+ if(!second)
+ return;
+
+ if(first){
+ if(*first){
+ for(sl = *first; sl->next; sl = sl->next)
+ ;
+
+ sl->next = second;
+ }
+ else
+ *first = second;
+ }
+}
+
+
+ABOOK_ENTRY_S *
+adrbk_list_of_possible_completions(AdrBk *ab, char *prefix)
+{
+ ABOOK_ENTRY_S *list = NULL, *biglist = NULL;
+
+ if(!ab || !prefix)
+ return(biglist);
+
+ if(ab->nick_trie){
+ list = adrbk_list_of_possible_trie_completions(ab->nick_trie, ab, prefix, ALC_NICK);
+ combine_abook_entry_lists(&biglist, list);
+ }
+
+ if(ab->full_trie){
+ list = adrbk_list_of_possible_trie_completions(ab->full_trie, ab, prefix, ALC_FULL);
+ combine_abook_entry_lists(&biglist, list);
+ }
+
+ if(ab->addr_trie){
+ list = adrbk_list_of_possible_trie_completions(ab->addr_trie, ab, prefix, ALC_ADDR);
+ combine_abook_entry_lists(&biglist, list);
+ }
+
+ if(ab->revfull_trie){
+ list = adrbk_list_of_possible_trie_completions(ab->revfull_trie, ab, prefix, ALC_REVFULL);
+ combine_abook_entry_lists(&biglist, list);
+ }
+
+ return(biglist);
+}
+
+
+/*
+ * Look in this address book for all nicknames, addresses, or fullnames
+ * which begin with the prefix prefix, and return an allocated
+ * list of them.
+ */
+ABOOK_ENTRY_S *
+adrbk_list_of_possible_trie_completions(AdrBk_Trie *trie, AdrBk *ab, char *prefix,
+ unsigned bit)
+{
+ AdrBk_Trie *t;
+ char *p, *lookthisup;
+ char buf[1000];
+ ABOOK_ENTRY_S *list = NULL;
+
+ if(!ab || !prefix || !trie)
+ return(list);
+
+ t = trie;
+
+ /* make lookup case independent */
+
+ for(p = prefix; *p && !(*p & 0x80) && islower((unsigned char) *p); p++)
+ ;
+
+ if(*p){
+ strncpy(buf, prefix, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ for(p = buf; *p; p++)
+ if(!(*p & 0x80) && isupper((unsigned char) *p))
+ *p = tolower(*p);
+
+ lookthisup = buf;
+ }
+ else
+ lookthisup = prefix;
+
+ p = lookthisup;
+
+ while(*p){
+ /* search for character at this level */
+ while(t->value != *p){
+ if(t->right == NULL)
+ return(list); /* no match */
+
+ t = t->right;
+ }
+
+ if(*++p == '\0') /* matched through end of prefix */
+ break;
+
+ /* need to go down to match next character */
+ if(t->down == NULL) /* no match */
+ return(list);
+
+ t = t->down;
+ }
+
+ /*
+ * If we get here that means we found at least
+ * one entry that matches up through prefix.
+ * Gather_abook_list recursively adds the nicknames starting at
+ * this node.
+ */
+ if(t->entrynum != NO_NEXT){
+ /*
+ * Add it to the list.
+ */
+ list = new_abook_entry_s(ab, t->entrynum, bit);
+ }
+
+ gather_abook_entry_list(ab, t->down, prefix, &list, bit);
+
+ return(list);
+}
+
+
+void
+gather_abook_entry_list(AdrBk *ab, AdrBk_Trie *node, char *prefix, ABOOK_ENTRY_S **list, unsigned bit)
+{
+ char *next_prefix = NULL;
+ size_t l;
+ ABOOK_ENTRY_S *newlist = NULL;
+
+ if(node){
+ if(node->entrynum != NO_NEXT || node->down || node->right){
+ l = strlen(prefix ? prefix : "");
+ if(node->entrynum != NO_NEXT){
+ /*
+ * Add it to the list.
+ */
+ newlist = new_abook_entry_s(ab, node->entrynum, bit);
+ combine_abook_entry_lists(list, newlist);
+ }
+
+ /* same prefix for node->right */
+ if(node->right)
+ gather_abook_entry_list(ab, node->right, prefix, list, bit);
+
+ /* prefix is one longer for node->down */
+ if(node->down){
+ next_prefix = (char *) fs_get((l+2) * sizeof(char));
+ strncpy(next_prefix, prefix ? prefix : "", l+2);
+ next_prefix[l] = node->value;
+ next_prefix[l+1] = '\0';
+ gather_abook_entry_list(ab, node->down, next_prefix, list, bit);
+
+ if(next_prefix)
+ fs_give((void **) &next_prefix);
+ }
+ }
+ }
+}
diff --git a/pith/ablookup.h b/pith/ablookup.h
new file mode 100644
index 00000000..4ffb7aa5
--- /dev/null
+++ b/pith/ablookup.h
@@ -0,0 +1,106 @@
+/*
+ * $Id: ablookup.h 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ABLOOKUP_INCLUDED
+#define PITH_ABLOOKUP_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/adrbklib.h"
+#include "../pith/bldaddr.h"
+
+
+/*
+ * Flags to adrbk_list_of_completions().
+ * ALC_INCLUDE_ADDRS means that the "addr" element
+ * should be included in the COMPLETE_S list. When
+ * this is not set the entries that match because
+ * of an addr match are still included in the list
+ * but the actual "addr" element itself is not filled
+ * in. That isn't needed by Web Alpine and it does
+ * have a cost, though rather small, to fill it in.
+ */
+#define ALC_INCLUDE_ADDRS 0x1
+#define ALC_INCLUDE_LDAP 0x2
+
+/*
+ * Values OR'd together in matches_bitmaps.
+ * These are not in the same namespace as the flags
+ * above, these are just bits for the matches_bitmaps member.
+ *
+ * If ALC_NICK is set that means there was a prefix match on the nickname.
+ * ALC_FULL is a match on the full address.
+ * ALC_ADDR is a match on the mailbox@host part of the address.
+ * ALC_REVFULL is a match on the Last, First Fullname if the user uses that.
+ * ALC_ABOOK means the entry came from an address book entry
+ * ALC_LDAP means the entry came from an LDAP lookup
+ * ALC_CURR means the entry came from the currently viewed message
+ * This is used in both ABOOK_ENTRY_S and COMPLETE_S structures.
+ */
+#define ALC_NICK 0x01
+#define ALC_FULL 0x02
+#define ALC_REVFULL 0x04
+#define ALC_ADDR 0x08
+#define ALC_ABOOK 0x10
+#define ALC_LDAP 0x20
+#define ALC_CURR 0x40
+#define ALC_FCC 0x80
+
+
+typedef struct abook_entry_list {
+ AdrBk *ab;
+ adrbk_cntr_t entrynum;
+ unsigned matches_bitmap;
+ struct abook_entry_list *next;
+} ABOOK_ENTRY_S;
+
+
+typedef struct completelist {
+ char *nickname; /* the nickname */
+ char *full_address; /* Some Body <someb@there.org> */
+ char *addr; /* someb@there.org (costs extra) */
+ char *rev_fullname; /* optional Last, First version of fullname */
+ char *fcc; /* optional fcc associated with address */
+ unsigned matches_bitmap;
+ struct completelist *next;
+} COMPLETE_S;
+
+
+/* exported protoypes */
+char *get_nickname_from_addr(ADDRESS *, char *, size_t);
+char *get_fcc_from_addr(ADDRESS *, char *, size_t);
+int get_contactinfo_from_addr(ADDRESS *, char **, char **, char **, char **);
+void address_in_abook(MAILSTREAM *, SEARCHSET *, int, PATTERN_S *);
+ADDRESS *abe_to_address(AdrBk_Entry *, AddrScrn_Disp *, AdrBk *, int *);
+char *abe_to_nick_or_addr_string(AdrBk_Entry *, AddrScrn_Disp *, int);
+int address_is_us(ADDRESS *, struct pine *);
+int address_is_same(ADDRESS *, ADDRESS *);
+int abes_are_equal(AdrBk_Entry *, AdrBk_Entry *);
+char *addr_lookup(char *, int *, int);
+AdrBk_Entry *adrbk_lookup_with_opens_by_nick(char *, int, int *, int);
+AdrBk_Entry *address_to_abe(ADDRESS *);
+int contains_regex_special_chars(char *str);
+COMPLETE_S *adrbk_list_of_completions(char *, MAILSTREAM *, imapuid_t, int);
+COMPLETE_S *new_complete_s(char *, char *, char *, char *, char *, unsigned);
+void free_complete_s(COMPLETE_S **);
+ABOOK_ENTRY_S *new_abook_entry_s(AdrBk *, a_c_arg_t, unsigned);
+void free_abook_entry_s(ABOOK_ENTRY_S **);
+void combine_abook_entry_lists(ABOOK_ENTRY_S **, ABOOK_ENTRY_S *);
+ABOOK_ENTRY_S *adrbk_list_of_possible_completions(AdrBk *, char *);
+
+
+#endif /* PITH_ABLOOKUP_INCLUDED */
diff --git a/pith/addrbook.c b/pith/addrbook.c
new file mode 100644
index 00000000..9828e4c6
--- /dev/null
+++ b/pith/addrbook.c
@@ -0,0 +1,369 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: addrbook.c 90 2006-07-19 22:30:36Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ addrbook.c
+ display format/support routines
+ ====*/
+
+#include "../c-client/c-client.h"
+
+#include <system.h>
+#include <general.h>
+
+#include "addrbook.h"
+#include "state.h"
+#include "adrbklib.h"
+#include "abdlc.h"
+#include "conf.h"
+#include "conftype.h"
+#include "status.h"
+#include "debug.h"
+#include "osdep/collate.h"
+
+
+/* internal prototypes */
+void parse_format(char *, COL_S *);
+
+
+
+
+/*
+ * We have to call this to set up the format of the columns. There is a
+ * separate format for each addrbook, so we need to call this for each
+ * addrbook. We call it when the pab's are built. It also depends on
+ * whether or not as.checkboxes is set, so if we go into a Select mode
+ * from the address book maintenance screen we need to re-call this. Since
+ * we can't go back out of ListMode we don't have that problem. Restore_state
+ * has to call it because of the as.checkboxes possibly being different in
+ * the two states.
+ */
+void
+addrbook_new_disp_form(PerAddrBook *pab, char **list, int addrbook_num,
+ int (*prefix_f)(PerAddrBook *, int *))
+{
+ char *last_one;
+ int column = 0;
+
+ dprint((9, "- init_disp_form(%s) -\n",
+ (pab && pab->abnick) ? pab->abnick : "?"));
+
+ memset((void *)pab->disp_form, 0, NFIELDS*sizeof(COL_S));
+ pab->disp_form[1].wtype = WeCalculate; /* so we don't get false AllAuto */
+
+ if(prefix_f)
+ as.do_bold = (*prefix_f)(pab, &column);
+
+ /* if custom format is specified */
+ if(list && list[0] && list[0][0]){
+ /* find the one for addrbook_num */
+ for(last_one = *list;
+ *list != NULL && addrbook_num;
+ addrbook_num--,list++)
+ last_one = *list;
+
+ /* If not enough to go around, last one repeats */
+ if(*list == NULL)
+ parse_format(last_one, &(pab->disp_form[column]));
+ else
+ parse_format(*list, &(pab->disp_form[column]));
+ }
+ else{ /* default */
+ /* If 2nd wtype is AllAuto, the widths are calculated old way */
+ pab->disp_form[1].wtype = AllAuto;
+
+ pab->disp_form[column++].type = Nickname;
+ pab->disp_form[column++].type = Fullname;
+ pab->disp_form[column++].type = Addr;
+ /* Fill in rest */
+ while(column < NFIELDS)
+ pab->disp_form[column++].type = Notused;
+ }
+}
+
+
+struct parse_tokens {
+ char *name;
+ ColumnType ctype;
+};
+
+struct parse_tokens ptokens[] = {
+ {"NICKNAME", Nickname},
+ {"FULLNAME", Fullname},
+ {"ADDRESS", Addr},
+ {"FCC", Filecopy},
+ {"COMMENT", Comment},
+ {"DEFAULT", Def},
+ {NULL, Notused}
+};
+
+/*
+ * Parse format_str and fill in disp_form structure based on what's there.
+ *
+ * Args: format_str -- The format string from pinerc.
+ * disp_form -- This is where we fill in the answer.
+ *
+ * The format string consists of special tokens which give the order of
+ * the columns to be displayed. The possible tokens are NICKNAME,
+ * FULLNAME, ADDRESS, FCC, COMMENT. If a token is followed by
+ * parens with an integer inside (FULLNAME(16)) then that means we
+ * make that variable that many characters wide. If it is a percentage, we
+ * allocate that percentage of the columns to that variable. If no
+ * parens, that means we calculate it for the user. The tokens are
+ * delimited by white space. A token of DEFAULT means to calculate the
+ * whole thing as we would if no spec was given. This makes it possible
+ * to specify default for one addrbook and something special for another.
+ */
+void
+parse_format(char *format_str, COL_S *disp_form)
+{
+ int column = 0;
+ char *p, *q;
+ struct parse_tokens *pt;
+ int nicknames, fullnames, addresses, not_allauto;
+ int warnings = 0;
+
+ p = format_str;
+ while(p && *p && column < NFIELDS){
+ p = skip_white_space(p); /* space for next word */
+
+ /* look for the ptoken this word matches */
+ for(pt = ptokens; pt->name; pt++)
+ if(!struncmp(pt->name, p, strlen(pt->name)))
+ break;
+
+ /* ignore unrecognized word */
+ if(!pt->name){
+ char *r;
+
+ if((r=strindex(p, SPACE)) != NULL)
+ *r = '\0';
+
+ dprint((2, "parse_format: ignoring unrecognized word \"%s\" in address-book-formats\n", p ? p : "?"));
+ q_status_message1(SM_ORDER, warnings++==0 ? 1 : 0, 4,
+ /* TRANSLATORS: an informative error message */
+ _("Ignoring unrecognized word \"%s\" in Address-Book-Formats"), p);
+ /* put back space */
+ if(r)
+ *r = SPACE;
+
+ /* skip unrecognized word */
+ while(p && *p && !isspace((unsigned char)(*p)))
+ p++;
+
+ continue;
+ }
+
+ disp_form[column].type = pt->ctype;
+
+ /* skip over name and look for parens */
+ p += strlen(pt->name);
+ if(*p == '('){
+ p++;
+ q = p;
+ while(p && *p && isdigit((unsigned char)*p))
+ p++;
+
+ if(p && *p && *p == ')' && p > q){
+ disp_form[column].wtype = Fixed;
+ disp_form[column].req_width = atoi(q);
+ }
+ else if(p && *p && *p == '%' && p > q){
+ disp_form[column].wtype = Percent;
+ disp_form[column].req_width = atoi(q);
+ }
+ else{
+ disp_form[column].wtype = WeCalculate;
+ if(disp_form[column].type == Nickname)
+ disp_form[column].req_width = 8;
+ else
+ disp_form[column].req_width = 3;
+ }
+ }
+ else{
+ disp_form[column].wtype = WeCalculate;
+ if(disp_form[column].type == Nickname)
+ disp_form[column].req_width = 8;
+ else
+ disp_form[column].req_width = 3;
+ }
+
+ if(disp_form[column].type == Def){
+ /* If any type is DEFAULT, the widths are calculated old way */
+assign_default:
+ column = 0;
+
+ disp_form[column].wtype = AllAuto;
+ disp_form[column++].type = Nickname;
+ disp_form[column].wtype = AllAuto;
+ disp_form[column++].type = Fullname;
+ disp_form[column].wtype = AllAuto;
+ disp_form[column++].type = Addr;
+ /* Fill in rest */
+ while(column < NFIELDS)
+ disp_form[column++].type = Notused;
+
+ return;
+ }
+
+ column++;
+ /* skip text at end of word */
+ while(p && *p && !isspace((unsigned char)(*p)))
+ p++;
+ }
+
+ if(column == 0){
+ q_status_message(SM_ORDER, 0, 4,
+ _("Address-Book-Formats has no recognizable words, using default format"));
+ goto assign_default;
+ }
+
+ /* Fill in rest */
+ while(column < NFIELDS)
+ disp_form[column++].type = Notused;
+
+ /* check to see if user is just re-ordering default fields */
+ nicknames = 0;
+ fullnames = 0;
+ addresses = 0;
+ not_allauto = 0;
+ for(column = 0; column < NFIELDS; column++){
+ if(disp_form[column].type != Notused
+ && disp_form[column].wtype != WeCalculate)
+ not_allauto++;
+
+ switch(disp_form[column].type){
+ case Nickname:
+ nicknames++;
+ break;
+
+ case Fullname:
+ fullnames++;
+ break;
+
+ case Addr:
+ addresses++;
+ break;
+
+ case Filecopy:
+ case Comment:
+ not_allauto++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Special case: if there is no address field specified, we put in
+ * a special field called WhenNoAddrDisplayed, which causes list
+ * entries to be displayable in all cases.
+ */
+ if(!addresses){
+ for(column = 0; column < NFIELDS; column++)
+ if(disp_form[column].type == Notused)
+ break;
+
+ if(column < NFIELDS){
+ disp_form[column].type = WhenNoAddrDisplayed;
+ disp_form[column].wtype = Special;
+ }
+ }
+
+ if(nicknames == 1 && fullnames == 1 && addresses == 1 && not_allauto == 0)
+ disp_form[0].wtype = AllAuto; /* set to do default widths */
+}
+
+
+/*
+ * Find the first selectable line greater than or equal to line. That is,
+ * the first line the cursor is allowed to start on.
+ * (If there are none >= line, it will find the highest one.)
+ *
+ * Returns the line number of the found line or NO_LINE if there isn't one.
+ */
+long
+first_selectable_line(long int line)
+{
+ long lineno;
+ register PerAddrBook *pab;
+ int i;
+
+ /* skip past non-selectable lines */
+ for(lineno=line;
+ !line_is_selectable(lineno) && dlist(lineno)->type != End;
+ lineno++)
+ ;/* do nothing */
+
+ if(line_is_selectable(lineno))
+ return(lineno);
+
+ /*
+ * There were no selectable lines from lineno on down. Trying looking
+ * back up the list.
+ */
+ for(lineno=line-1;
+ !line_is_selectable(lineno) && dlist(lineno)->type != Beginning;
+ lineno--)
+ ;/* do nothing */
+
+ if(line_is_selectable(lineno))
+ return(lineno);
+
+ /*
+ * No selectable lines at all.
+ * If some of the addrbooks are still not displayed, it is too
+ * early to set the no_op_possbl flag. Or, if some of the addrbooks
+ * are empty but writable, then we should not set it either.
+ */
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->ostatus != Open &&
+ pab->ostatus != HalfOpen &&
+ pab->ostatus != ThreeQuartOpen)
+ return NO_LINE;
+
+ if(pab->access == ReadWrite && adrbk_count(pab->address_book) == 0)
+ return NO_LINE;
+ }
+
+ as.no_op_possbl++;
+ return NO_LINE;
+}
+
+
+/*
+ * Returns 1 if this line is of a type that can have a cursor on it.
+ */
+int
+line_is_selectable(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && (dl->type == Text ||
+ dl->type == ListEmpty ||
+ dl->type == TitleCmb ||
+ dl->type == Beginning ||
+ dl->type == End)){
+
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/pith/addrbook.h b/pith/addrbook.h
new file mode 100644
index 00000000..9c328846
--- /dev/null
+++ b/pith/addrbook.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: addrbook.h 82 2006-07-12 23:36:59Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ADDRBOOK_INCLUDED
+#define PITH_ADDRBOOK_INCLUDED
+
+
+#include "adrbklib.h"
+
+
+/* exported protoypes */
+void addrbook_new_disp_form(PerAddrBook *, char **, int, int (*)(PerAddrBook *, int *));
+long first_selectable_line(long);
+int line_is_selectable(long);
+
+
+#endif /* PITH_ADDRBOOK_INCLUDED */
diff --git a/pith/addrstring.c b/pith/addrstring.c
new file mode 100644
index 00000000..928de43e
--- /dev/null
+++ b/pith/addrstring.c
@@ -0,0 +1,452 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: addrstring.c 770 2007-10-24 00:23:09Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/addrstring.h"
+#include "../pith/state.h"
+#include "../pith/copyaddr.h"
+#include "../pith/charset.h"
+#include "../pith/stream.h"
+
+
+/*
+ * Internal prototypes
+ */
+void rfc822_write_address_decode(char *, size_t, ADDRESS *, int);
+
+
+/*
+ * Format an address structure into a string
+ *
+ * Args: addr -- Single ADDRESS structure to turn into a string
+ *
+ * Result: Fills in buf and returns pointer to it.
+ * Just uses the c-client call to do this.
+ * (the address is not rfc1522 decoded)
+ */
+char *
+addr_string(struct mail_address *addr, char *buf, size_t buflen)
+{
+ ADDRESS *next_addr;
+ RFC822BUFFER rbuf;
+
+ *buf = '\0';
+ next_addr = addr->next;
+ addr->next = NULL;
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = buf;
+ rbuf.cur = buf;
+ rbuf.end = buf+buflen-1;
+ rfc822_output_address_list(&rbuf, addr, 0L, NULL);
+ *rbuf.cur = '\0';
+ addr->next = next_addr;
+ return(buf);
+}
+
+
+/*
+ * Same as addr_string only it doesn't have to be a
+ * single address.
+ */
+char *
+addr_string_mult(struct mail_address *addr, char *buf, size_t buflen)
+{
+ RFC822BUFFER rbuf;
+
+ *buf = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = buf;
+ rbuf.cur = buf;
+ rbuf.end = buf+buflen-1;
+ rfc822_output_address_list(&rbuf, addr, 0L, NULL);
+ *rbuf.cur = '\0';
+ return(buf);
+}
+
+
+/*
+ * Format an address structure into a simple string: "mailbox@host"
+ *
+ * Args: addr -- Single ADDRESS structure to turn into a string
+ * buf -- buffer to write address in;
+ *
+ * Result: Returns pointer to buf;
+ */
+char *
+simple_addr_string(struct mail_address *addr, char *buf, size_t buflen)
+{
+ RFC822BUFFER rbuf;
+
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = buf;
+ rbuf.cur = buf;
+ rbuf.end = buf+buflen-1;
+ rfc822_output_address(&rbuf, addr);
+ *rbuf.cur = '\0';
+
+ return(buf);
+}
+
+
+/*
+ * Format an address structure into a simple string: "mailbox@host"
+ * Like simple_addr_string but can be multiple addresses.
+ *
+ * Args: addr -- ADDRESS structure to turn into a string
+ * buf -- buffer to write address in;
+ * buflen -- length of buffer
+ * sep -- separator string
+ *
+ * Result: Returns pointer to internal static formatted string.
+ * Just uses the c-client call to do this.
+ */
+char *
+simple_mult_addr_string(struct mail_address *addr, char *buf, size_t buflen, char *sep)
+{
+ ADDRESS *a;
+ char *dest = buf;
+ size_t seplen = 0;
+
+ if(sep)
+ seplen = strlen(sep);
+
+ *dest = '\0';
+ for(a = addr; a; a = a->next){
+ if(dest > buf && seplen > 0 && buflen-1-(dest-buf) >= seplen){
+ strncpy(dest, sep, seplen);
+ dest += seplen;
+ *dest = '\0';
+ }
+
+ simple_addr_string(a, dest, buflen-(dest-buf));
+ dest += strlen(dest);
+ }
+
+ buf[buflen-1] = '\0';
+
+ return(buf);
+}
+
+
+/*
+ * 1522 encode the personal name portion of addr and return an allocated
+ * copy of the resulting address string.
+ */
+char *
+encode_fullname_of_addrstring(char *addr, char *charset)
+{
+ char *pers_encoded,
+ *tmp_a_string,
+ *ret = NULL;
+ ADDRESS *adr;
+ static char *fakedomain = "@";
+ RFC822BUFFER rbuf;
+ size_t len;
+
+ tmp_a_string = cpystr(addr ? addr : "");
+ adr = NULL;
+ rfc822_parse_adrlist(&adr, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+
+ if(!adr)
+ return(cpystr(""));
+
+ if(adr->personal && adr->personal[0]){
+ pers_encoded = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *)adr->personal,
+ charset));
+ fs_give((void **)&adr->personal);
+ adr->personal = pers_encoded;
+ }
+
+ len = est_size(adr);
+ ret = (char *) fs_get(len * sizeof(char));
+ ret[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = ret;
+ rbuf.cur = ret;
+ rbuf.end = ret+len-1;
+ rfc822_output_address_list(&rbuf, adr, 0L, NULL);
+ *rbuf.cur = '\0';
+ mail_free_address(&adr);
+ return(ret);
+}
+
+
+/*
+ * 1522 decode the personal name portion of addr and return an allocated
+ * copy of the resulting address string.
+ */
+char *
+decode_fullname_of_addrstring(char *addr, int verbose)
+{
+ char *pers_decoded,
+ *tmp_a_string,
+ *ret = NULL;
+ ADDRESS *adr;
+ static char *fakedomain = "@";
+ RFC822BUFFER rbuf;
+ size_t len;
+
+ tmp_a_string = cpystr(addr ? addr : "");
+ adr = NULL;
+ rfc822_parse_adrlist(&adr, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+
+ if(!adr)
+ return(cpystr(""));
+
+ if(adr->personal && adr->personal[0]){
+ pers_decoded
+ = cpystr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, adr->personal));
+ fs_give((void **)&adr->personal);
+ adr->personal = pers_decoded;
+ }
+
+ len = est_size(adr);
+ ret = (char *) fs_get(len * sizeof(char));
+ ret[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = ret;
+ rbuf.cur = ret;
+ rbuf.end = ret+len-1;
+ rfc822_output_address_list(&rbuf, adr, 0L, NULL);
+ *rbuf.cur = '\0';
+ mail_free_address(&adr);
+ return(ret);
+}
+
+
+/*
+ * Turn a list of address structures into a formatted string
+ *
+ * Args: adrlist -- An adrlist
+ * f -- Function to use to print one address in list. If NULL,
+ * use rfc822_write_address_decode to print whole list.
+ * do_quote -- Quote quotes and dots (only used if f == NULL).
+ * Result: comma separated list of addresses which is
+ * malloced here and returned
+ * (the list is rfc1522 decoded unless f is *not* NULL)
+ */
+char *
+addr_list_string(struct mail_address *adrlist,
+ char *(*f)(struct mail_address *, char *, size_t),
+ int do_quote)
+{
+ size_t len;
+ char *list, *s, string[MAX_ADDR_EXPN+1];
+ register ADDRESS *a;
+
+ if(!adrlist)
+ return(cpystr(""));
+
+ if(f){
+ len = 0;
+ for(a = adrlist; a; a = a->next)
+ len += (strlen((*f)(a, string, sizeof(string))) + 2);
+
+ list = (char *) fs_get((len+1) * sizeof(char));
+ s = list;
+ s[0] = '\0';
+
+ for(a = adrlist; a; a = a->next){
+ sstrncpy(&s, (*f)(a, string, sizeof(string)), len-(s-list));
+ if(a->next && len-(s-list) > 2){
+ *s++ = ',';
+ *s++ = SPACE;
+ }
+ }
+ }
+ else{
+ len = est_size(adrlist);
+ list = (char *) fs_get((len+1) * sizeof(char));
+ list[0] = '\0';
+ rfc822_write_address_decode(list, len+1, adrlist, do_quote);
+ removing_leading_and_trailing_white_space(list);
+ }
+
+ list[len] = '\0';
+ return(list);
+}
+
+
+static long rfc822_dummy_soutr (void *stream, char *string)
+{
+ return LONGT;
+}
+
+/*
+ * Copied from c-client/rfc822.c buf with dot and double quote removed.
+ */
+static const char *rspecials_minus_quote_and_dot = "()<>@,;:\\[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
+
+/* Write RFC822 address with 1522 decoding of personal name
+ * and optional quoting.
+ *
+ * The idea is that there are some places where we'd just like to display
+ * the personal name as is before applying confusing quoting. However,
+ * we do want to be careful not to break things that should be quoted so
+ * we'll only use this where we are sure. Quoting may look ugly but it
+ * doesn't usually break anything.
+ */
+void
+rfc822_write_address_decode(char *dest, size_t destlen, struct mail_address *adr, int do_quote)
+{
+ RFC822BUFFER buf;
+ extern const char *rspecials;
+ ADDRESS *copy, *a;
+
+ /*
+ * We want to print the adr list after decoding it. C-client knows
+ * how to parse and print, so we want to use that. But c-client
+ * doesn't decode. So we make a copy of the address list, decode
+ * things there, and let c-client print that.
+ */
+ copy = copyaddrlist(adr);
+ for(a = copy; a; a = a->next){
+ if(a->host){ /* ordinary address? */
+ if(a->personal && *a->personal){
+ unsigned char *p;
+
+ p = rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, a->personal);
+
+ if(p && (char *) p != a->personal){
+ fs_give((void **) &a->personal);
+ a->personal = cpystr((char *) p);
+ }
+ }
+ }
+ else if(a->mailbox && *a->mailbox){ /* start of group? */
+ unsigned char *p;
+
+ p = rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, a->mailbox);
+
+ if(p && (char *) p != a->mailbox){
+ fs_give((void **) &a->mailbox);
+ a->mailbox = cpystr((char *) p);
+ }
+ }
+ }
+
+ buf.end = (buf.beg = buf.cur = dest) + destlen;
+ buf.f = rfc822_dummy_soutr;
+ *buf.cur = '\0';
+ buf.s = NIL;
+
+ (void) rfc822_output_address_list(&buf, copy, 0,
+ do_quote ? rspecials : rspecials_minus_quote_and_dot);
+
+ *buf.cur = '\0';
+
+ if(copy)
+ mail_free_address(&copy);
+}
+
+
+/*
+ * Compute an upper bound on the size of the array required by
+ * rfc822_write_address for this list of addresses.
+ *
+ * Args: adrlist -- The address list.
+ *
+ * Returns -- an integer giving the upper bound
+ */
+int
+est_size(struct mail_address *a)
+{
+ int cnt = 0;
+
+ for(; a; a = a->next){
+
+ /* two times personal for possible quoting */
+ cnt += 2 * (a->personal ? (strlen(a->personal)+1) : 0);
+ cnt += 2 * (a->mailbox ? (strlen(a->mailbox)+1) : 0);
+ cnt += (a->adl ? strlen(a->adl) : 0);
+ cnt += (a->host ? strlen(a->host) : 0);
+
+ /*
+ * add room for:
+ * possible single space between fullname and addr
+ * left and right brackets
+ * @ sign
+ * possible : for route addr
+ * , <space>
+ *
+ * So I really think that adding 7 is enough. Instead, I'll add 10.
+ */
+ cnt += 10;
+ }
+
+ return(MAX(cnt, 50)); /* just making sure */
+}
+
+
+/*
+ * Returns the number of addresses in the list.
+ */
+int
+count_addrs(struct mail_address *adrlist)
+{
+ int cnt = 0;
+
+ while(adrlist){
+ if(adrlist->mailbox && adrlist->mailbox[0])
+ cnt++;
+
+ adrlist = adrlist->next;
+ }
+
+ return(cnt);
+}
+
+
+/*
+ * Buf is at least size maxlen+1
+ */
+void
+a_little_addr_string(struct mail_address *addr, char *buf, size_t maxlen)
+{
+ buf[0] = '\0';
+ if(addr){
+ if(addr->personal && addr->personal[0]){
+ char tmp[MAILTMPLEN];
+
+ snprintf(tmp, sizeof(tmp), "%s", addr->personal);
+ iutf8ncpy(buf, (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, tmp),
+ maxlen);
+ }
+ else if(addr->mailbox && addr->mailbox[0]){
+ strncpy(buf, addr->mailbox, maxlen);
+ buf[maxlen] = '\0';
+ if(addr->host && addr->host[0] && addr->host[0] != '.'){
+ strncat(buf, "@", maxlen+1-1-strlen(buf));
+ strncat(buf, addr->host, maxlen+1-1-strlen(buf));
+ }
+ }
+ }
+
+ buf[maxlen] = '\0';
+}
diff --git a/pith/addrstring.h b/pith/addrstring.h
new file mode 100644
index 00000000..16ae8e56
--- /dev/null
+++ b/pith/addrstring.h
@@ -0,0 +1,37 @@
+/*
+ * $Id: addrstring.h 770 2007-10-24 00:23:09Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ADDRSTRING_INCLUDED
+#define PITH_ADDRSTRING_INCLUDED
+
+
+#define RAWFIELD "-RAW-FIELD-"
+
+
+/* exported protoypes */
+char *addr_string(ADDRESS *, char *, size_t);
+char *addr_string_mult(ADDRESS *, char *, size_t);
+char *simple_addr_string(ADDRESS *, char *, size_t);
+char *simple_mult_addr_string(ADDRESS *, char *, size_t, char *);
+char *encode_fullname_of_addrstring(char *, char *);
+char *decode_fullname_of_addrstring(char *, int);
+char *addr_list_string(ADDRESS *, char *(*)(ADDRESS *, char *, size_t), int);
+int est_size(ADDRESS *);
+int count_addrs(ADDRESS *);
+void a_little_addr_string(ADDRESS *, char *, size_t);
+
+
+#endif /* PITH_ADDRSTRING_INCLUDED */
diff --git a/pith/adjtime.c b/pith/adjtime.c
new file mode 100644
index 00000000..bc125d35
--- /dev/null
+++ b/pith/adjtime.c
@@ -0,0 +1,61 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: adjtime.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/adjtime.h"
+
+
+/*
+ * MSC ver 7.0 and less times are since 1900, everybody else's time so far
+ * is since 1970. sheesh.
+ */
+#if defined(DOS) && (_MSC_VER == 700)
+#define EPOCH_ADJ ((time_t)((time_t)(70*365 + 18) * (time_t)86400))
+#endif
+
+/*
+ * Adjust the mtime to return time since Unix epoch. DOS is off by 70 years.
+ */
+time_t
+get_adj_time(void)
+{
+ time_t tt;
+
+ tt = time((time_t *)0);
+
+#ifdef EPOCH_ADJ
+ tt -= EPOCH_ADJ;
+#endif
+
+ return(tt);
+}
+
+
+time_t
+get_adj_name_file_mtime(char *name)
+{
+ time_t mtime;
+
+ mtime = name_file_mtime(name);
+
+#ifdef EPOCH_ADJ
+ if(mtime != (time_t)(-1))
+ mtime -= EPOCH_ADJ;
+#endif
+
+ return(mtime);
+}
diff --git a/pith/adjtime.h b/pith/adjtime.h
new file mode 100644
index 00000000..355b1020
--- /dev/null
+++ b/pith/adjtime.h
@@ -0,0 +1,35 @@
+/*
+ * $Id: adjtime.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_TIME_INCLUDED
+#define PITH_TIME_INCLUDED
+
+
+/*
+ * This is just like a struct timeval. We need it for portability to systems
+ * that don't have a struct timeval.
+ */
+typedef struct our_time_val {
+ long sec;
+ long usec;
+} TIMEVAL_S;
+
+
+/* exported protoypes */
+time_t get_adj_time(void);
+time_t get_adj_name_file_mtime(char *);
+
+
+#endif /* PITH_TIME_INCLUDED */
diff --git a/pith/adrbklib.c b/pith/adrbklib.c
new file mode 100644
index 00000000..01d00353
--- /dev/null
+++ b/pith/adrbklib.c
@@ -0,0 +1,6028 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: adrbklib.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/adrbklib.h"
+#include "../pith/abdlc.h"
+#include "../pith/addrbook.h"
+#include "../pith/addrstring.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/remote.h"
+#include "../pith/tempfile.h"
+#include "../pith/bldaddr.h"
+#include "../pith/signal.h"
+#include "../pith/busy.h"
+#include "../pith/util.h"
+
+
+AddrScrState as;
+
+void (*pith_opt_save_and_restore)(int, SAVE_STATE_S *);
+
+
+/*
+ * We don't want any end of line fixups to occur, so include "b" in DOS modes.
+ */
+#if defined(DOS) || defined(OS2)
+#define ADRBK_NAME "addrbook"
+#else
+#define ADRBK_NAME ".addressbook"
+#endif
+
+#define OPEN_WRITE_MODE (O_TRUNC|O_WRONLY|O_CREAT|O_BINARY)
+
+
+#ifndef MAXPATH
+#define MAXPATH 1000 /* Longest file path we can deal with */
+#endif
+
+#define TABWIDTH 8
+#define INDENTSTR " "
+#define INDENTXTRA " : "
+#define INDENT 3 /* length of INDENTSTR */
+
+
+#define SLOP 3
+
+static int writing; /* so we can give understandable error message */
+
+AdrBk *edited_abook;
+
+static char empty[] = "";
+
+jmp_buf jump_over_qsort;
+
+
+/* internal prototypes */
+int copy_abook_to_tempfile(AdrBk *, char *, size_t);
+char *dir_containing(char *);
+char *get_next_abook_entry(FILE *, int);
+void strip_addr_string(char *, char **, char **);
+void adrbk_check_local_validity(AdrBk *, long);
+int write_single_abook_entry(AdrBk_Entry *, FILE *, int *, int *, int *, int *);
+char *backcompat_encoding_for_abook(char *, size_t, char *, size_t, char *);
+int percent_abook_saved(void);
+void exp_del_nth(EXPANDED_S *, a_c_arg_t);
+void exp_add_nth(EXPANDED_S *, a_c_arg_t);
+int cmp_ae_by_full_lists_last(const qsort_t *,const qsort_t *);
+int cmp_cntr_by_full_lists_last(const qsort_t *, const qsort_t *);
+int cmp_ae_by_full(const qsort_t *, const qsort_t *);
+int cmp_cntr_by_full(const qsort_t *, const qsort_t *);
+int cmp_ae_by_nick_lists_last(const qsort_t *,const qsort_t *);
+int cmp_cntr_by_nick_lists_last(const qsort_t *, const qsort_t *);
+int cmp_ae_by_nick(const qsort_t *, const qsort_t *);
+int cmp_cntr_by_nick(const qsort_t *, const qsort_t *);
+int cmp_addr(const qsort_t *, const qsort_t *);
+void sort_addr_list(char **);
+int build_abook_datastruct(AdrBk *, char *, size_t);
+AdrBk_Entry *init_ae(AdrBk *, AdrBk_Entry *, char *);
+adrbk_cntr_t count_abook_entries_on_disk(AdrBk *, a_c_arg_t *);
+AdrBk_Entry *adrbk_get_delae(AdrBk *, a_c_arg_t);
+void free_ae_parts(AdrBk_Entry *);
+void add_entry_to_trie(AdrBk_Trie **, char *, a_c_arg_t);
+int build_abook_tries(AdrBk *, char *);
+void repair_abook_tries(AdrBk *);
+adrbk_cntr_t lookup_nickname_in_trie(AdrBk *, char *);
+adrbk_cntr_t lookup_address_in_trie(AdrBk *, char *);
+adrbk_cntr_t lookup_in_abook_trie(AdrBk_Trie *, char *);
+void free_abook_trie(AdrBk_Trie **);
+adrbk_cntr_t re_sort_particular_entry(AdrBk *, a_c_arg_t);
+void move_ab_entry(AdrBk *, a_c_arg_t, a_c_arg_t);
+void insert_ab_entry(AdrBk *, a_c_arg_t, AdrBk_Entry *, int);
+void delete_ab_entry(AdrBk *, a_c_arg_t, int);
+void defvalue_ae(AdrBk_Entry *);
+
+
+/*
+ * Open, read, and parse an address book.
+ *
+ * Args: pab -- the PerAddrBook structure
+ * homedir -- the user's home directory if specified
+ * warning -- put "why failed" message to user here
+ * (provide space of at least 201 chars)
+ *
+ * If filename is NULL, the default will be used in the homedir
+ * passed in. If homedir is NULL, the current dir will be used.
+ * If filename is not NULL and is an absolute path, just the filename
+ * will be used. Otherwise, it will be used relative to the homedir, or
+ * to the current dir depending on whether or not homedir is NULL.
+ *
+ * Expected addressbook file format is:
+ * <nickname>\t<fullname>\t<address_field>\t<fcc>\t<comment>
+ *
+ * The last two fields (\t<fcc>\t<comment>) are optional.
+ *
+ * Lines that start with SPACE are continuation lines. Ends of lines are
+ * treated as if they were spaces. The address field is either a single
+ * address or a list of comma-separated addresses inside parentheses.
+ *
+ * Fields missing from the end of an entry are considered blank.
+ *
+ * Commas in the address field will cause problems, as will tabs in any
+ * field.
+ *
+ * There may be some deleted entries in the addressbook that don't get
+ * used. They look like regular entries except their nicknames start
+ * with the string "#DELETED-YY/MM/DD#".
+ */
+AdrBk *
+adrbk_open(PerAddrBook *pab, char *homedir, char *warning, size_t warninglen, int sort_rule)
+{
+ char path[MAXPATH], *filename;
+ AdrBk *ab;
+ int abook_validity_was_minusone = 0;
+
+
+ filename = pab ? pab->filename : NULL;
+
+ dprint((2, "- adrbk_open(%s) -\n", filename ? filename : ""));
+
+ ab = (AdrBk *)fs_get(sizeof(AdrBk));
+ memset(ab, 0, sizeof(*ab));
+
+ ab->orig_filename = filename ? cpystr(filename) : NULL;
+
+ if(pab->type & REMOTE_VIA_IMAP){
+ int try_cache;
+
+ ab->type = Imap;
+
+ if(!ab->orig_filename || *(ab->orig_filename) != '{'){
+ dprint((1, "adrbk_open: remote: filename=%s\n",
+ ab->orig_filename ? ab->orig_filename : "NULL"));
+ goto bail_out;
+ }
+
+ if(!(ab->rd = rd_new_remdata(RemImap, ab->orig_filename, REMOTE_ABOOK_SUBTYPE))){
+ dprint((1,
+ "adrbk_open: remote: new_remdata failed: %s\n",
+ ab->orig_filename ? ab->orig_filename : "NULL"));
+ goto bail_out;
+ }
+
+ /* Transfer responsibility for the storage object */
+ ab->rd->so = pab->so;
+ pab->so = NULL;
+
+ try_cache = rd_read_metadata(ab->rd);
+
+ if(ab->rd->lf)
+ ab->filename = cpystr(ab->rd->lf);
+
+ /* Transfer responsibility for removal of temp file */
+ if(ab->rd->flags & DEL_FILE){
+ ab->flags |= DEL_FILE;
+ ab->rd->flags &= ~DEL_FILE;
+ }
+
+ if(pab->access == MaybeRorW){
+ if(ab->rd->read_status == 'R')
+ pab->access = ab->rd->access = ReadOnly;
+ else
+ pab->access = ab->rd->access = ReadWrite;
+ }
+ else if(pab->access == ReadOnly){
+ /*
+ * Pass on readonly-ness from being a global addrbook.
+ * This should cause us to open the remote folder readonly,
+ * avoiding error messages about readonly-ness.
+ */
+ ab->rd->access = ReadOnly;
+ }
+
+ /*
+ * The plan is to fetch addrbook data and copy into local file.
+ * Then we open the local copy for reading. We use the IMAP STATUS
+ * command to tell us if we need to update from the remote addrbook.
+ *
+ * If access is NoExists, that probably means we had trouble
+ * opening the remote folder in the adrbk_access routine.
+ * In that case we'll use a cached copy if we have one.
+ */
+ if(pab->access != NoExists){
+
+bootstrap_nocheck_policy:
+ if(try_cache && ps_global->remote_abook_validity == -1 &&
+ !abook_validity_was_minusone)
+ abook_validity_was_minusone++;
+ else{
+ rd_check_remvalid(ab->rd, 1L);
+ abook_validity_was_minusone = 0;
+ }
+
+ /*
+ * If the cached info on this addrbook says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(!(pab->type & GLOBAL) && ab->rd->read_status == 'R'){
+ /*
+ * We go to this trouble since readonly addrbooks
+ * are likely a mistake. They are usually supposed to be
+ * readwrite. So we open it and check if it's been fixed.
+ */
+ rd_check_readonly_access(ab->rd);
+ if(ab->rd->read_status == 'W'){
+ pab->access = ab->rd->access = ReadWrite;
+ ab->rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ pab->access = ab->rd->access = ReadOnly;
+ }
+
+ if(ab->rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(ab->rd) != 0){
+ dprint((1,
+ "adrbk_open: remote: rd_update_local failed\n"));
+ /*
+ * Don't give up altogether. We still may be
+ * able to use a cached copy.
+ */
+ }
+ else{
+ dprint((7,
+ "%s: copied remote to local (%ld)\n",
+ ab->rd->rn ? ab->rd->rn : "?",
+ (long)ab->rd->last_use));
+ }
+ }
+
+ if(pab->access == ReadWrite)
+ ab->rd->flags |= DO_REMTRIM;
+ }
+
+ /* If we couldn't get to remote folder, try using the cached copy */
+ if(pab->access == NoExists || ab->rd->flags & REM_OUTOFDATE){
+ if(try_cache){
+ pab->access = ab->rd->access = ReadOnly;
+ ab->rd->flags |= USE_OLD_CACHE;
+ q_status_message(SM_ORDER, 3, 4,
+ _("Can't contact remote address book server, using cached copy"));
+ dprint((2,
+ "Can't open remote addrbook %s, using local cached copy %s readonly\n",
+ ab->rd->rn ? ab->rd->rn : "?",
+ ab->rd->lf ? ab->rd->lf : "?"));
+ }
+ else
+ goto bail_out;
+ }
+ }
+ else{
+ ab->type = Local;
+
+ /*------------ figure out and save name of file to open ---------*/
+ if(filename == NULL){
+ if(homedir != NULL){
+ build_path(path, homedir, ADRBK_NAME, sizeof(path));
+ ab->filename = cpystr(path);
+ }
+ else
+ ab->filename = cpystr(ADRBK_NAME);
+ }
+ else{
+ if(is_absolute_path(filename)){
+ ab->filename = cpystr(filename);
+ }
+ else{
+ if(homedir != NULL){
+ build_path(path, homedir, filename, sizeof(path));
+ ab->filename = cpystr(path);
+ }
+ else
+ ab->filename = cpystr(filename);
+ }
+ }
+ }
+
+ if(ab->filename && ab->filename[0]){
+ char buf[MAXPATH];
+
+ strncpy(buf, ab->filename, sizeof(buf)-4);
+ buf[sizeof(buf)-4] = '\0';
+
+ /*
+ * Our_filecopy is used in _WINDOWS to allow
+ * multiple pines to update the address book. The problem is
+ * that if a file is open it can't be deleted, so we need to keep
+ * the main filename closed most of the time.
+ * In Unix, our_filecopy just points to filename.
+ */
+
+#ifdef _WINDOWS
+ /*
+ * If we can't write in the same directory as filename is in, put
+ * the copies in /tmp instead.
+ */
+ if(!(ab->our_filecopy = tempfile_in_same_dir(ab->filename, "a3", NULL)))
+ ab->our_filecopy = temp_nam(NULL, "a3");
+#endif /* _WINDOWS */
+
+ /*
+ * We don't need the copies on Unix because we can rename/delete
+ * open files. Turn the feature off by making the copies point to
+ * the originals.
+ */
+ if(!ab->our_filecopy)
+ ab->our_filecopy = ab->filename;
+ }
+ else{
+ dprint((1, "adrbk_open: ab->filename is NULL???\n"));
+ goto bail_out;
+ }
+
+
+ /*
+ * We're going to make our own copy of the address book file so that
+ * we won't conflict with other instances of pine trying to change it.
+ * In particular, on Windows the address book file cannot be deleted
+ * or renamed into if it is open in another process.
+ */
+ ab->flags |= FILE_OUTOFDATE;
+ if(copy_abook_to_tempfile(ab, warning, warninglen) < 0){
+ dprint((1, "adrbk_open: copy_file failed\n"));
+ if(abook_validity_was_minusone){
+ /*
+ * The file copy failed when it shouldn't have. If the user has
+ * remote_abook_validity == -1 then we'll go back and try to
+ * do the validity check in case that can get us the file we
+ * need to copy. Without the validity check first time we won't
+ * contact the imap server.
+ */
+ dprint((1, "adrbk_open: trying to bootstrap\n"));
+
+ if(ab->our_filecopy){
+ if(ab->our_filecopy != ab->filename){
+ our_unlink(ab->our_filecopy);
+ fs_give((void **)&ab->our_filecopy);
+ }
+
+ ab->our_filecopy = NULL;
+ }
+
+ goto bootstrap_nocheck_policy;
+ }
+
+ goto bail_out;
+ }
+
+ if(!ab->fp)
+ goto bail_out;
+
+ ab->sort_rule = sort_rule;
+ if(pab->access == ReadOnly)
+ ab->sort_rule = AB_SORT_RULE_NONE;
+
+ if(ab){
+ /* allocate header for expanded lists list */
+ ab->exp = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
+ /* first real element is NULL */
+ ab->exp->next = (EXPANDED_S *)NULL;
+
+ /* allocate header for checked entries list */
+ ab->checks = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
+ /* first real element is NULL */
+ ab->checks->next = (EXPANDED_S *)NULL;
+
+ /* allocate header for selected entries list */
+ ab->selects = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
+ /* first real element is NULL */
+ ab->selects->next = (EXPANDED_S *)NULL;
+
+ return(ab);
+ }
+
+bail_out:
+ dprint((2, "adrbk_open: bailing: filenames=%s %s %s fp=%s\n",
+ ab->orig_filename ? ab->orig_filename : "NULL",
+ ab->filename ? ab->filename : "NULL",
+ ab->our_filecopy ? ab->our_filecopy : "NULL",
+ ab->fp ? "open" : "NULL"));
+
+ if(ab->rd){
+ ab->rd->flags &= ~DO_REMTRIM;
+ rd_close_remdata(&ab->rd);
+ }
+
+ if(ab->fp)
+ (void)fclose(ab->fp);
+
+ if(ab->orig_filename)
+ fs_give((void **) &ab->orig_filename);
+
+ if(ab->our_filecopy && ab->our_filecopy != ab->filename){
+ our_unlink(ab->our_filecopy);
+ fs_give((void **) &ab->our_filecopy);
+ }
+
+ if(ab->filename)
+ fs_give((void **) &ab->filename);
+
+ if(pab->so){
+ so_give(&(pab->so));
+ pab->so = NULL;
+ }
+
+ fs_give((void **) &ab);
+
+ return NULL;
+}
+
+
+/*
+ * Copy the address book file to the temporary session copy. Also copy
+ * the hashfile. Any of these files which don't exist will be created.
+ *
+ * Returns 0 success
+ * -1 failure
+ */
+int
+copy_abook_to_tempfile(AdrBk *ab, char *warning, size_t warninglen)
+{
+ int got_it, fd, c,
+ ret = -1,
+ we_cancel = 0;
+ FILE *fp_read = (FILE *)NULL,
+ *fp_write = (FILE *)NULL;
+ char *lc;
+ time_t mtime;
+
+
+ dprint((3, "copy_file(%s) -\n",
+ (ab && ab->filename) ? ab->filename : ""));
+
+ if(!ab || !ab->filename || !ab->filename[0])
+ goto get_out;
+
+ if(!(ab->flags & FILE_OUTOFDATE))
+ return(0);
+
+ /* open filename for reading */
+ fp_read = our_fopen(ab->filename, "rb");
+ if(fp_read == NULL){
+
+ /*
+ * filename probably doesn't exist so we try to create it
+ */
+
+ /* don't want to create in these cases, should already be there */
+ if(ab->type == Imap){
+ if(warning){
+ if(ab->type == Imap){
+ snprintf(warning, warninglen,
+ /* TRANSLATORS: A temporary file for the address book can't
+ be opened. */
+ _("Temp addrbook file can't be opened: %s"),
+ ab->filename);
+ warning[warninglen-1] = '\0';
+ }
+ else{
+ strncpy(warning, _("Address book doesn't exist"), warninglen);
+ warning[warninglen-1] = '\0';
+ }
+ }
+
+ goto get_out;
+ }
+
+ q_status_message1(SM_INFO, 0, 3,
+ _("Address book %.200s doesn't exist, creating"),
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
+ dprint((2, "Address book %s doesn't exist, creating\n",
+ ab->filename ? ab->filename : "?"));
+
+ /*
+ * Just use user's umask for permissions. Mode is "w" so the create
+ * will happen. We close it right after creating and open it in
+ * read mode again later.
+ */
+ fp_read = our_fopen(ab->filename, "wb"); /* create */
+ if(fp_read == NULL ||
+ fclose(fp_read) == EOF ||
+ (fp_read = our_fopen(ab->filename, "rb")) == NULL){
+ /*--- Create failed, bail out ---*/
+ if(warning){
+ strncpy(warning, error_description(errno), warninglen);
+ warning[warninglen-1] = '\0';
+ }
+
+ dprint((2, "create failed: %s\n",
+ error_description(errno)));
+
+ goto get_out;
+ }
+ }
+
+ /* record new change date of addrbook file */
+ ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename);
+
+ ab->last_local_valid_chk = get_adj_time();
+
+
+ /* now there is an ab->filename and we have it open for reading */
+
+ got_it = 0;
+
+ /* copy ab->filename to ab->our_filecopy, preserving mtime */
+ if(ab->filename != ab->our_filecopy){
+ struct stat sbuf;
+ struct utimbuf times;
+ int valid_stat = 0;
+
+ dprint((7, "Before abook copies\n"));
+ if((fd = our_open(ab->our_filecopy, OPEN_WRITE_MODE, 0600)) < 0)
+ goto get_out;
+
+ fp_write = fdopen(fd, "wb");
+ rewind(fp_read);
+ if(fstat(fileno(fp_read), &sbuf)){
+ q_status_message1(SM_INFO, 0, 3,
+ "Error: can't stat addrbook \"%.200s\"",
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
+ dprint((2, "Error: can't stat addrbook \"%s\"\n",
+ ab->filename ? ab->filename : "?"));
+ }
+ else{
+ valid_stat++;
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ }
+
+ while((c = getc(fp_read)) != EOF)
+ if(putc(c, fp_write) == EOF)
+ goto get_out;
+
+ (void)fclose(fp_write);
+ fp_write = (FILE *)NULL;
+ if(valid_stat && our_utime(ab->our_filecopy, &times)){
+ q_status_message1(SM_INFO, 0, 3,
+ "Error: can't set mtime for \"%.200s\"",
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->our_filecopy);
+ dprint((2, "Error: can't set mtime for \"%s\"\n",
+ ab->our_filecopy ? ab->our_filecopy : "?"));
+ }
+
+ (void)fclose(fp_read);
+ fp_read = (FILE *)NULL;
+ if(!(ab->fp = our_fopen(ab->our_filecopy, "rb")))
+ goto get_out;
+
+ dprint((7, "After abook file copy\n"));
+ }
+ else{ /* already open to the right file */
+ ab->fp = fp_read;
+ fp_read = (FILE *)NULL;
+ }
+
+ /*
+ * Now we've copied filename to our_filecopy.
+ * Operate on the copy now. Ab->fp is open readonly on
+ * our_filecopy.
+ */
+
+ got_it = 0;
+ mtime = get_adj_name_file_mtime(ab->our_filecopy);
+ we_cancel = busy_cue(NULL, NULL, 1);
+ if(build_abook_datastruct(ab, (warning && !*warning) ? warning : NULL, warninglen)){
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ dprint((2, "failed in build_abook_datastruct\n"));
+ goto get_out;
+ }
+
+ if(ab->arr
+ && build_abook_tries(ab, (warning && !*warning) ? warning : NULL)){
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ dprint((2, "failed in build_abook_tries\n"));
+ goto get_out;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ ab->flags &= ~FILE_OUTOFDATE; /* turn off out of date flag */
+ ret = 0;
+
+get_out:
+ if(fp_read)
+ (void)fclose(fp_read);
+
+ if(fp_write)
+ (void)fclose(fp_write);
+
+ if(ret < 0 && ab){
+ if(ab->our_filecopy && ab->our_filecopy != ab->filename)
+ our_unlink(ab->our_filecopy);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns an allocated copy of the directory which contains filename.
+ */
+char *
+dir_containing(char *filename)
+{
+ char dir[MAXPATH+1];
+ char *dirp = NULL;
+
+ if(filename){
+ char *lc;
+
+ if((lc = last_cmpnt(filename)) != NULL){
+ int to_copy;
+
+ to_copy = (lc - filename > 1) ? (lc - filename - 1) : 1;
+ strncpy(dir, filename, MIN(to_copy, sizeof(dir)-1));
+ dir[MIN(to_copy, sizeof(dir)-1)] = '\0';
+ }
+ else{
+ dir[0] = '.';
+ dir[1] = '\0';
+ }
+
+ dirp = cpystr(dir);
+ }
+
+ return(dirp);
+}
+
+
+/*
+ * Checks whether or not the addrbook is sorted correctly according to
+ * the SortType. Returns 1 if is sorted correctly, 0 otherwise.
+ */
+int
+adrbk_is_in_sort_order(AdrBk *ab, int be_quiet)
+{
+ adrbk_cntr_t entry;
+ AdrBk_Entry *ae, *ae_prev;
+ int (*cmp_func)();
+ int we_cancel = 0;
+
+ dprint((9, "- adrbk_is_in_sort_order -\n"));
+
+ if(!ab)
+ return 0;
+
+ if(ab->sort_rule == AB_SORT_RULE_NONE || ab->count < 2)
+ return 1;
+
+ cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
+ cmp_ae_by_full_lists_last :
+ (ab->sort_rule == AB_SORT_RULE_FULL) ?
+ cmp_ae_by_full :
+ (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
+ cmp_ae_by_nick_lists_last :
+ /* (ab->sort_rule == AB_SORT_RULE_NICK) */
+ cmp_ae_by_nick;
+
+ ae_prev = adrbk_get_ae(ab, (a_c_arg_t) 0);
+
+ if(!be_quiet)
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ for(entry = 1, ae = adrbk_get_ae(ab, (a_c_arg_t) entry);
+ ae != (AdrBk_Entry *)NULL;
+ ae = adrbk_get_ae(ab, (a_c_arg_t) (++entry))){
+
+ if((*cmp_func)((qsort_t *)&ae_prev, (qsort_t *)&ae) > 0){
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ dprint((9, "- adrbk_is_in_sort_order : no (entry %ld) -\n", (long) entry));
+ return 0;
+ }
+
+ ae_prev = ae;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ dprint((9, "- adrbk_is_in_sort_order : yes -\n"));
+
+ return 1;
+}
+
+
+/*
+ * Look through the ondisk address book and count the number of entries.
+ *
+ * Args ab -- address book pointer
+ * deleted -- pointer to location to return number of deleted entries
+ * warning -- place to put warning
+ *
+ * Returns number of non-deleted entries.
+ */
+adrbk_cntr_t
+count_abook_entries_on_disk(AdrBk *ab, a_c_arg_t *deleted)
+{
+ FILE *fp_in;
+ char *nickname;
+ adrbk_cntr_t count = 0;
+ adrbk_cntr_t deleted_count = 0;
+ int rew = 1;
+ char nickbuf[50];
+
+ if(!ab || !ab->fp)
+ return -1;
+
+ fp_in = ab->fp;
+
+ while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){
+ rew = 0;
+ strncpy(nickbuf, nickname, sizeof(nickbuf));
+ nickbuf[sizeof(nickbuf)-1] = '\0';
+ fs_give((void **) &nickname);
+ if(strncmp(nickbuf, DELETED, DELETED_LEN) == 0
+ && isdigit((unsigned char)nickbuf[DELETED_LEN])
+ && isdigit((unsigned char)nickbuf[DELETED_LEN+1])
+ && nickbuf[DELETED_LEN+2] == '/'
+ && isdigit((unsigned char)nickbuf[DELETED_LEN+3])
+ && isdigit((unsigned char)nickbuf[DELETED_LEN+4])
+ && nickbuf[DELETED_LEN+5] == '/'
+ && isdigit((unsigned char)nickbuf[DELETED_LEN+6])
+ && isdigit((unsigned char)nickbuf[DELETED_LEN+7])
+ && nickbuf[DELETED_LEN+8] == '#'){
+ deleted_count++;
+ continue;
+ }
+
+ count++;
+ }
+
+ if(deleted)
+ *deleted = (a_c_arg_t) deleted_count;
+
+ return(count);
+}
+
+
+/*
+ * Builds the data structures used with the address book which is
+ * simply an array of entries.
+ */
+int
+build_abook_datastruct(AdrBk *ab, char *warning, size_t warninglen)
+{
+ FILE *fp_in;
+ char *nickname;
+ char *lc;
+ a_c_arg_t count, deleted;
+ adrbk_cntr_t used = 0, delused = 0;
+ int max_nick = 0,
+ max_full = 0, full_two = 0, full_three = 0,
+ max_fcc = 0, fcc_two = 0, fcc_three = 0,
+ max_addr = 0, addr_two = 0, addr_three = 0,
+ this_nick_width, this_full_width, this_addr_width,
+ this_fcc_width;
+ WIDTH_INFO_S *widths;
+ int rew = 1, is_deleted;
+ AdrBk_Entry *ae;
+
+ dprint((9, "- build_abook_datastruct -\n"));
+
+ if(!ab || !ab->fp)
+ return -1;
+
+ errno = 0;
+
+ fp_in = ab->fp;
+
+ /*
+ * If we used a list instead of an array to store the entries we
+ * could avoid this pass through the file to count the entries, which
+ * we use to allocate the array.
+ *
+ * Since we use entry_nums a lot to access address book entries it is
+ * convenient to have an array for quick access, so we'll probably
+ * leave this for now.
+ */
+ count = count_abook_entries_on_disk(ab, &deleted);
+
+ if(count < 0)
+ return -1;
+
+ ab->count = (adrbk_cntr_t) count;
+ ab->del_count = (adrbk_cntr_t) deleted;
+
+ if(count > 0){
+ ab->arr = (AdrBk_Entry *) fs_get(count * sizeof(AdrBk_Entry));
+ memset(ab->arr, 0, count * sizeof(AdrBk_Entry));
+ }
+
+ if(deleted > 0){
+ ab->del = (AdrBk_Entry *) fs_get(deleted * sizeof(AdrBk_Entry));
+ memset(ab->del, 0, deleted * sizeof(AdrBk_Entry));
+ }
+
+ while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){
+
+
+ ae = NULL;
+ rew = 0;
+ is_deleted = 0;
+
+ if(strncmp(nickname, DELETED, DELETED_LEN) == 0
+ && isdigit((unsigned char)nickname[DELETED_LEN])
+ && isdigit((unsigned char)nickname[DELETED_LEN+1])
+ && nickname[DELETED_LEN+2] == '/'
+ && isdigit((unsigned char)nickname[DELETED_LEN+3])
+ && isdigit((unsigned char)nickname[DELETED_LEN+4])
+ && nickname[DELETED_LEN+5] == '/'
+ && isdigit((unsigned char)nickname[DELETED_LEN+6])
+ && isdigit((unsigned char)nickname[DELETED_LEN+7])
+ && nickname[DELETED_LEN+8] == '#'){
+ is_deleted++;
+ }
+
+ ALARM_BLIP();
+ if(!is_deleted && (long) used > MAX_ADRBK_SIZE){
+ q_status_message2(SM_ORDER | SM_DING, 4, 5,
+ "Max addrbook size is %.200s, %.200s too large, giving up",
+ long2string(MAX_ADRBK_SIZE),
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
+ dprint((1, "build_ondisk: used=%ld > %s\n",
+ (long) used, long2string(MAX_ADRBK_SIZE)));
+ goto io_err;
+ }
+
+ if(is_deleted){
+ if(delused < ab->del_count)
+ ae = &ab->del[delused++];
+ }
+ else{
+ if(used < ab->count)
+ ae = &ab->arr[used++];
+ }
+
+ if(ae)
+ init_ae(ab, ae, nickname);
+
+ fs_give((void **) &nickname);
+
+ if(!ae || is_deleted)
+ continue;
+
+ /*
+ * We're calculating the widths as we read in the data.
+ * We could just go with some default widths to save time.
+ */
+ this_nick_width = 0;
+ this_full_width = 0;
+ this_addr_width = 0;
+ this_fcc_width = 0;
+
+ if(ae->nickname)
+ this_nick_width = (int) utf8_width(ae->nickname);
+
+ if(ae->fullname)
+ this_full_width = (int) utf8_width(ae->fullname);
+
+ if(ae->tag == Single){
+ if(ae->addr.addr)
+ this_addr_width = (int) utf8_width(ae->addr.addr);
+ }
+ else{
+ char **a2;
+
+ this_addr_width = 0;
+ for(a2 = ae->addr.list; *a2 != NULL; a2++)
+ this_addr_width = MAX(this_addr_width, (int) utf8_width(*a2));
+ }
+
+ if(ae->fcc)
+ this_fcc_width = (int) utf8_width(ae->fcc);
+
+ max_nick = MAX(max_nick, this_nick_width);
+
+ if(this_full_width > max_full){
+ full_three = full_two;
+ full_two = max_full;
+ max_full = this_full_width;
+ }
+ else if(this_full_width > full_two){
+ full_three = full_two;
+ full_two = this_full_width;
+ }
+ else if(this_full_width > full_three){
+ full_three = this_full_width;
+ }
+
+ if(this_addr_width > max_addr){
+ addr_three = addr_two;
+ addr_two = max_addr;
+ max_addr = this_addr_width;
+ }
+ else if(this_addr_width > addr_two){
+ addr_three = addr_two;
+ addr_two = this_addr_width;
+ }
+ else if(this_addr_width > addr_three){
+ addr_three = this_addr_width;
+ }
+
+ if(this_fcc_width > max_fcc){
+ fcc_three = fcc_two;
+ fcc_two = max_fcc;
+ max_fcc = this_fcc_width;
+ }
+ else if(this_fcc_width > fcc_two){
+ fcc_three = fcc_two;
+ fcc_two = this_fcc_width;
+ }
+ else if(this_fcc_width > fcc_three){
+ fcc_three = this_fcc_width;
+ }
+ }
+
+ widths = &ab->widths;
+ widths->max_nickname_width = MIN(max_nick, 99);
+ widths->max_fullname_width = MIN(max_full, 99);
+ widths->max_addrfield_width = MIN(max_addr, 99);
+ widths->max_fccfield_width = MIN(max_fcc, 99);
+ widths->third_biggest_fullname_width = MIN(full_three, 99);
+ widths->third_biggest_addrfield_width = MIN(addr_three, 99);
+ widths->third_biggest_fccfield_width = MIN(fcc_three, 99);
+
+ dprint((9, "- build_abook_datastruct done -\n"));
+ return 0;
+
+io_err:
+ if(warning && errno != 0){
+ strncpy(warning, error_description(errno), warninglen);
+ warning[warninglen-1] = '\0';
+ }
+
+ dprint((1, "build_ondisk: io_err: %s\n",
+ error_description(errno)));
+
+ return -1;
+}
+
+
+/*
+ * Builds the trees used for nickname and address lookups.
+ */
+int
+build_abook_tries(AdrBk *ab, char *warning)
+{
+ adrbk_cntr_t entry_num;
+ AdrBk_Entry *ae;
+ int we_cancel = 0;
+
+ if(!ab)
+ return -1;
+
+ dprint((9, "- build_abook_tries(%s) -\n", ab->filename));
+
+
+ if(ab->nick_trie)
+ free_abook_trie(&ab->nick_trie);
+
+ if(ab->addr_trie)
+ free_abook_trie(&ab->addr_trie);
+
+ if(ab->full_trie)
+ free_abook_trie(&ab->full_trie);
+
+ if(ab->revfull_trie)
+ free_abook_trie(&ab->revfull_trie);
+
+ /*
+ * Go through addrbook entries and add each to the tries it
+ * belongs in.
+ */
+ for(entry_num = 0; entry_num < ab->count; entry_num++){
+ ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);
+ if(ae){
+ /* every nickname in the nick trie */
+ if(ae->nickname && ae->nickname[0])
+ add_entry_to_trie(&ab->nick_trie, ae->nickname, (a_c_arg_t) entry_num);
+
+ if(ae->fullname && ae->fullname[0]){
+ char *reverse = NULL;
+ char *forward = NULL;
+ char *comma = NULL;
+
+ /*
+ * We have some fullnames stored as Last, First. Put both in.
+ */
+ if(ae->fullname[0] != '"'
+ && (comma=strindex(ae->fullname, ',')) != NULL
+ && comma - ae->fullname > 0){
+ forward = adrbk_formatname(ae->fullname, NULL, NULL);
+ if(forward && forward[0]){
+ *comma = '\0';
+ reverse = cpystr(ae->fullname);
+ *comma = ',';
+ }
+ else{
+ if(forward)
+ fs_give((void **) &forward);
+
+ forward = ae->fullname;
+ }
+ }
+ else
+ forward = ae->fullname;
+
+ if(forward){
+ char *addthis;
+
+ /*
+ * Make this add not only the full name as is (forward) but
+ * also add the middle name and last name (names after spaces).
+ * so that they'll be found.
+ */
+ for(addthis=forward;
+ addthis && (*addthis);
+ addthis = strindex(addthis, ' ')){
+ while(*addthis == ' ')
+ addthis++;
+
+ if(*addthis)
+ add_entry_to_trie(&ab->full_trie, addthis, (a_c_arg_t) entry_num);
+ }
+ }
+
+ if(reverse)
+ add_entry_to_trie(&ab->revfull_trie, reverse, (a_c_arg_t) entry_num);
+
+ if(forward && forward != ae->fullname)
+ fs_give((void **) &forward);
+ }
+
+ if(ae->tag == Single && ae->addr.addr && ae->addr.addr[0]){
+ char buf[1000];
+ char *tmp_a_string, *simple_addr = NULL;
+ ADDRESS *addr = NULL;
+ char *fakedomain = "@";
+
+ /*
+ * Isolate the actual address out of ae.
+ */
+ tmp_a_string = cpystr(ae->addr.addr);
+ rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **) &tmp_a_string);
+
+ if(addr){
+ if(addr->mailbox && addr->host
+ && !(addr->host[0] == '@' && addr->host[1] == '\0'))
+ simple_addr = simple_addr_string(addr, buf, sizeof(buf));
+
+ /*
+ * If the fullname wasn't set in the addrbook entry there
+ * may still be one that is part of the address. We need
+ * to be careful because we may be in the middle of opening
+ * the address book right now. Don't call something like
+ * our_build_address because it will probably re-open this
+ * same addrbook infinitely.
+ */
+ if(!(ae->fullname && ae->fullname[0])
+ && addr->personal && addr->personal[0])
+ add_entry_to_trie(&ab->full_trie, addr->personal,
+ (a_c_arg_t) entry_num);
+
+ mail_free_address(&addr);
+ }
+
+ if(simple_addr)
+ add_entry_to_trie(&ab->addr_trie, simple_addr, (a_c_arg_t) entry_num);
+ }
+ }
+ }
+
+ dprint((9, "- build_abook_tries done -\n"));
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return 0;
+}
+
+
+void
+add_entry_to_trie(AdrBk_Trie **head, char *str, a_c_arg_t entry_num)
+{
+ AdrBk_Trie *temp, *trail;
+ char *addthis, *p, buf[1000];
+
+ if(!head || !str || !*str)
+ return;
+
+ /* add as lower case */
+
+ for(p = str; *p && ((*p & 0x80) || !isupper((unsigned char) *p)); p++)
+ ;
+
+ if(*p){
+ strncpy(buf, str, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ for(p = buf; *p; p++)
+ if(!(*p & 0x80) && isupper((unsigned char) *p))
+ *p = tolower(*p);
+
+ addthis = buf;
+ }
+ else
+ addthis = str;
+
+ temp = trail = (*head);
+
+ /*
+ * Find way down the trie, adding missing nodes as we go.
+ */
+ for(p = addthis; *p;){
+ if(temp == NULL){
+ temp = (AdrBk_Trie *) fs_get(sizeof(*temp));
+ memset(temp, 0, sizeof(*temp));
+ temp->value = *p;
+ temp->entrynum = NO_NEXT;
+
+ if(*head == NULL)
+ *head = temp;
+ else
+ trail->down = temp;
+ }
+ else{
+ while((temp != NULL) && (temp->value != *p)){
+ trail = temp;
+ temp = temp->right;
+ }
+
+ /* wasn't there, add new node */
+ if(temp == NULL){
+ temp = (AdrBk_Trie *) fs_get(sizeof(*temp));
+ memset(temp, 0, sizeof(*temp));
+ temp->value = *p;
+ temp->entrynum = NO_NEXT;
+ trail->right = temp;
+ }
+ }
+
+ if(*(++p)){
+ trail = temp;
+ temp = temp->down;
+ }
+ }
+
+ /*
+ * If entrynum is already filled in there must be an entry with
+ * the same nickname earlier in the abook. Use that earlier entry.
+ */
+ if(temp != NULL && temp->entrynum == NO_NEXT)
+ temp->entrynum = (adrbk_cntr_t) entry_num;
+}
+
+
+/*
+ * Returns entry_num of first entry with this nickname, else NO_NEXT.
+ */
+adrbk_cntr_t
+lookup_nickname_in_trie(AdrBk *ab, char *nickname)
+{
+ if(!ab || !nickname || !ab->nick_trie)
+ return(-1L);
+
+ return(lookup_in_abook_trie(ab->nick_trie, nickname));
+}
+
+
+/*
+ * Returns entry_num of first entry with this address, else NO_NEXT.
+ */
+adrbk_cntr_t
+lookup_address_in_trie(AdrBk *ab, char *address)
+{
+ dprint((9, "lookup_address_in_trie: %s\n", ab ? (ab->addr_trie ? (address ? address : "?") : "null addr_trie") : "null ab"));
+ if(!ab || !address || !ab->addr_trie)
+ return(-1L);
+
+ return(lookup_in_abook_trie(ab->addr_trie, address));
+}
+
+
+adrbk_cntr_t
+lookup_in_abook_trie(AdrBk_Trie *t, char *str)
+{
+ char *p, *lookthisup;
+ char buf[1000];
+ adrbk_cntr_t ret = NO_NEXT;
+
+ if(!t || !str)
+ return(ret);
+
+ /* make lookup case independent */
+
+ for(p = str; *p && !(*p & 0x80) && islower((unsigned char) *p); p++)
+ ;
+
+ if(*p){
+ strncpy(buf, str, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ for(p = buf; *p; p++)
+ if(!(*p & 0x80) && isupper((unsigned char) *p))
+ *p = tolower(*p);
+
+ lookthisup = buf;
+ }
+ else
+ lookthisup = str;
+
+ p = lookthisup;
+
+ /*
+ * We usually return out from inside the loop (unless str == "").
+ */
+ while(*p){
+ /* search for character at this level */
+ while(t->value != *p){
+ if(t->right == NULL)
+ return(ret); /* no match */
+
+ t = t->right;
+ }
+
+ if(*++p == '\0') /* end of str, a match */
+ return(t->entrynum);
+
+ /* need to go down to match next character */
+ if(t->down == NULL) /* no match */
+ return(ret);
+
+ t = t->down;
+ }
+
+ return(ret);
+}
+
+
+void
+free_abook_trie(AdrBk_Trie **trie)
+{
+ if(trie){
+ if(*trie){
+ free_abook_trie(&(*trie)->down);
+ free_abook_trie(&(*trie)->right);
+ fs_give((void **) trie);
+ }
+ }
+}
+
+
+/*
+ * Returns pointer to start of next address book entry from disk file.
+ * The return will be in raw form from the file with newlines still
+ * embedded. Or NULL at end of file.
+ *
+ * If rew is set, rewind the file and start over at beginning.
+ */
+char *
+get_next_abook_entry(FILE *fp, int rew)
+{
+ char *returned_lines = NULL, *p;
+ char line[1024];
+ static int will_be_done_next_time = 0;
+ static long next_nickname_offset = 0L;
+ size_t lsize;
+ long offset, saved_offset;
+ long len = 0L;
+
+#define CHUNKSIZE 500
+
+ lsize = sizeof(line);
+
+ if(rew){
+ will_be_done_next_time = 0;
+ rewind(fp);
+ /* skip leading (bogus) continuation lines */
+ do{
+ offset = ftell(fp);
+ line[0] = '\0';
+ line[lsize-2] = '\0';
+ p = fgets(line, lsize, fp);
+
+ if(p == NULL)
+ return(NULL);
+
+ /* line is too long to fit, read the rest and discard */
+ while(line[lsize-2] != '\0' && line[lsize-2] != '\n'
+ && p != NULL){
+
+ /* get next lsize-1 characters, leaving line[0] */
+ line[lsize-2] = '\0';
+ p = fgets(line+1, lsize-1, fp);
+ }
+ }while(line[0] == SPACE);
+
+ /* offset is start of first good line now */
+ next_nickname_offset = offset;
+ }
+
+ if(will_be_done_next_time)
+ return(NULL);
+
+ /* we set this up in rew==1 case or on previous call to this routine */
+ offset = next_nickname_offset;
+
+ /*
+ * The rest is working on finding the start of the next entry so
+ * skip continuation lines
+ */
+ do{
+ next_nickname_offset = ftell(fp);
+ line[0] = '\0';
+ line[lsize-2] = '\0';
+ p = fgets(line, lsize, fp);
+
+ /* line is too long to fit, read the rest and discard */
+ while(line[lsize-2] != '\0' && line[lsize-2] != '\n'
+ && p != NULL){
+
+ /* get next lsize-1 characters, leaving line[0] */
+ line[lsize-2] = '\0';
+ p = fgets(line+1, lsize-1, fp);
+ }
+ }while(line[0] == SPACE);
+
+ /* next_nickname_offset is start of next entry now */
+
+ if(!line[0])
+ will_be_done_next_time = 1;
+
+ len = next_nickname_offset - offset;
+
+ returned_lines = (char *) fs_get((len + 1) * sizeof(char));
+
+ saved_offset = ftell(fp);
+ if(fseek(fp, offset, 0)){
+ dprint((2, "get_next_ab_entry: trouble fseeking\n"));
+ len = 0;
+ fs_give((void **) &returned_lines);
+ }
+ else{
+ if(fread(returned_lines, sizeof(char), (unsigned) len, fp) != len){
+ dprint((2, "get_next_ab_entry: trouble freading\n"));
+ len = 0;
+ fs_give((void **) &returned_lines);
+ }
+ }
+
+ if(fseek(fp, saved_offset, 0)){
+ dprint((2, "get_next_ab_entry: trouble fseeking to saved_offset\n"));
+ len = 0;
+ fs_give((void **) &returned_lines);
+ }
+
+ if(returned_lines)
+ returned_lines[len] = '\0';
+
+ return(returned_lines);
+}
+
+
+/*
+ * Returns a pointer to the start of the mailbox@host part of this
+ * address string, and a pointer to the end + 1. The caller can then
+ * replace the end char with \0 and call the hash function, then put
+ * back the end char. Start_addr and end_addr are assumed to be non-null.
+ */
+void
+strip_addr_string(char *addrstr, char **start_addr, char **end_addr)
+{
+ register char *q;
+ int in_quotes = 0,
+ in_comment = 0;
+ char prev_char = '\0';
+
+ if(!addrstr || !*addrstr){
+ *start_addr = NULL;
+ *end_addr = NULL;
+ return;
+ }
+
+ *start_addr = addrstr;
+
+ for(q = addrstr; *q; q++){
+ switch(*q){
+ case '<':
+ if(!in_quotes && !in_comment){
+ if(*++q){
+ *start_addr = q;
+ /* skip to > */
+ while(*q && *q != '>')
+ q++;
+
+ /* found > */
+ if(*q){
+ *end_addr = q;
+ return;
+ }
+ else
+ q--;
+ }
+ }
+
+ break;
+
+ case LPAREN:
+ if(!in_quotes && !in_comment)
+ in_comment = 1;
+ break;
+
+ case RPAREN:
+ if(in_comment && prev_char != BSLASH)
+ in_comment = 0;
+ break;
+
+ case QUOTE:
+ if(in_quotes && prev_char != BSLASH)
+ in_quotes = 0;
+ else if(!in_quotes && !in_comment)
+ in_quotes = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ prev_char = *q;
+ }
+
+ *end_addr = q;
+}
+
+
+/*
+ * Fill in the passed in ae pointer by parsing the str that is passed.
+ *
+ * Args ab --
+ * ae -- pointer we want to fill in. The individual members of
+ * the ae struct will be allocated here
+ * str -- the string from the on-disk address book file for this entry
+ *
+ * Returns a pointer to ae or NULL if there are problems.
+ */
+AdrBk_Entry *
+init_ae(AdrBk *ab, AdrBk_Entry *a, char *str)
+{
+ char *p;
+ char *addrfield = (char *) NULL;
+ char *addrfield_end;
+ char *nickname, *fullname, *fcc, *extra;
+
+ if(!ab){
+ dprint((2, "init_ae: found trouble: NULL ab\n"));
+ return((AdrBk_Entry *) NULL);
+ }
+
+ defvalue_ae(a);
+
+ p = str;
+
+ REPLACE_NEWLINES_WITH_SPACE(p);
+
+ nickname = p;
+ SKIP_TO_TAB(p);
+ if(!*p){
+ RM_END_SPACE(nickname, p);
+ a->nickname = cpystr(nickname);
+ }
+ else{
+ *p = '\0';
+ RM_END_SPACE(nickname, p);
+ a->nickname = cpystr(nickname);
+ p++;
+ SKIP_SPACE(p);
+ fullname = p;
+ SKIP_TO_TAB(p);
+ if(!*p){
+ RM_END_SPACE(fullname, p);
+ a->fullname = cpystr(fullname);
+ }
+ else{
+ *p = '\0';
+ RM_END_SPACE(fullname, p);
+ a->fullname = cpystr(fullname);
+ p++;
+ SKIP_SPACE(p);
+ addrfield = p;
+ SKIP_TO_TAB(p);
+ if(!*p){
+ RM_END_SPACE(addrfield, p);
+ }
+ else{
+ *p = '\0';
+ RM_END_SPACE(addrfield, p);
+ p++;
+ SKIP_SPACE(p);
+ fcc = p;
+ SKIP_TO_TAB(p);
+ if(!*p){
+ RM_END_SPACE(fcc, p);
+ a->fcc = cpystr(fcc);
+ }
+ else{
+ char *src, *dst;
+
+ *p = '\0';
+ RM_END_SPACE(fcc, p);
+ a->fcc = cpystr(fcc);
+ p++;
+ SKIP_SPACE(p);
+ extra = p;
+ p = extra + strlen(extra);
+ RM_END_SPACE(extra, p);
+
+ /*
+ * When we wrap long comments we insert an extra colon
+ * in the wrap so we can spot it and take it back out.
+ * Pretty much a hack since we thought of it a long
+ * time after designing it, but it eliminates the limit
+ * on length of comments. Here we are looking for
+ * <SP> <SP> : <SP> or
+ * <SP> <SP> <SP> : <SP> and replacing it with <SP>.
+ * There could have been a single \n or \r\n, so that
+ * is why we check for 2 or 3 spaces before the colon.
+ * (This was another mistake.)
+ */
+ dst = src = extra;
+ while(*src != '\0'){
+ if(*src == SPACE && *(src+1) == SPACE &&
+ *(src+2) == ':' && *(src+3) == SPACE){
+
+ /*
+ * If there was an extra space because of the
+ * CRLF (instead of LF) then we already put
+ * a SP in dst last time through the loop
+ * and don't need to add another.
+ */
+ if(src == extra || *(src-1) != SPACE)
+ *dst++ = *src;
+
+ src += 4;
+ }
+ else
+ *dst++ = *src++;
+ }
+
+ *dst = '\0';
+ a->extra = cpystr(extra);
+ }
+ }
+ }
+ }
+
+ /* decode and convert to UTF-8 if we need to */
+
+ convert_possibly_encoded_str_to_utf8(&a->nickname);
+ convert_possibly_encoded_str_to_utf8(&a->fullname);
+ convert_possibly_encoded_str_to_utf8(&a->fcc);
+ convert_possibly_encoded_str_to_utf8(&a->extra);
+
+ /* parse addrfield */
+ if(addrfield){
+ if(*addrfield == '('){ /* it's a list */
+ a->tag = List;
+ p = addrfield;
+ addrfield_end = p + strlen(p);
+
+ /*
+ * Get rid of the parens.
+ * If this isn't true the input file is messed up.
+ */
+ if(p[strlen(p)-1] == ')'){
+ char **ll;
+
+ p[strlen(p)-1] = '\0';
+ p++;
+ a->addr.list = parse_addrlist(p);
+ for(ll = a->addr.list; ll && *ll; ll++)
+ convert_possibly_encoded_str_to_utf8(ll);
+ }
+ else{
+ /* put back what was there to start with */
+ *addrfield_end = ')';
+ a->addr.list = (char **)fs_get(sizeof(char *) * 2);
+ a->addr.list[0] = cpystr(addrfield);
+ a->addr.list[1] = NULL;
+ dprint((2, "parsing error reading addressbook: missing right paren: %s\n",
+ addrfield ? addrfield : "?"));
+ }
+ }
+ else{ /* A plain, single address */
+
+ a->tag = Single;
+ a->addr.addr = cpystr(addrfield);
+ convert_possibly_encoded_str_to_utf8(&a->addr.addr);
+ }
+ }
+ else{
+ /*
+ * If no addrfield, assume an empty Single.
+ */
+ a->addr.addr = cpystr("");
+ a->tag = Single;
+ }
+
+ return(a);
+}
+
+
+/*
+ * Return the size of the address book
+ */
+adrbk_cntr_t
+adrbk_count(AdrBk *ab)
+{
+ return(ab ? ab->count : (adrbk_cntr_t) 0);
+}
+
+
+/*
+ * Return a pointer to the ae that has index number "entry_num".
+ */
+AdrBk_Entry *
+adrbk_get_ae(AdrBk *ab, a_c_arg_t entry_num)
+{
+ if(!ab || entry_num >= (a_c_arg_t) ab->count)
+ return((AdrBk_Entry *) NULL);
+
+ return(&ab->arr[entry_num]);
+}
+
+
+/*
+ * Return a pointer to the deleted ae that has index number "entry_num".
+ */
+AdrBk_Entry *
+adrbk_get_delae(AdrBk *ab, a_c_arg_t entry_num)
+{
+ if(!ab || entry_num >= (a_c_arg_t) ab->del_count)
+ return((AdrBk_Entry *) NULL);
+
+ return(&ab->del[entry_num]);
+}
+
+
+/*
+ * Look up an entry in the address book given a nickname
+ *
+ * Args: ab -- the address book
+ * nickname -- nickname to match
+ * entry_num -- if matched, return entry_num of match here
+ *
+ * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found.
+ *
+ * Lookups usually need to be recursive in case the address
+ * book references itself. This is left to the next level up.
+ * adrbk_clearrefs() is provided to clear all the reference tags in
+ * the address book for loop detection.
+ * When there are duplicates of the same nickname we return the first.
+ * This can only happen if addrbook was edited externally.
+ */
+AdrBk_Entry *
+adrbk_lookup_by_nick(AdrBk *ab, char *nickname, adrbk_cntr_t *entry_num)
+{
+ adrbk_cntr_t num;
+ AdrBk_Entry *ae;
+
+ dprint((5, "- adrbk_lookup_by_nick(%s) (in %s) -\n",
+ nickname ? nickname : "?",
+ (ab && ab->filename) ? ab->filename : "?"));
+
+ if(!ab || !nickname || !nickname[0])
+ return NULL;
+
+
+ num = lookup_nickname_in_trie(ab, nickname);
+
+ if(num != NO_NEXT){
+ ae = adrbk_get_ae(ab, (a_c_arg_t) num);
+ if(entry_num && ae)
+ *entry_num = num;
+
+ return(ae);
+ }
+ else
+ return((AdrBk_Entry *) NULL);
+}
+
+
+/*
+ * Look up an entry in the address book given an address
+ *
+ * Args: ab -- the address book
+ * address -- address to match
+ * entry_num -- if matched, return entry_num of match here
+ *
+ * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found.
+ *
+ * Note: When there are multiple occurrences of an address in an addressbook,
+ * which there will be if more than one nickname points to same address, then
+ * we want this to match the first occurrence so that the fcc you get will
+ * be predictable.
+ */
+AdrBk_Entry *
+adrbk_lookup_by_addr(AdrBk *ab, char *address, adrbk_cntr_t *entry_num)
+{
+ adrbk_cntr_t num;
+ AdrBk_Entry *ae;
+
+ dprint((5, "- adrbk_lookup_by_addr(%s) (in %s) -\n",
+ address ? address : "?",
+ (ab && ab->filename) ? ab->filename : "?"));
+
+ if(!ab || !address || !address[0])
+ return((AdrBk_Entry *)NULL);
+
+ num = lookup_address_in_trie(ab, address);
+
+ if(num != NO_NEXT){
+ ae = adrbk_get_ae(ab, (a_c_arg_t) num);
+ if(entry_num && ae)
+ *entry_num = num;
+
+ return(ae);
+ }
+ else
+ return((AdrBk_Entry *)NULL);
+}
+
+
+/*
+ * Format a full name.
+ *
+ * Args: fullname -- full name out of address book for formatting
+ * first -- Return a pointer to first name here.
+ * last -- Return a pointer to last name here.
+ *
+ * Result: Returns pointer to name formatted for a mail header. Space is
+ * allocated here and should be freed by caller.
+ *
+ * We need this because we store full names as Last, First.
+ * If the name has no comma, then no change is made.
+ * Otherwise the text before the first comma is moved to the end and
+ * the comma is deleted.
+ *
+ * Last and first have to be freed by caller.
+ */
+char *
+adrbk_formatname(char *fullname, char **first, char **last)
+{
+ char *comma;
+ char *new_name;
+
+ if(first)
+ *first = NULL;
+ if(last)
+ *last = NULL;
+
+ /*
+ * There is an assumption that the fullname is a UTF-8 string.
+ */
+
+ if(fullname[0] != '"' && (comma = strindex(fullname, ',')) != NULL){
+ size_t l;
+ int last_name_len = comma - fullname;
+
+ comma++;
+ while(*comma && isspace((unsigned char)*comma))
+ comma++;
+
+ if(first)
+ *first = cpystr(comma);
+
+ if(last){
+ *last = (char *)fs_get((last_name_len + 1) * sizeof(char));
+ strncpy(*last, fullname, last_name_len);
+ (*last)[last_name_len] = '\0';
+ }
+
+ l = strlen(comma) + 1 + last_name_len;
+ new_name = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(new_name, comma, l);
+ new_name[l] = '\0';
+ strncat(new_name, " ", l+1-1-strlen(new_name));
+ new_name[l] = '\0';
+ strncat(new_name, fullname, MIN(last_name_len,l+1-1-strlen(new_name)));
+ new_name[l] = '\0';
+ }
+ else
+ new_name = cpystr(fullname);
+
+ return(new_name);
+}
+
+
+/*
+ * Clear reference flags in preparation for a recursive lookup.
+ *
+ * For loop detection during address book look up. This clears all the
+ * referenced flags, then as the lookup proceeds the referenced flags can
+ * be checked and set.
+ */
+void
+adrbk_clearrefs(AdrBk *ab)
+{
+ adrbk_cntr_t entry_num;
+ AdrBk_Entry *ae;
+
+ dprint((9, "- adrbk_clearrefs -\n"));
+
+ if(!ab)
+ return;
+
+ for(entry_num = 0; entry_num < ab->count; entry_num++){
+ ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);
+ ae->referenced = 0;
+ }
+}
+
+
+/*
+ * Allocate a new AdrBk_Entry
+ */
+AdrBk_Entry *
+adrbk_newentry(void)
+{
+ AdrBk_Entry *ae;
+
+ ae = (AdrBk_Entry *) fs_get(sizeof(AdrBk_Entry));
+ defvalue_ae(ae);
+
+ return(ae);
+}
+
+
+/*
+ * Just sets ae values to default.
+ * Parts should be freed before calling this or they will leak.
+ */
+void
+defvalue_ae(AdrBk_Entry *ae)
+{
+ ae->nickname = empty;
+ ae->fullname = empty;
+ ae->addr.addr = empty;
+ ae->fcc = empty;
+ ae->extra = empty;
+ ae->tag = NotSet;
+ ae->referenced = 0;
+}
+
+
+AdrBk_Entry *
+copy_ae(AdrBk_Entry *src)
+{
+ AdrBk_Entry *a;
+
+ a = adrbk_newentry();
+ a->tag = src->tag;
+ a->nickname = cpystr(src->nickname ? src->nickname : "");
+ a->fullname = cpystr(src->fullname ? src->fullname : "");
+ a->fcc = cpystr(src->fcc ? src->fcc : "");
+ a->extra = cpystr(src->extra ? src->extra : "");
+ if(a->tag == Single)
+ a->addr.addr = cpystr(src->addr.addr ? src->addr.addr : "");
+ else if(a->tag == List){
+ char **p;
+ int i, n;
+
+ /* count list */
+ for(p = src->addr.list; p && *p; p++)
+ ;/* do nothing */
+
+ if(p == NULL)
+ n = 0;
+ else
+ n = p - src->addr.list;
+
+ a->addr.list = (char **)fs_get((n+1) * sizeof(char *));
+ for(i = 0; i < n; i++)
+ a->addr.list[i] = cpystr(src->addr.list[i]);
+
+ a->addr.list[n] = NULL;
+ }
+
+ return(a);
+}
+
+
+/*
+ * Add an entry to the address book, or modify an existing entry
+ *
+ * Args: ab -- address book to add to
+ * old_entry_num -- the entry we want to modify. If this is NO_NEXT, then
+ * we look up the nickname passed in to see if that's the
+ * entry to modify, else it is a new entry.
+ * nickname -- the nickname for new entry
+ * fullname -- the fullname for new entry
+ * address -- the address for new entry
+ * fcc -- the fcc for new entry
+ * extra -- the extra field for new entry
+ * tag -- the type of new entry
+ * new_entry_num -- return entry_num of new or modified entry here
+ * resort_happened -- means that more than just the current entry changed,
+ * either something was added or order was changed
+ * enable_intr -- tell adrbk_write to enable interrupt handling
+ * be_quiet -- tell adrbk_write to not do percent done messages
+ * write_it -- only do adrbk_write if this is set
+ *
+ * Result: return code: 0 all went well
+ * -2 error writing address book, check errno
+ * -3 no modification, the tag given didn't match
+ * existing tag
+ * -4 tabs are in one of the fields passed in
+ *
+ * If the nickname exists in the address book already, the operation is
+ * considered a modification even if the case does not match exactly,
+ * otherwise it is an add. The entry the operation occurs on is returned
+ * in new. All fields are set to those passed in; that is, passing in NULL
+ * even on a modification will set those fields to NULL as opposed to leaving
+ * them unchanged. It is acceptable to pass in the current strings
+ * in the entry in the case of modification. For address lists, the
+ * structure passed in is what is used, so the storage has to all have
+ * come from fs_get(). If the pointer passed in is the same as
+ * the current field, no change is made.
+ */
+int
+adrbk_add(AdrBk *ab, a_c_arg_t old_entry_num, char *nickname, char *fullname,
+ char *address, char *fcc, char *extra, Tag tag, adrbk_cntr_t *new_entry_num,
+ int *resort_happened, int enable_intr, int be_quiet, int write_it)
+{
+ AdrBk_Entry *a;
+ AdrBk_Entry *ae;
+ adrbk_cntr_t old_enum;
+ adrbk_cntr_t new_enum;
+ int (*cmp_func)();
+ int retval = 0;
+ int need_write = 0;
+ int set_mangled = 0;
+
+ dprint((3, "- adrbk_add(%s) -\n", nickname ? nickname : ""));
+
+ if(!ab)
+ return -2;
+
+ /* ---- Make sure there are no tabs in the stuff to add ------*/
+ if((nickname != NULL && strindex(nickname, TAB) != NULL) ||
+ (fullname != NULL && strindex(fullname, TAB) != NULL) ||
+ (fcc != NULL && strindex(fcc, TAB) != NULL) ||
+ (tag == Single && address != NULL && strindex(address, TAB) != NULL))
+ return -4;
+
+ /*
+ * Are we adding or updating ?
+ *
+ * If old_entry_num was passed in, we're updating that. If nickname
+ * already exists, we're updating that entry. Otherwise, this is an add.
+ */
+ if((adrbk_cntr_t)old_entry_num != NO_NEXT){
+ ae = adrbk_get_ae(ab, old_entry_num);
+ if(ae)
+ old_enum = (adrbk_cntr_t)old_entry_num;
+ }
+ else
+ ae = adrbk_lookup_by_nick(ab, nickname, &old_enum);
+
+ if(ae == NULL){ /*----- adding a new entry ----*/
+
+ ae = adrbk_newentry();
+ ae->tag = tag;
+ if(nickname)
+ ae->nickname = cpystr(nickname);
+ if(fullname)
+ ae->fullname = cpystr(fullname);
+ if(fcc)
+ ae->fcc = cpystr(fcc);
+ if(extra)
+ ae->extra = cpystr(extra);
+
+ if(tag == Single)
+ ae->addr.addr = cpystr(address);
+ else
+ ae->addr.list = (char **)NULL;
+
+ cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
+ cmp_ae_by_full_lists_last :
+ (ab->sort_rule == AB_SORT_RULE_FULL) ?
+ cmp_ae_by_full :
+ (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
+ cmp_ae_by_nick_lists_last :
+ /* (ab->sort_rule == AB_SORT_RULE_NICK) */
+ cmp_ae_by_nick;
+
+ if(ab->sort_rule == AB_SORT_RULE_NONE) /* put it last */
+ new_enum = ab->count;
+ else /* Find slot for it */
+ for(new_enum = 0, a = adrbk_get_ae(ab, (a_c_arg_t) new_enum);
+ a != (AdrBk_Entry *)NULL;
+ a = adrbk_get_ae(ab, (a_c_arg_t) (++new_enum))){
+ if((*cmp_func)((qsort_t *)&a, (qsort_t *)&ae) >= 0)
+ break;
+ }
+
+ /* Insert ae before entry new_enum. */
+ insert_ab_entry(ab, (a_c_arg_t) new_enum, ae, 0);
+
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps_global))
+ exp_add_nth(ab->exp, (a_c_arg_t)new_enum);
+
+ exp_add_nth(ab->selects, (a_c_arg_t)new_enum);
+
+ /*
+ * insert_ab_entry copies the pointers of ae so the things
+ * being pointed to (nickname, ...) are still in use.
+ * Don't free them but free the ae struct itself.
+ */
+ fs_give((void **) &ae);
+
+ /*---- return in pointer if requested -----*/
+ if(new_entry_num)
+ *new_entry_num = new_enum;
+
+ if(resort_happened)
+ *resort_happened = 1;
+
+ if(write_it)
+ retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num, &set_mangled,
+ enable_intr, be_quiet);
+ }
+ else{
+ /*----- Updating an existing entry ----*/
+
+ int fix_tries = 0;
+
+ if(ae->tag != tag)
+ return -3;
+
+ /*
+ * Instead of just freeing and reallocating here we attempt to re-use
+ * the space that was already allocated if possible.
+ */
+ if(ae->nickname != nickname
+ && ae->nickname != NULL
+ && nickname != NULL
+ && strcmp(nickname, ae->nickname) != 0){
+ need_write++;
+ fix_tries++;
+ /* can use already alloc'd space */
+ if(ae->nickname != NULL && nickname != NULL &&
+ strlen(nickname) <= strlen(ae->nickname)){
+
+ strncpy(ae->nickname, nickname, strlen(ae->nickname)+1);
+ }
+ else{
+ if(ae->nickname != NULL && ae->nickname != empty)
+ fs_give((void **)&ae->nickname);
+
+ ae->nickname = nickname ? cpystr(nickname) : nickname;
+ }
+ }
+
+ if(ae->fullname != fullname
+ && ae->fullname != NULL
+ && fullname != NULL
+ && strcmp(fullname, ae->fullname) != 0){
+ need_write++;
+ if(ae->fullname != NULL && fullname != NULL &&
+ strlen(fullname) <= strlen(ae->fullname)){
+
+ strncpy(ae->fullname, fullname, strlen(ae->fullname)+1);
+ }
+ else{
+ if(ae->fullname != NULL && ae->fullname != empty)
+ fs_give((void **)&ae->fullname);
+
+ ae->fullname = fullname ? cpystr(fullname) : fullname;
+ }
+ }
+
+ if(ae->fcc != fcc
+ && ae->fcc != NULL
+ && fcc != NULL
+ && strcmp(fcc, ae->fcc) != 0){
+ need_write++;
+ if(ae->fcc != NULL && fcc != NULL &&
+ strlen(fcc) <= strlen(ae->fcc)){
+
+ strncpy(ae->fcc, fcc, strlen(ae->fcc)+1);
+ }
+ else{
+ if(ae->fcc != NULL && ae->fcc != empty)
+ fs_give((void **)&ae->fcc);
+
+ ae->fcc = fcc ? cpystr(fcc) : fcc;
+ }
+ }
+
+ if(ae->extra != extra
+ && ae->extra != NULL
+ && extra != NULL
+ && strcmp(extra, ae->extra) != 0){
+ need_write++;
+ if(ae->extra != NULL && extra != NULL &&
+ strlen(extra) <= strlen(ae->extra)){
+
+ strncpy(ae->extra, extra, strlen(ae->extra)+1);
+ }
+ else{
+ if(ae->extra != NULL && ae->extra != empty)
+ fs_give((void **)&ae->extra);
+
+ ae->extra = extra ? cpystr(extra) : extra;
+ }
+ }
+
+ if(tag == Single){
+ /*---- Single ----*/
+ if(ae->addr.addr != address
+ && ae->addr.addr != NULL
+ && address != NULL
+ && strcmp(address, ae->addr.addr) != 0){
+ need_write++;
+ fix_tries++;
+ if(ae->addr.addr != NULL && address != NULL &&
+ strlen(address) <= strlen(ae->addr.addr)){
+
+ strncpy(ae->addr.addr, address, strlen(ae->addr.addr)+1);
+ }
+ else{
+ if(ae->addr.addr != NULL && ae->addr.addr != empty)
+ fs_give((void **)&ae->addr.addr);
+
+ ae->addr.addr = address ? cpystr(address) : address;
+ }
+ }
+ }
+ else{
+ /*---- List -----*/
+ /*
+ * We don't mess with lists here.
+ * The caller has to do it with adrbk_listadd().
+ */
+ ;/* do nothing */
+ }
+
+
+ /*---------- Make sure it's still in order ---------*/
+
+ /*
+ * old_enum is where ae is currently located
+ * put it where it belongs
+ */
+ if(need_write){
+ new_enum = re_sort_particular_entry(ab, (a_c_arg_t) old_enum);
+ if(old_enum != new_enum)
+ fix_tries++;
+ }
+ else
+ new_enum = old_enum;
+
+ /*---- return in pointer if requested -----*/
+ if(new_entry_num)
+ *new_entry_num = new_enum;
+
+ if(resort_happened)
+ *resort_happened = (old_enum != new_enum);
+
+ if(fix_tries)
+ repair_abook_tries(ab);
+
+ if(write_it && need_write){
+ int sort_happened = 0;
+
+ retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num,
+ &sort_happened, enable_intr, be_quiet);
+
+ set_mangled = sort_happened;
+
+ if(new_entry_num)
+ new_enum = (*new_entry_num);
+
+ if(resort_happened && (sort_happened || (old_enum != new_enum)))
+ *resort_happened = 1;
+ }
+ else
+ retval = 0;
+ }
+
+ if(set_mangled)
+ ps_global->mangled_screen = 1;
+
+ return(retval);
+}
+
+
+/*
+ * Similar to adrbk_add, but lower cost. No sorting is done, the new entry
+ * goes on the end. This won't work if it is an edit instead of an append.
+ * The address book is not committed to disk.
+ *
+ * Args: ab -- address book to add to
+ * nickname -- the nickname for new entry
+ * fullname -- the fullname for new entry
+ * address -- the address for new entry
+ * fcc -- the fcc for new entry
+ * extra -- the extra field for new entry
+ * tag -- the type of new entry
+ *
+ * Result: return code: 0 all went well
+ * -2 error writing address book, check errno
+ * -3 no modification, the tag given didn't match
+ * existing tag
+ * -4 tabs are in one of the fields passed in
+ */
+int
+adrbk_append(AdrBk *ab, char *nickname, char *fullname, char *address, char *fcc,
+ char *extra, Tag tag, adrbk_cntr_t *new_entry_num)
+{
+ AdrBk_Entry *ae;
+
+ dprint((3, "- adrbk_append(%s) -\n", nickname ? nickname : ""));
+
+ if(!ab)
+ return -2;
+
+ /* ---- Make sure there are no tabs in the stuff to add ------*/
+ if((nickname != NULL && strindex(nickname, TAB) != NULL) ||
+ (fullname != NULL && strindex(fullname, TAB) != NULL) ||
+ (fcc != NULL && strindex(fcc, TAB) != NULL) ||
+ (tag == Single && address != NULL && strindex(address, TAB) != NULL))
+ return -4;
+
+ ae = adrbk_newentry();
+ ae->tag = tag;
+ if(nickname)
+ ae->nickname = cpystr(nickname);
+ if(fullname)
+ ae->fullname = cpystr(fullname);
+ if(fcc)
+ ae->fcc = cpystr(fcc);
+ if(extra)
+ ae->extra = cpystr(extra);
+
+ if(tag == Single)
+ ae->addr.addr = cpystr(address);
+ else
+ ae->addr.list = (char **)NULL;
+
+ if(new_entry_num)
+ *new_entry_num = ab->count;
+
+ insert_ab_entry(ab, (a_c_arg_t) ab->count, ae, 0);
+
+ /*
+ * insert_ab_entry copies the pointers of ae so the things
+ * being pointed to (nickname, ...) are still in use.
+ * Don't free them but free the ae struct itself.
+ */
+ fs_give((void **) &ae);
+
+ return(0);
+}
+
+
+/*
+ * The entire address book is assumed sorted correctly except perhaps for
+ * entry number cur. Put it in the correct place. Return the new entry
+ * number for cur.
+ */
+adrbk_cntr_t
+re_sort_particular_entry(AdrBk *ab, a_c_arg_t cur)
+{
+ AdrBk_Entry *ae_cur, *ae_prev, *ae_next, *ae_small_enough, *ae_big_enough;
+ long small_enough;
+ adrbk_cntr_t big_enough;
+ adrbk_cntr_t new_entry_num;
+ int (*cmp_func)(const qsort_t *, const qsort_t *);
+
+ dprint((9, "- re_sort -\n"));
+
+ cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
+ cmp_ae_by_full_lists_last :
+ (ab->sort_rule == AB_SORT_RULE_FULL) ?
+ cmp_ae_by_full :
+ (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
+ cmp_ae_by_nick_lists_last :
+ /* (ab->sort_rule == AB_SORT_RULE_NICK) */
+ cmp_ae_by_nick;
+
+ new_entry_num = (adrbk_cntr_t) cur;
+
+ if(ab->sort_rule == AB_SORT_RULE_NONE)
+ return(new_entry_num);
+
+ ae_cur = adrbk_get_ae(ab, cur);
+
+ if(cur > 0)
+ ae_prev = adrbk_get_ae(ab, cur - 1);
+
+ if(cur < ab->count -1)
+ ae_next = adrbk_get_ae(ab, cur + 1);
+
+ /*
+ * A possible optimization here would be to implement some sort of
+ * binary search to find where it goes instead of stepping through the
+ * entries one at a time.
+ */
+ if(cur > 0 &&
+ (*cmp_func)((qsort_t *)&ae_cur,(qsort_t *)&ae_prev) < 0){
+ /*--- Out of order, needs to be moved up ----*/
+ for(small_enough = (long)cur - 2; small_enough >= 0L; small_enough--){
+ ae_small_enough = adrbk_get_ae(ab,(a_c_arg_t) small_enough);
+ if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_small_enough) >= 0)
+ break;
+ }
+ new_entry_num = (adrbk_cntr_t)(small_enough + 1L);
+ move_ab_entry(ab, cur, (a_c_arg_t) new_entry_num);
+ }
+ else if(cur < ab->count - 1 &&
+ (*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_next) > 0){
+ /*---- Out of order needs, to be moved towards end of list ----*/
+ for(big_enough = (adrbk_cntr_t)(cur + 2);
+ big_enough < ab->count;
+ big_enough++){
+ ae_big_enough = adrbk_get_ae(ab, (a_c_arg_t) big_enough);
+ if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_big_enough) <= 0)
+ break;
+ }
+ new_entry_num = big_enough - 1;
+ move_ab_entry(ab, cur, (a_c_arg_t) big_enough);
+ }
+
+ dprint((9, "- re_sort done -\n"));
+
+ return(new_entry_num);
+}
+
+
+/*
+ * Delete an entry from the address book
+ *
+ * Args: ab -- the address book
+ * entry_num -- entry to delete
+ * save_deleted -- save deleted as a #DELETED- entry
+ * enable_intr -- tell adrbk_write to enable interrupt handling
+ * be_quiet -- tell adrbk_write to not do percent done messages
+ * write_it -- only do adrbk_write if this is set
+ *
+ * Result: returns: 0 if all went well
+ * -1 if there is no such entry
+ * -2 error writing address book, check errno
+ */
+int
+adrbk_delete(AdrBk *ab, a_c_arg_t entry_num, int save_deleted, int enable_intr,
+ int be_quiet, int write_it)
+{
+ int retval = 0;
+ int set_mangled = 0;
+
+ dprint((3, "- adrbk_delete(%ld) -\n", (long)entry_num));
+
+ if(!ab)
+ return -2;
+
+ delete_ab_entry(ab, entry_num, save_deleted);
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps_global))
+ exp_del_nth(ab->exp, entry_num);
+
+ exp_del_nth(ab->selects, entry_num);
+
+ if(write_it)
+ retval = adrbk_write(ab, 0, NULL, &set_mangled, enable_intr, be_quiet);
+
+ if(set_mangled)
+ ps_global->mangled_screen = 1;
+
+ return(retval);
+}
+
+
+/*
+ * Delete an address out of an address list
+ *
+ * Args: ab -- the address book
+ * entry_num -- the address list we are deleting from
+ * addr -- address in above list to be deleted
+ *
+ * Result: 0: Deletion complete, address book written
+ * -1: Address for deletion not found
+ * -2: Error writing address book. Check errno.
+ *
+ * The address to be deleted is located by matching the string.
+ */
+int
+adrbk_listdel(AdrBk *ab, a_c_arg_t entry_num, char *addr)
+{
+ char **p, *to_free;
+ AdrBk_Entry *ae;
+ int ret;
+ int set_mangled = 0;
+
+ dprint((3, "- adrbk_listdel(%ld) -\n", (long) entry_num));
+
+ if(!ab || entry_num >= ab->count)
+ return -2;
+
+ if(!addr)
+ return -1;
+
+ ae = adrbk_get_ae(ab, entry_num);
+
+ if(ae->tag != List)
+ return -1;
+
+ for(p = ae->addr.list; *p; p++)
+ if(strcmp(*p, addr) == 0)
+ break;
+
+ if(*p == NULL)
+ return -1;
+
+ /* note storage to be freed */
+ if(*p != empty)
+ to_free = *p;
+ else
+ to_free = NULL;
+
+ /* slide all the entries below up (including NULL) */
+ for(; *p; p++)
+ *p = *(p+1);
+
+ if(to_free)
+ fs_give((void **) &to_free);
+
+ ret = adrbk_write(ab, 0, NULL, &set_mangled, 1, 0);
+
+ if(set_mangled)
+ ps_global->mangled_screen = 1;
+
+ return(ret);
+}
+
+
+/*
+ * Delete all addresses out of an address list
+ *
+ * Args: ab -- the address book
+ * entry_num -- the address list we are deleting from
+ *
+ * Result: 0: Deletion complete, address book written
+ * -1: Address for deletion not found
+ * -2: Error writing address book. Check errno.
+ */
+int
+adrbk_listdel_all(AdrBk *ab, a_c_arg_t entry_num)
+{
+ char **p;
+ AdrBk_Entry *ae;
+
+ dprint((3, "- adrbk_listdel_all(%ld) -\n", (long) entry_num));
+
+ if(!ab || entry_num >= ab->count)
+ return -2;
+
+ ae = adrbk_get_ae(ab, entry_num);
+
+ if(ae->tag != List)
+ return -1;
+
+ /* free old list */
+ for(p = ae->addr.list; p && *p; p++)
+ if(*p != empty)
+ fs_give((void **)p);
+
+ if(ae->addr.list)
+ fs_give((void **) &ae->addr.list);
+
+ ae->addr.list = NULL;
+
+ return 0;
+}
+
+
+/*
+ * Add a list of addresses to an already existing address list
+ *
+ * Args: ab -- the address book
+ * entry_num -- the address list we are adding to
+ * addrs -- address list to be added
+ * enable_intr -- tell adrbk_write to enable interrupt handling
+ * be_quiet -- tell adrbk_write to not do percent done messages
+ * write_it -- only do adrbk_write if this is set
+ *
+ * Result: returns 0 : addition made, address book written
+ * -1 : addition to non-list attempted
+ * -2 : error writing address book -- check errno
+ */
+int
+adrbk_nlistadd(AdrBk *ab, a_c_arg_t entry_num, adrbk_cntr_t *new_entry_num,
+ int *resort_happened, char **addrs,
+ int enable_intr, int be_quiet, int write_it)
+{
+ char **p;
+ int cur_size, size_of_additional_list, new_size;
+ int i, rc = 0;
+ int set_mangled = 0;
+ AdrBk_Entry *ae;
+
+ dprint((3, "- adrbk_nlistadd(%ld) -\n", (long) entry_num));
+
+ if(!ab || entry_num >= ab->count)
+ return -2;
+
+ ae = adrbk_get_ae(ab, entry_num);
+
+ if(ae->tag != List)
+ return -1;
+
+ /* count up size of existing list */
+ for(p = ae->addr.list; p != NULL && *p != NULL; p++)
+ ;/* do nothing */
+
+ cur_size = p - ae->addr.list;
+
+ /* count up size of new list */
+ for(p = addrs; p != NULL && *p != NULL; p++)
+ ;/* do nothing */
+
+ size_of_additional_list = p - addrs;
+ new_size = cur_size + size_of_additional_list;
+
+ /* make room at end of list for it */
+ if(cur_size == 0)
+ ae->addr.list = (char **) fs_get(sizeof(char *) * (new_size + 1));
+ else
+ fs_resize((void **) &ae->addr.list, sizeof(char *) * (new_size + 1));
+
+ /* Put new list at the end */
+ for(i = cur_size; i < new_size; i++)
+ (ae->addr.list)[i] = cpystr(addrs[i - cur_size]);
+
+ (ae->addr.list)[new_size] = NULL;
+
+ /*---- sort it into the correct place ------*/
+ if(ab->sort_rule != AB_SORT_RULE_NONE)
+ sort_addr_list(ae->addr.list);
+
+ if(write_it)
+ rc = adrbk_write(ab, entry_num, new_entry_num, &set_mangled, enable_intr, be_quiet);
+
+ if(set_mangled){
+ ps_global->mangled_screen = 1;
+ if(resort_happened)
+ *resort_happened = 1;
+ }
+
+ return(rc);
+}
+
+
+/*
+ * Set the valid variable if we determine that the address book has
+ * been changed by something other than us. This means we should update
+ * our view of the address book when next possible.
+ *
+ * Args ab -- AdrBk handle
+ * do_it_now -- If > 0, check now regardless
+ * If = 0, check if time since last chk more than default
+ * If < 0, check if time since last chk more than -do_it_now
+ */
+void
+adrbk_check_validity(AdrBk *ab, long int do_it_now)
+{
+ dprint((9, "- adrbk_check_validity(%s) -\n",
+ (ab && ab->filename) ? ab->filename : ""));
+
+ if(!ab || ab->flags & FILE_OUTOFDATE)
+ return;
+
+ adrbk_check_local_validity(ab, do_it_now);
+
+ if(ab->type == Imap && !(ab->flags & FILE_OUTOFDATE ||
+ ab->rd->flags & REM_OUTOFDATE))
+ rd_check_remvalid(ab->rd, do_it_now);
+}
+
+
+/*
+ * Set the valid variable if we determine that the address book has
+ * been changed by something other than us. This means we should update
+ * our view of the address book when next possible.
+ *
+ * Args ab -- AdrBk handle
+ * do_it_now -- If > 0, check now regardless
+ * If = 0, check if time since last chk more than default
+ * If < 0, check if time since last chk more than -do_it_now
+ */
+void
+adrbk_check_local_validity(AdrBk *ab, long int do_it_now)
+{
+ time_t mtime, chk_interval;
+
+ dprint((9, "- adrbk_check_local_validity(%s) -\n",
+ (ab && ab->filename) ? ab->filename : ""));
+
+ if(!ab)
+ return;
+
+ if(do_it_now < 0L){
+ chk_interval = -1L * do_it_now;
+ do_it_now = 0L;
+ }
+ else
+ chk_interval = FILE_VALID_CHK_INTERVAL;
+
+ if(!do_it_now &&
+ get_adj_time() <= ab->last_local_valid_chk + chk_interval)
+ return;
+
+ ab->last_local_valid_chk = get_adj_time();
+
+ /*
+ * Check local file for a modification time change.
+ * If this is out of date, don't even bother checking for the remote
+ * folder being out of date. That will get fixed when we reopen.
+ */
+ if(!(ab->flags & FILE_OUTOFDATE) &&
+ ab->last_change_we_know_about != (time_t)(-1) &&
+ (mtime=get_adj_name_file_mtime(ab->filename)) != (time_t)(-1) &&
+ ab->last_change_we_know_about != mtime){
+
+ dprint((2, "adrbk_check_local_validity: addrbook %s has changed\n",
+ ab->filename ? ab->filename : "?"));
+ ab->flags |= FILE_OUTOFDATE;
+ }
+}
+
+
+/*
+ * See if we can re-use an existing stream.
+ *
+ * [ We don't believe we need this anymore now that the stuff in pine.c ]
+ * [ is recycling streams for us, but we haven't thought it through all ]
+ * [ the way enough to get rid of this. Hubert 2003-07-09 ]
+ *
+ * Args name -- Name of folder we want a stream for.
+ *
+ * Returns -- A mail stream suitable for status cmd or append cmd.
+ * NULL if none were available.
+ */
+MAILSTREAM *
+adrbk_handy_stream(char *name)
+{
+ MAILSTREAM *stat_stream = NULL;
+ int i;
+
+ dprint((9, "- adrbk_handy_stream(%s) -\n", name ? name : "?"));
+
+ stat_stream = sp_stream_get(name, SP_SAME);
+
+ /*
+ * Look through our imap streams to see if there is one we can use.
+ */
+ for(i = 0; !stat_stream && i < as.n_addrbk; i++){
+ PerAddrBook *pab;
+
+ pab = &as.adrbks[i];
+
+ if(pab->address_book &&
+ pab->address_book->type == Imap &&
+ pab->address_book->rd &&
+ pab->address_book->rd->type == RemImap &&
+ same_stream(name, pab->address_book->rd->t.i.stream)){
+ stat_stream = pab->address_book->rd->t.i.stream;
+ pab->address_book->rd->last_use = get_adj_time();
+ dprint((7,
+ "%s: used other abook stream for status (%ld)\n",
+ pab->address_book->orig_filename
+ ? pab->address_book->orig_filename : "?",
+ (long)pab->address_book->rd->last_use));
+ }
+ }
+
+ dprint((9, "adrbk_handy_stream: returning %s\n",
+ stat_stream ? "good stream" : "NULL"));
+
+ return(stat_stream);
+}
+
+
+/*
+ * Close address book
+ *
+ * All that is done here is to free the storage, since the address book is
+ * rewritten on every change.
+ */
+void
+adrbk_close(AdrBk *ab)
+{
+ int we_cancel = 0;
+
+ dprint((4, "- adrbk_close(%s) -\n",
+ (ab && ab->filename) ? ab->filename : ""));
+
+ if(!ab)
+ return;
+
+ if(ab->rd)
+ rd_close_remdata(&ab->rd);
+
+ if(ab->fp)
+ (void)fclose(ab->fp);
+
+ if(ab->our_filecopy && ab->filename != ab->our_filecopy){
+ our_unlink(ab->our_filecopy);
+ fs_give((void**) &ab->our_filecopy);
+ }
+
+ if(ab->exp){
+ exp_free(ab->exp);
+ fs_give((void **)&ab->exp); /* free head of list, too */
+ }
+
+ if(ab->checks){
+ exp_free(ab->checks);
+ fs_give((void **)&ab->checks); /* free head of list, too */
+ }
+
+ if(ab->selects){
+ exp_free(ab->selects);
+ fs_give((void **)&ab->selects); /* free head of list, too */
+ }
+
+ if(ab->arr){
+ adrbk_cntr_t entry_num;
+
+ /*
+ * ab->arr is an allocated array. Each element of the array contains
+ * several pointers that point to other allocated stuff, so we
+ * free_ae_parts to get those and then free the array after the loop.
+ */
+ for(entry_num = 0; entry_num < ab->count; entry_num++)
+ free_ae_parts(&ab->arr[entry_num]);
+
+ fs_give((void **) &ab->arr);
+ }
+
+ if(ab->del){
+ adrbk_cntr_t entry_num;
+
+ for(entry_num = 0; entry_num < ab->del_count; entry_num++)
+ free_ae_parts(&ab->del[entry_num]);
+
+ fs_give((void **) &ab->del);
+ }
+
+ if(ab->nick_trie)
+ free_abook_trie(&ab->nick_trie);
+
+ if(ab->addr_trie)
+ free_abook_trie(&ab->addr_trie);
+
+ if(ab->full_trie)
+ free_abook_trie(&ab->full_trie);
+
+ if(ab->revfull_trie)
+ free_abook_trie(&ab->revfull_trie);
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(ab->filename){
+ if(ab->flags & DEL_FILE)
+ our_unlink(ab->filename);
+
+ fs_give((void**)&ab->filename);
+ }
+
+ if(ab->orig_filename)
+ fs_give((void**)&ab->orig_filename);
+
+ fs_give((void **) &ab);
+}
+
+
+void
+adrbk_partial_close(AdrBk *ab)
+{
+ dprint((4, "- adrbk_partial_close(%s) -\n",
+ (ab && ab->filename) ? ab->filename : ""));
+
+ exp_free(ab->exp); /* leaves head of list */
+ exp_free(ab->checks); /* leaves head of list */
+}
+
+
+/*
+ * It has been noticed that this stream is dead and it is about to
+ * be removed from the stream pool. We may have a pointer to it for one
+ * of the remote address books. We have to note that the pointer is stale.
+ */
+void
+note_closed_adrbk_stream(MAILSTREAM *stream)
+{
+ PerAddrBook *pab;
+ int i;
+
+ if(!stream)
+ return;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->address_book
+ && pab->address_book->type == Imap
+ && pab->address_book->rd
+ && pab->address_book->rd->type == RemImap
+ && (stream == pab->address_book->rd->t.i.stream)){
+ dprint((4, "- note_closed_adrbk_stream(%s) -\n",
+ (pab->address_book && pab->address_book->orig_filename)
+ ? pab->address_book->orig_filename : ""));
+ pab->address_book->rd->t.i.stream = NULL;
+ }
+ }
+}
+
+
+static adrbk_cntr_t tot_for_percent;
+static adrbk_cntr_t entry_num_for_percent;
+
+/*
+ * Write out the address book.
+ *
+ * If be_quiet is set, don't turn on busy_cue.
+ *
+ * If enable_intr_handling is set, turn on and off interrupt handling.
+ *
+ * Format is as in comment in the adrbk_open routine. Lines are wrapped
+ * to be under 80 characters. This is called on every change to the
+ * address book. Write is first to a temporary file,
+ * which is then renamed to be the real address book so that we won't
+ * destroy the real address book in case of something like a full file
+ * system.
+ *
+ * Writing a temp file and then renaming has the bad side affect of
+ * destroying links. It also overrides any read only permissions on
+ * the mail file since rename ignores such permissions. However, we
+ * handle readonly-ness in addrbook.c before we call this.
+ * We retain the permissions by doing a stat on the old file and a
+ * chmod on the new one (with file_attrib_copy).
+ *
+ * In pre-alpine pine the address book entries were encoded on disk.
+ * We would be happy with raw UTF-8 now but in order to preserve some
+ * backwards compatibility we encode the entries before writing.
+ * We first try to translate to the user's character set and encode
+ * in that, else we use UTF-8 but with 1522 encoding.
+ *
+ * Returns: 0 write was successful
+ * -2 write failed
+ * -5 interrupted
+ */
+int
+adrbk_write(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num,
+ int *sort_happened, int enable_intr_handling, int be_quiet)
+{
+ FILE *ab_stream = NULL;
+ AdrBk_Entry *ae = NULL;
+ adrbk_cntr_t entry_num;
+#ifndef DOS
+ void (*save_sighup)();
+#endif
+ int max_nick = 0,
+ max_full = 0, full_two = 0, full_three = 0,
+ max_fcc = 0, fcc_two = 0, fcc_three = 0,
+ max_addr = 0, addr_two = 0, addr_three = 0,
+ this_nick_width, this_full_width, this_addr_width,
+ this_fcc_width, fd, i;
+ int interrupt_happened = 0, we_cancel = 0, we_turned_on = 0;
+ char *temp_filename = NULL;
+ WIDTH_INFO_S *widths;
+
+ if(!ab)
+ return -2;
+
+ dprint((2, "- adrbk_write(\"%s\") - writing %lu entries\n",
+ ab->filename ? ab->filename : "", (unsigned long) ab->count));
+
+ errno = 0;
+
+ adrbk_check_local_validity(ab, 1L);
+ /* verify that file has not been changed by something else */
+ if(ab->flags & FILE_OUTOFDATE){
+ /* It has changed! */
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ /* TRANSLATORS: The address book was changed by something else so alpine
+ is not making the change the user wanted to make to avoid damaging
+ the address book. */
+ _("Addrbook changed by another process, aborting our change to avoid damage..."));
+ dprint((1, "adrbk_write: addrbook %s changed while we had it open, aborting write\n",
+ ab->filename ? ab->filename : "?"));
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+
+ /*
+ * Verify that remote folder has not been
+ * changed by something else (not if checked in last 5 seconds).
+ *
+ * The -5 is so we won't check every time on a series of quick writes.
+ */
+ rd_check_remvalid(ab->rd, -5L);
+ if(ab->type == Imap){
+ int ro;
+
+ /*
+ * We'll eventually need this open to write to remote, so see if
+ * we can open it now.
+ */
+ rd_open_remote(ab->rd);
+
+ /*
+ * Did someone else change the remote copy?
+ */
+ if((ro=rd_remote_is_readonly(ab->rd)) || ab->rd->flags & REM_OUTOFDATE){
+ if(ro == 1){
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ _("Can't access remote addrbook, aborting change..."));
+ dprint((1,
+ "adrbk_write: Can't write to remote addrbook %s, aborting write: open failed\n",
+ ab->rd->rn ? ab->rd->rn : "?"));
+ }
+ else if(ro == 2){
+ if(!(ab->rd->flags & NO_META_UPDATE)){
+ unsigned long save_chk_nmsgs;
+
+ /*
+ * Should have some non-type-specific method of doing this.
+ */
+ switch(ab->rd->type){
+ case RemImap:
+ save_chk_nmsgs = ab->rd->t.i.chk_nmsgs;
+ ab->rd->t.i.chk_nmsgs = 0;/* cause it to be OUTOFDATE */
+ rd_write_metadata(ab->rd, 0);
+ ab->rd->t.i.chk_nmsgs = save_chk_nmsgs;
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ "Adrbk_write: Type not supported");
+ break;
+ }
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ _("No write permission for remote addrbook, aborting change..."));
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ _("Remote addrbook changed, aborting our change to avoid damage..."));
+ dprint((1,
+ "adrbk_write: remote addrbook %s changed while we had it open, aborting write\n",
+ ab->orig_filename ? ab->orig_filename : "?"));
+ }
+
+ rd_close_remote(ab->rd);
+
+ longjmp(addrbook_changed_unexpectedly, 1);
+ /*NOTREACHED*/
+ }
+ }
+
+#ifndef DOS
+ save_sighup = (void (*)())signal(SIGHUP, SIG_IGN);
+#endif
+
+ /*
+ * If we want to be able to modify the address book, we will
+ * need a temp_filename in the same directory as our_filecopy.
+ */
+ if(!(ab->our_filecopy && ab->our_filecopy[0])
+ || !(temp_filename = tempfile_in_same_dir(ab->our_filecopy,"a1",NULL))
+ || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){
+ dprint((1, "adrbk_write(%s): failed opening temp file (%s)\n",
+ ab->filename ? ab->filename : "?",
+ temp_filename ? temp_filename : "NULL"));
+ goto io_error;
+ }
+
+ ab_stream = fdopen(fd, "wb");
+ if(ab_stream == NULL){
+ dprint((1, "adrbk_write(%s): fdopen failed\n", temp_filename ? temp_filename : "?"));
+ goto io_error;
+ }
+
+ if(adrbk_is_in_sort_order(ab, be_quiet)){
+ if(sort_happened)
+ *sort_happened = 0;
+
+ if(new_entry_num)
+ *new_entry_num = current_entry_num;
+ }
+ else{
+ if(sort_happened)
+ *sort_happened = 1;
+
+ (void) adrbk_sort(ab, current_entry_num, new_entry_num, be_quiet);
+ }
+
+ /* accept keyboard interrupts */
+ if(enable_intr_handling)
+ we_turned_on = intr_handling_on();
+
+ if(!be_quiet){
+ tot_for_percent = MAX(ab->count, 1);
+ entry_num_for_percent = 0;
+ we_cancel = busy_cue(_("Saving address book"), percent_abook_saved, 0);
+ }
+
+ writing = 1;
+
+ /*
+ * If there are any old deleted entries, copy them to new file.
+ */
+ if(ab->del_count > 0){
+ char *nickname;
+ long delindex;
+
+ /*
+ * If there are deleted entries old enough that we no longer want
+ * to save them, remove them from the list.
+ */
+ for(delindex = (long) ab->del_count-1;
+ delindex >= 0L && ab->del_count > 0; delindex--){
+
+ ae = adrbk_get_delae(ab, (a_c_arg_t) delindex);
+ nickname = ae->nickname;
+
+ if(strncmp(nickname, DELETED, DELETED_LEN) == 0
+ && isdigit((unsigned char)nickname[DELETED_LEN])
+ && isdigit((unsigned char)nickname[DELETED_LEN+1])
+ && nickname[DELETED_LEN+2] == '/'
+ && isdigit((unsigned char)nickname[DELETED_LEN+3])
+ && isdigit((unsigned char)nickname[DELETED_LEN+4])
+ && nickname[DELETED_LEN+5] == '/'
+ && isdigit((unsigned char)nickname[DELETED_LEN+6])
+ && isdigit((unsigned char)nickname[DELETED_LEN+7])
+ && nickname[DELETED_LEN+8] == '#'){
+ int year, month, day;
+ struct tm *tm_before;
+ time_t now, before;
+
+ now = time((time_t *)0);
+ before = now - (time_t)ABOOK_DELETED_EXPIRE_TIME;
+ tm_before = localtime(&before);
+ tm_before->tm_mon++;
+
+ /*
+ * Check to see if it is older than 100 days.
+ */
+ year = atoi(&nickname[DELETED_LEN]);
+ month = atoi(&nickname[DELETED_LEN+3]);
+ day = atoi(&nickname[DELETED_LEN+6]);
+ if(year < 95)
+ year += 100; /* this breaks in year 2095 */
+
+ /*
+ * remove it if it is more than 100 days old
+ */
+ if(!(year > tm_before->tm_year
+ || (year == tm_before->tm_year
+ && month > tm_before->tm_mon)
+ || (year == tm_before->tm_year
+ && month == tm_before->tm_mon
+ && day >= tm_before->tm_mday))){
+
+ /* it's old, dump it */
+ free_ae_parts(ae);
+ ab->del_count--;
+ /* patch the array by moving everything below up */
+ if(delindex < ab->del_count){
+ memmove(&ab->del[delindex],
+ &ab->del[delindex+1],
+ (ab->del_count - delindex) *
+ sizeof(AdrBk_Entry));
+ }
+ }
+ }
+ }
+
+ /* write out what remains */
+ if(ab->del_count){
+ for(delindex = 0L; delindex < ab->del_count; delindex++){
+ ae = adrbk_get_delae(ab, (a_c_arg_t) delindex);
+ if(write_single_abook_entry(ae, ab_stream, NULL, NULL,
+ NULL, NULL) == EOF){
+ dprint((1, "adrbk_write(%s): failed writing deleted entry\n",
+ temp_filename ? temp_filename : "?"));
+ goto io_error;
+ }
+ }
+ }
+ else{
+ /* nothing left, get rid of del array */
+ fs_give((void **) &ab->del);
+ }
+ }
+
+ if(ab->del_count)
+ dprint((4, " adrbk_write: saving %ld deleted entries\n",
+ (long) ab->del_count));
+
+ for(entry_num = 0; entry_num < ab->count; entry_num++){
+ entry_num_for_percent++;
+
+ ALARM_BLIP();
+
+ ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);
+
+ if(ae == (AdrBk_Entry *) NULL){
+ dprint((1, "adrbk_write(%s): can't find ae while writing addrbook, entry_num = %ld\n",
+ ab->filename ? ab->filename : "?",
+ (long) entry_num));
+ goto io_error;
+ }
+
+ /* write to temp file */
+ if(write_single_abook_entry(ae, ab_stream, &this_nick_width,
+ &this_full_width, &this_addr_width, &this_fcc_width) == EOF){
+ dprint((1, "adrbk_write(%s): failed writing for entry %ld\n",
+ temp_filename ? temp_filename : "?",
+ (long) entry_num));
+ goto io_error;
+ }
+
+ /* keep track of widths */
+ max_nick = MAX(max_nick, this_nick_width);
+ if(this_full_width > max_full){
+ full_three = full_two;
+ full_two = max_full;
+ max_full = this_full_width;
+ }
+ else if(this_full_width > full_two){
+ full_three = full_two;
+ full_two = this_full_width;
+ }
+ else if(this_full_width > full_three){
+ full_three = this_full_width;
+ }
+
+ if(this_addr_width > max_addr){
+ addr_three = addr_two;
+ addr_two = max_addr;
+ max_addr = this_addr_width;
+ }
+ else if(this_addr_width > addr_two){
+ addr_three = addr_two;
+ addr_two = this_addr_width;
+ }
+ else if(this_addr_width > addr_three){
+ addr_three = this_addr_width;
+ }
+
+ if(this_fcc_width > max_fcc){
+ fcc_three = fcc_two;
+ fcc_two = max_fcc;
+ max_fcc = this_fcc_width;
+ }
+ else if(this_fcc_width > fcc_two){
+ fcc_three = fcc_two;
+ fcc_two = this_fcc_width;
+ }
+ else if(this_fcc_width > fcc_three){
+ fcc_three = this_fcc_width;
+ }
+
+ /*
+ * Check to see if we've been interrupted. We check at the bottom
+ * of the loop so that we can handle an interrupt in the last
+ * iteration.
+ */
+ if(enable_intr_handling && ps_global->intr_pending){
+ interrupt_happened++;
+ goto io_error;
+ }
+ }
+
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ if(enable_intr_handling && we_turned_on){
+ intr_handling_off();
+ we_turned_on = 0;
+ }
+
+ if(fclose(ab_stream) == EOF){
+ dprint((1, "adrbk_write: fclose for %s failed\n",
+ temp_filename ? temp_filename : "?"));
+ goto io_error;
+ }
+
+ ab_stream = (FILE *) NULL;
+
+ file_attrib_copy(temp_filename, ab->our_filecopy);
+ if(ab->fp){
+ (void) fclose(ab->fp);
+ ab->fp = NULL; /* in case of problems */
+ }
+
+ if((i=rename_file(temp_filename, ab->our_filecopy)) < 0){
+ dprint((1, "adrbk_write: rename(%s, %s) failed: %s\n",
+ temp_filename ? temp_filename : "?",
+ ab->our_filecopy ? ab->our_filecopy : "?",
+ error_description(errno)));
+#ifdef _WINDOWS
+ if(i == -5){
+ q_status_message2(SM_ORDER | SM_DING, 5, 7,
+ _("Can't replace address book %.200sfile \"%.200s\""),
+ (ab->type == Imap) ? "cache " : "",
+ ab->filename);
+ q_status_message(SM_ORDER | SM_DING, 5, 7,
+ _("If another Alpine is running, quit that Alpine before updating address book."));
+ }
+#endif /* _WINDOWS */
+ goto io_error;
+ }
+
+ /* reopen fp to new file */
+ if(!(ab->fp = our_fopen(ab->our_filecopy, "rb"))){
+ dprint((1, "adrbk_write: can't reopen %s\n",
+ ab->our_filecopy ? ab->our_filecopy : "?"));
+ goto io_error;
+ }
+
+ if(temp_filename){
+ our_unlink(temp_filename);
+ fs_give((void **) &temp_filename);
+ }
+
+ /*
+ * Now copy our_filecopy back to filename.
+ */
+ if(ab->filename != ab->our_filecopy){
+ int c, err = 0;
+ char *lc;
+
+ if(!(ab->filename && ab->filename[0])
+ || !(temp_filename = tempfile_in_same_dir(ab->filename,"a1",NULL))
+ || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){
+ dprint((1,
+ "adrbk_write(%s): failed opening temp file (%s)\n",
+ ab->filename ? ab->filename : "?",
+ temp_filename ? temp_filename : "NULL"));
+ err++;
+ }
+
+ if(!err)
+ ab_stream = fdopen(fd, "wb");
+
+ if(ab_stream == NULL){
+ dprint((1, "adrbk_write(%s): fdopen failed\n",
+ temp_filename ? temp_filename : "?"));
+ err++;
+ }
+
+ if(!err){
+ rewind(ab->fp);
+ while((c = getc(ab->fp)) != EOF)
+ if(putc(c, ab_stream) == EOF){
+ err++;
+ break;
+ }
+ }
+
+ if(!err && fclose(ab_stream) == EOF)
+ err++;
+
+ if(!err){
+#ifdef _WINDOWS
+ int tries = 3;
+#endif
+
+ file_attrib_copy(temp_filename, ab->filename);
+
+ /*
+ * This could fail if somebody else has it open, but they should
+ * only have it open for a short time.
+ */
+ while(!err && rename_file(temp_filename, ab->filename) < 0){
+#ifdef _WINDOWS
+ if(i == -5){
+ if(--tries <= 0)
+ err++;
+
+ if(!err){
+ q_status_message2(SM_ORDER, 0, 3,
+ _("Replace of \"%.200s\" failed, trying %.200s"),
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->filename,
+ (tries > 1) ? "again" : "one more time");
+ display_message('x');
+ sleep(3);
+ }
+ }
+#else /* UNIX */
+ err++;
+#endif /* UNIX */
+ }
+ }
+
+ if(err){
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of addrbook to \"%.200s\" failed, changes NOT saved!"),
+ (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
+ dprint((2, "adrbk_write: failed copying our_filecopy (%s)back to filename (%s): %s, continuing without a net\n",
+ ab->our_filecopy ? ab->our_filecopy : "?",
+ ab->filename ? ab->filename : "?",
+ error_description(errno)));
+ }
+ }
+
+ widths = &ab->widths;
+ widths->max_nickname_width = MIN(max_nick, 99);
+ widths->max_fullname_width = MIN(max_full, 99);
+ widths->max_addrfield_width = MIN(max_addr, 99);
+ widths->max_fccfield_width = MIN(max_fcc, 99);
+ widths->third_biggest_fullname_width = MIN(full_three, 99);
+ widths->third_biggest_addrfield_width = MIN(addr_three, 99);
+ widths->third_biggest_fccfield_width = MIN(fcc_three, 99);
+
+ /* record new change date of addrbook file */
+ ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename);
+
+#ifndef DOS
+ (void)signal(SIGHUP, save_sighup);
+#endif
+
+ /*
+ * If this is a remote addressbook, copy the file over.
+ * If it fails we warn but continue to operate on the changed,
+ * locally cached addressbook file.
+ */
+ if(ab->type == Imap){
+ int e;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ we_cancel = busy_cue(_("Copying to remote addressbook"), NULL, 1);
+ /*
+ * We don't want a cookie upgrade to blast our data in rd->lf by
+ * copying back the remote data before doing the upgrade, and then
+ * proceeding to finish with that data. So we tell rd_upgrade_cookie
+ * about that here.
+ */
+ ab->rd->flags |= BELIEVE_CACHE;
+ if((e = rd_update_remote(ab->rd, datebuf)) != 0){
+ if(e == -1){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary addrbook file %.200s: %.200s"),
+ ab->rd->lf, error_description(errno));
+ dprint((1,
+ "adrbk_write: error opening temp file %s\n",
+ ab->rd->lf ? ab->rd->lf : "?"));
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %.200s: %.200s"),
+ ab->rd->rn, error_description(errno));
+ dprint((1,
+ "adrbk_write: error copying from %s to %s\n",
+ ab->rd->lf ? ab->rd->lf : "?",
+ ab->rd->rn ? ab->rd->rn : "?"));
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of addrbook to remote folder failed, changes NOT saved remotely"));
+ }
+ else{
+ rd_update_metadata(ab->rd, datebuf);
+ ab->rd->read_status = 'W';
+ dprint((7,
+ "%s: copied local to remote in adrbk_write (%ld)\n",
+ ab->rd->rn ? ab->rd->rn : "?",
+ (long)ab->rd->last_use));
+ }
+
+ ab->rd->flags &= ~BELIEVE_CACHE;
+ }
+
+ writing = 0;
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(temp_filename){
+ our_unlink(temp_filename);
+ fs_give((void **) &temp_filename);
+ }
+
+ return 0;
+
+
+io_error:
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(enable_intr_handling && we_turned_on)
+ intr_handling_off();
+
+#ifndef DOS
+ (void)signal(SIGHUP, save_sighup);
+#endif
+ if(interrupt_happened){
+ q_status_message(0, 1, 2, _("Interrupt! Reverting to previous version"));
+ display_message('x');
+ dprint((1, "adrbk_write(%s): Interrupt\n",
+ ab->filename ? ab->filename : "?"));
+ }
+ else
+ dprint((1, "adrbk_write(%s) (%s): some sort of io_error\n",
+ ab->filename ? ab->filename : "?",
+ ab->our_filecopy ? ab->our_filecopy : "?"));
+
+ writing = 0;
+
+ if(ab_stream){
+ fclose(ab_stream);
+ ab_stream = (FILE *)NULL;
+ }
+
+ if(temp_filename){
+ our_unlink(temp_filename);
+ fs_give((void **) &temp_filename);
+ }
+
+ adrbk_partial_close(ab);
+
+ if(interrupt_happened){
+ errno = EINTR; /* for nicer error message */
+ return -5;
+ }
+ else
+ return -2;
+}
+
+
+/*
+ * Writes one addrbook entry with wrapping. Fills in widths
+ * for display purposes. Returns 0, or EOF on error.
+ *
+ * Continuation lines always start with spaces. Tabs are treated as
+ * separators, never as whitespace. When we output tab separators we
+ * always put them on the ends of lines, never on the start of a line
+ * after a continuation. That is, there is always something printable
+ * after continuation spaces.
+ */
+int
+write_single_abook_entry(AdrBk_Entry *ae, FILE *fp, int *ret_nick_width,
+ int *ret_full_width, int *ret_addr_width, int *ret_fcc_width)
+{
+ int len = 0;
+ int nick_width = 0, full_width = 0, addr_width = 0, fcc_width = 0;
+ int tmplen, this_len;
+ char *write_this = NULL;
+
+ if(fp == (FILE *) NULL){
+ dprint((1, "write_single_abook_entry: fp is NULL\n"));
+ return(EOF);
+ }
+
+ if(ae->nickname){
+ nick_width = utf8_width(ae->nickname);
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->nickname);
+
+ this_len = strlen(write_this ? write_this : "");
+
+ /*
+ * We aren't too concerned with where it wraps.
+ * Long lines are ok as long as they aren't super long.
+ */
+ if(len > 100 || (len+this_len > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs ind1 failed\n"));
+ return(EOF);
+ }
+
+ len = this_len + INDENT;
+ }
+
+ len += this_len;
+
+ if(fputs(write_this, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs nick failed\n"));
+ return(EOF);
+ }
+ }
+ else
+ nick_width = 0;
+
+ putc(TAB, fp);
+ len++;
+
+ if(ae->fullname){
+ full_width = utf8_width(ae->fullname);
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fullname);
+
+ this_len = strlen(write_this ? write_this : "");
+
+
+ if(len > 100 || (len+this_len > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs ind2 failed\n"));
+ return(EOF);
+ }
+
+ len = this_len + INDENT;
+ }
+
+ len += this_len;
+
+ if(fputs(write_this, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs full failed\n"));
+ return(EOF);
+ }
+ }
+ else
+ full_width = 0;
+
+ putc(TAB, fp);
+ len++;
+
+ /* special case, make sure empty list has () */
+ if(ae->tag == List && ae->addr.list == NULL){
+ addr_width = 0;
+ putc('(', fp);
+ putc(')', fp);
+ len += 2;
+ }
+ else if(ae->addr.addr != NULL || ae->addr.list != NULL){
+ if(ae->tag == Single){
+ /*----- Single: just one address ----*/
+ if(ae->addr.addr){
+ addr_width = utf8_width(ae->addr.addr);
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->addr.addr);
+
+ this_len = strlen(write_this ? write_this : "");
+
+ if(len > 100 || (len+this_len > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs ind3 failed\n"));
+ return(EOF);
+ }
+
+ len = this_len + INDENT;
+ }
+
+ len += this_len;
+
+ if(fputs(write_this, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs addr failed\n"));
+ return(EOF);
+ }
+ }
+ else
+ addr_width = 0;
+ }
+ else if(ae->tag == List){
+ register char **a2;
+
+ /*----- List: a distribution list ------*/
+ putc('(', fp);
+ len++;
+ addr_width = 0;
+ for(a2 = ae->addr.list; *a2 != NULL; a2++){
+ if(*a2){
+ if(a2 != ae->addr.list){
+ putc(',', fp);
+ len++;
+ }
+
+ addr_width = MAX(addr_width, utf8_width(*a2));
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, *a2);
+
+ this_len = strlen(write_this ? write_this : "");
+
+ if(len > 100 || (len+this_len > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs ind3 failed\n"));
+ return(EOF);
+ }
+
+ len = this_len + INDENT;
+ }
+
+ len += this_len;
+
+ if(fputs(write_this, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs addrl failed\n"));
+ return(EOF);
+ }
+ }
+ }
+
+ putc(')', fp);
+ len++;
+ }
+ }
+
+ /* If either fcc or extra exists, output both, otherwise, neither */
+ if((ae->fcc && ae->fcc[0]) || (ae->extra && ae->extra[0])){
+ putc(TAB, fp);
+ len++;
+
+ if(ae->fcc && ae->fcc[0]){
+ fcc_width = utf8_width(ae->fcc);
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fcc);
+
+ this_len = strlen(write_this ? write_this : "");
+
+ if(len > 100 || (len+this_len > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs ind4 failed\n"));
+ return(EOF);
+ }
+
+ len = this_len + INDENT;
+ }
+
+ len += this_len;
+
+ if(fputs(write_this, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs fcc failed\n"));
+ return(EOF);
+ }
+ }
+ else
+ fcc_width = 0;
+
+ putc(TAB, fp);
+ len++;
+
+ if(ae->extra && ae->extra[0]){
+ int space;
+ char *cur, *end;
+ char *extra_copy;
+
+ write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
+ tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->extra);
+
+ /*
+ * Copy ae->extra and replace newlines with spaces.
+ * The reason we do this is because the continuation lines
+ * produced by rfc1522_encode may interact with the
+ * continuation lines produced below in a bad way.
+ * In particular, what can happen is that the 1522 continuation
+ * newline can be followed immediately by a newline produced
+ * below. That breaks the continuation since that is a
+ * line with nothing on it. Just turn 1522 continuations into
+ * spaces, which work fine with 1522_decode.
+ */
+ extra_copy = cpystr(write_this);
+ REPLACE_NEWLINES_WITH_SPACE(extra_copy);
+
+ tmplen = strlen(extra_copy);
+
+ if(len > 100 || (len+tmplen > 150 && len > 20)){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fprintf indent failed\n"));
+ return(EOF);
+ }
+
+ len = INDENT;
+ }
+
+ space = MAX(70 - len, 5);
+ cur = extra_copy;
+ end = cur + tmplen;
+ while(cur < end){
+ if(end-cur > space){
+ int i;
+
+ /* find first space after spot we want to break */
+ for(i = space; cur+i < end && cur[i] != SPACE; i++)
+ ;
+
+ cur[i] = '\0';
+ if(fputs(cur, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs extra failed\n"));
+ return(EOF);
+ }
+
+ cur += (i+1);
+
+ if(cur < end){
+ if(fprintf(fp, "%s%s", NEWLINE, INDENTXTRA) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fprintf indent failed\n"));
+ return(EOF);
+ }
+
+ space = 70 - INDENT;
+ }
+ }
+ else{
+ if(fputs(cur, fp) == EOF){
+ dprint((1,
+ "write_single_abook_entry: fputs extra failed\n"));
+ return(EOF);
+ }
+
+ cur = end;
+ }
+ }
+
+ if(extra_copy)
+ fs_give((void **)&extra_copy);
+ }
+ }
+ else
+ fcc_width = 0;
+
+ fprintf(fp, "%s", NEWLINE);
+
+ if(ret_nick_width)
+ *ret_nick_width = nick_width;
+ if(ret_full_width)
+ *ret_full_width = full_width;
+ if(ret_addr_width)
+ *ret_addr_width = addr_width;
+ if(ret_fcc_width)
+ *ret_fcc_width = fcc_width;
+
+ return(0);
+}
+
+
+char *
+backcompat_encoding_for_abook(char *buf1, size_t buf1len, char *buf2,
+ size_t buf2len, char *srcstr)
+{
+ char *encoded = NULL;
+ char *p;
+ int its_ascii = 1;
+
+
+ for(p = srcstr; *p && its_ascii; p++)
+ if(*p & 0x80)
+ its_ascii = 0;
+
+ /* if it is ascii, go with that */
+ if(its_ascii)
+ encoded = srcstr;
+ else{
+ char *trythischarset = NULL;
+
+ /*
+ * If it is possible to translate the UTF-8
+ * string into the user's character set then
+ * do that. For backwards compatibility with
+ * old pines.
+ */
+ if(ps_global->keyboard_charmap && ps_global->keyboard_charmap[0])
+ trythischarset = ps_global->keyboard_charmap;
+ else if(ps_global->display_charmap && ps_global->display_charmap[0])
+ trythischarset = ps_global->display_charmap;
+
+ if(trythischarset){
+ SIZEDTEXT src, dst;
+
+ src.data = (unsigned char *) srcstr;
+ src.size = strlen(srcstr);
+ memset(&dst, 0, sizeof(dst));
+ if(utf8_cstext(&src, trythischarset, &dst, 0)){
+ if(dst.data){
+ strncpy(buf1, (char *) dst.data, buf1len);
+ buf1[buf1len-1] = '\0';
+ fs_give((void **) &dst.data);
+ encoded = rfc1522_encode(buf2, buf2len,
+ (unsigned char *) buf1, trythischarset);
+ if(encoded)
+ REPLACE_NEWLINES_WITH_SPACE(encoded);
+ }
+ }
+ }
+
+ if(!encoded){
+ encoded = rfc1522_encode(buf1, buf1len, (unsigned char *) srcstr, "UTF-8");
+ if(encoded)
+ REPLACE_NEWLINES_WITH_SPACE(encoded);
+ }
+ }
+
+ return(encoded);
+}
+
+
+int
+percent_abook_saved(void)
+{
+ return((int)(((unsigned long)entry_num_for_percent * (unsigned long)100) /
+ (unsigned long)tot_for_percent));
+}
+
+
+/*
+ * Free memory associated with entry ae.
+ *
+ * Args: ae -- Address book entry to be freed.
+ */
+void
+free_ae(AdrBk_Entry **ae)
+{
+ if(!ae || !(*ae))
+ return;
+
+ free_ae_parts(*ae);
+ fs_give((void **) ae);
+}
+
+
+/*
+ * Free memory associated with entry ae but not ae itself.
+ */
+void
+free_ae_parts(AdrBk_Entry *ae)
+{
+ char **p;
+
+ if(!ae)
+ return;
+
+ if(ae->nickname && ae->nickname != empty)
+ fs_give((void **) &ae->nickname);
+
+ if(ae->fullname && ae->fullname != empty)
+ fs_give((void **) &ae->fullname);
+
+ if(ae->tag == Single){
+ if(ae->addr.addr && ae->addr.addr != empty)
+ fs_give((void **) &ae->addr.addr);
+ }
+ else if(ae->tag == List){
+ if(ae->addr.list){
+ for(p = ae->addr.list; *p; p++)
+ if(*p != empty)
+ fs_give((void **) p);
+
+ fs_give((void **) &ae->addr.list);
+ }
+ }
+
+ if(ae->fcc && ae->fcc != empty)
+ fs_give((void **) &ae->fcc);
+
+ if(ae->extra && ae->extra != empty)
+ fs_give((void **) &ae->extra);
+
+ defvalue_ae(ae);
+}
+
+
+/*
+ * Inserts element new_ae before element put_it_before_this.
+ */
+void
+insert_ab_entry(AdrBk *ab, a_c_arg_t put_it_before_this, AdrBk_Entry *new_ae,
+ int use_deleted_list)
+{
+ adrbk_cntr_t before, old_count, new_count;
+ AdrBk_Entry *ae_before, *ae_before_next;
+
+ dprint((7, "- insert_ab_entry(before_this=%ld) -\n", (long) put_it_before_this));
+
+ before = (adrbk_cntr_t) put_it_before_this;
+
+ if(!ab || before == NO_NEXT || (!use_deleted_list && before > ab->count)
+ || (use_deleted_list && before > ab->del_count)){
+ ;
+ }
+ else{
+ /*
+ * add space for new entry to array
+ * slide entries [before ... old_count-1] down one
+ * put new_ae into opened up slot
+ */
+ old_count = use_deleted_list ? ab->del_count : ab->count;
+ new_count = old_count + 1;
+ if(old_count == 0){
+ if(use_deleted_list){
+ if(ab->del) /* shouldn't happen */
+ fs_give((void **) &ab->del);
+
+ /* first entry in new array */
+ ab->del = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry));
+ defvalue_ae(ab->del);
+ }
+ else{
+ if(ab->arr) /* shouldn't happen */
+ fs_give((void **) &ab->arr);
+
+ /* first entry in new array */
+ ab->arr = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry));
+ defvalue_ae(ab->arr);
+ }
+ }
+ else{
+ if(use_deleted_list){
+ fs_resize((void **) &ab->del, new_count * sizeof(AdrBk_Entry));
+ defvalue_ae(&ab->del[new_count-1]);
+ }
+ else{
+ fs_resize((void **) &ab->arr, new_count * sizeof(AdrBk_Entry));
+ defvalue_ae(&ab->arr[new_count-1]);
+ }
+ }
+
+ if(use_deleted_list){
+ ab->del_count = new_count;
+
+ ae_before = adrbk_get_delae(ab, (a_c_arg_t) before);
+ if(ae_before){
+ if(before < old_count){
+ ae_before_next = adrbk_get_delae(ab, (a_c_arg_t) (before+1));
+ memmove(ae_before_next, ae_before,
+ (old_count-before) * sizeof(AdrBk_Entry));
+ }
+
+ memcpy(ae_before, new_ae, sizeof(AdrBk_Entry));
+ }
+ }
+ else{
+ ab->count = new_count;
+
+ ae_before = adrbk_get_ae(ab, (a_c_arg_t) before);
+ if(ae_before){
+ if(before < old_count){
+ ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1));
+ memmove(ae_before_next, ae_before,
+ (old_count-before) * sizeof(AdrBk_Entry));
+ }
+
+ memcpy(ae_before, new_ae, sizeof(AdrBk_Entry));
+ }
+ }
+
+ }
+
+ if(!use_deleted_list)
+ repair_abook_tries(ab);
+}
+
+
+/*
+ * Moves element move_this_one before element put_it_before_this.
+ */
+void
+move_ab_entry(AdrBk *ab, a_c_arg_t move_this_one, a_c_arg_t put_it_before_this)
+{
+ adrbk_cntr_t m, before;
+ AdrBk_Entry ae_tmp;
+ AdrBk_Entry *ae_m, *ae_m_next, *ae_before, *ae_before_prev, *ae_before_next;
+
+ dprint((7, "- move_ab_entry(move_this=%ld,before_this=%ld) -\n", (long) move_this_one, (long) put_it_before_this));
+
+ m = (adrbk_cntr_t) move_this_one;
+ before = (adrbk_cntr_t) put_it_before_this;
+
+ if(!ab || m == NO_NEXT || before == NO_NEXT
+ || m == before || m+1 == before || before > ab->count){
+ ;
+ }
+ else if(m+1 < before){
+ /*
+ * copy m
+ * slide entries [m+1 ... before-1] up one ("up" means smaller indices)
+ * put m into opened up slot
+ */
+ ae_m = adrbk_get_ae(ab, (a_c_arg_t) m);
+ ae_m_next = adrbk_get_ae(ab, (a_c_arg_t) (m+1));
+ ae_before_prev = adrbk_get_ae(ab, (a_c_arg_t) (before-1));
+ if(ae_m && ae_m_next && ae_before_prev){
+ memcpy(&ae_tmp, ae_m, sizeof(ae_tmp));
+ memmove(ae_m, ae_m_next, (before-m-1) * sizeof(ae_tmp));
+ memcpy(ae_before_prev, &ae_tmp, sizeof(ae_tmp));
+ }
+ }
+ else if(m > before){
+ /*
+ * copy m
+ * slide entries [before ... m-1] down one
+ * put m into opened up slot
+ */
+ ae_m = adrbk_get_ae(ab, (a_c_arg_t) m);
+ ae_before = adrbk_get_ae(ab, (a_c_arg_t) before);
+ ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1));
+ if(ae_m && ae_before && ae_before_next){
+ memcpy(&ae_tmp, ae_m, sizeof(ae_tmp));
+ memmove(ae_before_next, ae_before, (m-before) * sizeof(ae_tmp));
+ memcpy(ae_before, &ae_tmp, sizeof(ae_tmp));
+ }
+ }
+
+ dprint((9, "- move_ab_entry: done -\n"));
+
+ repair_abook_tries(ab);
+}
+
+
+/*
+ * Deletes element delete_this_one from in-core data structure.
+ * If save_it is set the deleted entry is moved to the deleted_list.
+ */
+void
+delete_ab_entry(AdrBk *ab, a_c_arg_t delete_this_one, int save_it)
+{
+ adrbk_cntr_t d;
+ AdrBk_Entry *ae_deleted, *ae_deleted_next;
+
+ dprint((7, "- delete_ab_entry(delete_this=%ld,save_it=%d) -\n", (long) delete_this_one, save_it));
+
+ d = (adrbk_cntr_t) delete_this_one;
+
+ if(!ab || d == NO_NEXT || d >= ab->count){
+ ;
+ }
+ else{
+ /*
+ * Move the entry to the deleted_list if asked to.
+ */
+ ae_deleted = adrbk_get_ae(ab, (a_c_arg_t) d);
+ if(ae_deleted){
+ if(save_it){
+ char *oldnick, *newnick;
+ size_t len;
+ struct tm *tm_now;
+ time_t now;
+
+ /*
+ * First prepend the prefix
+ * #DELETED-YY/MM/DD#
+ * to the nickname.
+ */
+ now = time((time_t) 0);
+ tm_now = localtime(&now);
+
+ oldnick = ae_deleted->nickname;
+ len = strlen(oldnick) + DELETED_LEN + strlen("YY/MM/DD#");
+
+ newnick = (char *) fs_get((len+1) * sizeof(char));
+ snprintf(newnick, len+1, "%s%02d/%02d/%02d#%s",
+ DELETED, (tm_now->tm_year)%100, tm_now->tm_mon+1,
+ tm_now->tm_mday, oldnick ? oldnick : "");
+ newnick[len] = '\0';
+
+ if(ae_deleted->nickname && ae_deleted->nickname != empty)
+ fs_give((void **) &ae_deleted->nickname);
+
+ ae_deleted->nickname = newnick;
+
+ /*
+ * Now insert this entry in the deleted_list.
+ */
+ insert_ab_entry(ab, (a_c_arg_t) ab->del_count, ae_deleted, 1);
+ }
+ else
+ free_ae_parts(ae_deleted);
+ }
+
+ /*
+ * slide entries [deleted+1 ... count-1] up one
+ */
+ if(d+1 < ab->count){
+ ae_deleted = adrbk_get_ae(ab, (a_c_arg_t) d);
+ ae_deleted_next = adrbk_get_ae(ab, (a_c_arg_t) (d+1));
+ if(ae_deleted && ae_deleted_next)
+ memmove(ae_deleted, ae_deleted_next, (ab->count-d-1) * sizeof(AdrBk_Entry));
+ }
+
+ ab->count--;
+
+ if(ab->count > 0)
+ fs_resize((void **) &ab->arr, ab->count * sizeof(AdrBk_Entry));
+ else
+ fs_give((void **) &ab->arr);
+ }
+
+ repair_abook_tries(ab);
+}
+
+
+/*
+ * We may want to be smarter about this and repair instead of
+ * rebuild these.
+ */
+void
+repair_abook_tries(AdrBk *ab)
+{
+ if(ab->arr){
+ AdrBk_Trie *save_nick_trie, *save_addr_trie,
+ *save_full_trie, *save_revfull_trie;
+
+ save_nick_trie = ab->nick_trie;
+ ab->nick_trie = NULL;
+ save_addr_trie = ab->addr_trie;
+ ab->addr_trie = NULL;
+ save_full_trie = ab->full_trie;
+ ab->full_trie = NULL;
+ save_revfull_trie = ab->revfull_trie;
+ ab->revfull_trie = NULL;
+ if(build_abook_tries(ab, NULL)){
+ dprint((2, "trouble rebuilding tries, restoring\n"));
+ if(ab->nick_trie)
+ free_abook_trie(&ab->nick_trie);
+
+ /* better than nothing */
+ ab->nick_trie = save_nick_trie;
+
+ if(ab->addr_trie)
+ free_abook_trie(&ab->addr_trie);
+
+ ab->addr_trie = save_addr_trie;
+
+ if(ab->full_trie)
+ free_abook_trie(&ab->full_trie);
+
+ ab->full_trie = save_full_trie;
+
+ if(ab->revfull_trie)
+ free_abook_trie(&ab->revfull_trie);
+
+ ab->revfull_trie = save_revfull_trie;
+ }
+ else{
+ if(save_nick_trie)
+ free_abook_trie(&save_nick_trie);
+
+ if(save_addr_trie)
+ free_abook_trie(&save_addr_trie);
+
+ if(save_full_trie)
+ free_abook_trie(&save_full_trie);
+
+ if(save_revfull_trie)
+ free_abook_trie(&save_revfull_trie);
+ }
+ }
+}
+
+
+/*
+ * Free the list of distribution lists which have been expanded.
+ * Leaves the head of the list alone.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ */
+void
+exp_free(EXPANDED_S *exp_head)
+{
+ EXPANDED_S *e, *the_next_one;
+
+ e = exp_head ? exp_head->next : NULL;
+
+ if(!e)
+ return;
+
+ while(e){
+ the_next_one = e->next;
+ fs_give((void **)&e);
+ e = the_next_one;
+ }
+
+ exp_head->next = (EXPANDED_S *)NULL;
+}
+
+
+/*
+ * Is entry n expanded?
+ *
+ * Args: exp_head -- Head of the expanded list.
+ * n -- The entry num to check
+ */
+int
+exp_is_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
+{
+ register EXPANDED_S *e;
+ adrbk_cntr_t nn;
+
+ nn = (adrbk_cntr_t)n;
+
+ e = exp_head ? exp_head->next : NULL;
+
+ /*
+ * The list is kept ordered, so we search until we find it or are
+ * past it.
+ */
+ while(e){
+ if(e->ent >= nn)
+ break;
+
+ e = e->next;
+ }
+
+ return(e && e->ent == nn);
+}
+
+
+/*
+ * How many entries expanded in this addrbook.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ */
+int
+exp_howmany_expanded(EXPANDED_S *exp_head)
+{
+ register EXPANDED_S *e;
+ int cnt = 0;
+
+ e = exp_head ? exp_head->next : NULL;
+
+ while(e){
+ cnt++;
+ e = e->next;
+ }
+
+ return(cnt);
+}
+
+
+/*
+ * Are any entries expanded?
+ *
+ * Args: exp_head -- Head of the expanded list.
+ */
+int
+exp_any_expanded(EXPANDED_S *exp_head)
+{
+ return(exp_head && exp_head->next != NULL);
+}
+
+
+/*
+ * Return next entry num in list.
+ *
+ * Args: cur -- Current position in the list.
+ *
+ * Result: Returns the number of the next entry, or NO_NEXT if there is
+ * no next entry. As a side effect, the cur pointer is incremented.
+ */
+adrbk_cntr_t
+exp_get_next(EXPANDED_S **cur)
+{
+ adrbk_cntr_t ret = NO_NEXT;
+
+ if(cur && *cur && (*cur)->next){
+ ret = (*cur)->next->ent;
+ *cur = (*cur)->next;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Mark entry n as being expanded.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ * n -- The entry num to mark
+ */
+void
+exp_set_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
+{
+ register EXPANDED_S *e;
+ EXPANDED_S *new;
+ adrbk_cntr_t nn;
+
+ nn = (adrbk_cntr_t)n;
+ if(!exp_head)
+ panic("exp_head not set in exp_set_expanded");
+
+ for(e = exp_head; e->next; e = e->next)
+ if(e->next->ent >= nn)
+ break;
+
+ if(e->next && e->next->ent == nn) /* already there */
+ return;
+
+ /* add new after e */
+ new = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
+ new->ent = nn;
+ new->next = e->next;
+ e->next = new;
+}
+
+
+/*
+ * Mark entry n as being *not* expanded.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ * n -- The entry num to mark
+ */
+void
+exp_unset_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
+{
+ register EXPANDED_S *e;
+ EXPANDED_S *delete_this_one = NULL;
+ adrbk_cntr_t nn;
+
+ nn = (adrbk_cntr_t)n;
+ if(!exp_head)
+ panic("exp_head not set in exp_unset_expanded");
+
+ for(e = exp_head; e->next; e = e->next)
+ if(e->next->ent >= nn)
+ break;
+
+ if(e->next && e->next->ent == nn){
+ delete_this_one = e->next;
+ e->next = e->next->next;
+ }
+
+ if(delete_this_one)
+ fs_give((void **)&delete_this_one);
+}
+
+
+/*
+ * Adjust the "expanded" list to correspond to addrbook entry n being
+ * deleted.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ * n -- The entry num being deleted
+ */
+void
+exp_del_nth(EXPANDED_S *exp_head, a_c_arg_t n)
+{
+ register EXPANDED_S *e;
+ int delete_when_done = 0;
+ adrbk_cntr_t nn;
+
+ nn = (adrbk_cntr_t)n;
+ if(!exp_head)
+ panic("exp_head not set in exp_del_nth");
+
+ e = exp_head->next;
+ while(e && e->ent < nn)
+ e = e->next;
+
+ if(e){
+ if(e->ent == nn){
+ delete_when_done++;
+ e = e->next;
+ }
+
+ while(e){
+ e->ent--; /* adjust entry nums */
+ e = e->next;
+ }
+
+ if(delete_when_done)
+ exp_unset_expanded(exp_head, n);
+ }
+}
+
+
+/*
+ * Adjust the "expanded" list to correspond to a new addrbook entry being
+ * added between current entries n-1 and n.
+ *
+ * Args: exp_head -- Head of the expanded list.
+ * n -- The entry num being added
+ *
+ * The new entry is not marked expanded.
+ */
+void
+exp_add_nth(EXPANDED_S *exp_head, a_c_arg_t n)
+{
+ register EXPANDED_S *e;
+ adrbk_cntr_t nn;
+
+ nn = (adrbk_cntr_t)n;
+ if(!exp_head)
+ panic("exp_head not set in exp_add_nth");
+
+ e = exp_head->next;
+ while(e && e->ent < nn)
+ e = e->next;
+
+ while(e){
+ e->ent++; /* adjust entry nums */
+ e = e->next;
+ }
+}
+
+
+static AdrBk *ab_for_sort;
+
+/*
+ * Compare two address book entries. Args are AdrBk_Entry **'s.
+ * Sorts lists after simple addresses and then sorts on Fullname field.
+ */
+int
+cmp_ae_by_full_lists_last(const qsort_t *a, const qsort_t *b)
+{
+ AdrBk_Entry **x = (AdrBk_Entry **)a,
+ **y = (AdrBk_Entry **)b;
+ int result;
+
+ if((*x)->tag == List && (*y)->tag == Single)
+ result = 1;
+ else if((*x)->tag == Single && (*y)->tag == List)
+ result = -1;
+ else{
+ register char *p, *q, *r, *s;
+
+ p = (*x)->fullname;
+ if(*p == '"' && *(p+1))
+ p++;
+
+ q = (*y)->fullname;
+ if(*q == '"' && *(q+1))
+ q++;
+
+ r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, p);
+
+ s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
+ SIZEOF_20KBUF-10000, q);
+
+ result = (*pcollator)(r, s);
+ if(result == 0)
+ result = (*pcollator)((*x)->nickname, (*y)->nickname);
+ }
+
+ return(result);
+}
+
+
+/*
+ * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s).
+ * Sorts lists after simple addresses and then sorts on Fullname field.
+ */
+int
+cmp_cntr_by_full_lists_last(const qsort_t *a, const qsort_t *b)
+{
+ adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */
+ *y = (adrbk_cntr_t *)b;
+ AdrBk_Entry *x_ae,
+ *y_ae;
+
+ if(ps_global->intr_pending)
+ longjmp(jump_over_qsort, 1);
+
+ ALARM_BLIP();
+
+ x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
+ y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));
+
+ return(cmp_ae_by_full_lists_last((const qsort_t *) &x_ae,
+ (const qsort_t *) &y_ae));
+}
+
+
+/*
+ * Compare two address book entries. Args are AdrBk_Entry **'s.
+ * Sorts on Fullname field.
+ */
+int
+cmp_ae_by_full(const qsort_t *a, const qsort_t *b)
+{
+ AdrBk_Entry **x = (AdrBk_Entry **)a,
+ **y = (AdrBk_Entry **)b;
+ int result;
+ register char *p, *q, *r, *s;
+
+ p = (*x)->fullname;
+ if(*p == '"' && *(p+1))
+ p++;
+
+ q = (*y)->fullname;
+ if(*q == '"' && *(q+1))
+ q++;
+
+ r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, p);
+ s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
+ SIZEOF_20KBUF-10000, q);
+ result = (*pcollator)(r, s);
+ if(result == 0)
+ result = (*pcollator)((*x)->nickname, (*y)->nickname);
+
+ return(result);
+}
+
+
+/*
+ * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s).
+ * Sorts on Fullname field.
+ */
+int
+cmp_cntr_by_full(const qsort_t *a, const qsort_t *b)
+{
+ adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */
+ *y = (adrbk_cntr_t *)b;
+ AdrBk_Entry *x_ae,
+ *y_ae;
+
+ if(ps_global->intr_pending)
+ longjmp(jump_over_qsort, 1);
+
+ ALARM_BLIP();
+
+ x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
+ y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));
+
+ return(cmp_ae_by_full((const qsort_t *) &x_ae, (const qsort_t *) &y_ae));
+}
+
+
+/*
+ * Compare two address book entries. Args are AdrBk_Entry **'s.
+ * Sorts lists after simple addresses and then sorts on Nickname field.
+ */
+int
+cmp_ae_by_nick_lists_last(const qsort_t *a, const qsort_t *b)
+{
+ AdrBk_Entry **x = (AdrBk_Entry **)a,
+ **y = (AdrBk_Entry **)b;
+ int result;
+
+ if((*x)->tag == List && (*y)->tag == Single)
+ result = 1;
+ else if((*x)->tag == Single && (*y)->tag == List)
+ result = -1;
+ else
+ result = (*pcollator)((*x)->nickname, (*y)->nickname);
+
+ return(result);
+}
+
+
+/*
+ * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s).
+ * Sorts lists after simple addresses and then sorts on Nickname field.
+ */
+int
+cmp_cntr_by_nick_lists_last(const qsort_t *a, const qsort_t *b)
+{
+ adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */
+ *y = (adrbk_cntr_t *)b;
+ AdrBk_Entry *x_ae,
+ *y_ae;
+
+ if(ps_global->intr_pending)
+ longjmp(jump_over_qsort, 1);
+
+ ALARM_BLIP();
+
+ x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
+ y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));
+
+ return(cmp_ae_by_nick_lists_last((const qsort_t *) &x_ae,
+ (const qsort_t *) &y_ae));
+}
+
+
+/*
+ * Compare two address book entries. Args are AdrBk_Entry **'s.
+ * Sorts on Nickname field.
+ */
+int
+cmp_ae_by_nick(const qsort_t *a, const qsort_t *b)
+{
+ AdrBk_Entry **x = (AdrBk_Entry **)a,
+ **y = (AdrBk_Entry **)b;
+
+ return((*pcollator)((*x)->nickname, (*y)->nickname));
+}
+
+
+/*
+ * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s).
+ * Sorts on Nickname field.
+ */
+int
+cmp_cntr_by_nick(const qsort_t *a, const qsort_t *b)
+{
+ adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */
+ *y = (adrbk_cntr_t *)b;
+ AdrBk_Entry *x_ae,
+ *y_ae;
+
+ if(ps_global->intr_pending)
+ longjmp(jump_over_qsort, 1);
+
+ ALARM_BLIP();
+
+ x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
+ y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));
+
+ return(cmp_ae_by_nick((const qsort_t *) &x_ae, (const qsort_t *) &y_ae));
+}
+
+
+/*
+ * For sorting a simple list of pointers to addresses (skip initial quotes)
+ */
+int
+cmp_addr(const qsort_t *a1, const qsort_t *a2)
+{
+ char *x = *(char **)a1, *y = *(char **)a2;
+ char *r, *s;
+
+ if(x && *x == '"')
+ x++;
+
+ if(y && *y == '"')
+ y++;
+
+ r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, x);
+ s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
+ SIZEOF_20KBUF-10000, y);
+ return((*pcollator)(r, s));
+}
+
+
+/*
+ * Sort an array of strings, except skip initial quotes.
+ */
+void
+sort_addr_list(char **list)
+{
+ register char **p;
+
+ /* find size of list */
+ for(p = list; *p != NULL; p++)
+ ;/* do nothing */
+
+ qsort((qsort_t *)list, (size_t)(p - list), sizeof(char *), cmp_addr);
+}
+
+
+/*
+ * Sort this address book.
+ *
+ * Args: ab -- address book to sort
+ * current_entry_num -- see next description
+ * new_entry_num -- return new entry_num of current_entry_num here
+ *
+ * Result: return code: 0 all went well
+ * -2 error writing address book, check errno
+ *
+ * The sorting strategy is to allocate an array of length ab->count which
+ * contains the element numbers 0, 1, ..., ab->count - 1, representing the
+ * entries in the addrbook, of course. Sort the array, then use that
+ * result to swap ae's in the in-core addrbook array before writing it out.
+ */
+int
+adrbk_sort(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num, int be_quiet)
+{
+ adrbk_cntr_t *sort_array, *inv, tmp;
+ long i, j, hi, count;
+ int skip_the_sort = 0, we_cancel = 0, we_turned_on = 0;
+ AdrBk_Entry ae_tmp, *ae_i, *ae_hi;
+ EXPANDED_S *e, *e2, *smallest;
+
+ dprint((5, "- adrbk_sort -\n"));
+
+ count = (long) (ab->count);
+
+ if(!ab)
+ return -2;
+
+ if(ab->sort_rule == AB_SORT_RULE_NONE)
+ return 0;
+
+ if(count < 2)
+ return 0;
+
+ sort_array = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t));
+ inv = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t));
+
+ for(i = 0L; i < count; i++)
+ sort_array[i] = (adrbk_cntr_t) i;
+
+ ab_for_sort = ab;
+
+ if(setjmp(jump_over_qsort))
+ skip_the_sort = 1;
+
+ if(!skip_the_sort){
+ we_turned_on = intr_handling_on();
+ if(!be_quiet)
+ we_cancel = busy_cue(_("Sorting address book"), NULL, 0);
+
+ qsort((qsort_t *)sort_array,
+ (size_t)count,
+ sizeof(adrbk_cntr_t),
+ (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
+ cmp_cntr_by_full_lists_last :
+ (ab->sort_rule == AB_SORT_RULE_FULL) ?
+ cmp_cntr_by_full :
+ (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
+ cmp_cntr_by_nick_lists_last :
+ /* (ab->sort_rule == AB_SORT_RULE_NICK) */
+ cmp_cntr_by_nick);
+ }
+
+ dprint((9, "- adrbk_sort: done with first sort -\n"));
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ if(skip_the_sort){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Address book sort cancelled, using old order for now"));
+ goto skip_the_write_too;
+ }
+
+ dprint((5, "- adrbk_sort (%s)\n",
+ ab->sort_rule==AB_SORT_RULE_FULL_LISTS ? "FullListsLast" :
+ ab->sort_rule==AB_SORT_RULE_FULL ? "Fullname" :
+ ab->sort_rule==AB_SORT_RULE_NICK_LISTS ? "NickListLast" :
+ ab->sort_rule==AB_SORT_RULE_NICK ? "Nickname" : "unknown"));
+
+ /*
+ * Rearrange the in-core array of ae's to be in the new order.
+ * We can do that by sorting the inverse into the correct order in parallel.
+ */
+ for(i = 0L; i < count; i++)
+ inv[sort_array[i]] = i;
+
+ if(new_entry_num && (adrbk_cntr_t) current_entry_num >= 0
+ && (adrbk_cntr_t) current_entry_num < count){
+ *new_entry_num = inv[(adrbk_cntr_t) current_entry_num];
+ }
+
+ /*
+ * The expanded and selected lists will be wrong now. Correct them.
+ * First the expanded list.
+ */
+ e = ab->exp ? ab->exp->next : NULL;
+ while(e){
+ if(e->ent >= 0 && e->ent < count)
+ e->ent = inv[e->ent];
+
+ e = e->next;
+ }
+
+ /*
+ * And sort into ascending order as expected by the exp_ routines.
+ */
+ e = ab->exp ? ab->exp->next : NULL;
+ while(e){
+ /* move smallest to e */
+ e2 = e;
+ smallest = e;
+ while(e2){
+ if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent)
+ smallest = e2;
+
+ e2 = e2->next;
+ }
+
+ /* swap values in e and smallest */
+ if(e != smallest){
+ tmp = e->ent;
+ e->ent = smallest->ent;
+ smallest->ent = tmp;
+ }
+
+ e = e->next;
+ }
+
+ /*
+ * Same thing for the selected list.
+ */
+ e = ab->selects ? ab->selects->next : NULL;
+ while(e){
+ if(e->ent >= 0 && e->ent < count)
+ e->ent = inv[e->ent];
+
+ e = e->next;
+ }
+
+ e = ab->selects ? ab->selects->next : NULL;
+ while(e){
+ /* move smallest to e */
+ e2 = e;
+ smallest = e;
+ while(e2){
+ if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent)
+ smallest = e2;
+
+ e2 = e2->next;
+ }
+
+ /* swap values in e and smallest */
+ if(e != smallest){
+ tmp = e->ent;
+ e->ent = smallest->ent;
+ smallest->ent = tmp;
+ }
+
+ e = e->next;
+ }
+
+ for(i = 0L; i < count; i++){
+ if(i != inv[i]){
+ /* find inv[j] which = i */
+ for(j = i+1; j < count; j++){
+ if(i == inv[j]){
+ hi = j;
+ break;
+ }
+ }
+
+ /* swap i and hi */
+ ae_i = adrbk_get_ae(ab, (a_c_arg_t) i);
+ ae_hi = adrbk_get_ae(ab, (a_c_arg_t) hi);
+ if(ae_i && ae_hi){
+ memcpy(&ae_tmp, ae_i, sizeof(ae_tmp));
+ memcpy(ae_i, ae_hi, sizeof(ae_tmp));
+ memcpy(ae_hi, &ae_tmp, sizeof(ae_tmp));
+ }
+ /* else can't happen */
+
+ inv[hi] = inv[i];
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ repair_abook_tries(ab);
+
+ dprint((9, "- adrbk_sort: done with rearranging -\n"));
+
+skip_the_write_too:
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(sort_array)
+ fs_give((void **) &sort_array);
+
+ if(inv)
+ fs_give((void **) &inv);
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if any addrbooks are in Open state, 0 otherwise.
+ *
+ * This is a test for ostatus == Open, not for whether or not the address book
+ * FILE is opened.
+ */
+int
+any_ab_open(void)
+{
+ int i, ret = 0;
+
+ if(as.initialized)
+ for(i = 0; ret == 0 && i < as.n_addrbk; i++)
+ if(as.adrbks[i].ostatus == Open)
+ ret++;
+
+ return(ret);
+}
+
+
+/*
+ * Make sure addrbooks are minimally initialized.
+ */
+void
+init_ab_if_needed(void)
+{
+ dprint((9, "- init_ab_if_needed -\n"));
+
+ if(!as.initialized)
+ (void)init_addrbooks(Closed, 0, 0, 1);
+}
+
+
+/*
+ * Sets everything up to get started.
+ *
+ * Args: want_status -- The desired OpenStatus for all addrbooks.
+ * reset_to_top -- Forget about the old location and put cursor
+ * at top.
+ * open_if_only_one -- If want_status is HalfOpen and there is only
+ * section to look at, then promote want_status
+ * to Open.
+ * ro_warning -- Set ReadOnly warning global
+ *
+ * Return: 1 if ok, 0 if problem
+ */
+int
+init_addrbooks(OpenStatus want_status, int reset_to_top, int open_if_only_one, int ro_warning)
+{
+ register PerAddrBook *pab;
+ char *q, **t;
+ long line;
+
+ dprint((4, "-- init_addrbooks(%s, %d, %d, %d) --\n",
+ want_status==Open ?
+ "Open" :
+ want_status==HalfOpen ?
+ "HalfOpen" :
+ want_status==ThreeQuartOpen ?
+ "ThreeQuartOpen" :
+ want_status==NoDisplay ?
+ "NoDisplay" :
+ "Closed",
+ reset_to_top, open_if_only_one, ro_warning));
+
+ as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)
+ - HEADER_ROWS(ps_global);
+ if(as.l_p_page <= 0)
+ as.no_op_possbl++;
+ else
+ as.no_op_possbl = 0;
+
+ as.ro_warning = ro_warning;
+
+ /* already been initialized */
+ if(as.initialized){
+ int i;
+
+ /*
+ * Special case. If there is only one addressbook we start the
+ * user out with that open.
+ */
+ if(want_status == HalfOpen &&
+ ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) ||
+ (F_ON(F_EXPANDED_ADDRBOOKS, ps_global) &&
+ F_ON(F_CMBND_ABOOK_DISP, ps_global))))
+ want_status = Open;
+
+ /* open to correct state */
+ for(i = 0; i < as.n_addrbk; i++)
+ init_abook(&as.adrbks[i], want_status);
+
+ if(reset_to_top){
+ warp_to_beginning();
+ as.top_ent = 0L;
+ line = first_selectable_line(0L);
+ if(line == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = line;
+
+ if(as.cur_row >= as.l_p_page)
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+
+ as.old_cur_row = as.cur_row;
+ }
+
+ dprint((4, "init_addrbooks: already initialized: %d books\n",
+ as.n_addrbk));
+ return((as.n_addrbk + as.n_serv) ? 1 : 0);
+ }
+
+ as.initialized = 1;
+
+ /* count directory servers */
+ as.n_serv = 0;
+ as.n_impl = 0;
+#ifdef ENABLE_LDAP
+ if(ps_global->VAR_LDAP_SERVERS &&
+ ps_global->VAR_LDAP_SERVERS[0] &&
+ ps_global->VAR_LDAP_SERVERS[0][0])
+ for(t = ps_global->VAR_LDAP_SERVERS; t[0] && t[0][0]; t++){
+ LDAP_SERV_S *info;
+
+ as.n_serv++;
+ info = break_up_ldap_server(*t);
+ as.n_impl += (info && info->impl) ? 1 : 0;
+ if(info)
+ free_ldap_server_info(&info);
+ }
+#endif /* ENABLE_LDAP */
+
+ /* count addressbooks */
+ as.how_many_personals = 0;
+ if(ps_global->VAR_ADDRESSBOOK &&
+ ps_global->VAR_ADDRESSBOOK[0] &&
+ ps_global->VAR_ADDRESSBOOK[0][0])
+ for(t = ps_global->VAR_ADDRESSBOOK; t[0] && t[0][0]; t++)
+ as.how_many_personals++;
+
+ as.n_addrbk = as.how_many_personals;
+ if(ps_global->VAR_GLOB_ADDRBOOK &&
+ ps_global->VAR_GLOB_ADDRBOOK[0] &&
+ ps_global->VAR_GLOB_ADDRBOOK[0][0])
+ for(t = ps_global->VAR_GLOB_ADDRBOOK; t[0] && t[0][0]; t++)
+ as.n_addrbk++;
+
+ if(want_status == HalfOpen &&
+ ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) ||
+ (F_ON(F_EXPANDED_ADDRBOOKS, ps_global) &&
+ F_ON(F_CMBND_ABOOK_DISP, ps_global))))
+ want_status = Open;
+
+
+ /*
+ * allocate array of PerAddrBooks
+ * (we don't give this up until we exit Pine, but it's small)
+ */
+ if(as.n_addrbk){
+ as.adrbks = (PerAddrBook *)fs_get(as.n_addrbk * sizeof(PerAddrBook));
+ memset((void *)as.adrbks, 0, as.n_addrbk * sizeof(PerAddrBook));
+
+ /* init PerAddrBook data */
+ for(as.cur = 0; as.cur < as.n_addrbk; as.cur++){
+ char *nickname = NULL,
+ *filename = NULL;
+
+ if(as.cur < as.how_many_personals)
+ q = ps_global->VAR_ADDRESSBOOK[as.cur];
+ else
+ q = ps_global->VAR_GLOB_ADDRBOOK[as.cur - as.how_many_personals];
+
+ pab = &as.adrbks[as.cur];
+
+ /* Parse entry for optional nickname and filename */
+ get_pair(q, &nickname, &filename, 0, 0);
+
+ if(nickname && !*nickname)
+ fs_give((void **)&nickname);
+
+ strncpy(tmp_20k_buf, filename, SIZEOF_20KBUF);
+ fs_give((void **)&filename);
+
+ filename = tmp_20k_buf;
+ if(nickname == NULL)
+ pab->abnick = cpystr(filename);
+ else
+ pab->abnick = nickname;
+
+ if(*filename == '~')
+ fnexpand(filename, SIZEOF_20KBUF);
+
+ if(*filename == '{' || is_absolute_path(filename)){
+ pab->filename = cpystr(filename); /* fully qualified */
+ }
+ else{
+ char book_path[MAXPATH+1];
+ char *lc = last_cmpnt(ps_global->pinerc);
+
+ book_path[0] = '\0';
+ if(lc != NULL){
+ strncpy(book_path, ps_global->pinerc,
+ MIN(lc - ps_global->pinerc, sizeof(book_path)-1));
+ book_path[MIN(lc - ps_global->pinerc,
+ sizeof(book_path)-1)] = '\0';
+ }
+
+ strncat(book_path, filename,
+ sizeof(book_path)-1-strlen(book_path));
+ pab->filename = cpystr(book_path);
+ }
+
+ if(*pab->filename == '{')
+ pab->type |= REMOTE_VIA_IMAP;
+
+ if(as.cur >= as.how_many_personals)
+ pab->type |= GLOBAL;
+
+ pab->access = adrbk_access(pab);
+
+ /* global address books are forced readonly */
+ if(pab->type & GLOBAL && pab->access != NoAccess)
+ pab->access = ReadOnly;
+
+ pab->ostatus = TotallyClosed;
+
+ /*
+ * and remember that the memset above initializes everything
+ * else to 0
+ */
+
+ init_abook(pab, want_status);
+ }
+ }
+
+ /*
+ * Have to reset_to_top in this case since this is the first open,
+ * regardless of the value of the argument, since these values haven't been
+ * set before here.
+ */
+ as.cur = 0;
+ as.top_ent = 0L;
+ warp_to_beginning();
+ line = first_selectable_line(0L);
+
+ if(line == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = line;
+
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = as.l_p_page - 1;
+ }
+
+ as.old_cur_row = as.cur_row;
+
+ return((as.n_addrbk + as.n_serv) ? 1 : 0);
+}
+
+
+/*
+ * Something was changed in options screen, so need to start over.
+ */
+void
+addrbook_reset(void)
+{
+ dprint((4, "- addrbook_reset -\n"));
+ completely_done_with_adrbks();
+}
+
+
+/*
+ * Sort was changed in options screen. Since we only sort normally
+ * when we actually make a change to the address book, we need to
+ * go out of our way to sort here.
+ */
+void
+addrbook_redo_sorts(void)
+{
+ int i;
+ PerAddrBook *pab;
+ AdrBk *ab;
+
+ dprint((4, "- addrbook_redo_sorts -\n"));
+
+ addrbook_reset();
+ init_ab_if_needed();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ init_abook(pab, NoDisplay);
+ ab = pab->address_book;
+
+ if(!adrbk_is_in_sort_order(ab, 0))
+ adrbk_write(ab, 0, NULL, NULL, 1, 0);
+ }
+
+ addrbook_reset();
+}
+
+
+/*
+ * Returns type of access allowed on this addrbook.
+ */
+AccessType
+adrbk_access(PerAddrBook *pab)
+{
+ char fbuf[MAXPATH+1];
+ AccessType access = NoExists;
+ CONTEXT_S *dummy_cntxt = NULL;
+
+ dprint((9, "- addrbook_access -\n"));
+
+ if(pab && pab->type & REMOTE_VIA_IMAP){
+ /*
+ * Open_fcc creates the folder if it didn't already exist.
+ */
+ if((pab->so = open_fcc(pab->filename, &dummy_cntxt, 1,
+ "Error: ",
+ " Can't fetch remote addrbook.")) != NULL){
+ /*
+ * We know the folder is there but don't know what access
+ * rights we have until we try to select it, which we don't
+ * want to do unless we have to. So delay evaluating.
+ */
+ access = MaybeRorW;
+ }
+ }
+ else if(pab){ /* local file */
+#if defined(NO_LOCAL_ADDRBOOKS)
+ /* don't allow any access to local addrbooks */
+ access = NoAccess;
+#else /* !NO_LOCAL_ADDRBOOKS) */
+ build_path(fbuf, is_absolute_path(pab->filename) ? NULL
+ : ps_global->home_dir,
+ pab->filename, sizeof(fbuf));
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * Microsoft networking causes some access calls to do a DNS query (!!)
+ * when it is turned on. In particular, if there is a / in the filename
+ * this seems to happen. So, just don't allow it.
+ */
+ if(strindex(fbuf, '/') != NULL){
+ dprint((2, "\"/\" not allowed in addrbook name\n"));
+ return NoAccess;
+ }
+#else /* !DOS */
+ /* also prevent backslash in non-DOS addrbook names */
+ if(strindex(fbuf, '\\') != NULL){
+ dprint((2, "\"\\\" not allowed in addrbook name\n"));
+ return NoAccess;
+ }
+#endif /* !DOS */
+
+ if(can_access(fbuf, ACCESS_EXISTS) == 0){
+ if(can_access(fbuf, EDIT_ACCESS) == 0){
+ char *dir, *p;
+
+ dir = ".";
+ if((p = last_cmpnt(fbuf)) != NULL){
+ *--p = '\0';
+ dir = *fbuf ? fbuf : "/";
+ }
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * If the dir has become a drive letter and : (e.g. "c:")
+ * then append a "\". The library function access() in the
+ * win 16 version of MSC seems to require this.
+ */
+ if(isalpha((unsigned char) *dir)
+ && *(dir+1) == ':' && *(dir+2) == '\0'){
+ *(dir+2) = '\\';
+ *(dir+3) = '\0';
+ }
+#endif /* DOS || OS2 */
+
+ /*
+ * Even if we can edit the address book file itself, we aren't
+ * going to be able to change it unless we can also write in
+ * the directory that contains it (because we write into a
+ * temp file and then rename).
+ */
+ if(can_access(dir, EDIT_ACCESS) == 0)
+ access = ReadWrite;
+ else{
+ access = ReadOnly;
+ q_status_message1(SM_ORDER, 2, 2,
+ "Address book directory (%.200s) is ReadOnly",
+ dir);
+ }
+ }
+ else if(can_access(fbuf, READ_ACCESS) == 0)
+ access = ReadOnly;
+ else
+ access = NoAccess;
+ }
+#endif /* !NO_LOCAL_ADDRBOOKS) */
+ }
+
+ return(access);
+}
+
+
+/*
+ * Trim back remote address books if necessary.
+ */
+void
+trim_remote_adrbks(void)
+{
+ register PerAddrBook *pab;
+ int i;
+
+ dprint((2, "- trim_remote_adrbks -\n"));
+
+ if(!as.initialized)
+ return;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->ostatus != TotallyClosed && pab->address_book
+ && pab->address_book->rd)
+ rd_trim_remdata(&pab->address_book->rd);
+ }
+}
+
+
+/*
+ * Free and close everything.
+ */
+void
+completely_done_with_adrbks(void)
+{
+ register PerAddrBook *pab;
+ int i;
+
+ dprint((2, "- completely_done_with_adrbks -\n"));
+
+ ab_nesting_level = 0;
+
+ if(!as.initialized)
+ return;
+
+ for(i = 0; i < as.n_addrbk; i++)
+ init_abook(&as.adrbks[i], TotallyClosed);
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+
+ if(pab->filename)
+ fs_give((void **)&pab->filename);
+
+ if(pab->abnick)
+ fs_give((void **)&pab->abnick);
+ }
+
+ done_with_dlc_cache();
+
+ if(as.adrbks)
+ fs_give((void **)&as.adrbks);
+
+ as.n_addrbk = 0;
+ as.initialized = 0;
+}
+
+
+/*
+ * Initialize or re-initialize this address book.
+ *
+ * Args: pab -- the PerAddrBook ptr
+ * want_status -- desired OpenStatus for this address book
+ */
+void
+init_abook(PerAddrBook *pab, OpenStatus want_status)
+{
+ register OpenStatus new_status;
+
+ dprint((4, "- init_abook -\n"));
+ dprint((7, " addrbook nickname = %s filename = %s",
+ pab->abnick ? pab->abnick : "<null>",
+ pab->filename ? pab->filename : "<null>"));
+ dprint((7, " ostatus was %s, want %s\n",
+ pab->ostatus==Open ? "Open" :
+ pab->ostatus==HalfOpen ? "HalfOpen" :
+ pab->ostatus==ThreeQuartOpen ? "ThreeQuartOpen" :
+ pab->ostatus==NoDisplay ? "NoDisplay" :
+ pab->ostatus==Closed ? "Closed" : "TotallyClosed",
+ want_status==Open ? "Open" :
+ want_status==HalfOpen ? "HalfOpen" :
+ want_status==ThreeQuartOpen ? "ThreeQuartOpen" :
+ want_status==NoDisplay ? "NoDisplay" :
+ want_status==Closed ? "Closed" : "TotallyClosed"));
+
+ new_status = want_status; /* optimistic default */
+
+ if(want_status == TotallyClosed){
+ if(pab->address_book != NULL){
+ adrbk_close(pab->address_book);
+ pab->address_book = NULL;
+ }
+
+ if(pab->so != NULL){
+ so_give(&pab->so);
+ pab->so = NULL;
+ }
+ }
+ /*
+ * If we don't need it, release some addrbook memory by calling
+ * adrbk_partial_close().
+ */
+ else if((want_status == Closed || want_status == HalfOpen) &&
+ pab->address_book != NULL){
+ adrbk_partial_close(pab->address_book);
+ }
+ /* If we want the addrbook read in and it hasn't been, do so */
+ else if(want_status == Open || want_status == NoDisplay){
+ if(pab->address_book == NULL){ /* abook handle is not currently active */
+ if(pab->access != NoAccess){
+ char warning[800]; /* place to put a warning */
+ int sort_rule;
+
+ warning[0] = '\0';
+ if(pab->access == ReadOnly)
+ sort_rule = AB_SORT_RULE_NONE;
+ else
+ sort_rule = ps_global->ab_sort_rule;
+
+ pab->address_book = adrbk_open(pab,
+ ps_global->home_dir, warning, sizeof(warning), sort_rule);
+
+ if(pab->address_book == NULL){
+ pab->access = NoAccess;
+ if(want_status == Open){
+ new_status = HalfOpen; /* best we can do */
+ q_status_message1(SM_ORDER | SM_DING, *warning?1:3, 4,
+ _("Error opening/creating address book %.200s"),
+ pab->abnick);
+ if(*warning)
+ q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s",
+ as.n_addrbk > 1 ? pab->abnick : "addressbook",
+ warning);
+ }
+ else
+ new_status = Closed;
+
+ dprint((1, "Error opening address book %s: %s\n",
+ pab->abnick ? pab->abnick : "?",
+ error_description(errno)));
+ }
+ else{
+ if(pab->access == NoExists)
+ pab->access = ReadWrite;
+
+ if(pab->access == ReadWrite){
+ /*
+ * Add forced entries if there are any. These are
+ * entries that are always supposed to show up in
+ * personal address books. They're specified in the
+ * global config file.
+ */
+ add_forced_entries(pab->address_book);
+ }
+
+ new_status = want_status;
+ dprint((2, "Address book %s (%s) opened with %ld items\n",
+ pab->abnick ? pab->abnick : "?",
+ pab->filename ? pab->filename : "?",
+ (long)adrbk_count(pab->address_book)));
+ if(*warning){
+ dprint((1,
+ "Addressbook parse error in %s (%s): %s\n",
+ pab->abnick ? pab->abnick : "?",
+ pab->filename ? pab->filename : "?",
+ warning));
+ if(!pab->gave_parse_warnings && want_status == Open){
+ pab->gave_parse_warnings++;
+ q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s",
+ as.n_addrbk > 1 ? pab->abnick : "addressbook",
+ warning);
+ }
+ }
+ }
+ }
+ else{
+ if(want_status == Open){
+ new_status = HalfOpen; /* best we can do */
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Insufficient permissions for opening address book %.200s",
+ pab->abnick);
+ }
+ else
+ new_status = Closed;
+ }
+ }
+ /*
+ * File handle was already open but we were in Closed or HalfOpen
+ * state. Check to see if someone else has changed the addrbook
+ * since we first opened it.
+ */
+ else if((pab->ostatus == Closed || pab->ostatus == HalfOpen) &&
+ ps_global->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix(pab, 1, 0, 0);
+ }
+
+ pab->ostatus = new_status;
+}
+
+
+/*
+ * Does a validity check on all the open address books.
+ *
+ * Return -- number of address books with invalid data
+ */
+int
+adrbk_check_all_validity_now(void)
+{
+ int i;
+ int something_out_of_date = 0;
+ PerAddrBook *pab;
+
+ dprint((7, "- adrbk_check_all_validity_now -\n"));
+
+ if(as.initialized){
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->address_book){
+ adrbk_check_validity(pab->address_book, 1L);
+ if(pab->address_book->flags & FILE_OUTOFDATE ||
+ (pab->address_book->rd &&
+ pab->address_book->rd->flags & REM_OUTOFDATE))
+ something_out_of_date++;
+ }
+ }
+ }
+
+ return(something_out_of_date);
+}
+
+
+/*
+ * Fix out-of-date address book. This only fixes the AdrBk part of the
+ * problem, not the pab and display. It closes and reopens the address_book
+ * file, clears the cache, reads new data.
+ *
+ * Arg pab -- Pointer to the PerAddrBook data for this addrbook
+ * safe -- It is safe to apply the fix
+ * low_freq -- This is a low frequency check (longer between checks)
+ *
+ * Returns non-zero if addrbook was fixed
+ */
+int
+adrbk_check_and_fix(PerAddrBook *pab, int safe, int low_freq, int check_now)
+{
+ int ret = 0;
+ long chk_interval;
+
+ if(!as.initialized || !pab)
+ return(ret);
+
+ dprint((7, "- adrbk_check_and_fix(%s) -\n",
+ pab->filename ? pab->filename : "?"));
+
+ if(pab->address_book){
+ if(check_now)
+ chk_interval = 1L;
+ else if(low_freq)
+ chk_interval = -60L * MAX(LOW_FREQ_CHK_INTERVAL,
+ ps_global->remote_abook_validity + 5);
+ else
+ chk_interval = 0L;
+
+ adrbk_check_validity(pab->address_book, chk_interval);
+
+ if(pab->address_book->flags & FILE_OUTOFDATE ||
+ (pab->address_book->rd &&
+ pab->address_book->rd->flags & REM_OUTOFDATE &&
+ !(pab->address_book->rd->flags & USER_SAID_NO))){
+ if(safe){
+ OpenStatus save_status;
+ int save_rem_abook_valid = 0;
+
+ dprint((2, "adrbk_check_and_fix %s: fixing %s\n",
+ debug_time(0,0),
+ pab->filename ? pab->filename : "?"));
+ if(ab_nesting_level > 0){
+ q_status_message3(SM_ORDER, 0, 2,
+ "Resyncing address book%.200s%.200s%.200s",
+ as.n_addrbk > 1 ? " \"" : "",
+ as.n_addrbk > 1 ? pab->abnick : "",
+ as.n_addrbk > 1 ? "\"" : "");
+ display_message('x');
+ fflush(stdout);
+ }
+
+ ret++;
+ save_status = pab->ostatus;
+
+ /* don't do the trim right now */
+ if(pab->address_book->rd)
+ pab->address_book->rd->flags &= ~DO_REMTRIM;
+
+ /*
+ * Have to change this from -1 or we won't actually do
+ * the resync.
+ */
+ if((pab->address_book->rd &&
+ pab->address_book->rd->flags & REM_OUTOFDATE) &&
+ ps_global->remote_abook_validity == -1){
+ save_rem_abook_valid = -1;
+ ps_global->remote_abook_validity = 0;
+ }
+
+ init_abook(pab, TotallyClosed);
+
+ pab->so = NULL;
+ /* this sets up pab->so, so is important */
+ pab->access = adrbk_access(pab);
+
+ /*
+ * If we just re-init to HalfOpen... we won't actually
+ * open the address book, which was open before. That
+ * would be fine but it's a little nicer if we can open
+ * it now so that we don't defer the resync until
+ * the next open, which would be a user action for sure.
+ * Right now we may be here during a newmail check
+ * timeout, so this is a good time to do the resync.
+ */
+ if(save_status == HalfOpen ||
+ save_status == ThreeQuartOpen ||
+ save_status == Closed)
+ init_abook(pab, NoDisplay);
+
+ init_abook(pab, save_status);
+
+ if(save_rem_abook_valid)
+ ps_global->remote_abook_validity = save_rem_abook_valid;
+
+ if(ab_nesting_level > 0 && pab->ostatus == save_status)
+ q_status_message3(SM_ORDER, 0, 2,
+ "Resynced address book%.200s%.200s%.200s",
+ as.n_addrbk > 1 ? " \"" : "",
+ as.n_addrbk > 1 ? pab->abnick : "",
+ as.n_addrbk > 1 ? "\"" : "");
+ }
+ else
+ dprint((2,
+ "adrbk_check_and_fix: not safe to fix %s\n",
+ pab->filename ? pab->filename : "?"));
+ }
+ }
+
+ return(ret);
+}
+
+
+static time_t last_check_and_fix_all;
+/*
+ * Fix out of date address books. This only fixes the AdrBk part of the
+ * problem, not the pab and display. It closes and reopens the address_book
+ * files, clears the caches, reads new data.
+ *
+ * Args safe -- It is safe to apply the fix
+ * low_freq -- This is a low frequency check (longer between checks)
+ *
+ * Returns non-zero if an addrbook was fixed
+ */
+int
+adrbk_check_and_fix_all(int safe, int low_freq, int check_now)
+{
+ int i, ret = 0;
+ PerAddrBook *pab;
+
+ if(!as.initialized)
+ return(ret);
+
+ dprint((7, "- adrbk_check_and_fix_all -\n"));
+
+ last_check_and_fix_all = get_adj_time();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ ret += adrbk_check_and_fix(pab, safe, low_freq, check_now);
+ }
+
+ return(ret);
+}
+
+
+/*
+ *
+ */
+void
+adrbk_maintenance(void)
+{
+ static time_t last_time_here = 0;
+ time_t now;
+ int i;
+ long low_freq_interval;
+
+ dprint((9, "- adrbk_maintenance -\n"));
+
+ if(!as.initialized)
+ return;
+
+ now = get_adj_time();
+
+ if(now < last_time_here + 120)
+ return;
+
+ last_time_here = now;
+
+ low_freq_interval = MAX(LOW_FREQ_CHK_INTERVAL,
+ ps_global->remote_abook_validity + 5);
+
+ /* check the time here to make it cheap */
+ if(ab_nesting_level == 0 &&
+ ps_global->remote_abook_validity > 0 &&
+ now > last_check_and_fix_all + low_freq_interval * 60L)
+ (void)adrbk_check_and_fix_all(1, 1, 0);
+
+ /* close down idle connections */
+ for(i = 0; i < as.n_addrbk; i++){
+ PerAddrBook *pab;
+
+ pab = &as.adrbks[i];
+
+ if(pab->address_book &&
+ pab->address_book->type == Imap &&
+ pab->address_book->rd &&
+ rd_stream_exists(pab->address_book->rd)){
+ dprint((7,
+ "adrbk_maint: %s: idle cntr %ld (%ld)\n",
+ pab->address_book->orig_filename
+ ? pab->address_book->orig_filename : "?",
+ (long)(now - pab->address_book->rd->last_use),
+ (long)pab->address_book->rd->last_use));
+
+ if(now > pab->address_book->rd->last_use + IMAP_IDLE_TIMEOUT){
+ dprint((2,
+ "adrbk_maint %s: closing idle (%ld secs) connection: %s\n",
+ debug_time(0,0),
+ (long)(now - pab->address_book->rd->last_use),
+ pab->address_book->orig_filename
+ ? pab->address_book->orig_filename : "?"));
+ rd_close_remote(pab->address_book->rd);
+ }
+ else{
+ /*
+ * If we aren't going to close it, we ping it instead to
+ * make sure it stays open and doesn't timeout on us.
+ * This shouldn't be necessary unless the server has a
+ * really short timeout. If we got killed for some reason
+ * we set imap.stream to NULL.
+ * Instead of just pinging, we may as well check for
+ * updates, too.
+ */
+ if(ab_nesting_level == 0 &&
+ ps_global->remote_abook_validity > 0){
+ time_t save_last_use;
+
+ /*
+ * We shouldn't count this as a real last_use.
+ */
+ save_last_use = pab->address_book->rd->last_use;
+ (void)adrbk_check_and_fix(pab, 1, 0, 1);
+ pab->address_book->rd->last_use = save_last_use;
+ }
+ /* just ping it if not safe to fix it */
+ else if(!rd_ping_stream(pab->address_book->rd)){
+ dprint((2,
+ "adrbk_maint: %s: abook stream closed unexpectedly: %s\n",
+ debug_time(0,0),
+ pab->address_book->orig_filename
+ ? pab->address_book->orig_filename : "?"));
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Parses a string of comma-separated addresses or nicknames into an
+ * array.
+ *
+ * Returns an allocated, null-terminated list, or NULL.
+ */
+char **
+parse_addrlist(char *addrfield)
+{
+#define LISTCHUNK 500 /* Alloc this many addresses for list at a time */
+ char **al, **ad;
+ char *next_addr, *cur_addr, *p, *q;
+ int slots = LISTCHUNK;
+
+ if(!addrfield)
+ return((char **)NULL);
+
+ /* allocate first chunk */
+ slots = LISTCHUNK;
+ al = (char **)fs_get(sizeof(char *) * (slots+1));
+ ad = al;
+
+ p = addrfield;
+
+ /* skip any leading whitespace */
+ for(q = p; *q && *q == SPACE; q++)
+ ;/* do nothing */
+
+ next_addr = (*q) ? q : NULL;
+
+ /* Loop adding each address in list to array al */
+ for(cur_addr = next_addr; cur_addr; cur_addr = next_addr){
+
+ next_addr = skip_to_next_addr(cur_addr);
+
+ q = cur_addr;
+ SKIP_SPACE(q);
+
+ /* allocate more space */
+ if((ad-al) >= slots){
+ slots += LISTCHUNK;
+ fs_resize((void **)&al, sizeof(char *) * (slots+1));
+ ad = al + slots - LISTCHUNK;
+ }
+
+ if(*q)
+ *ad++ = cpystr(q);
+ }
+
+ *ad++ = NULL;
+
+ /* free up any excess we've allocated */
+ fs_resize((void **)&al, sizeof(char *) * (ad - al));
+ return(al);
+}
+
+
+/*
+ * Args cur -- pointer to the start of the current addr in list.
+ *
+ * Returns a pointer to the start of the next addr or NULL if there are
+ * no more addrs.
+ *
+ * Side effect: current addr has trailing white space removed
+ * and is null terminated.
+ */
+char *
+skip_to_next_addr(char *cur)
+{
+ register char *p,
+ *q;
+ char *ret_pointer;
+ int in_quotes = 0,
+ in_comment = 0;
+ char prev_char = '\0';
+
+ /*
+ * Find delimiting comma or end.
+ * Quoted commas and commented commas don't count.
+ */
+ for(q = cur; *q; q++){
+ switch(*q){
+ case COMMA:
+ if(!in_quotes && !in_comment)
+ goto found_comma;
+ break;
+
+ case LPAREN:
+ if(!in_quotes && !in_comment)
+ in_comment = 1;
+ break;
+
+ case RPAREN:
+ if(in_comment && prev_char != BSLASH)
+ in_comment = 0;
+ break;
+
+ case QUOTE:
+ if(in_quotes && prev_char != BSLASH)
+ in_quotes = 0;
+ else if(!in_quotes && !in_comment)
+ in_quotes = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ prev_char = *q;
+ }
+
+found_comma:
+ if(*q){ /* trailing comma case */
+ *q = '\0';
+ ret_pointer = q + 1;
+ }
+ else
+ ret_pointer = NULL; /* no more addrs after cur */
+
+ /* remove trailing white space from cur */
+ for(p = q - 1; p >= cur && isspace((unsigned char)*p); p--)
+ *p = '\0';
+
+ return(ret_pointer);
+}
+
+
+/*
+ * Add entries specified by system administrator. If the nickname already
+ * exists, it is not touched.
+ */
+void
+add_forced_entries(AdrBk *abook)
+{
+ AdrBk_Entry *abe;
+ char *nickname, *fullname, *address;
+ char *end_of_nick, *end_of_full, **t;
+
+
+ if(!ps_global->VAR_FORCED_ABOOK_ENTRY ||
+ !ps_global->VAR_FORCED_ABOOK_ENTRY[0] ||
+ !ps_global->VAR_FORCED_ABOOK_ENTRY[0][0])
+ return;
+
+ for(t = ps_global->VAR_FORCED_ABOOK_ENTRY; t[0] && t[0][0]; t++){
+ nickname = *t;
+
+ /*
+ * syntax for each element is
+ * nick[whitespace]|[whitespace]Fullname[WS]|[WS]Address
+ */
+
+ /* find end of nickname */
+ end_of_nick = nickname;
+ while(*end_of_nick
+ && !isspace((unsigned char)*end_of_nick)
+ && *end_of_nick != '|')
+ end_of_nick++;
+
+ /* find the pipe character between nickname and fullname */
+ fullname = end_of_nick;
+ while(*fullname && *fullname != '|')
+ fullname++;
+
+ if(*fullname)
+ fullname++;
+
+ *end_of_nick = '\0';
+ abe = adrbk_lookup_by_nick(abook, nickname, NULL);
+
+ if(!abe){ /* If it isn't there, add it */
+
+ /* skip whitespace before fullname */
+ fullname = skip_white_space(fullname);
+
+ /* find the pipe character between fullname and address */
+ end_of_full = fullname;
+ while(*end_of_full && *end_of_full != '|')
+ end_of_full++;
+
+ if(!*end_of_full){
+ dprint((2,
+ "missing | in forced-abook-entry \"%s\"\n",
+ nickname ? nickname : "?"));
+ continue;
+ }
+
+ address = end_of_full + 1;
+
+ /* skip whitespace before address */
+ address = skip_white_space(address);
+
+ if(*address == '('){
+ dprint((2,
+ "no lists allowed in forced-abook-entry \"%s\"\n",
+ address ? address : "?"));
+ continue;
+ }
+
+ /* go back and remove trailing white space from fullname */
+ while(*end_of_full == '|' || isspace((unsigned char)*end_of_full)){
+ *end_of_full = '\0';
+ end_of_full--;
+ }
+
+ dprint((2,
+ "Adding forced abook entry \"%s\"\n", nickname ? nickname : ""));
+
+ (void)adrbk_add(abook,
+ NO_NEXT,
+ nickname,
+ fullname,
+ address,
+ NULL,
+ NULL,
+ Single,
+ (adrbk_cntr_t *)NULL,
+ (int *)NULL,
+ 1,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/pith/adrbklib.h b/pith/adrbklib.h
new file mode 100644
index 00000000..6b148251
--- /dev/null
+++ b/pith/adrbklib.h
@@ -0,0 +1,857 @@
+/*
+ * $Id: adrbklib.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ADRBKLIB_INCLUDED
+#define PITH_ADRBKLIB_INCLUDED
+
+
+#include "../pith/indxtype.h"
+#include "../pith/remtype.h"
+#include "../pith/store.h"
+#include "../pith/string.h"
+
+
+/*
+ * Some notes:
+ *
+ * The order that the address book is stored in on disk is the order that
+ * the entries will be displayed in. When an address book is opened,
+ * if it is ReadWrite the sort order is checked and it is sorted if it is
+ * out of order.
+ *
+ * Considerable complexity that previously existed in the address book
+ * code has been removed. Cpu speeds have improved significantly and
+ * memory has increased dramatically, as well. The cost of the complexity
+ * of the lookup file and hashtables and all that stuff is thought to be
+ * more than the benefits. See pre-5.00 pine adrbklib.h for the way it
+ * used to be.
+ *
+ * The display code has remained the same.
+ * There is also some allocation happening
+ * in addrbook.c. In particular, the display is a window into an array
+ * of rows, at least one row per addrbook entry plus more for lists.
+ * Each row is an AddrScrn_Disp structure and those should typically take
+ * up 6 or 8 bytes. A cached copy of addrbook entries is not kept, just
+ * the element number to look it up (and often get it out of the EntryRef
+ * cache). In order to avoid having to allocate all those rows, this is
+ * also in the form of a cache. Only 3 * screen_length rows are kept in
+ * the cache, and the cache is always a simple interval of rows. That is,
+ * rows between valid_low and valid_high are all in the cache. Row numbers
+ * in the display list are a little mysterious. There is no origin. That
+ * is, you don't necessarily know where the start or end of the display
+ * is. You only know how to go forward and backward and how to "warp"
+ * to new locations in the display and go forward and backward from there.
+ * This is because we don't know how many rows there are all together. It
+ * is also a way to avoid searching through everything to get somewhere.
+ * If you want to go to the End, you warp there and start backwards instead
+ * of reading through all the entries to get there. If you edit an entry
+ * so that it sorts into a new location, you warp to that new location to
+ * save processing all of the entries in between.
+ *
+ *
+ * Notes about RFC1522 encoding:
+ *
+ * If the fullname field contains other than US-ASCII characters, it is
+ * encoded using the rules of RFC1522 or its successor. The value actually
+ * stored in the file is encoded, even if it matches the character set of
+ * the user. This is so it is easy to pass the entry around or to change
+ * character sets without invalidating entries in the address book. When
+ * a fullname is displayed, it is first decoded. If the fullname field is
+ * encoded as being from a character set other than the user's character
+ * set, that will be retained until the user edits the field. Then it will
+ * change over to the user's character set. The comment field works in
+ * the same way, as do the "phrase" fields of any addresses. On outgoing
+ * mail, the correct character set will be retained if you use ComposeTo
+ * from the address book screen. However, if you use a nickname from the
+ * composer or ^T from the composer, the character set will be lost if it
+ * is different from the user's character set.
+ *
+ *
+ * Notes about RemoteViaImap address books:
+ *
+ * There are currently two types of address books, Local and Imap. Local means
+ * it is in a local file. Imap means it is stored in a folder on a remote
+ * IMAP server. The folder is a regular folder containing mail messages but
+ * the messages are special. The first message is a header message. The last
+ * message is the address book data. In between messages are old versions of
+ * the address book data. The address book data is stored in the message as
+ * it would be on disk, with no fancy mime encoding or anything. When it is
+ * used the data from the last message in the folder is copied to a local file
+ * and then it is used just like a local file. The local file is a cache for
+ * the remote data. We can tell the remote data has been changed by looking
+ * at the Date of the last message in the remote folder. If we see it has
+ * changed we copy the whole file over again and replace the cache file.
+ * A possibly quicker way to tell if it has changed is if the UID has
+ * changed or the number of messages in the folder has changed. We use those
+ * methods if possible since they don't require opening a new stream and
+ * selecting the folder. There is one metadata file for address book data.
+ * The name of that file is stored in the pinerc file. It contains the names
+ * of the cache files for RemoveViaImap address books plus other caching
+ * information for those address books (uid...).
+ */
+
+#define NFIELDS 11 /* one more than num of data fields in addrbook entry */
+
+/*
+ * The type a_c_arg_t is a little confusing. It's the type we use in
+ * place of adrbk_cntr_t when we want to pass an adrbk_cntr_t in an argument.
+ * We were running into problems with the integral promotion of adrbk_cntr_t
+ * args. A_c_arg_t has to be large enough to hold a promoted adrbk_cntr_t.
+ * So, if adrbk_cntr_t is unsigned short, then a_c_arg_t needs to be int if
+ * int is larger than short, or unsigned int if short is same size as int.
+ * Since usign16_t always fits in a short, a_c_arg_t of unsigned int should
+ * always work for !HUGE. For HUGE, UINT32 will be either an unsigned int
+ * or an unsigned long. If it is an unsigned long, then a_c_arg_t better be
+ * an unsigned long, too. If it is an unsigned int, then a_c_arg_t could
+ * be an unsigned int, too. However, if we just make it unsigned long, then
+ * it will be the same in all cases and big enough in all cases.
+ */
+
+#define adrbk_cntr_t UINT32 /* addrbook counter type */
+typedef unsigned long a_c_arg_t; /* type of arg passed for adrbk_cntr_t */
+#define NO_NEXT ((adrbk_cntr_t)-1)
+#define MAX_ADRBK_SIZE (2000000000L) /* leave room for extra display lines */
+
+/*
+ * The value NO_NEXT is reserved to mean that there is no next address, or that
+ * there is no address number to return. This is similar to getc returning
+ * -1 when there is no char to get, but since we've defined this to be
+ * unsigned we need to reserve one of the valid values for this purpose.
+ * With current implementation it needs to be all 1's, so memset initialization
+ * will work correctly.
+ */
+
+typedef enum {NotSet, Single, List} Tag;
+
+
+/* This is what is actually used by the routines that manipulate things */
+typedef struct adrbk_entry {
+ char *nickname; /* UTF-8 */
+ char *fullname; /* of simple addr or list (stored in UTF-8) */
+ union addr {
+ char *addr; /* for simple Single entries */
+ char **list; /* for distribution lists */
+ } addr;
+ char *fcc; /* fcc specific for when sending to this address */
+ char *extra; /* comments field (stored in UTF-8) */
+ char referenced; /* for detecting loops during lookup */
+ Tag tag; /* single addr (Single) or a list (List) */
+} AdrBk_Entry;
+
+
+typedef struct abook_tree_node {
+ struct abook_tree_node *down; /* next letter of nickname */
+ struct abook_tree_node *right; /* alternate letter of nickname */
+ char value; /* character at this node */
+ adrbk_cntr_t entrynum; /* use NO_NEXT as no-data indicator */
+} AdrBk_Trie;
+
+
+/* information useful for displaying the addrbook */
+typedef struct width_stuff {
+ int max_nickname_width;
+ int max_fullname_width;
+ int max_addrfield_width;
+ int max_fccfield_width;
+ int third_biggest_fullname_width;
+ int third_biggest_addrfield_width;
+ int third_biggest_fccfield_width;
+} WIDTH_INFO_S;
+
+
+typedef struct expanded_list {
+ adrbk_cntr_t ent;
+ struct expanded_list *next;
+} EXPANDED_S;
+
+
+typedef enum {Local, Imap} AdrbkType;
+
+
+#ifndef IMAP_IDLE_TIMEOUT
+#define IMAP_IDLE_TIMEOUT (10L * 60L) /* seconds */
+#endif
+#ifndef FILE_VALID_CHK_INTERVAL
+#define FILE_VALID_CHK_INTERVAL ( 15L) /* seconds */
+#endif
+#ifndef LOW_FREQ_CHK_INTERVAL
+#define LOW_FREQ_CHK_INTERVAL (240) /* minutes */
+#endif
+
+typedef struct adrbk {
+ AdrbkType type; /* type of address book */
+ char *orig_filename; /* passed in filename */
+ char *filename; /* addrbook filename */
+ char *our_filecopy; /* session copy of filename contents */
+ FILE *fp; /* fp for our_filecopy */
+ adrbk_cntr_t count; /* how many entries in addrbook */
+ adrbk_cntr_t del_count; /* how many #DELETED entries in abook */
+ AdrBk_Entry *arr; /* array of entries */
+ AdrBk_Entry *del; /* array of deleted entries */
+ AdrBk_Trie *nick_trie;
+ AdrBk_Trie *addr_trie;
+ AdrBk_Trie *full_trie;
+ AdrBk_Trie *revfull_trie;
+ time_t last_change_we_know_about;/* to look for others changing it*/
+ time_t last_local_valid_chk;/* when valid check was done */
+ unsigned flags; /* see defines in alpine.h (DEL_FILE...)*/
+ WIDTH_INFO_S widths; /* helps addrbook.c format columns */
+ int sort_rule;
+ EXPANDED_S *exp; /* this is for addrbook.c to use. A
+ list of expanded list entry nums is kept here */
+ EXPANDED_S *checks; /* this is for addrbook.c to use. A
+ list of checked entry nums is kept here */
+ EXPANDED_S *selects; /* this is for addrbook.c to use. A
+ list of selected entry nums is kept here */
+ REMDATA_S *rd;
+} AdrBk;
+
+
+/*
+ * The definitions starting here have to do with the virtual scrolling
+ * region view into the addressbooks. That is, the display.
+ *
+ * We could make every use of an AdrBk_Entry go through a function call
+ * like adrbk_get_ae(). Instead, we try to be smart and avoid the extra
+ * function calls by knowing when the addrbook entry is still valid, either
+ * because we haven't called any functions that could invalidate it or because
+ * we have locked it in the cache. If we do lock it, we need to be careful
+ * that it eventually gets unlocked. That can be done by an explicit
+ * adrbk_get_ae(Unlock) call, or it is done implicitly when the address book
+ * is written out. The reason it can get invalidated is that the abe that
+ * we get returned to us is just a pointer to a cached addrbook entry, and
+ * that entry can be flushed from the cache by other addrbook activity.
+ * So we need to be careful to make sure the abe is certain to be valid
+ * before using it.
+ *
+ * Data structures for the display of the address book. There's one
+ * of these structures per line on the screen.
+ *
+ * Types: Title -- The title line for the different address books. It has
+ * a ptr to the text of the Title line.
+ * ClickHereCmb -- This is the line that says to click here to
+ * expand. It changes types into the individual expanded
+ * components once it is expanded. It doesn't have any data
+ * other than an implicit title. This is only used with the
+ * combined-style addrbook display.
+ * ListClickHere --This is the line that says to click here to
+ * expand the members of a distribution list. It changes
+ * types into the individual expanded ListEnt's (if any)
+ * when it is expanded. It has a ptr to an AdrBk_Entry.
+ * ListEmpty -- Line that says this is an empty distribution list. No data.
+ * Empty -- Line that says this is an empty addressbook. No data.
+ * ZoomEmpty -- Line that says no addrs in zoomed view. No data.
+ * AddFirstGLob -- Place holder for adding an abook. No data.
+ * AddFirstPers -- Place holder for adding an abook. No data.
+ * DirServers -- Place holder for accessing directory servers. No data.
+ * Simple -- A single addressbook entry. It has a ptr to an AdrBk_Entry.
+ * When it is displayed, the fields are usually:
+ * <nickname> <fullname> <address or another nic>
+ * ListHead -- The head of an address list. This has a ptr to an
+ * AdrBk_Entry.
+ * <blank line> followed by
+ * <nickname> <fullname> "DISTRIBUTION LIST:"
+ * ListEnt -- The rest of an address list. It has a pointer to its
+ * ListHead element and a ptr (other) to this specific address
+ * (not a ptr to another AdrBk_Entry).
+ * <blank> <blank> <address or another nic>
+ * Text -- A ptr to text. For example, the ----- lines and
+ * whitespace lines.
+ * NoAbooks -- There are no address books at all.
+ * Beginnning -- The (imaginary) elements before the first real element
+ * End -- The (imaginary) elements after the last real element
+ */
+typedef enum {DlNotSet, Empty, ZoomEmpty, AddFirstPers, AddFirstGlob,
+ AskServer, Title, Simple, ListHead, ListClickHere,
+ ListEmpty, ListEnt, Text, Beginning, End, NoAbooks,
+ ClickHereCmb, TitleCmb} LineType;
+/* each line of address book display is one of these structures */
+typedef struct addrscrn_disp {
+ union {
+ struct {
+ adrbk_cntr_t ab_element_number; /* which addrbook entry */
+ adrbk_cntr_t ab_list_offset; /* which member of the list */
+ }addrbook_entry;
+ char *text_ptr; /* a UTF-8 string */
+ }union_to_save_space;
+ LineType type;
+} AddrScrn_Disp;
+#define usst union_to_save_space.text_ptr
+#define elnum union_to_save_space.addrbook_entry.ab_element_number
+#define l_offset union_to_save_space.addrbook_entry.ab_list_offset
+
+#define entry_is_checked exp_is_expanded
+#define entry_get_next exp_get_next
+#define entry_set_checked exp_set_expanded
+#define entry_unset_checked exp_unset_expanded
+#define any_checked exp_any_expanded
+#define howmany_checked exp_howmany_expanded
+
+#define entry_is_selected exp_is_expanded
+#define entry_set_selected exp_set_expanded
+#define entry_unset_selected exp_unset_expanded
+#define any_selected exp_any_expanded
+#define howmany_selected exp_howmany_expanded
+
+#define entry_is_deleted exp_is_expanded
+#define entry_set_deleted exp_set_expanded
+#define howmany_deleted exp_howmany_expanded
+
+#define entry_is_added exp_is_expanded
+#define entry_set_added exp_set_expanded
+
+/*
+ * Argument to expand_address and build_address_internal is a BuildTo,
+ * which is either a char * address or an AdrBk_Entry * (if we have already
+ * looked it up in an addrbook).
+ */
+typedef enum {Str, Abe} Build_To_Arg_Type;
+typedef struct build_to {
+ Build_To_Arg_Type type;
+ union {
+ char *str; /* normal looking address string */
+ AdrBk_Entry *abe; /* addrbook entry */
+ }arg;
+} BuildTo;
+
+
+/* Display lines used up by each top-level addrbook, counting blanks */
+#define LINES_PER_ABOOK (3)
+/* How many of those lines are visible (not blank) */
+#define VIS_LINES_PER_ABOOK (2)
+/* How many extra lines are between the personal and global sections */
+#define XTRA_LINES_BETWEEN (1)
+/* How many lines does the PerAdd line take, counting blank line */
+#define LINES_PER_ADD_LINE (2)
+/* Extra title lines above first entry that are shown when the combined-style
+ display is turned on. */
+#define XTRA_TITLE_LINES_IN_OLD_ABOOK_DISP (4)
+
+typedef enum {DlcNotSet,
+ DlcPersAdd, /* config screen only */
+ DlcGlobAdd, /* " " " */
+
+ DlcTitle, /* top level displays */
+ DlcTitleNoPerm, /* " " " */
+ DlcSubTitle, /* " " " */
+ DlcTitleBlankTop, /* " " " */
+ DlcGlobDelim1, /* " " " */
+ DlcGlobDelim2, /* " " " */
+ DlcDirDelim1, /* " " " */
+ DlcDirDelim2, /* " " " */
+ DlcDirAccess, /* " " " */
+ DlcDirSubTitle, /* " " " */
+ DlcDirBlankTop, /* " " " */
+
+ DlcTitleDashTopCmb, /* combined-style top level display */
+ DlcTitleCmb, /* " " " " " */
+ DlcTitleDashBottomCmb, /* " " " " " */
+ DlcTitleBlankBottomCmb, /* " " " " " */
+ DlcClickHereCmb, /* " " " " " */
+ DlcTitleBlankTopCmb, /* " " " " " */
+ DlcDirDelim1a, /* " " " " " */
+ DlcDirDelim1b, /* " " " " " */
+ DlcDirDelim1c, /* " " " " " */
+
+ DlcEmpty, /* display of a single address book */
+ DlcZoomEmpty, /* " */
+ DlcNoPermission, /* " */
+ DlcSimple, /* " */
+ DlcListHead, /* " */
+ DlcListClickHere, /* " */
+ DlcListEmpty, /* " */
+ DlcListEnt, /* " */
+ DlcListBlankTop, /* " */
+ DlcListBlankBottom, /* " */
+ DlcNoAbooks, /* " */
+
+ DlcOneBeforeBeginning, /* used in both */
+ DlcTwoBeforeBeginning, /* " " " */
+ DlcBeginning, /* " " " */
+ DlcEnd} DlCacheType;
+
+typedef enum {Initialize, FirstEntry, LastEntry, ArbitraryStartingPoint,
+ DoneWithCache, FlushDlcFromCache, Lookup} DlMgrOps;
+typedef enum {Warp, DontWarp} HyperType;
+
+/*
+ * The DlCacheTypes are the types that a dlcache element can be labeled.
+ * The idea is that there needs to be enough information in the single
+ * cache element by itself so that you can figure out what the next and
+ * previous dl rows are by just knowing this one row.
+ *
+ * In the top-level display, there are DlcTitle lines or DlcTitleNoPerm
+ * lines, which are the same except we know that we can't access the
+ * address book in the latter case. DlcSubTitle lines follow each of the
+ * types of Title lines, and Titles within a section are separated by
+ * DlcTitleBlankTop lines, which belong to (have the same adrbk_num as)
+ * the Title they are above.
+ * If there are no address books and no directory servers defined, we
+ * have a DlcNoAbooks line. When we are displaying an individual address
+ * book (not in the top-level display) there is another set of types. An
+ * empty address book consists of one line of type DlcEmpty. An address
+ * book without read permission is a DlcNoPermission. Simple address book
+ * entries consist of a single DlcSimple line. Lists begin with a
+ * DlcListHead. If the list is not expanded the DlcListHead is followed by
+ * a DlcListClickHere. If it is known to be a list with no members the
+ * DlcListHead is followed by a DlcListEmpty. If there are members and
+ * the list is expanded, each list member is a single line of type
+ * DlcListEnt. Two lists are separated by a DlcListBlankBottom belonging
+ * to the first list. A list followed or preceded by a DlcSimple address
+ * row has a DlcListBlank(Top or Bottom) separating it from the
+ * DlcSimple. Above the top row of the display is an imaginary line of
+ * type DlcOneBeforeBeginning. Before that is a DlcTwoBeforeBeginning. And
+ * before that all the lines are just DlcBeginning lines. After the last
+ * display line is a DlcEnd.
+ *
+ * The DlcDirAccess's are indexed by adrbk_num (re-used for this).
+ * Adrbk_num -1 means access all of the servers.
+ * Adrbk_num 0 ... n_serv -1 means access all a particular server.
+ * Adrbk_num n_serv means access as if from composer using config setup.
+ *
+ * Here are the types of lines and where they fall in the top-level display:
+ *
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcTwoBeforeBeginning
+ * (not a visible line) DlcOneBeforeBeginning
+ * Title DlcTitle (or TitleNoPerm)
+ * Subtitle DlcSubTitle
+ * ---this is blank---------------- DlcTitleBlankTop
+ * Title DlcTitle (or TitleNoPerm)
+ * Subtitle DlcSubTitle
+ * ---this is blank---------------- DlcGlobDelim1
+ * ---this is blank---------------- DlcGlobDelim2
+ * Title DlcTitle (or TitleNoPerm)
+ * Subtitle DlcSubTitle
+ * ---this is blank---------------- DlcTitleBlankTop
+ * Title DlcTitle (or TitleNoPerm)
+ * Subtitle DlcSubTitle
+ * ---this is blank---------------- DlcDirDelim1
+ * ---this is blank---------------- DlcDirDelim2
+ * Directory (query server 1) DlcDirAccess (adrbk_num 0)
+ * Subtitle DlcDirSubTitle (adrbk_num 0)
+ * ---this is blank---------------- DlcDirBlankTop
+ * Directory (query server 2) DlcDirAccess (adrbk_num 1)
+ * Subtitle DlcDirSubTitle (adrbk_num 1)
+ * (not a visible line) DlcEnd
+ * (not a visible line) DlcEnd
+ *
+ *
+ * There is a combined-style display triggered by the F_CMBND_ABOOK_DISP
+ * feature. It's a mixture of the top-level and open addrbook displays. When an
+ * addrbook is opened the rest of the addrbooks don't disappear from the
+ * screen. In this view, the ClickHere lines can be replaced with the entire
+ * contents of the addrbook, but the other stuff remains on the screen, too.
+ * Here are the types of lines and where they fall in the
+ * combined-style display:
+ *
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcTwoBeforeBeginning
+ * (not a visible line) DlcOneBeforeBeginning
+ * -------------------------------- DlcTitleDashTopOld
+ * Title DlcTitleOld
+ * -------------------------------- DlcTitleDashBottomOld
+ * ---this is blank---------------- DlcTitleBlankBottom
+ * ClickHere DlcClickHereOld
+ * ---this is blank---------------- DlcTitleBlankTop
+ * -------------------------------- DlcTitleDashTopOld
+ * Title DlcTitleOld
+ * -------------------------------- DlcTitleDashBottomOld
+ * ---this is blank---------------- DlcTitleBlankBottom
+ * ClickHere DlcClickHereOld
+ * ---this is blank---------------- DlcDirDelim1
+ * -------------------------------- DlcDirDelim1a
+ * Directories DlcDirDelim1b
+ * -------------------------------- DlcDirDelim1c
+ * ---this is blank---------------- DlcDirDelim2
+ * Directory (query server 1) DlcDirAccess (adrbk_num 0)
+ * Subtitle DlcDirSubTitle (adrbk_num 0)
+ * ---this is blank---------------- DlcDirBlankTop
+ * Directory (query server 2) DlcDirAccess (adrbk_num 1)
+ * Subtitle DlcDirSubTitle (adrbk_num 1)
+ * (not a visible line) DlcEnd
+ * (not a visible line) DlcEnd
+ *
+ * If there are no addrbooks in either of the two sections, or no Directory
+ * servers, then that section is left out of the display. If there is only
+ * one address book and no Directories, then the user goes directly into the
+ * single addressbook view which looks like:
+ *
+ * if(no entries in addrbook)
+ *
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcTwoBeforeBeginning
+ * (not a visible line) DlcOneBeforeBeginning
+ * Empty or NoPerm or NoAbooks DlcEmpty, DlcZoomEmpty, DlcNoPermission,
+ * or DlcNoAbooks
+ * (not a visible line) DlcEnd
+ * (not a visible line) DlcEnd
+ *
+ * else
+ *
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcBeginning
+ * (not a visible line) DlcTwoBeforeBeginning
+ * (not a visible line) DlcOneBeforeBeginning
+ * Simple Entry DlcSimple
+ * Simple Entry DlcSimple
+ * Simple Entry DlcSimple
+ * DlcListBlankTop
+ * List Header DlcListHead
+ * Unexpanded List DlcListClickHere
+ * or
+ * Empty List DlcListEmpty
+ * or
+ * List Entry 1 DlcListEnt
+ * List Entry 2 DlcListEnt
+ * DlcListBlankBottom
+ * List Header DlcListHead
+ * List Entry 1 DlcListEnt
+ * List Entry 2 DlcListEnt
+ * List Entry 3 DlcListEnt
+ * DlcListBlankBottom
+ * Simple Entry DlcSimple
+ * DlcListBlankTop
+ * List Header DlcListHead
+ * Unexpanded List DlcListClickHere
+ * (not a visible line) DlcEnd
+ * (not a visible line) DlcEnd
+ *
+ * The config screen view is similar to the top-level view except there
+ * is no directory section (it has it's own config screen) and if there
+ * are zero personal addrbooks or zero global addrbooks then a placeholder
+ * line of type DlcPersAdd or DlcGlobAdd takes the place of the DlcTitle
+ * line.
+ */
+typedef struct dl_cache {
+ long global_row; /* disp_list row number */
+ adrbk_cntr_t dlcelnum; /* which elnum from that addrbook */
+ adrbk_cntr_t dlcoffset; /* offset in a list, only for ListEnt rows */
+ short adrbk_num; /* which address book we're related to */
+ DlCacheType type; /* type of this row */
+ AddrScrn_Disp dl; /* the actual dl that goes with this row */
+} DL_CACHE_S;
+
+
+typedef enum {Nickname, Fullname, Addr, Filecopy, Comment, Notused,
+ Def, WhenNoAddrDisplayed, Checkbox, Selected} ColumnType;
+
+/*
+ * Users can customize the addrbook display, so this tells us which data
+ * is in a particular column and how wide the column is. There is an
+ * array of these per addrbook, of length NFIELDS (number of possible cols).
+ */
+typedef struct column_description {
+ ColumnType type;
+ WidthType wtype;
+ int req_width; /* requested width (for fixed and percent types) */
+ int width; /* actual width to use */
+ int old_width;
+} COL_S;
+
+
+/* address book attributes for peraddrbook type */
+#define GLOBAL 0x1 /* else it is personal */
+#define REMOTE_VIA_IMAP 0x2 /* else it is a local file */
+
+
+typedef enum {TotallyClosed, /* hash tables not even set up yet */
+ Closed, /* data not read in, no display list */
+ NoDisplay, /* data is accessible, no display list */
+ HalfOpen, /* data not accessible, initial display list is set */
+ ThreeQuartOpen, /* like HalfOpen without partial_close */
+ Open /* data is accessible and display list is set */
+ } OpenStatus;
+
+/*
+ * There is one of these per addressbook.
+ */
+typedef struct peraddrbook {
+ unsigned type;
+ AccessType access;
+ OpenStatus ostatus;
+ char *abnick, /* kept internally in UTF-8 */
+ *filename;
+ AdrBk *address_book; /* the address book handle */
+ int gave_parse_warnings;
+ COL_S disp_form[NFIELDS]; /* display format */
+ int nick_is_displayed; /* these are for convenient, */
+ int full_is_displayed; /* fast access. Could get */
+ int addr_is_displayed; /* same info from disp_form. */
+ int fcc_is_displayed;
+ int comment_is_displayed;
+ STORE_S *so; /* storage obj for addrbook
+ temporarily stored here */
+} PerAddrBook;
+
+
+/*
+ * This keeps track of the state of the screen and information about all
+ * the address books. We usually only have one of these but sometimes
+ * we save a version of this state (with save_state) and re-call the
+ * address book functions. Then when we pop back up to where we were
+ * (with restore_state) the screen and the state of the address books
+ * is restored to what it was.
+ */
+typedef struct addrscreenstate {
+ PerAddrBook *adrbks; /* array of addrbooks */
+ int initialized, /* have we done at least simple init? */
+ n_addrbk, /* how many addrbooks are there */
+ how_many_personals, /* how many of those are personal? */
+ cur, /* current addrbook */
+ cur_row, /* currently selected line */
+ old_cur_row, /* previously selected line */
+ l_p_page; /* lines per (screen) page */
+ long top_ent; /* index in disp_list of top entry on screen */
+ int ro_warning, /* whether or not to give warning */
+ checkboxes, /* whether or not to display checkboxes */
+ selections, /* whether or not to display selections */
+ do_bold, /* display selections in bold */
+ no_op_possbl, /* user can't do anything with current conf */
+ zoomed, /* zoomed into view only selected entries */
+ config, /* called from config screen */
+ n_serv, /* how many directory servers are there */
+ n_impl; /* how many of those have impl bit set */
+#ifdef _WINDOWS
+ long last_ent; /* index of last known entry */
+#endif
+} AddrScrState;
+
+
+/*
+ * AddrBookScreen and AddrBookConfig are the maintenance screens, all the
+ * others are selection screens. The AddrBookConfig screen is an entry
+ * point from the Setup/Addressbooks command in the main menu. Those that
+ * end in Com are called from the pico HeaderEditor, either while in the
+ * composer or while editing an address book entry. SelectManyNicks
+ * returns a comma-separated list of nicknames. SelectAddrLccCom and
+ * SelectNicksCom return a comma-separated list of nicknames.
+ * SelectNickTake, SelectNickCom, and SelectNick all return a single
+ * nickname. The ones that returns multiple nicknames or multiple
+ * addresses all allow ListMode. They are SelectAddrLccCom,
+ * SelectNicksCom, and SelectMultNoFull.
+ */
+typedef enum {AddrBookScreen, /* maintenance screen */
+ AddrBookConfig, /* config screen */
+ SelectAddrLccCom, /* returns list of nicknames of lists */
+ SelectNicksCom, /* just like SelectAddrLccCom, but allows
+ selecting simple *and* list entries */
+ SelectNick, /* returns single nickname */
+ SelectNickTake, /* Same as SelectNick but different help */
+ SelectNickCom, /* Same as SelectNick but from composer */
+ SelectManyNicks, /* Returns list of nicks */
+ SelectAddr, /* Returns single address */
+ SelectAddrNoFull, /* Returns single address without fullname */
+ SelectMultNoFull /* Returns mult addresses without fullname */
+ } AddrBookArg;
+
+
+typedef struct save_state_struct {
+ AddrScrState *savep;
+ OpenStatus *stp;
+ DL_CACHE_S *dlc_to_warp_to;
+} SAVE_STATE_S;
+
+
+typedef struct act_list {
+ PerAddrBook *pab;
+ adrbk_cntr_t num,
+ num_in_dst;
+ unsigned int skip:1,
+ dup:1;
+} ACTION_LIST_S;
+
+
+typedef struct ta_abook_state {
+ PerAddrBook *pab;
+ SAVE_STATE_S state;
+} TA_STATE_S;
+
+
+/*
+ * Many of these should really only have a single value but we give them
+ * an array for uniformity.
+ */
+typedef struct _vcard_info {
+ char **nickname;
+ char **fullname;
+ char *first;
+ char *middle;
+ char *last;
+ char **fcc;
+ char **note;
+ char **title;
+ char **tel;
+ char **email;
+} VCARD_INFO_S;
+
+
+extern AddrScrState as;
+extern jmp_buf addrbook_changed_unexpectedly;
+extern long msgno_for_pico_callback;
+extern BODY *body_for_pico_callback;
+extern ENVELOPE *env_for_pico_callback;
+extern int ab_nesting_level;
+
+
+/*
+ * These constants are supposed to be suitable for use as longs where the longs
+ * are representing a line number or message number.
+ * These constants aren't suitable for use with type adrbk_cntr_t. There is
+ * a constant called NO_NEXT which you probably want for that.
+ */
+#define NO_LINE (2147483645L)
+#define CHANGED_CURRENT (NO_LINE + 1L)
+
+
+/*
+ * The do-while stuff is so these are statements and can be written with
+ * a following ; like a regular statement without worrying about braces and all.
+ */
+#define SKIP_SPACE(p) do{while(*p && *p == SPACE)p++;}while(0)
+#define SKIP_TO_TAB(p) do{while(*p && *p != TAB)p++;}while(0)
+#define RM_END_SPACE(start,end) \
+ do{char *_ptr = end; \
+ while(--_ptr >= start && *_ptr == SPACE)*_ptr = '\0';}while(0)
+#define REPLACE_NEWLINES_WITH_SPACE(p) \
+ do{register char *_qq; \
+ for(_qq = p; *_qq; _qq++) \
+ if(*_qq == '\n' || *_qq == '\r') \
+ *_qq = SPACE;}while(0)
+#define DELETED "#DELETED-"
+#define DELETED_LEN 9
+
+
+#define ONE_HUNDRED_DAYS (60L * 60L * 24L * 100L)
+
+/*
+ * When address book entries are deleted, they are left in the file
+ * with the nickname prepended with a string like #DELETED-96/01/25#,
+ * which stands for year 96, month 1, day 25 of the month. When one of
+ * these entries is more than ABOOK_DELETED_EXPIRE_TIME seconds old,
+ * then it will be totally removed from the address book the next time
+ * an adrbk_write() is done. This is for emergencies where somebody
+ * deletes something from their address book and would like to get it
+ * back. You get it back by editing the nickname field manually to remove
+ * the extra 18 characters off the front.
+ */
+#ifndef ABOOK_DELETED_EXPIRE_TIME
+#define ABOOK_DELETED_EXPIRE_TIME ONE_HUNDRED_DAYS
+#endif
+
+
+#ifdef ENABLE_LDAP
+typedef struct _cust_filt {
+ char *filt;
+ int combine;
+} CUSTOM_FILT_S;
+
+#define RUN_LDAP "LDAP: "
+#define LEN_RL 6
+#define QRUN_LDAP "\"LDAP: "
+#define LEN_QRL 7
+#define LDAP_DISP "[ LDAP Lookup ]"
+#endif
+
+
+/*
+ * There are no restrictions on the length of any of the fields, except that
+ * there are some restrictions in the current input routines.
+ */
+
+/*
+ * The on-disk address book has entries that look like:
+ *
+ * Nickname TAB Fullname TAB Address_Field TAB Fcc TAB Comment
+ *
+ * An entry may be broken over more than one line but only at certain
+ * spots. A continuation line starts with spaces (spaces, not white space).
+ * One place a line break can occur is after any of the TABs. The other
+ * place is in the middle of a list of addresses, between addresses.
+ * The Address_Field may be either a simple address without the fullname
+ * or brackets, or it may be an address list. An address list is
+ * distinguished by the fact that it begins with "(" and ends with ")".
+ * Addresses within a list are comma separated and each address in the list
+ * may be a full rfc822 address, including Fullname and so on.
+ *
+ * Examples:
+ * fred TAB Flintstone, Fred TAB fred@bedrock.net TAB fcc-flintstone TAB comment
+ * or
+ * fred TAB Flintstone, Fred TAB \n
+ * fred@bedrock.net TAB fcc-flintstone TAB \n
+ * comment
+ * somelist TAB Some List TAB (fred, \n
+ * Barney Rubble <barney@bedrock.net>, wilma@bedrock.net) TAB \n
+ * fcc-for-some-list TAB comment
+ */
+
+
+/* exported prototypes */
+AdrBk *adrbk_open(PerAddrBook *, char *, char *, size_t, int);
+int adrbk_is_in_sort_order(AdrBk *, int);
+adrbk_cntr_t adrbk_count(AdrBk *);
+AdrBk_Entry *adrbk_get_ae(AdrBk *, a_c_arg_t);
+AdrBk_Entry *adrbk_lookup_by_nick(AdrBk *, char *, adrbk_cntr_t *);
+AdrBk_Entry *adrbk_lookup_by_addr(AdrBk *, char *, adrbk_cntr_t *);
+char *adrbk_formatname(char *, char **, char **);
+void adrbk_clearrefs(AdrBk *);
+AdrBk_Entry *adrbk_newentry(void);
+AdrBk_Entry *copy_ae(AdrBk_Entry *);
+int adrbk_add(AdrBk *, a_c_arg_t, char *, char *, char *, char *,
+ char *, Tag, adrbk_cntr_t *, int *, int, int, int);
+int adrbk_append(AdrBk *, char *, char *, char *,
+ char *, char *, Tag, adrbk_cntr_t *);
+int adrbk_delete(AdrBk *, a_c_arg_t, int, int, int, int);
+int adrbk_listdel(AdrBk *, a_c_arg_t, char *);
+int adrbk_listdel_all(AdrBk *, a_c_arg_t);
+int adrbk_nlistadd(AdrBk *, a_c_arg_t,adrbk_cntr_t *,int *,char **,int,int,int);
+void adrbk_check_validity(AdrBk *, long);
+MAILSTREAM *adrbk_handy_stream(char *);
+void adrbk_close(AdrBk *);
+void adrbk_partial_close(AdrBk *);
+void note_closed_adrbk_stream(MAILSTREAM *);
+int adrbk_write(AdrBk *, a_c_arg_t, adrbk_cntr_t *, int *, int, int);
+void free_ae(AdrBk_Entry **);
+void exp_free(EXPANDED_S *);
+int exp_is_expanded(EXPANDED_S *, a_c_arg_t);
+int exp_howmany_expanded(EXPANDED_S *);
+int exp_any_expanded(EXPANDED_S *);
+adrbk_cntr_t exp_get_next(EXPANDED_S **);
+void exp_set_expanded(EXPANDED_S *, a_c_arg_t);
+void exp_unset_expanded(EXPANDED_S *, a_c_arg_t);
+int adrbk_sort(AdrBk *, a_c_arg_t, adrbk_cntr_t *, int);
+int any_ab_open(void);
+void init_ab_if_needed(void);
+int init_addrbooks(OpenStatus, int, int, int);
+void addrbook_reset(void);
+void addrbook_redo_sorts(void);
+AccessType adrbk_access(PerAddrBook *);
+void trim_remote_adrbks(void);
+void completely_done_with_adrbks(void);
+void init_abook(PerAddrBook *, OpenStatus);
+int adrbk_check_all_validity_now(void);
+int adrbk_check_and_fix(PerAddrBook *, int, int, int);
+int adrbk_check_and_fix_all(int, int, int);
+void adrbk_maintenance(void);
+char **parse_addrlist(char *);
+char *skip_to_next_addr(char *);
+void add_forced_entries(AdrBk *);
+
+
+#endif /* PITH_ADRBKLIB_INCLUDED */
diff --git a/pith/atttype.h b/pith/atttype.h
new file mode 100644
index 00000000..081aa7a9
--- /dev/null
+++ b/pith/atttype.h
@@ -0,0 +1,46 @@
+/*
+ * $Id: atttype.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ATTTYPE_INCLUDED
+#define PITH_ATTTYPE_INCLUDED
+
+
+typedef struct attachment {
+ char *description;
+ BODY *body;
+ unsigned test_deferred:1;
+ unsigned can_display:4;
+ unsigned shown:1;
+ unsigned suppress_editorial:1;
+ char *number;
+ char size[25];
+} ATTACH_S;
+
+
+/*
+ * struct to help peruse a, possibly fragmented ala RFC 2231, parm list
+ */
+typedef struct parmlist {
+ PARAMETER *list,
+ *seen;
+ char attrib[32],
+ *value;
+} PARMLIST_S;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_ATTTYPE_INCLUDED */
diff --git a/pith/bitmap.h b/pith/bitmap.h
new file mode 100644
index 00000000..d79e160e
--- /dev/null
+++ b/pith/bitmap.h
@@ -0,0 +1,39 @@
+/*
+ * $Id: bitmap.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_BITMAP_INCLUDED
+#define PITH_BITMAP_INCLUDED
+
+
+#include "../pith/conftype.h"
+
+
+/*
+ * Max size of a bitmap based on largest customer: feature list count
+ * Problems if a screen's keymenu bitmap ever gets wider than feature list.
+ */
+#define BM_SIZE ((F_FEATURE_LIST_COUNT / 8) \
+ + ((F_FEATURE_LIST_COUNT % 8) ? 1 : 0))
+
+typedef unsigned char bitmap_t[BM_SIZE];
+
+/* clear entire bitmap */
+#define clrbitmap(map) memset((void *)(map), 0, (size_t)BM_SIZE)
+
+/* set entire bitmap */
+#define setbitmap(map) memset((void *)(map), 0xff, (size_t)BM_SIZE)
+
+
+#endif /* PITH_BITMAP_INCLUDED */
diff --git a/pith/bldaddr.c b/pith/bldaddr.c
new file mode 100644
index 00000000..9feeae38
--- /dev/null
+++ b/pith/bldaddr.c
@@ -0,0 +1,1263 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: bldaddr.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ buildaddr.c
+ Support for build_address function and low-level display cache.
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/bldaddr.h"
+#include "../pith/ldap.h"
+#include "../pith/conf.h"
+#include "../pith/adrbklib.h"
+#include "../pith/copyaddr.h"
+#include "../pith/remote.h"
+#include "../pith/status.h"
+#include "../pith/search.h"
+#include "../pith/addrstring.h"
+#include "../pith/util.h"
+#include "../pith/ablookup.h"
+#include "../pith/news.h"
+#include "../pith/stream.h"
+#include "../pith/mailcmd.h"
+#include "../pith/osdep/pw_stuff.h"
+
+
+/*
+ * Jump back to this location if we discover that one of the open addrbooks
+ * has been changed by some other process.
+ */
+jmp_buf addrbook_changed_unexpectedly;
+
+
+/*
+ * Sometimes the address book code calls something outside of the address
+ * book and that calls back to the address book. For example, you could be
+ * in the address book mgmt screen, call ab_compose, and then a ^T could
+ * call back to an address book select screen. Or moving out of a line
+ * calls back to build_address. This keeps track of the nesting level so
+ * that we can know when it is safe to update an out of date address book.
+ * For example, if we know an address book is open and displayed, it isn't
+ * safe to update it because that would pull the cache out from under the
+ * displayed lines. If we don't have it displayed, then it is ok to close
+ * it and re-open it (adrbk_check_and_fix()).
+ */
+int ab_nesting_level = 0;
+
+
+
+/*
+ * This is like build_address() only it doesn't close
+ * everything down when it is done, and it doesn't open addrbooks that
+ * are already open. Other than that, it has the same functionality.
+ * It opens addrbooks that haven't been opened and saves and restores the
+ * addrbooks open states (if save_and_restore is set).
+ *
+ * Args: to -- the address to attempt expanding (see the
+ * description in expand_address)
+ * full_to -- a pointer to result
+ * This will be allocated here and freed by the caller.
+ * error -- a pointer to an error message, if non-null
+ * fcc -- a pointer to returned fcc, if non-null
+ * This will be allocated here and freed by the caller.
+ * *fcc should be null on entry.
+ * save_and_restore -- restore addrbook states when finished
+ *
+ * Results: 0 -- address is ok
+ * -1 -- address is not ok
+ * full_to contains the expanded address on success, or a copy of to
+ * on failure
+ * *error will point to an error message on failure it it is non-null
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+our_build_address(BuildTo to, char **full_to, char **error, char **fcc,
+ void (*save_and_restore_f)(int, SAVE_STATE_S *))
+{
+ int ret;
+
+ dprint((7, "- our_build_address - (%s)\n",
+ (to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
+ : (to.arg.abe->nickname ? to.arg.abe->nickname
+ : "no nick")));
+
+ if((to.type == Str && !to.arg.str) || (to.type == Abe && !to.arg.abe)){
+ if(full_to)
+ *full_to = cpystr("");
+ ret = 0;
+ }
+ else
+ ret = build_address_internal(to, full_to, error, fcc, NULL, NULL,
+ save_and_restore_f, 0, NULL);
+
+ dprint((8, " our_build_address says %s address\n",
+ ret ? "BAD" : "GOOD"));
+
+ return(ret);
+}
+
+
+
+#define FCC_SET 1 /* Fcc was set */
+#define LCC_SET 2 /* Lcc was set */
+#define FCC_NOREPO 4 /* Fcc was set in a non-reproducible way */
+#define LCC_NOREPO 8 /* Lcc was set in a non-reproducible way */
+/*
+ * Given an address, expand it based on address books, local domain, etc.
+ * This will open addrbooks if needed before checking (actually one of
+ * its children will open them).
+ *
+ * Args: to -- The given address to expand (see the description
+ * in expand_address)
+ * full_to -- Returned value after parsing to.
+ * error -- This gets pointed at error message, if any
+ * fcc -- Returned value of fcc for first addr in to
+ * no_repo -- Returned value, set to 1 if the fcc or lcc we're
+ * returning is not reproducible from the expanded
+ * address. That is, if we were to run
+ * build_address_internal again on the resulting full_to,
+ * we wouldn't get back the fcc again. For example,
+ * if we expand a list and use the list fcc from the
+ * addrbook, the full_to no longer contains the
+ * information that this was originally list foo.
+ * save_and_restore -- restore addrbook state when done
+ *
+ * Result: 0 is returned if address was OK,
+ * -1 if address wasn't OK.
+ * The address is expanded, fully-qualified, and personal name added.
+ *
+ * Input may have more than one address separated by commas.
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+build_address_internal(BuildTo to, char **full_to, char **error, char **fcc,
+ int *no_repo, char **lcc,
+ void (*save_and_restore_f)(int, SAVE_STATE_S *),
+ int simple_verify, int *mangled)
+{
+ ADDRESS *a;
+ int loop, i;
+ int tried_route_addr_hack = 0;
+ int did_set = 0;
+ char *tmp = NULL;
+ SAVE_STATE_S state;
+ PerAddrBook *pab;
+
+ dprint((8, "- build_address_internal - (%s)\n",
+ (to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
+ : (to.arg.abe->nickname ? to.arg.abe->nickname
+ : "no nick")));
+
+ init_ab_if_needed();
+ if(save_and_restore_f)
+ (*save_and_restore_f)(SAR_SAVE, &state);
+
+start:
+ loop = 0;
+ ps_global->c_client_error[0] = '\0';
+ wp_exit = wp_nobail = 0;
+
+ a = expand_address(to, ps_global->maildomain,
+ F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
+ ? ps_global->maildomain : NULL,
+ &loop, fcc, &did_set, lcc, error,
+ 0, simple_verify, mangled);
+
+ /*
+ * If the address is a route-addr, expand_address() will have rejected
+ * it unless it was enclosed in brackets, since route-addrs can't stand
+ * alone. Try it again with brackets. We should really be checking
+ * each address in the list of addresses instead of assuming there is
+ * only one address, but we don't want to have this function know
+ * all about parsing rfc822 addrs.
+ */
+ if(!tried_route_addr_hack &&
+ ps_global->c_client_error[0] != '\0' &&
+ ((to.type == Str && to.arg.str && to.arg.str[0] == '@') ||
+ (to.type == Abe && to.arg.abe->tag == Single &&
+ to.arg.abe->addr.addr[0] == '@'))){
+ BuildTo bldto;
+
+ tried_route_addr_hack++;
+
+ tmp = (char *)fs_get((size_t)(MAX_ADDR_FIELD + 3));
+
+ /* add brackets to whole thing */
+ strncpy(tmp, "<", MAX_ADDR_FIELD+3);
+ tmp[MAX_ADDR_FIELD+3-1] = '\0';
+ if(to.type == Str)
+ strncat(tmp, to.arg.str, MAX_ADDR_FIELD+3-strlen(tmp)-1);
+ else
+ strncat(tmp, to.arg.abe->addr.addr, MAX_ADDR_FIELD+3-strlen(tmp)-1);
+
+ tmp[MAX_ADDR_FIELD+3-1] = '\0';
+ strncat(tmp, ">", MAX_ADDR_FIELD+3-strlen(tmp)-1);
+ tmp[MAX_ADDR_FIELD+3-1] = '\0';
+
+ loop = 0;
+ ps_global->c_client_error[0] = '\0';
+
+ bldto.type = Str;
+ bldto.arg.str = tmp;
+
+ if(a)
+ mail_free_address(&a);
+
+ /* try it */
+ a = expand_address(bldto, ps_global->maildomain,
+ F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
+ ? ps_global->maildomain : NULL,
+ &loop, fcc, &did_set, lcc, error,
+ 0, simple_verify, mangled);
+
+ /* if no error this time, use it */
+ if(ps_global->c_client_error[0] == '\0'){
+ if(save_and_restore_f)
+ (*save_and_restore_f)(SAR_RESTORE, &state);
+
+ /*
+ * Clear references so that Addrbook Entry caching in adrbklib.c
+ * is allowed to throw them out of cache.
+ */
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->ostatus == Open || pab->ostatus == NoDisplay)
+ adrbk_clearrefs(pab->address_book);
+ }
+
+ goto ok;
+ }
+ else{ /* go back and use what we had before, so we get the error */
+ if(tmp)
+ fs_give((void **)&tmp);
+
+ tmp = NULL;
+ goto start;
+ }
+ }
+
+ if(save_and_restore_f)
+ (*save_and_restore_f)(SAR_RESTORE, &state);
+
+ /*
+ * Clear references so that Addrbook Entry caching in adrbklib.c
+ * is allowed to throw them out of cache.
+ */
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->ostatus == Open || pab->ostatus == NoDisplay)
+ adrbk_clearrefs(pab->address_book);
+ }
+
+ if(ps_global->c_client_error[0] != '\0'){
+ /* Parse error. Return string as is and error message */
+ if(full_to){
+ if(to.type == Str)
+ *full_to = cpystr(to.arg.str);
+ else{
+ if(to.arg.abe->nickname && to.arg.abe->nickname[0])
+ *full_to = cpystr(to.arg.abe->nickname);
+ else if(to.arg.abe->tag == Single)
+ *full_to = cpystr(to.arg.abe->addr.addr);
+ else
+ *full_to = cpystr("");
+ }
+ }
+
+ if(error != NULL){
+ /* display previous error and add new one */
+ if(*error){
+ q_status_message(SM_ORDER, 3, 5, *error);
+ display_message('x');
+ fs_give((void **)error);
+ }
+
+ *error = cpystr(ps_global->c_client_error);
+ }
+
+ dprint((2,
+ "build_address_internal returning parse error: %s\n",
+ ps_global->c_client_error ? ps_global->c_client_error : "?"));
+ if(a)
+ mail_free_address(&a);
+
+ if(tmp)
+ fs_give((void **)&tmp);
+
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return -1;
+ }
+
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ /*
+ * If there's a loop in the addressbook, we modify the address and
+ * send an error back, but we still return 0.
+ */
+ok:
+ if(loop && error != NULL){
+ /* display previous error and add new one */
+ if(*error){
+ q_status_message(SM_ORDER, 3, 5, *error);
+ display_message('x');
+ fs_give((void **)error);
+ }
+
+ *error = cpystr(_("Loop or Duplicate detected in addressbook!"));
+ }
+
+
+ if(full_to){
+ if(simple_verify){
+ if(tmp){
+ *full_to = tmp; /* add the brackets to route addr */
+ tmp = NULL;
+ }
+ else{
+ /* try to return what they sent us */
+ if(to.type == Str)
+ *full_to = cpystr(to.arg.str);
+ else{
+ if(to.arg.abe->nickname && to.arg.abe->nickname[0])
+ *full_to = cpystr(to.arg.abe->nickname);
+ else if(to.arg.abe->tag == Single)
+ *full_to = cpystr(to.arg.abe->addr.addr);
+ else
+ *full_to = cpystr("");
+ }
+ }
+ }
+ else{
+ RFC822BUFFER rbuf;
+ size_t len;
+
+ len = est_size(a);
+ *full_to = (char *) fs_get(len * sizeof(char));
+ (*full_to)[0] = '\0';
+ /*
+ * Assume that quotes surrounding the whole personal name are
+ * not meant to be literal quotes. That is, the name
+ * "Joe College, PhD." is quoted so that we won't do the
+ * switcheroo of Last, First, not so that the quotes will be
+ * literal. Rfc822_write_address will put the quotes back if they
+ * are needed, so Joe College would end up looking like
+ * "Joe College, PhD." <joe@somewhere.edu> but not like
+ * "\"Joe College, PhD.\"" <joe@somewhere.edu>.
+ */
+ strip_personal_quotes(a);
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = *full_to;
+ rbuf.cur = *full_to;
+ rbuf.end = (*full_to)+len-1;
+ rfc822_output_address_list(&rbuf, a, 0L, NULL);
+ *rbuf.cur = '\0';
+ }
+ }
+
+ if(no_repo && (did_set & FCC_NOREPO || did_set & LCC_NOREPO))
+ *no_repo = 1;
+
+ /*
+ * The condition in the leading if means that addressbook fcc's
+ * override the fcc-rule (because did_set will be set).
+ */
+ if(fcc && !(did_set & FCC_SET)){
+ char *fcc_got = NULL;
+
+ if((ps_global->fcc_rule == FCC_RULE_LAST
+ || ps_global->fcc_rule == FCC_RULE_CURRENT)
+ && strcmp(fcc_got = get_fcc(NULL), ps_global->VAR_DEFAULT_FCC)){
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ *fcc = cpystr(fcc_got);
+ }
+ else if(a && a->host){ /* not group syntax */
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ if(!tmp)
+ tmp = (char *)fs_get((size_t)200);
+
+ if((ps_global->fcc_rule == FCC_RULE_RECIP ||
+ ps_global->fcc_rule == FCC_RULE_NICK_RECIP) &&
+ get_uname(a ? a->mailbox : NULL, tmp, 200))
+ *fcc = cpystr(tmp);
+ else
+ *fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
+ }
+ else{ /* first addr is group syntax */
+ if(!*fcc)
+ *fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
+ /* else, leave it alone */
+ }
+
+ if(fcc_got)
+ fs_give((void **)&fcc_got);
+ }
+
+ if(a)
+ mail_free_address(&a);
+
+ if(tmp)
+ fs_give((void **)&tmp);
+
+ if(wp_exit)
+ return -1;
+
+ return 0;
+}
+
+
+/*
+ * Expand an address string against the address books, local names, and domain.
+ *
+ * Args: to -- this is either an address string to parse (one or more
+ * address strings separated by commas) or it is an
+ * AdrBk_Entry, in which case it refers to a single addrbook
+ * entry. If it is an abe, then it is treated the same as
+ * if the nickname of this entry was passed in and we
+ * looked it up in the addrbook, except that it doesn't
+ * actually have to have a non-null nickname.
+ * userdomain -- domain the user is in
+ * localdomain -- domain of the password file (usually same as userdomain)
+ * loop_detected -- pointer to an int we set if we detect a loop in the
+ * address books (or a duplicate in a list)
+ * fcc -- Returned value of fcc for first addr in a_string
+ * did_set -- expand_address set the fcc (need this in case somebody
+ * sets fcc explicitly to a value equal to default-fcc)
+ * simple_verify -- don't add list full names or expand 2nd level lists
+ *
+ * Result: An adrlist of expanded addresses is returned
+ *
+ * If the localdomain is NULL, then no lookup against the password file will
+ * be done.
+ */
+ADDRESS *
+expand_address(BuildTo to, char *userdomain, char *localdomain, int *loop_detected,
+ char **fcc, int *did_set, char **lcc, char **error, int recursing,
+ int simple_verify, int *mangled)
+{
+ size_t domain_length, length;
+ ADDRESS *adr, *a, *a_tail, *adr2, *a2, *a_temp, *wp_a;
+ AdrBk_Entry *abe, *abe2;
+ char *list, *l1, **l2;
+ char *tmp_a_string, *q;
+ BuildTo bldto;
+ static char *fakedomain;
+
+ dprint((9, "- expand_address - (%s)\n",
+ (to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
+ : (to.arg.abe->nickname ? to.arg.abe->nickname
+ : "no nick")));
+ /*
+ * We use the domain "@" to detect an unqualified address. If it comes
+ * back from rfc822_parse_adrlist with the host part set to "@", then
+ * we know it must have been unqualified (so we should look it up in the
+ * addressbook). Later, we also use a c-client hack. If an ADDRESS has
+ * a host part that begins with @ then rfc822_write_address()
+ * will write only the local part and leave off the @domain part.
+ *
+ * We also malloc enough space here so that we can strcpy over host below.
+ */
+ domain_length = MAX(localdomain!=NULL ? strlen(localdomain) : (size_t)0,
+ userdomain!=NULL ? strlen(userdomain) : (size_t)0);
+ if(!recursing){
+ fakedomain = (char *)fs_get(domain_length + 1);
+ memset((void *)fakedomain, '@', domain_length);
+ fakedomain[domain_length] = '\0';
+ }
+
+ adr = NULL;
+
+ if(to.type == Str){
+ /* rfc822_parse_adrlist feels free to destroy input so send copy */
+ tmp_a_string = cpystr(to.arg.str);
+ /* remove trailing comma */
+ for(q = tmp_a_string + strlen(tmp_a_string) - 1;
+ q >= tmp_a_string && (*q == SPACE || *q == ',');
+ q--)
+ *q = '\0';
+
+ if(as.n_impl)
+ mail_parameters(NIL, SET_PARSEPHRASE, (void *)massage_phrase_addr);
+
+ rfc822_parse_adrlist(&adr, tmp_a_string, fakedomain);
+
+ if(as.n_impl)
+ mail_parameters(NIL, SET_PARSEPHRASE, NULL);
+
+ /*
+ * Short circuit the process if there was a parsing error.
+ */
+ if(!recursing && ps_global->c_client_error[0] != '\0')
+ mail_free_address(&adr);
+
+ fs_give((void **)&tmp_a_string);
+ }
+ else{
+ if(!to.arg.abe ||
+ (to.arg.abe->tag == Single &&
+ (!to.arg.abe->addr.addr || to.arg.abe->addr.addr[0] == '\0')) ||
+ (to.arg.abe->tag == List &&
+ (!to.arg.abe->addr.list || !to.arg.abe->addr.list[0] ||
+ to.arg.abe->addr.list[0][0] == '\0'))){
+ adr = NULL;
+ }
+ else{
+ /* if we've already looked it up, fake an adr */
+ adr = mail_newaddr();
+ adr->mailbox = cpystr(to.arg.abe->nickname);
+ adr->host = cpystr(fakedomain);
+ }
+ }
+
+ for(a = adr, a_tail = adr; a;){
+
+ /* start or end of c-client group syntax */
+ if(!a->host){
+ a_tail = a;
+ a = a->next;
+ continue;
+ }
+ else if(a->host[0] != '@'){
+ /* Already fully qualified hostname */
+ a_tail = a;
+ a = a->next;
+ }
+ else{
+ /*
+ * Hostname is "@" indicating name wasn't qualified.
+ * Need to look up in address book, and the password file.
+ * If no match then fill in the local domain for host.
+ */
+ if(to.type == Str)
+ abe = adrbk_lookup_with_opens_by_nick(a->mailbox,
+ !recursing,
+ (int *)NULL, -1);
+ else
+ abe = to.arg.abe;
+
+ if(simple_verify && abe == NULL){
+ /*--- Move to next address in list -----*/
+ a_tail = a;
+ a = a->next;
+ }
+ else if(abe == NULL){
+ WP_ERR_S wp_err;
+
+ if(F_OFF(F_COMPOSE_REJECTS_UNQUAL, ps_global)){
+ if(localdomain != NULL && a->personal == NULL){
+ /* lookup in passwd file for local full name */
+ a->personal = local_name_lookup(a->mailbox);
+ /* we know a->host is long enough for localdomain */
+ if(a->personal){
+ /* we know a->host is long enough for userdomain */
+ strncpy(a->host, localdomain, domain_length+1);
+ a->host[domain_length] = '\0';
+ }
+ }
+ }
+
+ /*
+ * Didn't find it in address book or password
+ * file, try white pages.
+ */
+ memset(&wp_err, 0, sizeof(wp_err));
+ wp_err.mangled = mangled;
+ if(!wp_exit && a->personal == NULL &&
+ (wp_a = wp_lookups(a->mailbox, &wp_err, recursing))){
+ if(wp_a->mailbox && wp_a->mailbox[0] &&
+ wp_a->host && wp_a->host[0]){
+ a->personal = wp_a->personal;
+ if(a->adl)fs_give((void **)&a->adl);
+ a->adl = wp_a->adl;
+ if(a->mailbox)fs_give((void **)&a->mailbox);
+ a->mailbox = wp_a->mailbox;
+ if(a->host)fs_give((void **)&a->host);
+ a->host = wp_a->host;
+ }
+
+ fs_give((void **)&wp_a);
+ }
+
+ if(wp_err.error){
+ /*
+ * If wp_err has already been displayed long enough
+ * just get rid of it. Otherwise, try to fit it in
+ * with any other error messages we have had.
+ * In that case we may display error messages in a
+ * weird order. We'll have to see if this is a problem
+ * in real life.
+ */
+ if(status_message_remaining() && error && !wp_exit){
+ if(*error){
+ q_status_message(SM_ORDER, 3, 5, *error);
+ display_message('x');
+ fs_give((void **)error);
+ }
+
+ *error = wp_err.error;
+ }
+ else
+ fs_give((void **)&wp_err.error);
+ }
+
+ /* still haven't found it */
+ if(a->host[0] == '@' && !wp_nobail){
+ int space_phrase;
+
+ /*
+ * Figure out if there is a space in the mailbox so
+ * that user probably meant it to resolve on the
+ * directory server.
+ */
+ space_phrase = (a->mailbox && strindex(a->mailbox, SPACE));
+
+ if(!wp_nobail && (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global) ||
+ (space_phrase && as.n_impl))){
+ char ebuf[200];
+
+ /* TRANSLATORS: The first %s is a mailbox, the second is either
+ directory or addressbook */
+ snprintf(ebuf, sizeof(ebuf), _("Address for \"%s\" not in %s"),
+ a->mailbox,
+ (space_phrase && as.n_impl) ? _("directory")
+ : _("addressbook"));
+
+ if(error){
+ /* display previous error and add new one */
+ if(*error){
+ if(!wp_exit){
+ q_status_message(SM_ORDER, 3, 5, *error);
+ display_message('x');
+ }
+
+ fs_give((void **)error);
+ }
+
+ if(!wp_exit)
+ *error = cpystr(ebuf);
+ }
+
+ if(!wp_exit)
+ strncpy(ps_global->c_client_error, ebuf, 200);
+
+ if(!recursing)
+ fs_give((void **)&fakedomain);
+
+ if(adr)
+ mail_free_address(&adr);
+
+ return(adr);
+ }
+ else if(wp_err.wp_err_occurred){
+ if(!recursing){
+ if(error && *error && !wp_exit)
+ strncpy(ps_global->c_client_error, *error, sizeof(ps_global->c_client_error));
+ else
+ strncpy(ps_global->c_client_error, " ", sizeof(ps_global->c_client_error));
+
+ ps_global->c_client_error[sizeof(ps_global->c_client_error)-1] = '\0';
+
+ fs_give((void **)&fakedomain);
+ if(adr)
+ mail_free_address(&adr);
+
+ return(adr);
+ }
+ }
+ else{
+ /* we know a->host is long enough for userdomain */
+ strncpy(a->host, userdomain, domain_length+1);
+ a->host[domain_length] = '\0';
+ }
+ }
+
+ /*--- Move to next address in list -----*/
+ a_tail = a;
+ a = a->next;
+
+ }
+ /* expand first list, but not others if simple_verify */
+ else if(abe->tag == List && simple_verify && recursing){
+ /*--- Move to next address in list -----*/
+ a_tail = a;
+ a = a->next;
+ }
+ else{
+ /*
+ * There was a match in the address book. We have to do a lot
+ * here because the item from the address book might be a
+ * distribution list. Treat the string just like an address
+ * passed in to parse and recurse on it. Then combine
+ * the personal names from address book. Lastly splice
+ * result into address list being processed
+ */
+
+ /* first addr in list and fcc needs to be filled in */
+ if(!recursing && a == adr && fcc && !(*did_set & FCC_SET)){
+ /*
+ * Easy case for fcc. This is a nickname that has
+ * an fcc associated with it.
+ */
+ if(abe->fcc && abe->fcc[0]){
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ if(!strcmp(abe->fcc, "\"\""))
+ *fcc = cpystr("");
+ else
+ *fcc = cpystr(abe->fcc);
+
+ /*
+ * After we expand the list, we no longer remember
+ * that it came from this address book entry, so
+ * we wouldn't be able to set the fcc again based
+ * on the result. This tells our caller to remember
+ * that for us.
+ */
+ *did_set |= (FCC_SET | FCC_NOREPO);
+ }
+ /*
+ * else if fcc-rule=fcc-by-nickname, use that
+ */
+ else if(abe->nickname && abe->nickname[0] &&
+ (ps_global->fcc_rule == FCC_RULE_NICK ||
+ ps_global->fcc_rule == FCC_RULE_NICK_RECIP)){
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ *fcc = cpystr(abe->nickname);
+ /*
+ * After we expand the list, we no longer remember
+ * that it came from this address book entry, so
+ * we wouldn't be able to set the fcc again based
+ * on the result. This tells our caller to remember
+ * that for us.
+ */
+ *did_set |= (FCC_SET | FCC_NOREPO);
+ }
+ }
+
+ /* lcc needs to be filled in */
+ if(a == adr &&
+ lcc &&
+ (!*lcc || !**lcc)){
+ ADDRESS *atmp;
+ char *tmp;
+
+ /* return fullname for To line */
+ if(abe->fullname && *abe->fullname){
+ RFC822BUFFER rbuf;
+ size_t l, len;
+
+ if(*lcc)
+ fs_give((void **)lcc);
+
+ atmp = mail_newaddr();
+ atmp->mailbox = cpystr(abe->fullname);
+ len = est_size(atmp);
+ tmp = (char *) fs_get(len * sizeof(char));
+ tmp[0] = '\0';
+ /* write the phrase with quoting */
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmp;
+ rbuf.cur = tmp;
+ rbuf.end = tmp+len-1;
+ rfc822_output_address_list(&rbuf, atmp, 0L, NULL);
+ *rbuf.cur = '\0';
+ l = strlen(tmp)+1;
+ *lcc = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(*lcc, tmp, l);
+ (*lcc)[l] = '\0';
+ strncat(*lcc, ";", l+1-strlen(*lcc)-1);
+ (*lcc)[l] = '\0';
+ mail_free_address(&atmp);
+ fs_give((void **)&tmp);
+ *did_set |= (LCC_SET | LCC_NOREPO);
+ }
+ }
+
+ if(recursing && abe->referenced){
+ /*---- caught an address loop! ----*/
+ fs_give(((void **)&a->host));
+ (*loop_detected)++;
+ a->host = cpystr("***address-loop-in-addressbooks***");
+ continue;
+ }
+
+ abe->referenced++; /* For address loop detection */
+ if(abe->tag == List){
+ length = 0;
+ for(l2 = abe->addr.list; *l2; l2++)
+ length += (strlen(*l2) + 1);
+
+ list = (char *)fs_get(length + 1);
+ list[0] = '\0';
+ l1 = list;
+ for(l2 = abe->addr.list; *l2; l2++){
+ if(l1 != list && length+1-(l1-list) > 0)
+ *l1++ = ',';
+
+ strncpy(l1, *l2, length+1-(l1-list));
+ if(l1 > &list[length])
+ l1 = &list[length];
+
+ list[length] = '\0';
+
+ l1 += strlen(l1);
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = list;
+ adr2 = expand_address(bldto, userdomain, localdomain,
+ loop_detected, fcc, did_set,
+ lcc, error, 1, simple_verify,
+ mangled);
+ fs_give((void **)&list);
+ }
+ else if(abe->tag == Single){
+ if(strucmp(abe->addr.addr, a->mailbox)){
+ bldto.type = Str;
+ bldto.arg.str = abe->addr.addr;
+ adr2 = expand_address(bldto, userdomain,
+ localdomain, loop_detected,
+ fcc, did_set, lcc,
+ error, 1, simple_verify,
+ mangled);
+ }
+ else{
+ /*
+ * A loop within plain single entry is ignored.
+ * Set up so later code thinks we expanded.
+ */
+ adr2 = mail_newaddr();
+ adr2->mailbox = cpystr(abe->addr.addr);
+ adr2->host = cpystr(userdomain);
+ adr2->adl = cpystr(a->adl);
+ }
+ }
+
+ abe->referenced--; /* Janet Jackson <janet@dialix.oz.au> */
+ if(adr2 == NULL){
+ /* expanded to nothing, hack out of list */
+ a_temp = a;
+ if(a == adr){
+ adr = a->next;
+ a = adr;
+ a_tail = adr;
+ }
+ else{
+ a_tail->next = a->next;
+ a = a->next;
+ }
+
+ a_temp->next = NULL; /* So free won't do whole list */
+ mail_free_address(&a_temp);
+ continue;
+ }
+
+ /*
+ * Personal names: If the expanded address has a personal
+ * name and the address book entry is a list with a fullname,
+ * tack the full name from the address book on in front.
+ * This mainly occurs with a distribution list where the
+ * list has a full name, and the first person in the list also
+ * has a full name.
+ *
+ * This algorithm doesn't work very well if lists are
+ * included within lists, but it's not clear what would
+ * be better.
+ */
+ if(abe->fullname && abe->fullname[0]){
+ if(adr2->personal && adr2->personal[0]){
+ if(abe->tag == List){
+ /* combine list name and existing name */
+ char *name;
+
+ if(!simple_verify){
+ size_t l;
+
+ l = strlen(adr2->personal) + strlen(abe->fullname) + 4;
+ name = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(name, l+1, "%s -- %s", abe->fullname,
+ adr2->personal);
+ fs_give((void **)&adr2->personal);
+ adr2->personal = name;
+ }
+ }
+ else{
+ /* replace with nickname fullname */
+ fs_give((void **)&adr2->personal);
+ adr2->personal = adrbk_formatname(abe->fullname,
+ NULL, NULL);
+ }
+ }
+ else{
+ if(abe->tag != List || !simple_verify){
+ if(adr2->personal)
+ fs_give((void **)&adr2->personal);
+
+ adr2->personal = adrbk_formatname(abe->fullname,
+ NULL, NULL);
+ }
+ }
+ }
+
+ /* splice new list into old list and remove replaced addr */
+ for(a2 = adr2; a2->next != NULL; a2 = a2->next)
+ ;/* do nothing */
+
+ a2->next = a->next;
+ if(a == adr)
+ adr = adr2;
+ else
+ a_tail->next = adr2;
+
+ /* advance to next item, and free replaced ADDRESS */
+ a_tail = a2;
+ a_temp = a;
+ a = a->next;
+ a_temp->next = NULL; /* So free won't do whole list */
+ mail_free_address(&a_temp);
+ }
+ }
+
+ if((a_tail == adr && fcc && !(*did_set & FCC_SET))
+ || !a_tail->personal)
+ /*
+ * This looks for the addressbook entry that matches the given
+ * address. It looks through all the addressbooks
+ * looking for an exact match and then returns that entry.
+ */
+ abe2 = address_to_abe(a_tail);
+ else
+ abe2 = NULL;
+
+ /*
+ * If there is no personal name yet but we found the address in
+ * an address book, then we take the fullname from that address
+ * book entry and use it. One consequence of this is that if I
+ * have an address book entry with address hubert@cac.washington.edu
+ * and a fullname of Steve Hubert, then there is no way I can
+ * send mail to hubert@cac.washington.edu without having the
+ * personal name filled in for me.
+ */
+ if(!a_tail->personal && abe2 && abe2->fullname && abe2->fullname[0])
+ a_tail->personal = adrbk_formatname(abe2->fullname, NULL, NULL);
+
+ /* if it's first addr in list and fcc hasn't been set yet */
+ if(!recursing && a_tail == adr && fcc && !(*did_set & FCC_SET)){
+ if(abe2 && abe2->fcc && abe2->fcc[0]){
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ if(!strcmp(abe2->fcc, "\"\""))
+ *fcc = cpystr("");
+ else
+ *fcc = cpystr(abe2->fcc);
+
+ *did_set |= FCC_SET;
+ }
+ else if(abe2 && abe2->nickname && abe2->nickname[0] &&
+ (ps_global->fcc_rule == FCC_RULE_NICK ||
+ ps_global->fcc_rule == FCC_RULE_NICK_RECIP)){
+ if(*fcc)
+ fs_give((void **)fcc);
+
+ *fcc = cpystr(abe2->nickname);
+ *did_set |= FCC_SET;
+ }
+ }
+
+ /*
+ * Lcc needs to be filled in.
+ * Bug: if ^T select was used to put the list in the lcc field, then
+ * the list will have been expanded already and the fullname for
+ * the list will be mixed with the initial fullname in the list,
+ * and we don't have anyway to tell them apart. We could look for
+ * the --. We could change expand_address so it doesn't combine
+ * those two addresses.
+ */
+ if(adr &&
+ a_tail == adr &&
+ lcc &&
+ (!*lcc || !**lcc)){
+ if(adr->personal){
+ ADDRESS *atmp;
+ char *tmp;
+ RFC822BUFFER rbuf;
+ size_t l, len;
+
+ if(*lcc)
+ fs_give((void **)lcc);
+
+ atmp = mail_newaddr();
+ atmp->mailbox = cpystr(adr->personal);
+ len = est_size(atmp);
+ tmp = (char *) fs_get(len * sizeof(char));
+ tmp[0] = '\0';
+ /* write the phrase with quoting */
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmp;
+ rbuf.cur = tmp;
+ rbuf.end = tmp+len-1;
+ rfc822_output_address_list(&rbuf, atmp, 0L, NULL);
+ *rbuf.cur = '\0';
+ l = strlen(tmp)+1;
+ *lcc = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(*lcc, tmp, l);
+ (*lcc)[l] = '\0';
+ strncat(*lcc, ";", l+1-strlen(*lcc)-1);
+ (*lcc)[l] = '\0';
+ mail_free_address(&atmp);
+ fs_give((void **)&tmp);
+ *did_set |= (LCC_SET | LCC_NOREPO);
+ }
+ }
+ }
+
+ if(!recursing)
+ fs_give((void **)&fakedomain);
+
+ return(adr);
+}
+
+
+/*
+ * This is a call back from rfc822_parse_adrlist. If we return NULL then
+ * it does its regular parsing. However, if we return and ADDRESS then it
+ * returns that address to us.
+ *
+ * We want a phrase with no address to be parsed just like a nickname would
+ * be in expand_address. That is, the phrase gets put in the mailbox name
+ * and the host is set to the fakedomain. Then expand_address will try to
+ * look that up using wp_lookup().
+ *
+ * Args phrase -- The start of the phrase of the address
+ * end -- The first character after the phrase
+ * host -- The defaulthost that was passed to rfc822_parse_adrlist
+ *
+ * Returns An allocated address, or NULL.
+ */
+ADDRESS *
+massage_phrase_addr(char *phrase, char *end, char *host)
+{
+ ADDRESS *adr = NULL;
+ size_t size;
+
+ if((size = end - phrase) > 0){
+ char *mycopy;
+
+ mycopy = (char *)fs_get((size+1) * sizeof(char));
+ strncpy(mycopy, phrase, size);
+ mycopy[size] = '\0';
+ removing_trailing_white_space(mycopy);
+
+ /*
+ * If it is quoted we want to leave it alone. It will be treated
+ * like an atom in parse_adrlist anyway, which is what we want to
+ * have happen, and we'd have to remove the quotes ourselves here
+ * if we did it. The problem then is that we don't know if we
+ * removed the quotes here or they just weren't there in the
+ * first place.
+ */
+ if(*mycopy == '"' && mycopy[strlen(mycopy)-1] == '"')
+ fs_give((void **)&mycopy);
+ else{
+ adr = mail_newaddr();
+ adr->mailbox = mycopy;
+ adr->host = cpystr(host);
+ }
+ }
+
+ return(adr);
+}
+
+
+/*
+ * Run through the adrlist "adr" and strip off any enclosing quotes
+ * around personal names. That is, change "Joe L. Normal" to
+ * Joe L. Normal.
+ */
+void
+strip_personal_quotes(struct mail_address *adr)
+{
+ int len;
+ register char *p, *q;
+
+ while(adr){
+ if(adr->personal){
+ len = strlen(adr->personal);
+ if(len > 1
+ && adr->personal[0] == '"'
+ && adr->personal[len-1] == '"'){
+ adr->personal[len-1] = '\0';
+ p = adr->personal;
+ q = p + 1;
+ while((*p++ = *q++) != '\0')
+ ;
+ }
+ }
+
+ adr = adr->next;
+ }
+}
+
+
+static char *last_fcc_used;
+/*
+ * Returns alloc'd fcc.
+ */
+char *
+get_fcc(char *fcc_arg)
+{
+ char *fcc;
+
+ /*
+ * Use passed in arg unless it is the same as default (and then
+ * may use that anyway below).
+ */
+ if(fcc_arg && strcmp(fcc_arg, ps_global->VAR_DEFAULT_FCC))
+ fcc = cpystr(fcc_arg);
+ else{
+ if(ps_global->fcc_rule == FCC_RULE_LAST && last_fcc_used)
+ fcc = cpystr(last_fcc_used);
+ else if(ps_global->fcc_rule == FCC_RULE_CURRENT
+ && ps_global->mail_stream
+ && !sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && !IS_NEWS(ps_global->mail_stream)
+ && ps_global->cur_folder
+ && ps_global->cur_folder[0]){
+ CONTEXT_S *cntxt = ps_global->context_current;
+ char *rs = NULL;
+
+ if(((cntxt->use) & CNTXT_SAVEDFLT))
+ rs = ps_global->cur_folder;
+ else
+ rs = ps_global->mail_stream->mailbox;
+
+ fcc = cpystr((rs&&*rs) ? rs : ps_global->VAR_DEFAULT_FCC);
+ }
+ else
+ fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
+ }
+
+ return(fcc);
+}
+
+
+/*
+ * Save the fcc for use with next composition.
+ */
+void
+set_last_fcc(char *fcc)
+{
+ size_t l;
+
+ if(fcc){
+ if(!last_fcc_used)
+ last_fcc_used = cpystr(fcc);
+ else if(strcmp(last_fcc_used, fcc)){
+ if((l=strlen(last_fcc_used)) >= strlen(fcc)){
+ strncpy(last_fcc_used, fcc, l+1);
+ last_fcc_used[l] = '\0';
+ }
+ else{
+ fs_give((void **)&last_fcc_used);
+ last_fcc_used = cpystr(fcc);
+ }
+ }
+ }
+}
+
+
+/*
+ * Figure out what the fcc is based on the given to address.
+ *
+ * Returns an allocated copy of the fcc, to be freed by the caller, or NULL.
+ */
+char *
+get_fcc_based_on_to(struct mail_address *to)
+{
+ ADDRESS *next_addr;
+ char *bufp, *fcc;
+ BuildTo bldto;
+ size_t len;
+
+ if(!to || !to->host || to->host[0] == '.')
+ return(NULL);
+
+ /* pick off first address */
+ next_addr = to->next;
+ to->next = NULL;
+ len = est_size(to);
+ bufp = (char *) fs_get(len * sizeof(char));
+ bufp[0] = '\0';
+
+ bldto.type = Str;
+ bldto.arg.str = cpystr(addr_string(to, bufp, len));
+
+ fs_give((void **)&bufp);
+ to->next = next_addr;
+
+ fcc = NULL;
+
+ (void) build_address_internal(bldto, NULL, NULL, &fcc, NULL, NULL, NULL, 0, NULL);
+
+ if(bldto.arg.str)
+ fs_give((void **) &bldto.arg.str);
+
+ return(fcc);
+}
+
+
+/*
+ * Free storage in headerentry.bldr_private.
+ */
+void
+free_privatetop(PrivateTop **pt)
+{
+ if(pt && *pt){
+ if((*pt)->affector)
+ fs_give((void **)&(*pt)->affector);
+
+ fs_give((void **)pt);
+ }
+}
diff --git a/pith/bldaddr.h b/pith/bldaddr.h
new file mode 100644
index 00000000..964fd5c8
--- /dev/null
+++ b/pith/bldaddr.h
@@ -0,0 +1,122 @@
+/*
+ * $Id: bldaddr.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_BLDADDR_INCLUDED
+#define PITH_BLDADDR_INCLUDED
+
+
+#include "../pith/adrbklib.h"
+#include "../pith/state.h"
+#include "../pith/ldap.h"
+#include "../pith/pattern.h"
+#include "../pith/remtype.h"
+
+
+/*
+ * This is the structure that the builders impose on the private data
+ * that is pointed to by the bldr_private pointer in each header entry.
+ *
+ * The bldr_private pointer points to a PrivateTop which consists of two
+ * parts, whose purposes are independent:
+ * ******* This part no longer exists ******
+ * encoded -- This is used to preserve the charset information for
+ * addresses in this entry. For example, if the user types
+ * in a nickname which has a charset in it, the encoded
+ * version containing the charset information is saved here
+ * along with a checksum of the text that created it (the
+ * line containing the nickname).
+ * etext -- Pointer to the encoded text.
+ * chksumlen -- Length of string that produced the etext.
+ * chksumval -- Checksum of that string.
+ * The string that produced the etext is just the displayed
+ * value of the entry, not the nickname before it was expanded.
+ * That's so we can check on the next builder call to see if it
+ * was changed or not. Appending or prepending more addresses to
+ * what's there will work, the etext from the old contents will
+ * be combined with the etext from the appended stuff. (The check
+ * is for appending or prepending so appending AND prepending all
+ * at once won't work, charset will be lost.) If the middle of the
+ * text is edited the charset is lost. If text is removed, the
+ * charset is lost.
+ *
+ * affector -- This is for entries which affect the contents of other
+ * fields. For example, the Lcc field affects what goes in
+ * the To and Fcc fields, and the To field affects what goes
+ * in the Fcc field.
+ * who -- Who caused this field to be set last.
+ * chksumlen -- Length of string in the "who" field that caused the effect.
+ * chksumval -- Checksum of that string.
+ * The string that is being checksummed is the one that is displayed
+ * in the field doing the affecting. So, for the affector in the
+ * Fcc headerentry, the who might point to either To or Lcc and
+ * then the checksummed string would be either the To or Lcc displayed
+ * string. The purpose of the affector is to remember that the
+ * affected field was set from an address book entry that is no
+ * longer identifiable once it is expanded. For example, if a list
+ * is entered into the Lcc field, then the To field gets the list
+ * fullname and the fcc field gets the fcc entry for the list. If
+ * we move out of the Lcc field and back in and call the builder
+ * again, the list has been expanded and we can't tell (except for
+ * the affector) that the same list is what caused the results.
+ * Same for the To field. A nickname entered there will cause the
+ * fcc of that nickname to go in the Fcc field and the affector
+ * will cause it to stick as long as the To field is only appended to.
+ *
+ * It may seem a little strange that the PrivateAffector doesn't have a text
+ * field. The reason is that the text is actually displayed in that field
+ * and so is contained in the entry itself, unlike the PrivateEncoded
+ * which has etext which is not displayed and is only used when we go to
+ * send the mail.
+ */
+
+typedef enum {BP_Unset, BP_To, BP_Lcc} WhoSetUs;
+
+typedef struct private_affector {
+ WhoSetUs who;
+ int cksumlen;
+ unsigned long cksumval;
+} PrivateAffector;
+
+/*
+ * This used to have more than one member. We could get rid
+ * of this structure now if we wanted to, and just have the
+ * PrivateAffector at the top-level, but it's easier alone.
+ * Who knows, we may have a need for something else to be added
+ * to the structure in the future.
+ */
+typedef struct private_top {
+ PrivateAffector *affector;
+} PrivateTop;
+
+/* save_and_restore flags */
+#define SAR_SAVE 1
+#define SAR_RESTORE 2
+
+/* exported prototypes */
+int our_build_address(BuildTo, char **, char **, char **, void (*)(int, SAVE_STATE_S *));
+int build_address_internal(BuildTo, char **, char **, char **, int *, char **,
+ void (*)(int, SAVE_STATE_S *), int, int *);
+ADDRESS *expand_address(BuildTo, char *, char *, int *, char **, \
+ int *, char **, char **, int, int, int *);
+ADDRESS *massage_phrase_addr(char *, char *, char *);
+char *get_fcc(char *);
+void set_last_fcc(char *);
+char *get_fcc_based_on_to(ADDRESS *);
+void free_privatetop(PrivateTop **);
+void strip_personal_quotes(ADDRESS *);
+
+
+#endif /* PITH_BLDADDR_INCLUDED */
diff --git a/pith/busy.h b/pith/busy.h
new file mode 100644
index 00000000..f3be42b9
--- /dev/null
+++ b/pith/busy.h
@@ -0,0 +1,38 @@
+/*
+ * $Id: busy.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_BUSY_INCLUDED
+#define PITH_BUSY_INCLUDED
+
+
+typedef int (*percent_done_t)(); /* returns %done for progress status msg */
+
+
+/* used to tweak busy without it */
+#ifndef ALARM_BLIP
+#define ALARM_BLIP()
+#endif
+
+
+/* exported protoypes */
+
+
+/* currently mandatory to implement stubs */
+
+int busy_cue(char *, percent_done_t, int);
+void cancel_busy_cue(int);
+
+
+#endif /* PITH_BUSY_INCLUDED */
diff --git a/pith/charconv/Makefile.am b/pith/charconv/Makefile.am
new file mode 100644
index 00000000..615a6082
--- /dev/null
+++ b/pith/charconv/Makefile.am
@@ -0,0 +1,19 @@
+## Process this file with automake to produce Makefile.in
+## Use aclocal -I m4; automake
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+noinst_LIBRARIES = libpithcc.a
+
+libpithcc_a_SOURCES = filesys.c utf8.c
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
diff --git a/pith/charconv/Makefile.in b/pith/charconv/Makefile.in
new file mode 100644
index 00000000..3a6f5220
--- /dev/null
+++ b/pith/charconv/Makefile.in
@@ -0,0 +1,527 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = pith/charconv
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/VERSION \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+libpithcc_a_AR = $(AR) $(ARFLAGS)
+libpithcc_a_LIBADD =
+am_libpithcc_a_OBJECTS = filesys.$(OBJEXT) utf8.$(OBJEXT)
+libpithcc_a_OBJECTS = $(am_libpithcc_a_OBJECTS)
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(libpithcc_a_SOURCES)
+DIST_SOURCES = $(libpithcc_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@
+AM_LDFLAGS = @AM_LDFLAGS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CP = @CP@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+C_CLIENT_CFLAGS = @C_CLIENT_CFLAGS@
+C_CLIENT_GCCOPTLEVEL = @C_CLIENT_GCCOPTLEVEL@
+C_CLIENT_LDFLAGS = @C_CLIENT_LDFLAGS@
+C_CLIENT_SPECIALS = @C_CLIENT_SPECIALS@
+C_CLIENT_TARGET = @C_CLIENT_TARGET@
+C_CLIENT_WITH_IPV6 = @C_CLIENT_WITH_IPV6@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISPELLPROG = @ISPELLPROG@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN = @LN@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKE = @MAKE@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NPA_PROG = @NPA_PROG@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PWPROG = @PWPROG@
+RANLIB = @RANLIB@
+REGEX_BUILD = @REGEX_BUILD@
+RM = @RM@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPELLPROG = @SPELLPROG@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEB_BINDIR = @WEB_BINDIR@
+WEB_BUILD = @WEB_BUILD@
+WEB_PUBCOOKIE_BUILD = @WEB_PUBCOOKIE_BUILD@
+WEB_PUBCOOKIE_LIB = @WEB_PUBCOOKIE_LIB@
+WEB_PUBCOOKIE_LINK = @WEB_PUBCOOKIE_LINK@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+acx_pthread_config = @acx_pthread_config@
+alpine_interactive_spellcheck = @alpine_interactive_spellcheck@
+alpine_simple_spellcheck = @alpine_simple_spellcheck@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libpithcc.a
+libpithcc_a_SOURCES = filesys.c utf8.c
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign pith/charconv/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign pith/charconv/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libpithcc.a: $(libpithcc_a_OBJECTS) $(libpithcc_a_DEPENDENCIES)
+ -rm -f libpithcc.a
+ $(libpithcc_a_AR) libpithcc.a $(libpithcc_a_OBJECTS) $(libpithcc_a_LIBADD)
+ $(RANLIB) libpithcc.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filesys.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-noinstLIBRARIES ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pith/charconv/filesys.c b/pith/charconv/filesys.c
new file mode 100644
index 00000000..c9ef0f05
--- /dev/null
+++ b/pith/charconv/filesys.c
@@ -0,0 +1,721 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: filesys.c 770 2007-10-24 00:23:09Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/* includable WITHOUT dependency on c-client */
+#include "../../c-client/mail.h"
+#include "../../c-client/utf8.h"
+
+#ifdef _WINDOWS
+/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */
+#undef ERROR
+#endif
+
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/fs.h"
+
+/* includable WITHOUT dependency on pico */
+#include "../../pico/keydefs.h"
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+#include "filesys.h"
+#include "utf8.h"
+
+
+#define bad_char ((UCS) '?')
+
+
+/*
+ * Make it easier to use the convert_to_locale function for filenames
+ * and directory names. Note, only one at a time because there's only
+ * one buffer.
+ * This isn't being freed as it stands now.
+ */
+char *
+fname_to_locale(char *fname)
+{
+ static char *fname_locale_buf = NULL;
+ static size_t fname_locale_len = 0;
+ char *converted_fname, *p;
+
+ p = convert_to_locale(fname);
+ if(p)
+ converted_fname = p;
+ else
+ converted_fname = fname;
+
+ if(converted_fname){
+ if(strlen(converted_fname)+1 > fname_locale_len){
+ if(fname_locale_buf)
+ fs_give((void **) &fname_locale_buf);
+
+ fname_locale_len = strlen(converted_fname)+1;
+ fname_locale_buf = (char *) fs_get(fname_locale_len * sizeof(char));
+ }
+
+ strncpy(fname_locale_buf, converted_fname, fname_locale_len);
+ fname_locale_buf[fname_locale_len-1] = '\0';
+ }
+ else{
+ if(fname_locale_len == 0){
+ fname_locale_len = 1;
+ fname_locale_buf = (char *) fs_get(fname_locale_len * sizeof(char));
+ }
+
+ fname_locale_buf[0] = '\0';
+ }
+
+ if(p)
+ fs_give((void **) &p);
+
+ return(fname_locale_buf);
+}
+
+
+/*
+ * Make it easier to use the convert_to_utf8 function for filenames
+ * and directory names. Note, only one at a time because there's only
+ * one buffer.
+ * This isn't being freed as it stands now.
+ */
+char *
+fname_to_utf8(char *fname)
+{
+ static char *fname_utf8_buf = NULL;
+ static size_t fname_utf8_len = 0;
+ char *converted_fname, *p;
+
+ p = convert_to_utf8(fname, NULL, 0);
+ if(p)
+ converted_fname = p;
+ else
+ converted_fname = fname;
+
+ if(converted_fname){
+ if(strlen(converted_fname)+1 > fname_utf8_len){
+ if(fname_utf8_buf)
+ fs_give((void **) &fname_utf8_buf);
+
+ fname_utf8_len = strlen(converted_fname)+1;
+ fname_utf8_buf = (char *) fs_get(fname_utf8_len * sizeof(char));
+ }
+
+ strncpy(fname_utf8_buf, converted_fname, fname_utf8_len);
+ fname_utf8_buf[fname_utf8_len-1] = '\0';
+ }
+ else{
+ if(fname_utf8_len == 0){
+ fname_utf8_len = 1;
+ fname_utf8_buf = (char *) fs_get(fname_utf8_len * sizeof(char));
+ }
+
+ fname_utf8_buf[0] = '\0';
+ }
+
+ if(p)
+ fs_give((void **) &p);
+
+ return(fname_utf8_buf);
+}
+
+
+/*
+ * The fp file pointer is open for read on a file which has contents
+ * that are encoded in the user's locale charset. That multibyte stream
+ * of characters is converted to wide characters and returned one at
+ * a time.
+ *
+ * Not sure what to do if an uninterpretable character happens. Returning
+ * the bad character now.
+ */
+UCS
+read_a_wide_char(FILE *fp,
+ void *input_cs) /* input_cs ignored in Windows */
+{
+#ifdef _WINDOWS
+ _TINT val;
+
+ val = _fgettc(fp);
+ if(val == _TEOF)
+ return(CCONV_EOF);
+
+ return((UCS) val);
+#else /* UNIX */
+ unsigned long octets_so_far, remaining_octets;
+ unsigned char *inputp;
+ unsigned char inputbuf[20];
+ int c;
+ UCS ucs;
+
+ c = fgetc(fp);
+ if(c == EOF)
+ return(CCONV_EOF);
+
+ /*
+ * Read enough bytes to make up a character and convert it to UCS-4.
+ */
+ memset(inputbuf, 0, sizeof(inputbuf));
+ inputbuf[0] = (unsigned char) c;
+ octets_so_far = 1;
+ for(;;){
+ remaining_octets = octets_so_far;
+ inputp = inputbuf;
+ ucs = mbtow(input_cs, &inputp, &remaining_octets);
+ switch(ucs){
+ case CCONV_BADCHAR:
+ return(bad_char);
+
+ case CCONV_NEEDMORE:
+ if(octets_so_far >= sizeof(inputbuf))
+ return(bad_char);
+
+ c = fgetc(fp);
+ if(c == EOF)
+ return(CCONV_EOF);
+
+ inputbuf[octets_so_far++] = (unsigned char) c;
+ break;
+
+ default:
+ /* got a good UCS-4 character */
+ return(ucs);
+ }
+ }
+
+ return(bad_char);
+#endif /* UNIX */
+}
+
+
+int
+write_a_wide_char(UCS ucs, FILE *fp)
+{
+#ifdef _WINDOWS
+ int rv = 1;
+ TCHAR w;
+
+ w = (TCHAR) ucs;
+ if(_fputtc(w, fp) == _TEOF)
+ rv = EOF;
+
+ return(rv);
+#else /* UNIX */
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if(ucs < 0x80){
+ obuf[0] = (unsigned char) ucs;
+ outchars = 1;
+ }
+ else{
+ outchars = wtomb((char *) obuf, ucs);
+ if(outchars < 0){
+ outchars = 1;
+ obuf[0] = bad_char; /* ??? */
+ }
+ }
+
+ for(i = 0; i < outchars; i++)
+ if(fputc(obuf[i], fp) == EOF){
+ rv = EOF;
+ break;
+ }
+
+ return(rv);
+#endif /* UNIX */
+}
+
+
+int
+our_stat(char *filename, struct stat *sbuf)
+{
+#ifdef _WINDOWS
+ LPTSTR f = NULL;
+ int ret = -1;
+ struct _stat s;
+
+ f = utf8_to_lptstr((LPSTR) filename);
+ if(f){
+ ret = _tstat(f, &s);
+
+ sbuf->st_dev = s.st_dev;
+ sbuf->st_ino = s.st_ino;
+ sbuf->st_mode = s.st_mode;
+ sbuf->st_nlink = s.st_nlink;
+ sbuf->st_uid = s.st_uid;
+ sbuf->st_gid = s.st_gid;
+ sbuf->st_rdev = s.st_rdev;
+ sbuf->st_size = s.st_size;
+ sbuf->st_atime = (time_t) s.st_atime;
+ sbuf->st_mtime = (time_t) s.st_mtime;
+ sbuf->st_ctime = (time_t) s.st_ctime;
+
+ fs_give((void **) &f);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(stat(fname_to_locale(filename), sbuf));
+#endif /* UNIX */
+}
+
+
+int
+our_lstat(char *filename, struct stat *sbuf)
+{
+#ifdef _WINDOWS
+ assert(0); /* lstat not used in Windows */
+ return(-1);
+#else /* UNIX */
+ return(lstat(fname_to_locale(filename), sbuf));
+#endif /* UNIX */
+}
+
+
+FILE *
+our_fopen(char *path, char *mode)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL, m = NULL;
+ FILE *ret = NULL;
+ char *mode_with_ccs = NULL;
+ char buf[500];
+ size_t len;
+
+ if(mode && (*mode == 'r' || *mode == 'a')){
+ char *force_bom_check = ", ccs=UNICODE";
+
+ if(strchr(mode, 'b'))
+ mode_with_ccs = mode;
+ else{
+ /*
+ * The docs seem to say that we don't need the ccs parameter and
+ * if the file has a BOM at the beginning it will notice that and
+ * use it. However, we're not seeing that. Instead, what we see is
+ * that giving a parameter of UNICODE causes the desired behavior.
+ * This causes it to check for a BOM and if it finds one it uses it.
+ * If it doesn't find one, it treats the file as ANSI, which is what
+ * we want.
+ */
+ if((len = strlen(mode) + strlen(force_bom_check)) < sizeof(buf)){
+ len = sizeof(buf)-1;
+ mode_with_ccs = buf;
+ }
+ else
+ mode_with_ccs = (char *) MemAlloc((len+1) * sizeof(char));
+
+ if(mode_with_ccs)
+ snprintf(mode_with_ccs, len+1, "%s%s", mode, force_bom_check);
+ else
+ mode_with_ccs = mode; /* can't happen */
+ }
+ }
+ else if(mode && (*mode == 'w')){
+ char *force_utf8 = ", ccs=UTF-8";
+
+ if(strchr(mode, 'b'))
+ mode_with_ccs = mode;
+ else{
+ if((len = strlen(mode) + strlen(force_utf8)) < sizeof(buf)){
+ len = sizeof(buf)-1;
+ mode_with_ccs = buf;
+ }
+ else
+ mode_with_ccs = (char *) MemAlloc((len+1) * sizeof(char));
+
+ if(mode_with_ccs)
+ snprintf(mode_with_ccs, len+1, "%s%s", mode, force_utf8);
+ else
+ mode_with_ccs = mode; /* can't happen */
+ }
+ }
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ m = utf8_to_lptstr((LPSTR) mode_with_ccs);
+ if(m){
+ ret = _tfopen(p, m);
+ MemFree((void *) m);
+ }
+
+ fs_give((void **) &p);
+ }
+
+ if(mode_with_ccs && mode_with_ccs != buf && mode_with_ccs != mode)
+ MemFree((void *) mode_with_ccs);
+
+ return ret;
+#else /* UNIX */
+ return(fopen(fname_to_locale(path), mode));
+#endif /* UNIX */
+}
+
+
+int
+our_open(char *path, int flags, mode_t mode)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ /*
+ * Setting the _O_WTEXT flag when opening a file for reading
+ * will cause us to read the first few bytes to check for
+ * a BOM and to translate from that encoding if we find it.
+ * This only works with stream I/O, not low-level read/write.
+ *
+ * When opening for writing the flag _O_U8TEXT will cause
+ * us to put a UTF-8 BOM at the start of the file.
+ *
+ * O_TEXT will cause LF -> CRLF on output, opposite on input
+ * O_BINARY suppresses that.
+ * _O_U8TEXT implies O_TEXT.
+ */
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ ret = _topen(p, flags, mode);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(open(fname_to_locale(path), flags, mode));
+#endif /* UNIX */
+}
+
+
+int
+our_creat(char *path, mode_t mode)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ ret = _tcreat(p, mode);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(creat(fname_to_locale(path), mode));
+#endif /* UNIX */
+}
+
+
+int
+our_mkdir(char *path, mode_t mode)
+{
+#ifdef _WINDOWS
+ /* mode is a noop for _WINDOWS */
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ ret = _tmkdir(p);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(mkdir(fname_to_locale(path), mode));
+#endif /* UNIX */
+}
+
+
+int
+our_rename(char *oldpath, char *newpath)
+{
+#ifdef _WINDOWS
+ LPTSTR pold = NULL, pnew = NULL;
+ int ret = -1;
+
+ pold = utf8_to_lptstr((LPSTR) oldpath);
+ pnew = utf8_to_lptstr((LPSTR) newpath);
+
+ if(pold && pnew)
+ ret = _trename(pold, pnew);
+
+ if(pold)
+ fs_give((void **) &pold);
+ if(pnew)
+ fs_give((void **) &pnew);
+
+ return ret;
+#else /* UNIX */
+ char *p, *pold;
+ size_t len;
+ int ret = -1;
+
+ p = fname_to_locale(oldpath);
+ if(p){
+ len = strlen(p);
+ pold = (char *) fs_get((len+1) * sizeof(char));
+ strncpy(pold, p, len+1);
+ pold[len] = '\0';
+
+ ret = rename(pold, fname_to_locale(newpath));
+ fs_give((void **) &pold);
+ }
+
+ return ret;
+#endif /* UNIX */
+}
+
+
+int
+our_unlink(char *path)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ ret = _tunlink(p);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(unlink(fname_to_locale(path)));
+#endif /* UNIX */
+}
+
+
+int
+our_link(char *oldpath, char *newpath)
+{
+#ifdef _WINDOWS
+ assert(0); /* link not used in Windows */
+ return(-1);
+#else /* UNIX */
+ char *p, *pold;
+ size_t len;
+ int ret = -1;
+
+ p = fname_to_locale(oldpath);
+ if(p){
+ len = strlen(p);
+ pold = (char *) fs_get((len+1) * sizeof(char));
+ strncpy(pold, p, len+1);
+ pold[len] = '\0';
+
+ ret = link(pold, fname_to_locale(newpath));
+ fs_give((void **) &pold);
+ }
+
+ return ret;
+#endif /* UNIX */
+}
+
+
+int
+our_truncate(char *path, off_t size)
+{
+ int ret = -1;
+#if defined(_WINDOWS) || !defined(HAVE_TRUNCATE)
+ int fdes;
+#endif
+
+#ifdef _WINDOWS
+ if((fdes = our_open(path, O_RDWR | O_CREAT | S_IREAD | S_IWRITE | _O_U8TEXT, 0600)) != -1){
+ if(chsize(fdes, size) == 0)
+ ret = 0;
+
+ close(fdes);
+ }
+
+#else /* UNIX */
+
+#ifdef HAVE_TRUNCATE
+ ret = truncate(fname_to_locale(path), size);
+#else /* !HAVE_TRUNCATE */
+
+ if((fdes = our_open(path, O_RDWR, 0600)) != -1){
+ ret = chsize(fdes, size) ;
+
+ if(close(fdes))
+ ret = -1;
+ }
+#endif /* !HAVE_TRUNCATE */
+#endif /* UNIX */
+
+ return ret;
+}
+
+
+int
+our_chmod(char *path, mode_t mode)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+ if(p){
+ ret = _tchmod(p, mode);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(chmod(fname_to_locale(path), mode));
+#endif /* UNIX */
+}
+
+
+int
+our_chown(char *path, uid_t owner, gid_t group)
+{
+#ifdef _WINDOWS
+ return 0;
+#else /* UNIX */
+ return(chown(fname_to_locale(path), owner, group));
+#endif /* UNIX */
+}
+
+
+int
+our_utime(char *path, struct utimbuf *buf)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+
+ if(p){
+ ret = _tutime(p, buf);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(utime(fname_to_locale(path), buf));
+#endif /* UNIX */
+}
+
+/*
+ * Return a malloc'd utf8-encoded char * of the provided environment
+ * variable. The env_variable argument is assumed not to be UTF-8. Returns
+ * NULL if no such environment variable.
+ *
+ * We'll pretty much swap out getenv's where convenient. Windows pretty
+ * much doesn't want to do getenv once we do unicode
+ */
+char *
+our_getenv(char *env_variable)
+{
+#ifdef _WINDOWS
+ TCHAR lptstr_env_variable[MAXPATH+1], *p;
+ int i;
+
+ for(i = 0; env_variable[i] && i < MAXPATH; i++)
+ lptstr_env_variable[i] = env_variable[i];
+ lptstr_env_variable[i] = '\0';
+ if(p = _tgetenv(lptstr_env_variable))
+ return(lptstr_to_utf8(p));
+ else
+ return(NULL);
+#else /* !_WINDOWS */
+ char *p, *utf8_p, *env_cpy;
+ size_t len;
+ if((p = getenv(env_variable)) != NULL){
+ /* all this when what we want is a cpystr */
+ utf8_p = fname_to_utf8(p);
+ len = strlen(utf8_p);
+ env_cpy = (char *)fs_get((len+1)*sizeof(char));
+ strncpy(env_cpy, utf8_p, len+1);
+ env_cpy[len] = '\0';
+
+ return(env_cpy);
+ }
+ else
+ return(NULL);
+#endif /* !_WINDOWS */
+}
+
+
+int
+our_access(char *path, int mode)
+{
+#ifdef _WINDOWS
+ LPTSTR p = NULL;
+ int ret = -1;
+
+ p = utf8_to_lptstr((LPSTR) path);
+ if(p){
+ ret = _taccess(p, mode);
+ fs_give((void **) &p);
+ }
+
+ return ret;
+#else /* UNIX */
+ return(access(fname_to_locale(path), mode));
+#endif /* UNIX */
+}
+
+
+/*
+ * Fgets that doesn't do any character encoding translation or any
+ * of that Windows stuff.
+ */
+char *
+fgets_binary(char *s, int size, FILE *fp)
+{
+#ifdef _WINDOWS
+ char *p;
+ char c;
+ int r;
+
+ /*
+ * Use fread low-level input instead of fgets.
+ * Maybe if we understood better we wouldn't need this.
+ */
+ if(!s)
+ return s;
+
+ p = s;
+ while(p-s < size-1 && (r=fread(&c, sizeof(c), (size_t) 1, fp)) == 1 && c != '\n')
+ *p++ = c;
+
+ if(p-s < size-1 && r == 1){
+ /* must have gotten to end of line */
+ *p++ = '\n';
+ }
+
+ *p = '\0';
+ return(s);
+
+#else /* UNIX */
+ return(fgets(s, size, fp));
+#endif /* UNIX */
+}
diff --git a/pith/charconv/filesys.h b/pith/charconv/filesys.h
new file mode 100644
index 00000000..07703092
--- /dev/null
+++ b/pith/charconv/filesys.h
@@ -0,0 +1,50 @@
+/*-----------------------------------------------------------------------
+ $Id: filesys.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ -----------------------------------------------------------------------*/
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CHARCONV_FILESYS_INCLUDED
+#define PITH_CHARCONV_FILESYS_INCLUDED
+
+
+#include <general.h>
+
+
+/*
+ * Exported Prototypes
+ */
+char *fname_to_locale(char *);
+char *fname_to_utf8(char *);
+UCS read_a_wide_char(FILE *fp, void *input_cs);
+int write_a_wide_char(UCS ucs, FILE *fp);
+int our_stat(char *, struct stat *);
+FILE *our_fopen(char *, char *);
+int our_open(char *, int, mode_t);
+int our_creat(char *, mode_t);
+int our_mkdir(char *, mode_t);
+int our_rename(char *, char *);
+int our_unlink(char *);
+int our_link(char *, char *);
+int our_lstat(char *, struct stat *);
+int our_chmod(char *, mode_t);
+int our_chown(char *, uid_t, gid_t);
+int our_truncate(char *, off_t);
+int our_utime(char *, struct utimbuf *);
+int our_access(char *, int);
+char *our_getenv(char *);
+char *fgets_binary(char *, int, FILE *);
+
+
+#endif /* PITH_CHARCONV_FILESYS_INCLUDED */
diff --git a/pith/charconv/makefile.wnt b/pith/charconv/makefile.wnt
new file mode 100644
index 00000000..6700ec3b
--- /dev/null
+++ b/pith/charconv/makefile.wnt
@@ -0,0 +1,58 @@
+# $Id: makefile.wnt 14098 2005-10-03 18:54:13Z jpf@u.washington.edu $
+#
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+#
+#
+# Makefile for WIN NT version of libpithcc.lib
+#
+#
+CC=cl
+RM=del
+CP=copy
+RC=rc
+
+#includes symbol info for debugging
+CDEBUG= #-Zi -Od
+LDEBUG= /DEBUG /DEBUGTYPE:CV
+
+STDCFLAGS= -I..\..\include -I../../regex -nologo -MT -DWIN32 -DDOS -D_WINDOWS -DJOB_CONTROL -DMSC_MALLOC
+
+CFLAGS= $(CDEBUG) $(STDCFLAGS) $(NET) $(EXTRACFLAGS)
+
+LFLAGS= $(LDEBUG) $(EXTRALDFLAGS)
+
+RCFLAGS =
+
+# switches for library building
+LIBER=lib
+LIBARGS=/nologo /verbose
+
+HFILES= ../../include/system.h ../../include/general.h \
+ filesys.h utf8.h
+
+OFILES= filesys.obj utf8.obj
+
+all: libpithcc.lib
+
+.c.obj:
+ $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c
+
+$(OFILES): $(HFILES)
+
+libpithcc.lib: $(OFILES)
+ $(RM) libpithcc.lib || rem
+ $(LIBER) /out:libpithcc.lib $(OFILES)
+
+clean:
+ $(RM) *.lib
+ $(RM) *.obj
diff --git a/pith/charconv/utf8.c b/pith/charconv/utf8.c
new file mode 100644
index 00000000..411e1ddd
--- /dev/null
+++ b/pith/charconv/utf8.c
@@ -0,0 +1,2512 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: utf8.c 1184 2008-12-16 23:52:15Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+/* includable WITHOUT dependency on c-client */
+#include "../../c-client/mail.h"
+#include "../../c-client/utf8.h"
+
+#ifdef _WINDOWS
+/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */
+#undef ERROR
+#endif
+
+#include <system.h>
+
+#include "../../c-client/fs.h"
+
+/* includable WITHOUT dependency on pico */
+#include "../../pico/keydefs.h"
+
+#include "../osdep/collate.h"
+#include "../filttype.h"
+
+#include "utf8.h"
+
+#include <stdarg.h>
+
+
+unsigned single_width_chars_a_to_b(UCS *, int, int);
+
+
+static char locale_charmap[50];
+
+static int native_utf8;
+static void *display_data;
+
+void
+init_utf8_display(int utf8, void *rmap)
+{
+ native_utf8 = utf8;
+ display_data = rmap;
+}
+
+
+/*
+ * Argument is a UCS-4 wide character.
+ * Returns the environment dependent cell width of the
+ * character when printed to the screen.
+ * This will be -1 if the character is not printable.
+ * It will be >= zero if it is printable.
+ *
+ * Note that in the case it is not printable but it is still sent to
+ * Writechar, Writechar will print a '?' with width 1.
+ */
+int
+wcellwidth(UCS ucs)
+{
+ char dummy[32];
+ long w;
+
+ /*
+ * We believe that on modern unix systems wchar_t is a UCS-4 character.
+ * That's the assumption here.
+ */
+
+ if(native_utf8){ /* display is UTF-8 capable */
+ w = ucs4_width((unsigned long) ucs);
+ return((w & U4W_ERROR) ? -1 : w);
+ }
+ else if(display_data){
+ if(wtomb(dummy, ucs) < 0)
+ return(-1);
+ else{
+ w = ucs4_width((unsigned long) ucs);
+ return((w & U4W_ERROR) ? -1 : w);
+ }
+ }
+#ifndef _WINDOWS
+ else
+ return(wcwidth((wchar_t) ucs));
+#else
+ return(0);
+#endif
+}
+
+
+/*
+ * Argument is a UCS-4 wide character.
+ * It is converted to the multibyte version (for example UTF8 or EUC-JP).
+ * Dest is a buffer at least xx chars wide where the multi-byte version
+ * of the wide character will be written.
+ * The returned value is the number of bytes written to dest or -1
+ * if the conversion can't be done.
+ */
+int
+wtomb(char *dest, UCS ucs)
+{
+ /*
+ * We believe that on modern unix systems wchar_t is a UCS-4 character.
+ * That's the assumption here.
+ */
+
+ if(native_utf8){
+ unsigned char *newdptr;
+
+ newdptr = utf8_put((unsigned char *) dest, (unsigned long) ucs);
+ return((newdptr == (unsigned char *) dest) ? -1 : newdptr - (unsigned char *) dest);
+ }
+ else if(display_data){
+ unsigned long ucs4;
+ int ret;
+
+ ucs4 = (unsigned long) ucs;
+ ret = ucs4_rmaplen(&ucs4, 1, (unsigned short *) display_data, 0);
+ if(ret >= 0)
+ ucs4_rmapbuf((unsigned char *) dest, &ucs4, 1, (unsigned short *) display_data, 0);
+ else
+ ret = -1;
+
+ return(ret);
+ }
+ else
+ return(wcrtomb(dest, (wchar_t) ucs, NULL));
+}
+
+
+/*
+ * This function does not necessarily update inputp and remaining_octets, so
+ * don't rely on that. The c-client version does but the other doesn't.
+ */
+UCS
+mbtow(void *input_cs, unsigned char **inputp, unsigned long *remaining_octets)
+{
+ UCS ucs;
+
+ if(input_cs){
+ CHARSET *cast_input_cs;
+
+ cast_input_cs = (CHARSET *) input_cs;
+
+ switch((ucs = (UCS) ucs4_cs_get(cast_input_cs, inputp, remaining_octets))){
+ case U8G_ENDSTRG:
+ case U8G_ENDSTRI:
+ return(CCONV_NEEDMORE);
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON)
+ return(CCONV_BADCHAR);
+
+ return(ucs);
+ }
+ }
+ else{
+ size_t ret;
+ wchar_t w;
+
+ /*
+ * Warning: input_cs and remaining_octets are unused in this
+ * half of the if/else.
+ *
+ * Unfortunately, we can't tell the difference between a source string
+ * that is just not long enough and one that has characters that can't
+ * be converted even though it is long enough. We return NEEDMORE in both cases.
+ */
+ ret = mbstowcs(&w, (char *) (*inputp), 1);
+ if(ret == (size_t)(-1))
+ return(CCONV_NEEDMORE);
+ else{
+ ucs = (UCS) w;
+ return(ucs);
+ }
+ }
+}
+
+
+void
+set_locale_charmap(char *charmap)
+{
+ if(charmap){
+ strncpy(locale_charmap, charmap, sizeof(locale_charmap));
+ locale_charmap[sizeof(locale_charmap)-1] = '\0';
+ }
+ else
+ locale_charmap[0] = '\0';
+}
+
+
+/*
+ * This ensures that the string is UTF-8. If str is already a UTF-8 string,
+ * NULL is returned. Otherwise, an allocated string which is UTF-8 is returned.
+ * The caller is responsible for freeing the returned value.
+ *
+ * Args str -- the string to convert
+ */
+char *
+convert_to_utf8(char *str, char *fromcharset, int flags)
+{
+ char *ret = NULL;
+ char *fcharset;
+ SIZEDTEXT src, result;
+ const CHARSET *cs;
+ int try;
+
+ src.data = (unsigned char *) str;
+ src.size = strlen(str);
+
+ /* already UTF-8, return NULL */
+ if(!(flags & CU8_NOINFER)
+ && (cs = utf8_infercharset(&src))
+ && (cs->type == CT_ASCII || cs->type == CT_UTF8))
+ return(ret);
+
+ try = 1;
+ while(try < 5){
+ switch(try){
+ case 1:
+ fcharset = fromcharset;
+ if(fcharset && strucmp("UTF-8", fcharset) != 0)
+ break; /* give it a try */
+ else
+ try++; /* fall through */
+
+ case 2:
+ if(!(flags & CU8_NOINFER)){
+ fcharset = cs ? cs->name : NULL;
+ if(fcharset && strucmp("UTF-8", fcharset) != 0)
+ break;
+ else
+ try++; /* fall through */
+ }
+ else
+ try++; /* fall through */
+
+ case 3:
+ fcharset = locale_charmap;
+ if(fcharset && strucmp("UTF-8", fcharset) != 0)
+ break;
+ else
+ try++; /* fall through */
+
+ default:
+ fcharset = "ISO-8859-1"; /* this will "work" */
+ break;
+ }
+
+ memset(&result, 0, sizeof(result));
+
+ if(fcharset && utf8_text(&src, fcharset, &result, 0L)){
+ if(!(result.size == src.size && result.data == src.data)){
+ ret = (char *) fs_get((result.size+1) * sizeof(char));
+ strncpy(ret, (char *) result.data, result.size);
+ ret[result.size] = '\0';
+ }
+ /* else no conversion necessary */
+
+ return(ret);
+ }
+
+ try++;
+ }
+
+ /* won't make it to here */
+ return(ret);
+}
+
+
+/*
+ * Convert from UTF-8 to user's locale charset.
+ * This actually uses the wtomb routine to do the conversion, and that
+ * relies on setup_for_input_output having been called.
+ * If no conversion is necessary, NULL is returned, otherwise an allocated
+ * string in the locale charset is returned and the caller is responsible
+ * for freeing it.
+ */
+char *
+convert_to_locale(char *utf8str)
+{
+#define CHNK 500
+ char *inp, *retp, *ret = NULL;
+ CBUF_S cb;
+ int r, alloced;
+
+ if(native_utf8 || !utf8str || !utf8str[0])
+ return(NULL);
+
+ cb.cbuf[0] = '\0';
+ cb.cbufp = cb.cbufend = cb.cbuf;
+ inp = utf8str;
+
+ alloced = CHNK;
+ ret = (char *) fs_get(alloced * sizeof(char));
+ retp = ret;
+
+ /*
+ * There's gotta be a better way to do this but utf8_to_locale was
+ * available and everything looks like a nail when all you have
+ * is a hammer.
+ */
+ while(*inp){
+ /*
+ * We're placing the outgoing stream of characters in ret, a multi-byte
+ * array of characters in the user's locale charset. See if there is
+ * enough room for the next wide characters worth of output chars
+ * and allocate more space if not.
+ */
+ if((alloced - (retp-ret)) < MAX(MB_LEN_MAX,32)){
+ alloced += CHNK;
+ fs_resize((void **) &ret, alloced * sizeof(char));
+ }
+
+ r = utf8_to_locale((int) *inp++, &cb,
+ (unsigned char *) retp, alloced-(retp-ret));
+
+ retp += r;
+ }
+
+ *retp = '\0';
+
+ fs_resize((void **) &ret, strlen(ret)+1);
+
+ return(ret);
+}
+
+
+/*
+ * Pass in a stream of UTF-8 characters in 'c' and return obuf
+ * filled in with multi-byte characters. The return value is the
+ * number of valid characters in obuf to be used.
+ */
+int
+utf8_to_locale(int c, CBUF_S *cb, unsigned char obuf[], size_t obuf_size)
+{
+ int outchars = 0;
+
+ if(!(cb && cb->cbufp))
+ return(0);
+
+ if(cb->cbufp < cb->cbuf+sizeof(cb->cbuf)){
+ unsigned char *inputp;
+ unsigned long remaining_octets;
+ UCS ucs;
+
+ *(cb->cbufp)++ = (unsigned char) c;
+ inputp = cb->cbuf;
+ remaining_octets = (cb->cbufp - cb->cbuf) * sizeof(unsigned char);
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+
+ switch(ucs){
+ case U8G_ENDSTRG: /* incomplete character, wait */
+ case U8G_ENDSTRI: /* incomplete character, wait */
+ break;
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * None of these cases is supposed to happen. If it
+ * does happen then the input stream isn't UTF-8
+ * so something is wrong. Treat each character in the
+ * input buffer as a separate error character and
+ * print a '?' for each.
+ */
+ for(inputp = cb->cbuf; inputp < cb->cbufp; inputp++)
+ obuf[outchars++] = '?';
+
+ cb->cbufp = cb->cbuf;
+ }
+ else{
+ if(ucs >= 0x80 && wcellwidth(ucs) < 0){
+ /*
+ * This happens when we have a UTF-8 character that
+ * we aren't able to print in our locale. For example,
+ * if the locale is setup with the terminal
+ * expecting ISO-8859-1 characters then there are
+ * lots of UTF-8 characters that can't be printed.
+ * Print a '?' instead.
+ */
+ obuf[outchars++] = '?';
+ }
+ else{
+ /*
+ * Convert the ucs into the multibyte
+ * character that corresponds to the
+ * ucs in the users locale.
+ */
+ outchars = wtomb((char *) obuf, ucs);
+ if(outchars < 0){
+ obuf[0] = '?';
+ outchars = 1;
+ }
+ }
+
+ /* update the input buffer */
+ if(inputp >= cb->cbufp) /* this should be the case */
+ cb->cbufp = cb->cbuf;
+ else{ /* extra chars for some reason? */
+ unsigned char *q, *newcbufp;
+
+ newcbufp = (cb->cbufp - inputp) + cb->cbuf;
+ q = cb->cbuf;
+ while(inputp < cb->cbufp)
+ *q++ = *inputp++;
+
+ cb->cbufp = newcbufp;
+ }
+ }
+
+ break;
+ }
+ }
+ else{ /* error */
+ obuf[0] = '?';
+ outchars = 1;
+ cb->cbufp = cb->cbuf; /* start over */
+ }
+
+ return(outchars);
+}
+
+
+/*
+ * Returns the screen cells width of the UCS-4 string argument.
+ * The source string is zero terminated.
+ */
+unsigned
+ucs4_str_width(UCS *ucsstr)
+{
+ unsigned width = 0;
+ int w;
+
+ if(ucsstr)
+ while(*ucsstr){
+ w = wcellwidth(*ucsstr++);
+ if(w != U4W_CTLSRGT)
+ width += (w < 0 ? 1 : w);
+ }
+
+ return width;
+}
+
+
+/*
+ * Returns the screen cells width of the UCS-4 string argument
+ * from ucsstr[a] through (inclusive) ucsstr[b].
+ * No checking is done to make sure a starts in the middle
+ * of a UCS-4 array.
+ */
+unsigned
+ucs4_str_width_a_to_b(UCS *ucsstr, int a, int b)
+{
+ unsigned width = 0;
+ int i, w;
+
+ if(ucsstr)
+ for(i = a; i <= b && ucsstr[i]; i++){
+ w = wcellwidth(ucsstr[i]);
+ if(w != U4W_CTLSRGT)
+ width += (w < 0 ? 1 : w);
+ }
+
+ return width;
+}
+
+
+/*
+ * Returns the screen cells width of the UCS-4 string argument
+ * from ustart through (exclusive) uend.
+ * No checking is done to make sure it starts in the middle
+ * of a UCS-4 array.
+ */
+unsigned
+ucs4_str_width_ptr_to_ptr(UCS *ustart, UCS *uend)
+{
+ UCS *u;
+ unsigned width = 0;
+ int w;
+
+ if(!ustart)
+ return width;
+
+ if(ustart)
+ for(u = ustart; u < uend; u++){
+ w = wcellwidth(*u);
+ if(w != U4W_CTLSRGT)
+ width += (w < 0 ? 1 : w);
+ }
+
+ return(width);
+}
+
+
+/*
+ * Return the largest possible pointer into ucs4str so that the width
+ * of the string from ucs4str to the pointer (exclusive)
+ * is maxwidth or less. Also stops at a null character.
+ */
+UCS *
+ucs4_particular_width(UCS *ucs4str, int maxwidth)
+{
+ UCS *u;
+ int w_consumed = 0, w, done = 0;
+
+ u = ucs4str;
+
+ if(u)
+ while(!done && *u && w_consumed <= maxwidth){
+ w = wcellwidth(*u);
+ w = (w >= 0 ? w : 1);
+ if(w_consumed + w <= maxwidth){
+ w_consumed += w;
+ ++u;
+ }
+ else
+ ++done;
+ }
+
+ return(u);
+}
+
+
+/*
+ * Convert and copy a UTF-8 string into a UCS-4 NULL
+ * terminated array. Just like cpystr only it converts
+ * from UTF-8 to UCS-4.
+ *
+ * Returned UCS-4 string needs to be freed by caller.
+ */
+UCS *
+utf8_to_ucs4_cpystr(char *utf8src)
+{
+ size_t retsize;
+ UCS *ret = NULL;
+ UCS ucs;
+ unsigned long remaining_octets;
+ unsigned char *readptr;
+ size_t arrayindex;
+
+ /*
+ * We don't know how big to allocate the return array
+ * because variable numbers of octets in the src array
+ * will combine to make UCS-4 characters. The number of
+ * UCS-4 characters is less than or equal to the number
+ * of src characters, though.
+ */
+
+ if(!utf8src)
+ return NULL;
+
+ retsize = strlen(utf8src) + 1;
+
+ ret = (UCS *) fs_get(retsize * sizeof(*ret));
+ memset(ret, 0, retsize * sizeof(*ret));
+
+ readptr = (unsigned char *) utf8src;
+ remaining_octets = retsize-1;
+ arrayindex = 0;
+
+ while(remaining_octets > 0 && *readptr && arrayindex < retsize-1){
+ ucs = (UCS) utf8_get(&readptr, &remaining_octets);
+
+ if(ucs & U8G_ERROR || ucs == UBOGON)
+ remaining_octets = 0;
+ else
+ ret[arrayindex++] = ucs;
+ }
+
+ ret[arrayindex] = '\0';
+
+ /* get rid of excess size */
+ if(arrayindex+1 < retsize)
+ fs_resize((void **) &ret, (arrayindex + 1) * sizeof(*ret));
+
+ return ret;
+}
+
+
+/*
+ * Convert and copy a UCS-4 zero-terminated array into a UTF-8 NULL
+ * terminated string. Just like cpystr only it converts
+ * from UCS-4 to UTF-8.
+ *
+ * Returned UTF-8 string needs to be freed by caller.
+ */
+char *
+ucs4_to_utf8_cpystr(UCS *ucs4src)
+{
+ unsigned char *ret = NULL;
+ unsigned char *writeptr;
+ int i;
+
+ if(!ucs4src)
+ return NULL;
+
+ /*
+ * Over-allocate and then resize at the end.
+ */
+
+ /* count characters in source */
+ for(i = 0; ucs4src[i]; i++)
+ ;
+
+ ret = (unsigned char *) fs_get((6*i + 1) * sizeof(*ret));
+ memset(ret, 0, (6*i + 1) * sizeof(*ret));
+
+ writeptr = ret;
+ for(i = 0; ucs4src[i]; i++)
+ writeptr = utf8_put(writeptr, (unsigned long) ucs4src[i]);
+
+ /* get rid of excess size */
+ fs_resize((void **) &ret, (writeptr - ret + 1) * sizeof(*ret));
+
+ return ((char *) ret);
+}
+
+
+/*
+ * Similar to above but copy a fixed number of source
+ * characters instead of going until null terminator.
+ */
+char *
+ucs4_to_utf8_cpystr_n(UCS *ucs4src, int ucs4src_len)
+{
+ unsigned char *ret = NULL;
+ unsigned char *writeptr;
+ int i;
+
+ if(!ucs4src)
+ return NULL;
+
+ /*
+ * Over-allocate and then resize at the end.
+ */
+
+ ret = (unsigned char *) fs_get((6*ucs4src_len + 1) * sizeof(*ret));
+ memset(ret, 0, (6*ucs4src_len + 1) * sizeof(*ret));
+
+ writeptr = ret;
+ for(i = 0; i < ucs4src_len; i++)
+ writeptr = utf8_put(writeptr, (unsigned long) ucs4src[i]);
+
+ /* get rid of excess size */
+ fs_resize((void **) &ret, (writeptr - ret + 1) * sizeof(*ret));
+
+ return ((char *) ret);
+}
+
+
+#ifdef _WINDOWS
+/*
+ * Convert a UTF-8 argument into an LPTSTR version
+ * of that argument. The result is allocated here
+ * and should be freed by the caller.
+ */
+LPTSTR
+utf8_to_lptstr(LPSTR arg_utf8)
+{
+ int lptstr_len;
+ LPTSTR lptstr_ret = NULL;
+
+ lptstr_len = MultiByteToWideChar( CP_UTF8, 0, arg_utf8, -1, NULL, 0 );
+ if(lptstr_len > 0)
+ {
+ lptstr_ret = (LPTSTR)fs_get(lptstr_len * sizeof(TCHAR));
+ lptstr_len = MultiByteToWideChar( CP_UTF8, 0,
+ arg_utf8, -1, lptstr_ret, lptstr_len );
+ }
+
+ if(!lptstr_len)
+ {
+ /* check GetLastError()? */
+ lptstr_ret = (LPTSTR)fs_get(sizeof(TCHAR));
+ lptstr_ret[0] = 0;
+ }
+
+ return lptstr_ret;
+}
+
+
+/*
+ * Convert an LPTSTR argument into a UTF-8 version
+ * of that argument. The result is allocated here
+ * and should be freed by the caller.
+ */
+LPSTR
+lptstr_to_utf8(LPTSTR arg_lptstr)
+{
+ int utf8str_len;
+ LPSTR utf8str_ret = NULL;
+
+ utf8str_len = WideCharToMultiByte( CP_UTF8, 0, arg_lptstr, -1, NULL, 0, NULL, NULL );
+ if(utf8str_len > 0)
+ {
+ utf8str_ret = (LPSTR)fs_get(utf8str_len * sizeof(CHAR));
+ utf8str_len = WideCharToMultiByte( CP_UTF8, 0,
+ arg_lptstr, -1, utf8str_ret, utf8str_len, NULL, NULL );
+ }
+
+ if(!utf8str_len)
+ {
+ /* check GetLastError()? */
+ utf8str_ret = (LPSTR)fs_get(sizeof(CHAR));
+ utf8str_ret[0] = 0;
+ }
+
+ return utf8str_ret;
+}
+
+
+/*
+ * Convert a UCS4 argument into an LPTSTR version
+ * of that argument. The result is allocated here
+ * and should be freed by the caller.
+ */
+LPTSTR
+ucs4_to_lptstr(UCS *arg_ucs4)
+{
+ LPTSTR ret_lptstr = NULL;
+ size_t len;
+ size_t i;
+
+ if(arg_ucs4){
+ len = ucs4_strlen(arg_ucs4);
+ ret_lptstr = (LPTSTR) fs_get((len+1) * sizeof(TCHAR));
+ /* bogus conversion ignores UTF-16 */
+ for(i = 0; i < len; i++)
+ ret_lptstr[i] = arg_ucs4[i];
+
+ ret_lptstr[len] = '\0';
+ }
+
+ return(ret_lptstr);
+}
+
+
+/*
+ * Convert an LPTSTR argument into a UCS4 version
+ * of that argument. The result is MemAlloc'd here
+ * and should be freed by the caller.
+ */
+UCS *
+lptstr_to_ucs4(LPTSTR arg_lptstr)
+{
+ UCS *ret_ucs4 = NULL;
+ size_t len;
+ size_t i;
+
+ if(arg_lptstr){
+ len = _tcslen(arg_lptstr);
+ ret_ucs4 = (UCS *) fs_get((len+1)*sizeof(UCS));
+ /* bogus conversion ignores UTF-16 */
+ for(i = 0; i < len; i++)
+ ret_ucs4[i] = arg_lptstr[i];
+
+ ret_ucs4[len] = '\0';
+ }
+
+ return(ret_ucs4);
+}
+
+#endif /* _WINDOWS */
+
+
+/*
+ * Pass in a stream of UTF-8 characters 1-at-a-time in 'c' and return obuf
+ * 1-at-a-time filled in with UCS characters. The return value is the
+ * number of valid characters in obuf to be used. It can only
+ * be 1 or 0 characters since we're only getting one UTF-8 character
+ * at a time.
+ */
+int
+utf8_to_ucs4_oneatatime(int c, CBUF_S *cb, UCS *obuf, int *obufwidth)
+{
+ int width = 0, outchars = 0;
+
+ if(!(cb && cb->cbufp))
+ return(0);
+
+ if(cb->cbufp < cb->cbuf+sizeof(cb->cbuf)){
+ unsigned char *inputp;
+ unsigned long remaining_octets;
+ UCS ucs;
+
+ *cb->cbufp++ = (unsigned char) c;
+ inputp = cb->cbuf;
+ remaining_octets = (cb->cbufp - cb->cbuf) * sizeof(unsigned char);
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+
+ switch(ucs){
+ case U8G_ENDSTRG: /* incomplete character, wait */
+ case U8G_ENDSTRI: /* incomplete character, wait */
+ break;
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * None of these cases is supposed to happen. If it
+ * does happen then the input stream isn't UTF-8
+ * so something is wrong.
+ */
+ outchars++;
+ *obuf = '?';
+ cb->cbufp = cb->cbuf;
+ width = 1;
+ }
+ else{
+ outchars++;
+ if(ucs < 0x80 && ucs >= 0x20)
+ width = 1;
+
+ if(ucs >= 0x80 && (width=wcellwidth(ucs)) < 0){
+ /*
+ * This happens when we have a UTF-8 character that
+ * we aren't able to print in our locale. For example,
+ * if the locale is setup with the terminal
+ * expecting ISO-8859-1 characters then there are
+ * lots of UTF-8 characters that can't be printed.
+ * Print a '?' instead.
+ * Don't think this should happen in Windows.
+ */
+ *obuf = '?';
+ }
+ else{
+ *obuf = ucs;
+ }
+
+ /* update the input buffer */
+ if(inputp >= cb->cbufp) /* this should be the case */
+ cb->cbufp = cb->cbuf;
+ else{ /* extra chars for some reason? */
+ unsigned char *q, *newcbufp;
+
+ newcbufp = (cb->cbufp - inputp) + cb->cbuf;
+ q = cb->cbuf;
+ while(inputp < cb->cbufp)
+ *q++ = *inputp++;
+
+ cb->cbufp = newcbufp;
+ }
+ }
+
+ break;
+ }
+ }
+ else{ /* error */
+ *obuf = '?';
+ outchars = 1;
+ width = 1;
+ cb->cbufp = cb->cbuf; /* start over */
+ }
+
+ if(obufwidth)
+ *obufwidth = width;
+
+ return(outchars);
+}
+
+
+/*
+ * Return an allocated copy of a zero-terminated UCS-4 string.
+ */
+UCS *
+ucs4_cpystr(UCS *ucs4src)
+{
+ size_t arraysize;
+ UCS *ret = NULL;
+ size_t i;
+
+ if(!ucs4src)
+ return NULL;
+
+ arraysize = ucs4_strlen(ucs4src);
+
+ ret = (UCS *) fs_get((arraysize+1) * sizeof(*ret));
+ memset(ret, 0, (arraysize+1) * sizeof(*ret));
+
+ for(i = 0; i < arraysize; i++)
+ ret[i] = ucs4src[i];
+
+ return ret;
+}
+
+
+UCS *
+ucs4_strncpy(UCS *ucs4dst, UCS *ucs4src, size_t n)
+{
+ size_t i;
+
+ if(ucs4src && ucs4dst){
+ for(i = 0; i < n; i++){
+ ucs4dst[i] = ucs4src[i];
+ if(ucs4dst[i] == '\0')
+ break;
+ }
+ }
+
+ return ucs4dst;
+}
+
+
+UCS *
+ucs4_strncat(UCS *ucs4dst, UCS *ucs4src, size_t n)
+{
+ size_t i;
+ UCS *u;
+
+ if(ucs4src && ucs4dst){
+ for(u = ucs4dst; *u; u++)
+ ;
+
+ for(i = 0; i < n; i++){
+ u[i] = ucs4src[i];
+ if(u[i] == '\0')
+ break;
+ }
+
+ if(i == n)
+ u[i] = '\0';
+ }
+
+ return ucs4dst;
+}
+
+
+/*
+ * Like strlen only this returns the number of non-zero characters
+ * in a zero-terminated UCS-4 array.
+ */
+size_t
+ucs4_strlen(UCS *ucs4str)
+{
+ size_t i = 0;
+
+ if(ucs4str)
+ while(ucs4str[i])
+ i++;
+
+ return(i);
+}
+
+
+int
+ucs4_strcmp(UCS *s1, UCS *s2)
+{
+ for(; *s1 == *s2; s1++, s2++)
+ if(*s1 == '\0')
+ return 0;
+
+ return((*s1 < *s2) ? -1 : 1);
+}
+
+
+UCS *
+ucs4_strchr(UCS *s, UCS c)
+{
+ if(!s)
+ return NULL;
+
+ while(*s && *s != c)
+ s++;
+
+ if(*s || !c)
+ return s;
+ else
+ return NULL;
+}
+
+
+UCS *
+ucs4_strrchr(UCS *s, UCS c)
+{
+ UCS *ret = NULL;
+
+ if(!s)
+ return ret;
+
+ while(*s){
+ if(*s == c)
+ ret = s;
+
+ s++;
+ }
+
+ return ret;
+}
+
+
+/*
+ * Returns the screen cells width of the UTF-8 string argument.
+ */
+unsigned
+utf8_width(char *str)
+{
+ unsigned width = 0;
+ int this_width;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *readptr;
+
+ if(!(str && *str))
+ return(width);
+
+ readptr = str;
+ remaining_octets = readptr ? strlen(readptr) : 0;
+
+ while(remaining_octets > 0 && *readptr){
+
+ ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * This should not happen, but do something to handle it anyway.
+ * Treat each character as a single width character, which is what should
+ * probably happen when we actually go to write it out.
+ */
+ remaining_octets--;
+ readptr++;
+ this_width = 1;
+ }
+ else{
+ this_width = wcellwidth(ucs);
+
+ /*
+ * If this_width is -1 that means we can't print this character
+ * with our current locale. Writechar will print a '?'.
+ */
+ if(this_width < 0)
+ this_width = 1;
+ }
+
+ width += (unsigned) this_width;
+ }
+
+ return(width);
+}
+
+
+/*
+ * Copy UTF-8 characters from src into dst.
+ * This is intended to be used if you want to truncate a string at
+ * the start instead of the end. For example, you have a long string
+ * like
+ * this_is_a_long_string
+ * but not enough space to fit it into a particular field. You want to
+ * end up with
+ * s_a_long_string
+ * where that fits in a particular width. Perhaps you'd use this with ...
+ * to get
+ * ...s_a_long_string
+ * This right adjusts the end of the string in the width space and
+ * cuts it off at the start. If there is enough width for the whole
+ * string it will copy the string into dst with no padding.
+ *
+ * Copy enough characters so that the result will have screen width of
+ * want_width screen cells in current locale.
+ *
+ * Dstlen is the available space in dst. No more than dstlen bytes will be written
+ * to dst. This is just for protection, it shouldn't be relied on to
+ * do anything useful. Dstlen should be large enough. Otherwise you'll get
+ * characters truncated in the middle or something like that.
+ *
+ * Returned value is the number of bytes written to dst, not including
+ * the possible terminating null.
+ *
+ * If we can't hit want_width exactly because of double width characters
+ * then we will pad the end of the string with space in order to make
+ * the width exact.
+ */
+size_t
+utf8_to_width_rhs(char *dst, /* destination buffer */
+ char *src, /* source string */
+ size_t dstlen, /* space in dest */
+ unsigned want_width) /* desired screen width */
+{
+ int this_width;
+ unsigned width_consumed = 0;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *readptr, *goodreadptr, *savereadptr, *endptr;
+ size_t nb = 0;
+
+ if(!src){
+ if(dstlen > 0)
+ dst[0] = '\0';
+
+ return nb;
+ }
+
+ /*
+ * Start at the end of the source string and go backwards until we
+ * get to the desired width, but not more than the width.
+ */
+ readptr = src + strlen(src);
+ endptr = readptr;
+ goodreadptr = readptr;
+ width_consumed = 0;
+ savereadptr = readptr;
+
+ for(readptr = savereadptr-1; readptr >= src && width_consumed < want_width && (endptr - readptr) < dstlen;
+ readptr = savereadptr-1){
+
+ savereadptr = readptr;
+ remaining_octets = goodreadptr - readptr;
+ ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+
+ /*
+ * Handling the error case is tough because an error will be the normal thing that
+ * happens as we back through the string. So we're just going to punt on the
+ * error for now.
+ */
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ if(remaining_octets > 0){
+ /*
+ * This means there are some bad octets after this good
+ * character so things are not going to work out well.
+ * Bail out.
+ */
+ savereadptr = src; /* we're done */
+ }
+ else{
+ this_width = wcellwidth(ucs);
+
+ if(this_width < 0)
+ this_width = 1;
+
+ if(width_consumed + (unsigned) this_width <= want_width){ /* ok */
+ width_consumed += (unsigned) this_width;
+ goodreadptr = savereadptr;
+ }
+ else
+ savereadptr = src; /* we're done */
+ }
+ }
+ }
+
+ /*
+ * Copy characters from goodreadptr to endptr into dst.
+ */
+ nb = MIN(endptr-goodreadptr, dstlen-1);
+ strncpy(dst, goodreadptr, nb);
+ dst[nb] = '\0';
+
+ /*
+ * Pad out with spaces in order to hit width exactly.
+ */
+ while(width_consumed < want_width && nb < dstlen-1){
+ dst[nb++] = ' ';
+ dst[nb] = '\0';
+ width_consumed++;
+ }
+
+ return nb;
+}
+
+
+/*
+ * The arguments being converted are UTF-8 strings.
+ * This routine attempts to make it possible to use screen cell
+ * widths in a format specifier. In a one-byte per screen cell
+ * world we might have used %10.10s to cause a string to occupy
+ * 10 screen positions. Since the width and precision are really
+ * referring to numbers of bytes instead of screen positions that
+ * won't work with UTF-8 input. We emulate that behavior with
+ * the format string %w. %m.nw means to use the m and n as
+ * screen width indicators instead of bytes indicators.
+ *
+ * There is no reason to use this routine unless you want to use
+ * min field with or precision with the specifier. A plain %w without
+ * widths is equivalent exactly to a plain %s in a regular printf.
+ *
+ * Double-width characters complicate things. It may not be possible
+ * to satisfy the request exactly. For example, %3w for an input
+ * string that is made up of two double-width characters.
+ * This routine will arbitrarily use a trailing space character if
+ * needed to make the width come out correctly where a half of a
+ * double-width character would have been needed. We'll see how
+ * that works for us.
+ *
+ * %w only works for strings (it's a %s replacement).
+ *
+ * Buffer overflow is handled by the size argument. %.30s will work
+ * to limit a particular string to 30 bytes, but you lose that
+ * ability with %w, since it may write more than precision bytes
+ * in order to get to the desired width. It is best to choose
+ * size large enough so that it doesn't come into play, otherwise
+ * it may be possible to get partial UTF-8 characters because of
+ * the truncation.
+ *
+ * The return value isn't quite the same as the return value
+ * of snprintf. It is the number of bytes written, not counting
+ * the trailing null, just like snprintf. However, if it is
+ * truncated due to size then the output is size, not the
+ * number of characters that would have been written.
+ */
+int
+utf8_snprintf(char *dest, size_t size, char *fmt, ...)
+{
+ char newfmt[100], buf[20], *q, *pdest, *width_str, *end;
+ char *start_of_specifier;
+ char *input_str;
+ int int_arg;
+ double double_arg;
+ void *ptr_arg;
+ unsigned got_width;
+ int more_flags, ret, w;
+ int min_field_width, field_precision, modifier;
+ int flags_minus, flags_plus, flags_space, flags_zero, flags_pound;
+ va_list args;
+
+ newfmt[0] = '\0';
+ q = newfmt;
+
+ pdest = dest;
+
+#define IS_ROOM_IN_DEST(n_more_chars) \
+ ((pdest - dest + (n_more_chars) <= size) ? 1 : 0)
+
+ /*
+ * Strategy: Look through the fmt string for %w's. Replace the
+ * %w's in the format string with %s's but with possibly different
+ * width and precision arguments which will make it come out right.
+ * Then call the regular system vsnprintf with the altered format
+ * string but same arguments.
+ *
+ * That would be nice but it doesn't quite work. Why? Because a
+ * %*w will need to have the value in the integer argument the *
+ * refers to modified. Can't do it as far as I can tell. Or we could
+ * remove the integer argument somehow before calling printf. Can't
+ * do it. Or we could somehow add an additional conversion specifier
+ * that caused nothing to be printed but ate up the integer arg.
+ * Can't figure out how to do that either.
+ *
+ * Since we can't figure out how to do it, the alternative is to
+ * construct the result one piece at a time, pasting together the
+ * pieces from the different conversions.
+ */
+ va_start(args, fmt);
+
+ while(*fmt && IS_ROOM_IN_DEST(1)){
+ if(*fmt == '%'){
+ start_of_specifier = fmt++;
+
+ min_field_width = field_precision = -1;
+ flags_minus = flags_plus = flags_space = flags_zero = flags_pound = 0;
+
+ /* flags */
+ more_flags = 1;
+ while(more_flags){
+ switch(*fmt){
+ case '-':
+ flags_minus++;
+ fmt++;
+ break;
+
+ case '+':
+ flags_plus++;
+ fmt++;
+ break;
+
+ case ' ':
+ flags_space++;
+ fmt++;
+ break;
+
+ case '0':
+ flags_zero++;
+ fmt++;
+ break;
+
+ case '#':
+ flags_pound++;
+ fmt++;
+ break;
+
+ default:
+ more_flags = 0;
+ break;
+ }
+ }
+
+ /* minimum field width */
+ if(*fmt == '*'){
+ min_field_width = va_arg(args, int);
+ fmt++;
+ }
+ else if(*fmt >= '0' && *fmt <= '9'){
+ width_str = fmt;
+ while (*fmt >= '0' && *fmt <= '9')
+ fmt++;
+
+ strncpy(buf, width_str, MIN(fmt-width_str,sizeof(buf)));
+ if(sizeof(buf) > fmt-width_str)
+ buf[fmt-width_str] = '\0';
+
+ buf[sizeof(buf)-1] = '\0';
+
+ min_field_width = atoi(width_str);
+ }
+
+ /* field precision */
+ if(*fmt == '.'){
+ fmt++;
+ if(*fmt == '*'){
+ field_precision = va_arg(args, int);
+ fmt++;
+ }
+ else if(*fmt >= '0' && *fmt <= '9'){
+ width_str = fmt;
+ while (*fmt >= '0' && *fmt <= '9')
+ fmt++;
+
+ strncpy(buf, width_str, MIN(fmt-width_str,sizeof(buf)));
+ if(sizeof(buf) > fmt-width_str)
+ buf[fmt-width_str] = '\0';
+
+ buf[sizeof(buf)-1] = '\0';
+
+ field_precision = atoi(width_str);
+ }
+ }
+
+ /* length modifier */
+ if(*fmt == 'h' || *fmt == 'l' || *fmt == 'L')
+ modifier = *fmt++;
+
+ /* conversion character */
+ switch(*fmt){
+ case 'w':
+ /*
+ * work with va_arg(char *) to figure out width
+ * and precision needed to produce the screen width
+ * and precision asked for in %w using some of the
+ * utf8 width routines we have.
+ */
+
+ input_str = va_arg(args, char *);
+ if(field_precision >=0 || min_field_width >= 0)
+ w = utf8_width(input_str);
+
+ if(field_precision >= 0){
+ if(w <= field_precision)
+ field_precision = -1; /* print it all */
+ else{
+ /*
+ * We need to cut off some of the input_str
+ * in this case.
+ */
+ end = utf8_count_forw_width(input_str, field_precision, &got_width);
+ field_precision = (int) (end - input_str);
+ /* new w with this field_precision */
+ w = got_width;
+ }
+ }
+
+ /* need some padding */
+ if(min_field_width >= 0)
+ min_field_width = ((field_precision >= 0) ? field_precision : strlen(input_str)) +
+ MAX(0, min_field_width - w);
+
+ /*
+ * Now we just need to get the new format string
+ * set correctly in newfmt.
+ */
+ q = newfmt;
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '%';
+
+ if(flags_minus && q-newfmt < sizeof(newfmt))
+ *q++ = '-';
+ if(flags_plus && q-newfmt < sizeof(newfmt))
+ *q++ = '+';
+ if(flags_space && q-newfmt < sizeof(newfmt))
+ *q++ = ' ';
+ if(flags_zero && q-newfmt < sizeof(newfmt))
+ *q++ = '0';
+ if(flags_pound && q-newfmt < sizeof(newfmt))
+ *q++ = '#';
+
+ if(min_field_width >= 0){
+ snprintf(buf, sizeof(buf), "%d", min_field_width);
+ sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt));
+ }
+
+ if(field_precision >= 0){
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '.';
+
+ snprintf(buf, sizeof(buf), "%d", field_precision);
+ sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt));
+ }
+
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = 's';
+
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '\0';
+
+ snprintf(pdest, size - (pdest-dest), newfmt, input_str);
+ pdest += strlen(pdest);
+
+ break;
+
+ case '\0':
+ fmt--;
+ break;
+
+ default:
+ /* make a new format which leaves out the dynamic '*' arguments */
+ q = newfmt;
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '%';
+
+ if(flags_minus && q-newfmt < sizeof(newfmt))
+ *q++ = '-';
+ if(flags_plus && q-newfmt < sizeof(newfmt))
+ *q++ = '+';
+ if(flags_space && q-newfmt < sizeof(newfmt))
+ *q++ = ' ';
+ if(flags_zero && q-newfmt < sizeof(newfmt))
+ *q++ = '0';
+ if(flags_pound && q-newfmt < sizeof(newfmt))
+ *q++ = '#';
+
+ if(min_field_width >= 0){
+ snprintf(buf, sizeof(buf), "%d", min_field_width);
+ sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt));
+ }
+
+ if(field_precision >= 0){
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '.';
+
+ snprintf(buf, sizeof(buf), "%d", field_precision);
+ sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt));
+ }
+
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = *fmt;
+
+ if(q-newfmt < sizeof(newfmt))
+ *q++ = '\0';
+
+ switch(*fmt){
+ case 'd': case 'i': case 'o':
+ case 'x': case 'X': case 'u': case 'c':
+ int_arg = va_arg(args, int);
+ snprintf(pdest, size - (pdest-dest), newfmt, int_arg);
+ pdest += strlen(pdest);
+ break;
+
+ case 's':
+ input_str = va_arg(args, char *);
+ snprintf(pdest, size - (pdest-dest), newfmt, input_str);
+ pdest += strlen(pdest);
+ break;
+
+ case 'f': case 'e': case 'E':
+ case 'g': case 'G':
+ double_arg = va_arg(args, double);
+ snprintf(pdest, size - (pdest-dest), newfmt, double_arg);
+ pdest += strlen(pdest);
+ break;
+
+ case 'p':
+ ptr_arg = va_arg(args, void *);
+ snprintf(pdest, size - (pdest-dest), newfmt, ptr_arg);
+ pdest += strlen(pdest);
+ break;
+
+ case '%':
+ if(IS_ROOM_IN_DEST(1))
+ *pdest++ = '%';
+
+ break;
+
+ default:
+ /* didn't think of this type */
+ assert(0);
+ break;
+ }
+
+ break;
+ }
+
+ fmt++;
+ }
+ else{
+ if(IS_ROOM_IN_DEST(1))
+ *pdest++ = *fmt++;
+ }
+ }
+
+ ret = pdest - dest;
+
+ if(IS_ROOM_IN_DEST(1))
+ *pdest++ = '\0';
+
+ va_end(args);
+
+ return ret;
+}
+
+
+/*
+ * Copy UTF-8 characters from src into dst.
+ * Copy enough characters so that the result will have (<=) screen width of
+ * want_width screen cells in current locale.
+ *
+ * Dstlen is the available space in dst. No more than dstlen bytes will be written
+ * to dst.
+ *
+ * Returned value is the number of bytes written to dst, not including
+ * the possible terminating null.
+ * Got_width is another returned value. It is the width in screen cells of
+ * the string placed in dst. It will be the same as want_width if there
+ * are enough characters in the src to do that and if the character widths
+ * hit the width exactly. It will be less than want_width if we run out
+ * of src characters or if the next character width would skip over the
+ * width we want, because it is double width.
+ *
+ * Zero width characters are collected and included at the end of the string.
+ * That is, if we make it to want_width but there is still a zero length
+ * character sitting in src, we add that to dst. This might be an accent
+ * or something like that.
+ */
+size_t
+utf8_to_width(char *dst, /* destination buffer */
+ char *src, /* source string */
+ size_t dstlen, /* space in dst */
+ unsigned want_width, /* desired screen width */
+ unsigned *got_width) /* returned screen width in dst */
+{
+ int this_width;
+ unsigned width_consumed = 0;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *writeptr, *readptr, *savereadptr, *endptr;
+ int ran_out_of_space = 0;
+
+ readptr = src;
+
+ remaining_octets = readptr ? strlen(readptr) : 0;
+
+ writeptr = dst;
+ endptr = writeptr + dstlen;
+
+ if(readptr && writeptr){
+ while(width_consumed <= want_width && remaining_octets > 0 && writeptr < dst + dstlen && !ran_out_of_space){
+ savereadptr = readptr;
+ ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+
+ if(ucs & U8G_ERROR || ucs == UBOGON)
+ remaining_octets = 0;
+ else{
+ this_width = wcellwidth(ucs);
+
+ /*
+ * If this_width is -1 that means we can't print this character
+ * with our current locale. Writechar will print a '?'.
+ */
+ if(this_width < 0)
+ this_width = 1;
+
+ if(width_consumed + (unsigned) this_width <= want_width){
+ /* append this utf8 character to dst if it will fit */
+ if(writeptr + (readptr - savereadptr) < endptr){
+ width_consumed += this_width;
+ while(savereadptr < readptr)
+ *writeptr++ = *savereadptr++;
+ }
+ else
+ ran_out_of_space++; /* no more utf8 to dst */
+ }
+ else
+ remaining_octets = 0; /* we're done */
+ }
+ }
+
+ if(writeptr < endptr)
+ *writeptr = '\0';
+ }
+
+ if(got_width)
+ *got_width = width_consumed;
+
+ return(writeptr ? (writeptr - dst) : 0);
+}
+
+
+/*
+ * Str is a UTF-8 string.
+ * Count forward width screencell positions and return a pointer to the
+ * end of the string that is width wide.
+ * The returned pointer points at the next character (where the null would
+ * be placed).
+ *
+ * Got_width is another returned value. It is the width in screen cells of
+ * the string from str to the returned pointer. It will be the same as
+ * want_width if there are enough characters in the str to do that
+ * and if the character widths hit the width exactly. It will be less
+ * than want_width if we run out of characters or if the next character
+ * width would skip over the width we want, because it is double width.
+ */
+char *
+utf8_count_forw_width(char *str, unsigned want_width, unsigned *got_width)
+{
+ int this_width;
+ unsigned width_consumed = 0;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *readptr;
+ char *retptr;
+
+ retptr = readptr = str;
+
+ remaining_octets = readptr ? strlen(readptr) : 0;
+
+ while(width_consumed <= want_width && remaining_octets > 0){
+
+ ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * This should not happen, but do something to handle it anyway.
+ * Treat each character as a single width character, which is what should
+ * probably happen when we actually go to write it out.
+ */
+ remaining_octets--;
+ readptr++;
+ this_width = 1;
+ }
+ else{
+ this_width = wcellwidth(ucs);
+
+ /*
+ * If this_width is -1 that means we can't print this character
+ * with our current locale. Writechar will print a '?'.
+ */
+ if(this_width < 0)
+ this_width = 1;
+ }
+
+ if(width_consumed + (unsigned) this_width <= want_width){
+ width_consumed += (unsigned) this_width;
+ retptr = readptr;
+ }
+ else
+ remaining_octets = 0; /* we're done */
+ }
+
+ if(got_width)
+ *got_width = width_consumed;
+
+ return(retptr);
+}
+
+
+/*
+ * Copy a null terminator into a UTF-8 string in place so that the string is
+ * no more than a certain screen width wide. If the string is already less
+ * than or equal in width to the requested width, no change is made.
+ *
+ * The actual width accomplished is returned. Note that it may be less than
+ * max_width due to double width characters as well as due to the fact that
+ * it fits wholly in the max_width.
+ *
+ * Returned value is the actual screen width of str when done.
+ *
+ * A side effect is that a terminating null may have been written into
+ * the passed in string.
+ */
+unsigned
+utf8_truncate(char *str, unsigned max_width)
+{
+ int this_width;
+ unsigned width_consumed = 0;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *readptr, *savereadptr;
+
+ readptr = str;
+
+ remaining_octets = readptr ? strlen(readptr) : 0;
+
+ if(readptr){
+ while(width_consumed <= max_width && remaining_octets > 0){
+
+ savereadptr = readptr;
+ ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * This should not happen, but do something to handle it anyway.
+ * Treat each character as a single width character, which is what should
+ * probably happen when we actually go to write it out.
+ */
+ remaining_octets--;
+ readptr++;
+ this_width = 1;
+ }
+ else{
+ this_width = wcellwidth(ucs);
+
+ /*
+ * If this_width is -1 that means we can't print this character
+ * with our current locale. Writechar will print a '?'.
+ */
+ if(this_width < 0)
+ this_width = 1;
+ }
+
+ if(width_consumed + (unsigned) this_width <= max_width){
+ width_consumed += (unsigned) this_width;
+ }
+ else{
+ remaining_octets = 0; /* we're done */
+ *savereadptr = '\0';
+ }
+ }
+ }
+
+ return(width_consumed);
+}
+
+
+/*
+ * Copy UTF-8 characters from src into dst.
+ * Copy enough characters so that the result will have screen width of
+ * want_width screen cells in current locale.
+ * If there aren't enough characters in src to get to want_width, pad on
+ * left or right according to left_adjust argument.
+ *
+ * Dstlen is the available space in dst. No more than dstlen bytes will be written
+ * to dst. Dst will be null terminated if there is enough room, but not
+ * if that would overflow dst's len.
+ *
+ * Returned value is the number of bytes written to dst, not including
+ * the possible terminating null.
+ */
+size_t
+utf8_pad_to_width(char *dst, /* destination buffer */
+ char *src, /* source string */
+ size_t dstlen, /* space in dst */
+ unsigned want_width, /* desired screen width */
+ int left_adjust) /* adjust left or right in want_width columns */
+{
+ unsigned got_width = 0;
+ int need_more, howmany;
+ size_t len_left, bytes_used;
+
+ bytes_used = utf8_to_width(dst, src, dstlen, want_width, &got_width);
+ len_left = dstlen - bytes_used;
+
+ need_more = want_width - got_width;
+ howmany = MIN(need_more, len_left);
+
+ if(howmany > 0){
+ char *end, *newend, *p, *q;
+
+ end = dst + bytes_used;
+ newend = end + howmany;
+ if(left_adjust){
+ /*
+ * Add padding to end of string. Simply append
+ * the needed number of spaces, or however many will fit
+ * if we don't have enough space.
+ */
+ for(q = end; q < newend; q++)
+ *q = ' ';
+ }
+ else{
+ /*
+ * Add padding to start of string.
+ */
+
+ /* slide existing string over */
+ for(p = end - 1, q = newend - 1; p >= dst; p--, q--)
+ *q = *p;
+
+ /* fill rest with spaces */
+ for(; q >= dst; q--)
+ *q = ' ';
+ }
+
+ bytes_used += howmany;
+ }
+
+ if(bytes_used < dstlen)
+ dst[bytes_used] = '\0';
+
+ return(bytes_used);
+}
+
+
+/*
+ * Str is a UTF-8 string.
+ * Start_here is a pointer into the string. It points one position past
+ * the last byte that should be considered a part of the length string.
+ * Count back want_width screencell positions and return a pointer to the
+ * start of the string that is want_width wide and ends with start_here.
+ *
+ * Since characters may be more than one cell width wide we may end up
+ * skipping over the exact width. That is, if we need to we'll go back
+ * too far (by one cell width). Account for that in the call by looking
+ * at got_width.
+ *
+ * Note that this call gives a possible got_width == want_width+1 as
+ * opposed to utf8_count_forw_width which gives got_width == want-1 instead.
+ * That was just what was needed at the time, maybe it needs to be
+ * optional.
+ */
+char *
+utf8_count_back_width(char *str, char *start_here, unsigned want_width, unsigned *got_width)
+{
+ unsigned width_consumed = 0;
+ int this_width;
+ UCS ucs;
+ unsigned long remaining_octets;
+ char *ptr, *savereadptr, *goodreadptr;
+
+ savereadptr = start_here;
+ goodreadptr = start_here;
+
+ for(ptr = savereadptr - 1; width_consumed < want_width && ptr >= str; ptr = savereadptr - 1){
+
+ savereadptr = ptr;
+ remaining_octets = goodreadptr - ptr;
+ ucs = (UCS) utf8_get((unsigned char **) &ptr, &remaining_octets);
+
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ if(remaining_octets > 0){
+ /*
+ * This means there are some bad octets after this good
+ * character so things are not going to work out well.
+ * Bail out.
+ */
+ savereadptr = str; /* we're done */
+ }
+ else{
+ this_width = wcellwidth(ucs);
+
+ /*
+ * If this_width is -1 that means we can't print this character
+ * with our current locale. Writechar will print a '?'.
+ */
+ if(this_width < 0)
+ this_width = 1;
+
+ width_consumed += (unsigned) this_width;
+ goodreadptr = savereadptr;
+ }
+ }
+ }
+
+ if(got_width)
+ *got_width = width_consumed;
+
+ return(savereadptr);
+}
+
+
+/*----------------------------------------------------------------------
+ copy the source string onto the destination string returning with
+ the destination string pointer at the end of the destination text
+
+ motivation for this is to avoid twice passing over a string that's
+ being appended to twice (i.e., strcpy(t, x); t += strlen(t))
+
+ This doesn't really belong here but it is used here.
+ ----*/
+void
+sstrncpy(char **d, char *s, int n)
+{
+ while(n-- > 0 && (**d = *s++) != '\0')
+ (*d)++;
+}
+
+
+/*
+ * If use_system_routines is set then NULL is the return value and it is
+ * not an error. Display_charmap and keyboard_charmap should come over as
+ * malloced strings and will be filled in with the result.
+ *
+ * Returns a void pointer to the input_cs CHARSET which is
+ * passed to mbtow via kbseq().
+ * If !use_system_routines && NULL is returned, that is an error and err should
+ * have a message.
+ * display_charmap and keyboard_charmap should be malloced data and may be
+ * realloced and changed here.
+ */
+int
+setup_for_input_output(int use_system_routines, char **display_charmap,
+ char **keyboard_charmap, void **input_cs_arg, char **err)
+{
+ const CHARSET *cs;
+ const CHARSET *input_cs = NULL;
+ int already_tried = 0;
+ int supported = 0;
+ char buf[1000];
+
+#define cpstr(s) strcpy((char *)fs_get(1+strlen(s)), s)
+
+ if(err)
+ *err = NULL;
+
+ if(!display_charmap || !keyboard_charmap || !input_cs_arg){
+ *err = cpstr("Bad call to setup_for_input_output");
+ return(-1);
+ }
+
+ if(use_system_routines){
+#if PREREQ_FOR_SYS_TRANSLATION
+ char *dcm;
+
+ dcm = nl_langinfo_codeset_wrapper();
+ dcm = dcm ? dcm : "US-ASCII";
+
+ init_utf8_display(0, NULL);
+ if(*display_charmap){
+ if(dcm && strucmp(*display_charmap, dcm)){
+ snprintf(buf, sizeof(buf),
+ _("Display character set \"%s\" is ignored when using system translation"),
+ *display_charmap);
+
+ *err = cpstr(buf);
+ }
+
+ fs_give((void **) display_charmap);
+ }
+
+ if(*keyboard_charmap){
+ if(!*err && dcm && strucmp(*keyboard_charmap, dcm)){
+ snprintf(buf, sizeof(buf),
+ _("Keyboard character set \"%s\" is ignored when using system translation"),
+ *keyboard_charmap);
+
+ *err = cpstr(buf);
+ }
+
+ fs_give((void **) keyboard_charmap);
+ }
+
+ *display_charmap = cpstr(dcm);
+ *keyboard_charmap = cpstr(dcm);
+#else
+ *err = cpstr("Bad call to setup_for_input_output");
+#endif
+
+ *input_cs_arg = NULL;
+ return(0);
+ }
+
+
+try_again1:
+ if(!(*display_charmap))
+ *display_charmap = cpstr("US-ASCII");
+
+ if(!(*keyboard_charmap))
+ *keyboard_charmap = cpstr(*display_charmap);
+
+ if(*keyboard_charmap){
+ supported = input_charset_is_supported(*keyboard_charmap);
+
+ if(supported){
+ if(!strucmp(*keyboard_charmap, "utf-8"))
+ input_cs = utf8_charset(*keyboard_charmap);
+ else if((cs = utf8_charset(*keyboard_charmap)) != NULL)
+ input_cs = cs;
+ }
+ else{
+ if(err && !*err){
+ int iso2022jp = 0;
+
+ if(!strucmp(*keyboard_charmap, "ISO-2022-JP"))
+ iso2022jp = 1;
+
+ snprintf(buf, sizeof(buf),
+ /* TRANSLATORS: The first argument is the name of the character
+ set the user is trying to use (which is unsupported by alpine).
+ The second argument is " (except for posting)" if they are
+ trying to use ISO-2022-JP for something other than posting. */
+ _("Character set \"%s\" is unsupported%s, using US-ASCII"),
+ *keyboard_charmap,
+ iso2022jp ? _(" (except for posting)") : "");
+
+ *err = cpstr(buf);
+ }
+
+ input_cs = NULL;
+ fs_give((void **) keyboard_charmap);
+ *keyboard_charmap = cpstr("US-ASCII");
+ if(!already_tried){
+ already_tried++;
+ goto try_again1;
+ }
+ }
+ }
+
+
+try_again2:
+ if(!(*display_charmap))
+ *display_charmap = cpstr("US-ASCII");
+
+ if(*display_charmap){
+ supported = output_charset_is_supported(*display_charmap);
+ if(supported){
+ if(!strucmp(*display_charmap, "utf-8"))
+ init_utf8_display(1, NULL);
+ else if((cs = utf8_charset(*display_charmap)) != NULL)
+ init_utf8_display(0, utf8_rmap_gen(cs, NULL));
+ }
+ else{
+ if(err && !*err){
+ int iso2022jp = 0;
+
+ if(!strucmp(*display_charmap, "ISO-2022-JP"))
+ iso2022jp = 1;
+
+ snprintf(buf, sizeof(buf),
+ _("Character set \"%s\" is unsupported%s, using US-ASCII"),
+ *display_charmap,
+ iso2022jp ? _(" (except for posting)") : "");
+
+ *err = cpstr(buf);
+ }
+
+ fs_give((void **) display_charmap);
+ if(!already_tried){
+ already_tried++;
+ goto try_again2;
+ }
+ }
+ }
+ else{
+ if(err && !*err)
+ *err = cpstr(_("Help, can't figure out display character set or even use US-ASCII."));
+ }
+
+#undef cpstr
+
+ *input_cs_arg = (void *) input_cs;
+
+ return(0);
+}
+
+
+int
+input_charset_is_supported(char *input_charset)
+{
+ const CHARSET *cs;
+
+ if(!(input_charset && *input_charset))
+ return 0;
+
+ if(!strucmp(input_charset, "utf-8"))
+ return 1;
+
+ if((cs = utf8_charset(input_charset)) != NULL){
+
+ /*
+ * This was true 2006-09-25.
+ */
+ switch(cs->type){
+ case CT_ASCII: case CT_1BYTE0: case CT_1BYTE:
+ case CT_1BYTE8: case CT_EUC: case CT_DBYTE:
+ case CT_DBYTE2: case CT_SJIS: case CT_UCS2:
+ case CT_UCS4: case CT_UTF16:
+ return 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+int
+output_charset_is_supported(char *output_charset)
+{
+ const CHARSET *cs;
+
+ if(!(output_charset && *output_charset))
+ return 0;
+
+ if(!strucmp(output_charset, "utf-8"))
+ return 1;
+
+ if((cs = utf8_charset(output_charset)) != NULL && utf8_rmap_gen(cs, NULL))
+ return 1;
+
+ return 0;
+}
+
+
+int
+posting_charset_is_supported(char *posting_charset)
+{
+ return(posting_charset && *posting_charset
+ && (!strucmp(posting_charset, "ISO-2022-JP")
+ || output_charset_is_supported(posting_charset)));
+}
+
+
+/*
+ * This function is only defined in this special case and so calls
+ * to it should be wrapped in the same macro conditionals.
+ *
+ * Returns the default display charset for a UNIX terminal emulator,
+ * it is what nl_langinfo(CODESET) should return but we need to
+ * wrap nl_langinfo because we know of strange behaving implementations.
+ */
+#if !defined(_WINDOWS) && HAVE_LANGINFO_H && defined(CODESET)
+char *
+nl_langinfo_codeset_wrapper(void)
+{
+ char *ret = NULL;
+
+ ret = nl_langinfo(CODESET);
+
+ /*
+ * If the value returned from nl_langinfo() is not a real charset,
+ * see if we can figure out what they meant. If we can't figure it
+ * out return NULL and let the caller decide what to do.
+ */
+ if(ret && *ret && !output_charset_is_supported(ret)){
+ if(!strcmp("ANSI_X3.4-1968", ret)
+ || !strcmp("646", ret)
+ || !strcmp("ASCII", ret)
+ || !strcmp("C", ret)
+ || !strcmp("POSIX", ret))
+ ret = "US-ASCII";
+ else if(!strucmp(ret, "UTF8"))
+ ret = "UTF-8";
+ else if(!strucmp(ret, "EUCJP"))
+ ret = "EUC-JP";
+ else if(!strucmp(ret, "EUCKP"))
+ ret = "EUC-KP";
+ else if(!strucmp(ret, "SJIS"))
+ ret = "SHIFT-JIS";
+ else if(strstr(ret, "8859")){
+ char *p;
+
+ /* check for digits after 8859 */
+ p = strstr(ret, "8859");
+ p += 4;
+ if(!isdigit(*p))
+ p++;
+
+ if(isdigit(*p)){
+ static char buf[12];
+
+ memset(buf, 0, sizeof(buf));
+ strncpy(buf, "ISO-8859-", sizeof(buf));
+ buf[9] = *p++;
+ if(isdigit(*p))
+ buf[10] = *p;
+
+ ret = buf;
+ }
+ }
+ }
+
+ if(ret && !output_charset_is_supported(ret))
+ ret = NULL;
+
+ return(ret);
+}
+#endif
+
+
+/*
+ * Convert the "orig" string from UTF-8 to "charset". If no conversion is
+ * needed the return value will point to orig. If a conversion is done,
+ * the return string should be freed by the caller.
+ * If not possible, returns NULL.
+ */
+char *
+utf8_to_charset(char *orig, char *charset, int report_err)
+{
+ SIZEDTEXT src, dst;
+ char *ret = orig;
+
+ if(!charset || !charset[0] || !orig || !orig[0] || !strucmp(charset, "utf-8"))
+ return ret;
+
+ src.size = strlen(orig);
+ src.data = (unsigned char *) orig;
+
+ if(!strucmp(charset, "us-ascii")){
+ size_t i;
+
+ for(i = 0; i < src.size; i++)
+ if(src.data[i] & 0x80)
+ return NULL;
+
+ return ret;
+ }
+
+ /*
+ * This works for ISO-2022-JP because of special code in utf8_cstext
+ * but not for other 2022 charsets.
+ */
+ memset(&dst, 0, sizeof(dst));
+ if(utf8_cstext(&src, charset, &dst, report_err ? 0 : '?') && dst.size > 0 && dst.data)
+ ret = (char *) dst.data; /* c-client already null terminates it */
+ else
+ ret = NULL;
+
+ if((unsigned char *) ret != dst.data && dst.data)
+ fs_give((void **) &dst.data);
+
+ return ret;
+}
+
+
+/*
+ * Turn a number into a string with comma's
+ *
+ * Args: number -- The long to be turned into a string.
+ *
+ * Result: pointer to static string representing number with commas
+ * Can use up to 3 comatose results at once.
+ */
+char *
+comatose(long int number)
+{
+ long i, x, done_one;
+ static char buf[3][50];
+ static int whichbuf = 0;
+ char *b;
+
+ whichbuf = (whichbuf + 1) % 3;
+
+ if(number == 0){
+ strncpy(buf[whichbuf], "0", sizeof(buf[0]));
+ buf[whichbuf][sizeof(buf[0])-1] = '\0';
+ return(buf[whichbuf]);
+ }
+
+ done_one = 0;
+ b = buf[whichbuf];
+ for(i = 1000000000; i >= 1; i /= 1000) {
+ x = number / i;
+ number = number % i;
+ if(x != 0 || done_one) {
+ if(b != buf[whichbuf] && (b-buf[whichbuf]) < sizeof(buf[0]))
+ *b++ = ',';
+
+ snprintf(b, sizeof(buf[0])-(b-buf[whichbuf]), done_one ? "%03ld" : "%ld", x);
+ b += strlen(b);
+ done_one = 1;
+ }
+ }
+
+ if(b-buf[whichbuf] < sizeof(buf[0]))
+ *b = '\0';
+
+ return(buf[whichbuf]);
+}
+
+
+/* leave out the commas */
+char *
+tose(long int number)
+{
+ static char buf[3][50];
+ static int whichbuf = 0;
+
+ whichbuf = (whichbuf + 1) % 3;
+
+ snprintf(buf[whichbuf], sizeof(buf[0]), "%ld", number);
+
+ return(buf[whichbuf]);
+}
+
+
+/*
+ * line_paint - where the real work of managing what is displayed gets done.
+ */
+void
+line_paint(int offset, /* current dot offset into vl */
+ struct display_line *displ,
+ int *passwd) /* flag to hide display of chars */
+{
+ int i, w, w2, already_got_one = 0;
+ int vfirst, vlast, dfirst, dlast, vi, di;
+ int new_vbase;
+ unsigned (*width_a_to_b)(UCS *, int, int);
+
+ /*
+ * Set passwd to 10 in caller if you want to conceal the
+ * password but not print asterisks for feedback.
+ *
+ * Set passwd to 1 in caller to conceal by printing asterisks.
+ */
+ if(passwd && *passwd >= 10){ /* don't show asterisks */
+ if(*passwd > 10)
+ return;
+ else
+ *passwd = 11; /* only blat once */
+
+ i = 0;
+ (*displ->movecursor)(displ->row, displ->col);
+ while(i++ <= displ->dwid)
+ (*displ->writechar)(' ');
+
+ (*displ->movecursor)(displ->row, displ->col);
+ return;
+ }
+
+ if(passwd && *passwd)
+ width_a_to_b = single_width_chars_a_to_b;
+ else
+ width_a_to_b = ucs4_str_width_a_to_b;
+
+ /*
+ * vl is the virtual line (the actual data). We operate on it by typing
+ * characters to be added and deleting and so forth. In this routine we
+ * copy a subset of those UCS-4 characters in vl into dl, the display
+ * array, and show that subset on the screen.
+ *
+ * Offset is the location of the cursor in vl.
+ *
+ * We will display the string starting from vbase.
+ * We have dwid screen cells to work in.
+ * We may have to adjust vbase in order to display the
+ * part of the string that contains the cursor.
+ *
+ * We'll make the display look like
+ * vl a b c d e f g h i j k l m
+ * xxxxxxxxxxxxx <- width dwid window
+ * < d e f g h >
+ * |
+ * vbase
+ * The < will be there if vbase > 0.
+ * The > will be there if the string from vbase to the
+ * end can't all fit in the window.
+ */
+
+ memset(displ->dl, 0, displ->dlen * sizeof(UCS));
+
+ /*
+ * Adjust vbase so offset is not out of the window to the right.
+ * (The +2 in w + 2 is for a possible " >" if the string goes past
+ * the right hand edge of the window and if the last visible character
+ * is double wide. We don't want the offset to be under that > character.)
+ */
+ for(w = (*width_a_to_b)(displ->vl, displ->vbase, offset);
+ w + 2 + (displ->vbase ? 1 : 0) > displ->dwid;
+ w = (*width_a_to_b)(displ->vl, displ->vbase, offset)){
+ /*
+ * offset is off the window to the right
+ * It looks like a b c d e f g h
+ * | |
+ * vbase offset
+ * and offset is either past the right edge,
+ * or right at the right edge (and maybe under >),
+ * or one before right at the edge (and maybe on space
+ * for half a character).
+ *
+ * Since the characters may be double width it is slightly
+ * complicated to figure out how far to increase vbase.
+ * We're going to scoot over past width w/2 characters and
+ * then see if that's sufficient.
+ */
+ new_vbase = displ->vbase + 1;
+ for(w2 = (*width_a_to_b)(displ->vl, displ->vbase+1, new_vbase);
+ w2 < displ->dwid/2;
+ w2 = (*width_a_to_b)(displ->vl, displ->vbase+1, new_vbase))
+ new_vbase++;
+
+ displ->vbase = new_vbase;
+ }
+
+ /* adjust so offset is not out of the window to the left */
+ while(displ->vbase > 0 && displ->vbase >= offset){
+ /* add about dwid/2 more width */
+ new_vbase = displ->vbase - 1;
+ for(w2 = (*width_a_to_b)(displ->vl, new_vbase, displ->vbase);
+ w2 < (displ->dwid+1)/2 && new_vbase > 0;
+ w2 = (*width_a_to_b)(displ->vl, new_vbase, displ->vbase))
+ new_vbase--;
+
+ /* but don't let it get too small, recheck off right end */
+ for(w = (*width_a_to_b)(displ->vl, new_vbase, offset);
+ w + 2 + (new_vbase ? 1 : 0) > displ->dwid;
+ w = (*width_a_to_b)(displ->vl, displ->vbase, offset))
+ new_vbase++;
+
+ displ->vbase = MAX(new_vbase, 0);
+ }
+
+ if(displ->vbase == 1 && ((passwd && *passwd) || wcellwidth(displ->vl[0]) == 1))
+ displ->vbase = 0;
+
+ vfirst = displ->vbase;
+ dfirst = 0;
+ if(displ->vbase > 0){ /* off screen cue left */
+ dfirst = 1; /* index which matches vfirst */
+ displ->dl[0] = '<';
+ }
+
+ vlast = displ->vused-1; /* end */
+ w = (*width_a_to_b)(displ->vl, vfirst, vlast);
+
+ if(w + dfirst > displ->dwid){ /* off window right */
+
+ /* find last ucs character to be printed */
+ while(w + dfirst > displ->dwid - 1) /* -1 for > */
+ w = (*width_a_to_b)(displ->vl, vfirst, --vlast);
+
+ /* worry about double-width characters */
+ if(w + dfirst == displ->dwid - 1){ /* no prob, hit it exactly */
+ dlast = dfirst + vlast - vfirst + 1; /* +1 for > */
+ displ->dl[dlast] = '>';
+ }
+ else{
+ dlast = dfirst + vlast - vfirst + 1;
+ displ->dl[dlast++] = ' ';
+ displ->dl[dlast] = '>';
+ }
+ }
+ else
+ dlast = dfirst + vlast - vfirst;
+
+ /*
+ * Copy the relevant part of the virtual line into the display line.
+ */
+ for(vi = vfirst, di = dfirst; vi <= vlast; vi++, di++)
+ if(passwd && *passwd)
+ displ->dl[di] = '*'; /* to conceal password */
+ else
+ displ->dl[di] = displ->vl[vi];
+
+ /*
+ * Add spaces to clear the rest of the line.
+ * We have dwid total space to fill.
+ */
+ w = (*width_a_to_b)(displ->dl, 0, dlast); /* width through dlast */
+ for(di = dlast+1, i = displ->dwid - w; i > 0 ; i--)
+ displ->dl[di++] = ' ';
+
+ /*
+ * Draw from left to right, skipping until we get to
+ * something that is different. Characters may be different
+ * widths than they were initially so paint from there the
+ * rest of the way.
+ */
+ for(di = 0; displ->dl[di]; di++){
+ if(already_got_one || displ->dl[di] != displ->olddl[di]){
+ /* move cursor first time */
+ if(!already_got_one++){
+ w = (di > 0) ? (*width_a_to_b)(displ->dl, 0, di-1) : 0;
+ (*displ->movecursor)(displ->row, displ->col + w);
+ }
+
+ (*displ->writechar)(displ->dl[di]);
+ displ->olddl[di] = displ->dl[di];
+ }
+ }
+
+ memset(&displ->olddl[di], 0, (displ->dlen - di) * sizeof(UCS));
+
+ /*
+ * Move the cursor to the offset.
+ *
+ * The offset is relative to the start of the virtual array. We need
+ * to find the location on the screen. The offset into the display array
+ * will be offset-vbase+dfirst. We want to be at the start of that
+ * character, so we need to find the width of all the characters up
+ * to that point.
+ */
+ w = (offset > 0) ? (*width_a_to_b)(displ->dl, 0, offset-displ->vbase+dfirst-1) : 0;
+
+ (*displ->movecursor)(displ->row, displ->col + w);
+}
+
+
+/*
+ * This is just like ucs4_str_width_a_to_b() except all of the characters
+ * are assumed to be of width 1. This is for printing out *'s when user
+ * enters a password, while still managing to use the same code to do the
+ * display.
+ */
+unsigned
+single_width_chars_a_to_b(UCS *ucsstr, int a, int b)
+{
+ unsigned width = 0;
+ int i;
+
+ if(ucsstr)
+ for(i = a; i <= b && ucsstr[i]; i++)
+ width++;
+
+ return width;
+}
diff --git a/pith/charconv/utf8.h b/pith/charconv/utf8.h
new file mode 100644
index 00000000..d22a8a7c
--- /dev/null
+++ b/pith/charconv/utf8.h
@@ -0,0 +1,106 @@
+/*-----------------------------------------------------------------------
+ $Id: utf8.h 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $
+ -----------------------------------------------------------------------*/
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CHARCONV_UTF8_INCLUDED
+#define PITH_CHARCONV_UTF8_INCLUDED
+
+
+#include <general.h>
+#include "../filttype.h"
+
+
+/* flags for convert_to_utf8 */
+#define CU8_NONE 0x00
+#define CU8_NOINFER 0x01 /* Not ok to infer charset */
+
+
+/*
+ * The data in vl and dl is UCS-4 characters.
+ * They are arrays of size vlen and dlen of unsigned longs.
+ */
+struct display_line {
+ int row, col; /* where display starts */
+ UCS *vl; /* virtual line, the actual data string */
+ int vlen; /* size of vl array */
+ int vused; /* elements of vl in use */
+ int vbase; /* index into array, first virtual char on display */
+ UCS *dl; /* visible part of virtual line on display */
+ UCS *olddl;
+ int dlen; /* size of dl array */
+ int dwid; /* screenwidth avail for dl */
+ void (*movecursor)(int, int);
+ void (*writechar)(UCS);
+};
+
+
+/*
+ * Exported Prototypes
+ */
+void init_utf8_display(int, void *);
+int wcellwidth(UCS);
+int wtomb(char *, UCS);
+UCS mbtow(void *, unsigned char **, unsigned long *);
+void set_locale_charmap(char *);
+char *convert_to_utf8(char *, char *, int);
+char *convert_to_locale(char *);
+int utf8_to_locale(int c, CBUF_S *cb, unsigned char obuf[], size_t obuf_size);
+unsigned ucs4_str_width(UCS *);
+unsigned ucs4_str_width_a_to_b(UCS *, int, int);
+unsigned ucs4_str_width_ptr_to_ptr(UCS *, UCS *);
+UCS *ucs4_particular_width(UCS*, int);
+UCS *utf8_to_ucs4_cpystr(char *);
+char *ucs4_to_utf8_cpystr(UCS *);
+char *ucs4_to_utf8_cpystr_n(UCS *, int);
+#ifdef _WINDOWS
+LPTSTR utf8_to_lptstr(LPSTR);
+LPSTR lptstr_to_utf8(LPTSTR);
+LPTSTR ucs4_to_lptstr(UCS *);
+UCS *lptstr_to_ucs4(LPTSTR);
+#endif /* _WINDOWS */
+int utf8_to_ucs4_oneatatime(int, CBUF_S *, UCS *, int *);
+size_t ucs4_strlen(UCS *s);
+int ucs4_strcmp(UCS *s1, UCS *s2);
+UCS *ucs4_cpystr(UCS *s);
+UCS *ucs4_strncpy(UCS *ucs4dst, UCS *ucs4src, size_t n);
+UCS *ucs4_strncat(UCS *ucs4dst, UCS *ucs4src, size_t n);
+UCS *ucs4_strchr(UCS *s, UCS c);
+UCS *ucs4_strrchr(UCS *s, UCS c);
+unsigned utf8_width(char *);
+size_t utf8_to_width_rhs(char *, char *, size_t, unsigned);
+int utf8_snprintf(char *, size_t, char *, ...);
+size_t utf8_to_width(char *, char *, size_t, unsigned, unsigned *);
+size_t utf8_pad_to_width(char *, char *, size_t, unsigned, int);
+unsigned utf8_truncate(char *, unsigned);
+char *utf8_count_back_width(char *, char *, unsigned, unsigned *);
+char *utf8_count_forw_width(char *, unsigned, unsigned *);
+void sstrncpy(char **, char *, int);
+int setup_for_input_output(int, char **, char **, void **, char **);
+int input_charset_is_supported(char *);
+int output_charset_is_supported(char *);
+int posting_charset_is_supported(char *);
+char *utf8_to_charset(char *, char *, int);
+char *comatose(long);
+char *tose(long);
+void line_paint(int, struct display_line *, int *);
+
+#if !defined(_WINDOWS) && HAVE_LANGINFO_H && defined(CODESET)
+char *nl_langinfo_codeset_wrapper(void);
+#endif
+
+
+#endif /* PITH_CHARCONV_UTF8_INCLUDED */
diff --git a/pith/charset.c b/pith/charset.c
new file mode 100644
index 00000000..6177c7c4
--- /dev/null
+++ b/pith/charset.c
@@ -0,0 +1,929 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: charset.c 1032 2008-04-11 00:30:04Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/charset.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/escapes.h"
+#include "../pith/mimedesc.h"
+#include "../pith/filter.h"
+#include "../pith/string.h"
+#include "../pith/options.h"
+
+
+/*
+ * Internal prototypes
+ */
+int rfc1522_token(char *, int (*)(int), char *, char **);
+int rfc1522_valtok(int);
+int rfc1522_valenc(int);
+int rfc1522_valid(char *, char **, char **, char **, char **);
+void rfc1522_copy_and_transliterate(unsigned char *, unsigned char **, size_t,
+ unsigned char *, unsigned long, char *);
+unsigned char *rfc1522_encoded_word(unsigned char *, int, char *);
+char *rfc1522_8bit(void *, int);
+char *rfc1522_binary(void *, int);
+
+
+char *
+body_charset(MAILSTREAM *stream, long int msgno, unsigned char *section)
+{
+ BODY *body;
+ char *charset;
+
+
+ if((body = mail_body(stream, msgno, section)) && body->type == TYPETEXT){
+ if(!(charset = parameter_val(body->parameter, "charset")))
+ charset = cpystr("US-ASCII");
+
+ return(charset);
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * Copies the source string into allocated space with the 8-bit EUC codes
+ * (on Unix) or the Shift-JIS (on PC) converted into ISO-2022-JP.
+ * Caller is responsible for freeing the result.
+ */
+unsigned char *
+trans_euc_to_2022_jp(unsigned char *src)
+{
+ size_t len, alloc;
+ unsigned char *rv, *p, *q;
+ int inside_esc_seq = 0;
+ int c1 = -1; /* remembers first of pair for Shift-JIS */
+
+ if(!src)
+ return(NULL);
+
+ len = strlen((char *) src);
+
+ /*
+ * Worst possible increase is every other character an 8-bit character.
+ * In that case, each of those gets 6 extra charactes for the escape
+ * sequences. We're not too concerned about the extra length because
+ * these are relatively short strings.
+ */
+ alloc = len + 1 + ((len+1)/2) * 6;
+ rv = (unsigned char *) fs_get(alloc * sizeof(char));
+
+ for(p = src, q = rv; *p; p++){
+ if(inside_esc_seq){
+ if(c1 >= 0){ /* second of a pair? */
+ int adjust = *p < 159;
+ int rowOffset = c1 < 160 ? 112 : 176;
+ int cellOffset = adjust ? (*p > 127 ? 32 : 31) : 126;
+
+ *q++ = ((c1 - rowOffset) << 1) - adjust;
+ *q++ = *p - cellOffset;
+ c1 = -1;
+ }
+ else if(*p & 0x80){
+ *q++ = (*p & 0x7f);
+ }
+ else{
+ *q++ = '\033';
+ *q++ = '(';
+ *q++ = 'B';
+ *q++ = (*p);
+ c1 = -1;
+ inside_esc_seq = 0;
+ }
+ }
+ else{
+ if(*p & 0x80){
+ *q++ = '\033';
+ *q++ = '$';
+ *q++ = 'B';
+ *q++ = (*p & 0x7f);
+ inside_esc_seq = 1;
+ }
+ else{
+ *q++ = (*p);
+ }
+ }
+ }
+
+ if(inside_esc_seq){
+ *q++ = '\033';
+ *q++ = '(';
+ *q++ = 'B';
+ }
+
+ *q = '\0';
+
+ return(rv);
+}
+
+
+/*
+ * * * * * * * * * RFC 1522 support routines * * * * * * * *
+ *
+ * RFC 1522 support is *very* loosely based on code contributed
+ * by Lars-Erik Johansson <lej@cdg.chalmers.se>. Thanks to Lars-Erik,
+ * and appologies for taking such liberties with his code.
+ */
+
+#define RFC1522_INIT "=?"
+#define RFC1522_INIT_L 2
+#define RFC1522_TERM "?="
+#define RFC1522_TERM_L 2
+#define RFC1522_DLIM "?"
+#define RFC1522_DLIM_L 1
+#define RFC1522_MAXW 256 /* RFC's say 75, but no senders seem to care*/
+#define ESPECIALS "()<>@,;:\"/[]?.="
+#define RFC1522_OVERHEAD(S) (RFC1522_INIT_L + RFC1522_TERM_L + \
+ (2 * RFC1522_DLIM_L) + strlen(S) + 1);
+#define RFC1522_ENC_CHAR(C) (((C) & 0x80) || !rfc1522_valtok(C) \
+ || (C) == '_' )
+
+/*
+ * rfc1522_decode_to_utf8 - try to decode the given source string ala RFC 2047
+ * (obsoleted RFC 1522) into the given destination buffer,
+ * encoded in UTF-8.
+ *
+ * How large should d be? The decoded string of octets will fit in
+ * the same size string as the source string. However, because we're
+ * translating that into UTF-8 the result may expand. Currently the
+ * Thai character set has single octet characters which expand to
+ * three octets in UTF-8. So it would be safe to use 3 * strlen(s)
+ * for the size of d. One can imagine a currently non-existent
+ * character set that expanded to 4 octets instead, so use 4 to be
+ * super safe.
+ *
+ * Returns: pointer to either the destination buffer containing the
+ * decoded text, or a pointer to the source buffer if there was
+ * no valid 'encoded-word' found during scanning.
+ */
+unsigned char *
+rfc1522_decode_to_utf8(unsigned char *d, size_t len, char *s)
+{
+ unsigned char *rv = NULL, *p;
+ char *start = s, *sw, *enc, *txt, *ew, **q, *lang;
+ char *cset;
+ unsigned long l;
+ int i;
+
+ *d = '\0'; /* init destination */
+
+ while(s && (sw = strstr(s, RFC1522_INIT))){
+ if(!rv) /* there's something to do, init it */
+ rv = d;
+ /* validate the rest of the encoded-word */
+ if(rfc1522_valid(sw, &cset, &enc, &txt, &ew)){
+ /*
+ * We may have been putting off copying the first part of the
+ * source while waiting to see if we have to copy at all.
+ */
+ if(rv == d && s != start){
+ rfc1522_copy_and_transliterate(rv, &d, len, (unsigned char *) start,
+ sw - start, NULL);
+ s = sw;
+ }
+
+ /* copy everything between s and sw to destination */
+ for(i = 0; &s[i] < sw; i++)
+ if(!isspace((unsigned char)s[i])){ /* if some non-whitespace */
+ while(s < sw && d-rv<len-1)
+ *d++ = (unsigned char) *s++;
+
+ break;
+ }
+
+ enc[-1] = txt[-1] = ew[0] = '\0'; /* tie off token strings */
+
+ if((lang = strchr(cset, '*')) != NULL)
+ *lang++ = '\0';
+
+ /* based on encoding, write the encoded text to output buffer */
+ switch(*enc){
+ case 'Q' : /* 'Q' encoding */
+ case 'q' :
+ /* special hocus-pocus to deal with '_' exception, too bad */
+ for(l = 0L, i = 0; txt[l]; l++)
+ if(txt[l] == '_')
+ i++;
+
+ if(i){
+ q = (char **) fs_get((i + 1) * sizeof(char *));
+ for(l = 0L, i = 0; txt[l]; l++)
+ if(txt[l] == '_'){
+ q[i++] = &txt[l];
+ txt[l] = SPACE;
+ }
+
+ q[i] = NULL;
+ }
+ else
+ q = NULL;
+
+ if((p = rfc822_qprint((unsigned char *)txt, strlen(txt), &l)) != NULL){
+ rfc1522_copy_and_transliterate(rv, &d, len, p, l, cset);
+ fs_give((void **)&p); /* free encoded buf */
+ }
+ else{
+ if(q)
+ fs_give((void **) &q);
+
+ goto bogus;
+ }
+
+ if(q){ /* restore underscores */
+ for(i = 0; q[i]; i++)
+ *(q[i]) = '_';
+
+ fs_give((void **)&q);
+ }
+
+ break;
+
+ case 'B' : /* 'B' encoding */
+ case 'b' :
+ if((p = rfc822_base64((unsigned char *) txt, strlen(txt), &l)) != NULL){
+ rfc1522_copy_and_transliterate(rv, &d, len, p, l, cset);
+ fs_give((void **)&p); /* free encoded buf */
+ }
+ else
+ goto bogus;
+
+ break;
+
+ default:
+ rfc1522_copy_and_transliterate(rv, &d, len, (unsigned char *) txt,
+ strlen(txt), NULL);
+ dprint((1, "RFC1522_decode: Unknown ENCODING: %s\n",
+ enc ? enc : "?"));
+ break;
+ }
+
+ /* restore trompled source string */
+ enc[-1] = txt[-1] = '?';
+ ew[0] = RFC1522_TERM[0];
+
+ /* advance s to start of text after encoded-word */
+ s = ew + RFC1522_TERM_L;
+
+ if(lang)
+ lang[-1] = '*';
+ }
+ else{
+ /*
+ * Found intro, but bogus data followed, treat it as normal text.
+ */
+ l = (sw - s) + RFC1522_INIT_L;
+ rfc1522_copy_and_transliterate(rv, &d, len, (unsigned char *) s, l, NULL);
+ for(; isspace((unsigned char) *(s+l)) && d-rv<len-1;l++)
+ *d++ = *(s+l); /* copy any trailing space */
+ rv[len-1] = '\0';
+ *d = '\0';
+ s += l;
+ }
+ }
+
+ if(rv){
+ if(s && *s){ /* copy remaining text */
+ rfc1522_copy_and_transliterate(rv, &d, len, (unsigned char *) s, strlen(s), NULL);
+ rv[len-1] = '\0';
+ }
+ }
+ else if(s){
+ rv = d;
+ rfc1522_copy_and_transliterate(rv, &d, len, (unsigned char *) s, strlen(s), NULL);
+ rv[len-1] = '\0';
+ }
+
+ return(rv ? rv : (unsigned char *) start);
+
+ bogus:
+ dprint((1, "RFC1522_decode: BOGUS INPUT: -->%s<--\n",
+ start ? start : "?"));
+ return((unsigned char *) start);
+}
+
+
+/*
+ * rfc1522_token - scan the given source line up to the end_str making
+ * sure all subsequent chars are "valid" leaving endp
+ * a the start of the end_str.
+ * Returns: TRUE if we got a valid token, FALSE otherwise
+ */
+int
+rfc1522_token(char *s, int (*valid) (int), char *end_str, char **endp)
+{
+ while(*s){
+ if((char) *s == *end_str /* test for matching end_str */
+ && ((end_str[1])
+ ? !strncmp((char *)s + 1, end_str + 1, strlen(end_str + 1))
+ : 1)){
+ *endp = s;
+ return(TRUE);
+ }
+
+ if(!(*valid)(*s++)) /* test for valid char */
+ break;
+ }
+
+ return(FALSE);
+}
+
+
+/*
+ * rfc1522_valtok - test for valid character in the RFC 1522 encoded
+ * word's charset and encoding fields.
+ */
+int
+rfc1522_valtok(int c)
+{
+ return(!(c == SPACE || iscntrl(c & 0x7f) || strindex(ESPECIALS, c)));
+}
+
+
+/*
+ * rfc1522_valenc - test for valid character in the RFC 1522 encoded
+ * word's encoded-text field.
+ */
+int
+rfc1522_valenc(int c)
+{
+ return(!(c == '?' || c == SPACE) && isprint((unsigned char)c));
+}
+
+
+/*
+ * rfc1522_valid - validate the given string as to it's rfc1522-ness
+ */
+int
+rfc1522_valid(char *s, char **charset, char **enc, char **txt, char **endp)
+{
+ char *c, *e, *t, *p;
+ int rv;
+
+ rv = rfc1522_token(c = s+RFC1522_INIT_L, rfc1522_valtok, RFC1522_DLIM, &e)
+ && rfc1522_token(++e, rfc1522_valtok, RFC1522_DLIM, &t)
+ && rfc1522_token(++t, rfc1522_valenc, RFC1522_TERM, &p)
+ && p - s <= RFC1522_MAXW;
+
+ if(charset)
+ *charset = c;
+
+ if(enc)
+ *enc = e;
+
+ if(txt)
+ *txt = t;
+
+ if(endp)
+ *endp = p;
+
+ return(rv);
+}
+
+
+/*
+ * rfc1522_copy_and_transliterate - copy given buf to destination buffer
+ * as UTF-8 characters
+ */
+void
+rfc1522_copy_and_transliterate(unsigned char *rv,
+ unsigned char **d,
+ size_t len,
+ unsigned char *s,
+ unsigned long l,
+ char *cset)
+{
+ unsigned long i;
+ SIZEDTEXT src, xsrc;
+
+ src.data = s;
+ src.size = l;
+ memset(&xsrc, 0, sizeof(SIZEDTEXT));
+
+ /* transliterate decoded segment to utf-8 */
+ if(cset){
+ if(strucmp((char *) cset, "us-ascii")
+ && strucmp((char *) cset, "utf-8")){
+ if(utf8_charset(cset)){
+ if(!utf8_text(&src, cset, &xsrc, 0L)){
+ /* should not happen */
+ panic("c-client failed to transliterate recognized characterset");
+ }
+ }
+ else{
+ /* non-xlatable charset */
+ for(i = 0; i < l; i++)
+ if(src.data[i] & 0x80){
+ xsrc.data = (unsigned char *) fs_get((l+1) * sizeof(unsigned char));
+ xsrc.size = l;
+ for(i = 0; i < l; i++)
+ xsrc.data[i] = (src.data[i] & 0x80) ? '?' : src.data[i];
+
+ break;
+ }
+ }
+ }
+ }
+ else{
+ const CHARSET *cs;
+
+ src.data = s;
+ src.size = strlen((char *) s);
+
+ if((cs = utf8_infercharset(&src))){
+ if(!(cs->type == CT_ASCII || cs->type == CT_UTF8)){
+ if(!utf8_text_cs(&src, cs, &xsrc, 0L, 0L)){
+ /* should not happen */
+ panic("c-client failed to transliterate recognized characterset");
+ }
+ }
+ }
+ else if((cset=ps_global->VAR_UNK_CHAR_SET)
+ && strucmp((char *) cset, "us-ascii")
+ && strucmp((char *) cset, "utf-8")
+ && utf8_charset(cset)){
+ if(!utf8_text(&src, cset, &xsrc, 0L)){
+ /* should not happen */
+ panic("c-client failed to transliterate recognized character set");
+ }
+ }
+ else{
+ /* unknown bytes - mask off high bit chars */
+ for(i = 0; i < l; i++)
+ if(src.data[i] & 0x80){
+ xsrc.data = (unsigned char *) fs_get((l+1) * sizeof(unsigned char));
+ xsrc.size = l;
+ for(i = 0; i < l; i++)
+ xsrc.data[i] = (src.data[i] & 0x80) ? '?' : src.data[i];
+
+ break;
+ }
+ }
+ }
+
+ if(xsrc.data){
+ s = xsrc.data;
+ l = xsrc.size;
+ }
+
+ i = MIN(l,len-1-((*d)-rv));
+ strncpy((char *) (*d), (char *) s, i);
+ (*d)[i] = '\0';
+ *d += l; /* advance dest ptr to EOL */
+ if((*d)-rv > len-1)
+ *d = rv+len-1;
+
+ if(xsrc.data && src.data != xsrc.data)
+ fs_give((void **) &xsrc.data);
+}
+
+
+
+/*
+ * rfc1522_encode - encode the given source string ala RFC 1522,
+ * IF NECESSARY, into the given destination buffer.
+ * Don't bother copying if it turns out encoding
+ * isn't necessary.
+ *
+ * Returns: pointer to either the destination buffer containing the
+ * encoded text, or a pointer to the source buffer if we didn't
+ * have to encode anything.
+ */
+char *
+rfc1522_encode(char *d, size_t dlen, unsigned char *s, char *charset)
+{
+ unsigned char *p, *q;
+ int n;
+
+ if(!s)
+ return((char *) s);
+
+ if(!charset)
+ charset = UNKNOWN_CHARSET;
+
+ /* look for a reason to encode */
+ for(p = s, n = 0; *p; p++)
+ if((*p) & 0x80){
+ n++;
+ }
+ else if(*p == RFC1522_INIT[0]
+ && !strncmp((char *) p, RFC1522_INIT, RFC1522_INIT_L)){
+ if(rfc1522_valid((char *) p, NULL, NULL, NULL, (char **) &q))
+ p = q + RFC1522_TERM_L - 1; /* advance past encoded gunk */
+ }
+ else if(*p == ESCAPE && match_escapes((char *)(p+1))){
+ n++;
+ }
+
+ if(n){ /* found, encoding to do */
+ char *rv = d, *t,
+ enc = (n > (2 * (p - s)) / 3) ? 'B' : 'Q';
+
+ while(*s){
+ if(d-rv < dlen-1-(RFC1522_INIT_L+2*RFC1522_DLIM_L+1)){
+ sstrncpy(&d, RFC1522_INIT, dlen-(d-rv)); /* insert intro header, */
+ sstrncpy(&d, charset, dlen-(d-rv)); /* character set tag, */
+ sstrncpy(&d, RFC1522_DLIM, dlen-(d-rv)); /* and encoding flavor */
+ if(dlen-(d-rv) > 0)
+ *d++ = enc;
+
+ sstrncpy(&d, RFC1522_DLIM, dlen-(d-rv));
+ }
+
+ /*
+ * feed lines to encoder such that they're guaranteed
+ * less than RFC1522_MAXW.
+ */
+ p = rfc1522_encoded_word(s, enc, charset);
+ if(enc == 'B') /* insert encoded data */
+ sstrncpy(&d, t = rfc1522_binary(s, p - s), dlen-1-(d-rv));
+ else /* 'Q' encoding */
+ sstrncpy(&d, t = rfc1522_8bit(s, p - s), dlen-1-(d-rv));
+
+ sstrncpy(&d, RFC1522_TERM, dlen-1-(d-rv)); /* insert terminator */
+ fs_give((void **) &t);
+ if(*p) /* more src string follows */
+ sstrncpy(&d, "\015\012 ", dlen-1-(d-rv)); /* insert cont. line */
+
+ s = p; /* advance s */
+ }
+
+ rv[dlen-1] = '\0';
+ return(rv);
+ }
+ else
+ return((char *) s); /* no work for us here */
+}
+
+
+
+/*
+ * rfc1522_encoded_word -- cut given string into max length encoded word
+ *
+ * Return: pointer into 's' such that the encoded 's' is no greater
+ * than RFC1522_MAXW
+ *
+ * NOTE: this line break code is NOT cognizant of any SI/SO
+ * charset requirements nor similar strategies using escape
+ * codes. Hopefully this will matter little and such
+ * representation strategies don't also include 8bit chars.
+ */
+unsigned char *
+rfc1522_encoded_word(unsigned char *s, int enc, char *charset)
+{
+ int goal = RFC1522_MAXW - RFC1522_OVERHEAD(charset);
+
+ if(enc == 'B') /* base64 encode */
+ for(goal = ((goal / 4) * 3) - 2; goal && *s; goal--, s++)
+ ;
+ else /* special 'Q' encoding */
+ for(; goal && *s; s++)
+ if((goal -= RFC1522_ENC_CHAR(*s) ? 3 : 1) < 0)
+ break;
+
+ return(s);
+}
+
+
+
+/*
+ * rfc1522_8bit -- apply RFC 1522 'Q' encoding to the given 8bit buffer
+ *
+ * Return: alloc'd buffer containing encoded string
+ */
+char *
+rfc1522_8bit(void *src, int slen)
+{
+ char *ret = (char *) fs_get ((size_t) (3*slen + 2));
+ char *d = ret;
+ unsigned char c;
+ unsigned char *s = (unsigned char *) src;
+
+ while (slen--) { /* for each character */
+ if (((c = *s++) == '\015') && (*s == '\012') && slen) {
+ *d++ = '\015'; /* true line break */
+ *d++ = *s++;
+ slen--;
+ }
+ else if(c == SPACE){ /* special encoding case */
+ *d++ = '_';
+ }
+ else if(RFC1522_ENC_CHAR(c)){
+ *d++ = '='; /* quote character */
+ C2XPAIR(c, d);
+ }
+ else
+ *d++ = (char) c; /* ordinary character */
+ }
+
+ *d = '\0'; /* tie off destination */
+ return(ret);
+}
+
+
+/*
+ * rfc1522_binary -- apply RFC 1522 'B' encoding to the given 8bit buffer
+ *
+ * Return: alloc'd buffer containing encoded string
+ */
+char *
+rfc1522_binary (void *src, int srcl)
+{
+ static char *v =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ unsigned char *s = (unsigned char *) src;
+ char *ret, *d;
+
+ d = ret = (char *) fs_get ((size_t) ((((srcl + 2) / 3) * 4) + 1));
+ for (; srcl; s += 3) { /* process tuplets */
+ /* byte 1: high 6 bits (1) */
+ *d++ = v[s[0] >> 2];
+ /* byte 2: low 2 bits (1), high 4 bits (2) */
+ *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
+ /* byte 3: low 4 bits (2), high 2 bits (3) */
+ *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) :0)) & 0x3f] :'=';
+ /* byte 4: low 6 bits (3) */
+ *d++ = srcl ? v[s[2] & 0x3f] : '=';
+ if(srcl)
+ srcl--; /* count third character if processed */
+ }
+
+ *d = '\0'; /* tie off string */
+ return(ret); /* return the resulting string */
+}
+
+
+/*
+ * Checks if charset conversion is possible and which quality could be achieved
+ *
+ * args: from_cs -- charset to convert from
+ * to_cs -- charset to convert to
+ *
+ * Results:
+ * CONV_TABLE->table -- conversion table, NULL if conversion not needed
+ * or not supported
+ * CONV_TABLE->quality -- conversion quality (conversion not supported, not
+ * needed, loses special chars, or loses letters
+ *
+ * The other entries of CONV_TABLE are used inside this function only
+ * and may not be used outside unless this documentation is updated.
+ */
+CONV_TABLE *
+conversion_table(char *from_cs, char *to_cs)
+{
+ int i, j;
+ unsigned char *p = NULL;
+ unsigned short *fromtab, *totab;
+ CONV_TABLE *ct = NULL;
+ const CHARSET *from, *to;
+ static CONV_TABLE null_tab;
+
+ if(!(from_cs && *from_cs && to_cs && *to_cs) || !strucmp(from_cs, to_cs)){
+ memset(&null_tab, 0, sizeof(null_tab));
+ null_tab.quality = CV_NO_TRANSLATE_NEEDED;
+ return(&null_tab);
+ }
+
+ /*
+ * First check to see if we are already set up for this pair of charsets.
+ */
+ if((ct = ps_global->conv_table) != NULL
+ && ct->from_charset && ct->to_charset
+ && !strucmp(ct->from_charset, from_cs)
+ && !strucmp(ct->to_charset, to_cs))
+ return(ct);
+
+ /*
+ * No such luck. Get rid of the cache of the previous translation table
+ * and build a new one.
+ */
+ if(ct){
+ if(ct->table && (ct->convert != gf_convert_utf8_charset))
+ fs_give((void **) &ct->table);
+
+ if(ct->from_charset)
+ fs_give((void **) &ct->from_charset);
+
+ if(ct->to_charset)
+ fs_give((void **) &ct->to_charset);
+ }
+ else
+ ct = ps_global->conv_table = (CONV_TABLE *) fs_get(sizeof(*ct));
+
+ memset(ct, 0, sizeof(*ct));
+
+ ct->from_charset = cpystr(from_cs);
+ ct->to_charset = cpystr(to_cs);
+ ct->quality = CV_NO_TRANSLATE_POSSIBLE;
+
+ /*
+ * Check to see if a translation is feasible.
+ */
+ from = utf8_charset(from_cs);
+ to = utf8_charset(to_cs);
+
+ if(from && to){ /* if both charsets found */
+ /* no mapping if same or from is ASCII */
+ if((from->type == to->type && from->tab == to->tab)
+ || (from->type == CT_ASCII))
+ ct->quality = CV_NO_TRANSLATE_NEEDED;
+ else switch(from->type){
+ case CT_1BYTE0: /* 1 byte no table */
+ case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */
+ case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */
+ switch(to->type){
+ case CT_1BYTE0: /* 1 byte no table */
+ case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */
+ case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */
+ ct->quality = (from->script & to->script) ?
+ CV_LOSES_SOME_LETTERS : CV_LOSES_SPECIAL_CHARS;
+ break;
+ }
+ break;
+ case CT_UTF8: /* variable UTF-8 encoded Unicode no table */
+ /* If source is UTF-8, see if destination charset has an 8 or 16 bit
+ * coded character set that we can translate to. By special
+ * dispensation, kludge ISO-2022-JP to EUC or Shift-JIS, but don't
+ * try to do any other ISO 2022 charsets or UTF-7.
+ */
+ switch (to->type){
+ case CT_SJIS: /* 2 byte Shift-JIS */
+ /* only win if can get EUC-JP chartab */
+ if(utf8_charset("EUC-JP"))
+ ct->quality = CV_LOSES_SOME_LETTERS;
+ break;
+ case CT_ASCII: /* 7-bit ASCII no table */
+ case CT_1BYTE0: /* 1 byte no table */
+ case CT_1BYTE: /* 1 byte ASCII + table 0x80-0xff */
+ case CT_1BYTE8: /* 1 byte table 0x00 - 0xff */
+ case CT_EUC: /* 2 byte ASCII + utf8_eucparam base/CS2/CS3 */
+ case CT_DBYTE: /* 2 byte ASCII + utf8_eucparam */
+ case CT_DBYTE2: /* 2 byte ASCII + utf8_eucparam plane1/2 */
+ ct->quality = CV_LOSES_SOME_LETTERS;
+ break;
+ }
+ break;
+ }
+
+ switch (ct->quality) { /* need to map? */
+ case CV_NO_TRANSLATE_POSSIBLE:
+ case CV_NO_TRANSLATE_NEEDED:
+ break; /* no mapping needed */
+ default: /* do mapping */
+ switch (from->type) {
+ case CT_UTF8: /* UTF-8 to legacy character set */
+ if((ct->table = utf8_rmap (to_cs)) != NULL)
+ ct->convert = gf_convert_utf8_charset;
+ break;
+
+ case CT_1BYTE0: /* ISO 8859-1 */
+ case CT_1BYTE: /* low part ASCII, high part other */
+ case CT_1BYTE8: /* low part has some non-ASCII */
+ /*
+ * The fromtab and totab tables are mappings from the 128 character
+ * positions 128-255 to their Unicode values (so unsigned shorts).
+ * The table we are creating is such that if
+ *
+ * from_char_value -> unicode_value
+ * to_char_value -> same_unicode_value
+ *
+ * then we want to map from_char_value -> to_char_value
+ *
+ * To simplify conversions we create the whole 256 element array,
+ * with the first 128 positions just the identity. If there is no
+ * conversion for a particular from_char_value (that is, no
+ * to_char_value maps to the same unicode character) then we put
+ * '?' in that character. We may want to output blob on the PC,
+ * but don't so far.
+ *
+ * If fromtab or totab are NULL, that means the mapping is simply
+ * the identity mapping. Since that is still useful to us, we
+ * create it on the fly.
+ */
+ fromtab = (unsigned short *) from->tab;
+ totab = (unsigned short *) to->tab;
+
+ ct->convert = gf_convert_8bit_charset;
+ p = ct->table = (unsigned char *)
+ fs_get(256 * sizeof(unsigned char));
+ for(i = 0; i < 256; i++){
+ unsigned int fc;
+ p[i] = '?';
+ switch(from->type){ /* get "from" UCS-2 codepoint */
+ case CT_1BYTE0: /* ISO 8859-1 */
+ fc = i;
+ break;
+ case CT_1BYTE: /* low part ASCII, high part other */
+ fc = (i < 128) ? i : fromtab[i-128];
+ break;
+ case CT_1BYTE8: /* low part has some non-ASCII */
+ fc = fromtab[i];
+ break;
+ }
+ switch(to->type){ /* match against "to" UCS-2 codepoint */
+ case CT_1BYTE0: /* identity match for ISO 8859-1*/
+ if(fc < 256)
+ p[i] = fc;
+ break;
+ case CT_1BYTE: /* ASCII is identity, search high part */
+ if(fc < 128) p[i] = fc;
+ else for(j = 0; j < 128; j++){
+ if(fc == totab[j]){
+ p[i] = 128 + j;
+ break;
+ }
+ }
+ break;
+ case CT_1BYTE8: /* search all codepoints */
+ for(j = 0; j < 256; j++){
+ if(fc == totab[j]){
+ p[i] = j;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return(ct);
+}
+
+
+/*
+ * Replace personal names in list of addresses with
+ * decoded personal names in UTF-8.
+ * Assumes we can free and reallocate the name.
+ */
+void
+decode_addr_names_to_utf8(struct mail_address *a)
+{
+ for(; a; a = a->next)
+ if(a->personal)
+ convert_possibly_encoded_str_to_utf8(&a->personal);
+}
+
+
+/*
+ * Strp is a pointer to an allocated string.
+ * This routine will convert the string to UTF-8, possibly
+ * freeing and re-allocating it.
+ * The source string may or may not have RFC1522 encoding
+ * which will be undone using rfc1522_decode.
+ * The string will have been converted on return.
+ */
+void
+convert_possibly_encoded_str_to_utf8(char **strp)
+{
+ size_t len, lensrc, lenresult;
+ char *bufp, *decoded;
+
+ if(!strp || !*strp || **strp == '\0')
+ return;
+
+ len = 4 * strlen(*strp) + 1;
+ bufp = (char *) fs_get(len);
+
+ decoded = (char *) rfc1522_decode_to_utf8((unsigned char *) bufp, len, *strp);
+ if(decoded != (*strp)){ /* unchanged */
+ if((lensrc=strlen(*strp)) >= (lenresult=strlen(decoded))){
+ strncpy(*strp, decoded, lensrc);
+ (*strp)[lensrc] = '\0';
+ }
+ else{
+ fs_give((void **) strp);
+ if(decoded == bufp){ /* this will be true */
+ fs_resize((void **) &bufp, lenresult+1);
+ *strp = bufp;
+ bufp = NULL;
+ }
+ else{ /* this is unreachable */
+ *strp = cpystr(decoded);
+ }
+ }
+ }
+ /* else, already UTF-8 */
+
+ if(bufp)
+ fs_give((void **) &bufp);
+}
diff --git a/pith/charset.h b/pith/charset.h
new file mode 100644
index 00000000..f89c6e4c
--- /dev/null
+++ b/pith/charset.h
@@ -0,0 +1,56 @@
+/*
+ * $Id: charset.h 765 2007-10-23 23:51:37Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CHARSET_INCLUDED
+#define PITH_CHARSET_INCLUDED
+
+
+#include "../pith/filttype.h"
+
+
+typedef struct conversion_table {
+ char *from_charset;
+ char *to_charset;
+ int quality;
+ void *table;
+ filter_t convert;
+} CONV_TABLE;
+
+
+/* Conversion table quality of tranlation */
+#define CV_NO_TRANSLATE_POSSIBLE 1 /* We don't know how to */
+ /* translate this pair */
+#define CV_NO_TRANSLATE_NEEDED 2 /* Not necessary, no-op */
+#define CV_LOSES_SPECIAL_CHARS 3 /* Letters will translate */
+ /* ok but some special chars */
+ /* may be lost */
+#define CV_LOSES_SOME_LETTERS 4 /* Some special chars and */
+ /* some letters may be lost */
+
+
+#define CSET_MAX 64
+
+
+/* exported protoypes */
+char *body_charset(MAILSTREAM *, long, unsigned char *);
+unsigned char *trans_euc_to_2022_jp(unsigned char *);
+unsigned char *rfc1522_decode_to_utf8(unsigned char *, size_t, char *);
+char *rfc1522_encode(char *, size_t, unsigned char *, char *);
+CONV_TABLE *conversion_table(char *, char *);
+void decode_addr_names_to_utf8(ADDRESS *);
+void convert_possibly_encoded_str_to_utf8(char **);
+
+
+#endif /* PITH_CHARSET_INCLUDED */
diff --git a/pith/color.c b/pith/color.c
new file mode 100644
index 00000000..9794294b
--- /dev/null
+++ b/pith/color.c
@@ -0,0 +1,234 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: color.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/color.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/filter.h"
+
+
+char *
+color_embed(char *fg, char *bg)
+{
+ static char buf[(2 * RGBLEN) + 5], *p;
+
+ p = buf;
+ if(fg){
+ if(sizeof(buf)-(p-buf) > 1){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_FGCOLOR;
+ }
+
+ sstrncpy(&p, color_to_asciirgb(fg), sizeof(buf)-(p-buf));
+ }
+
+ if(bg){
+ if(sizeof(buf)-(p-buf) > 1){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_BGCOLOR;
+ }
+
+ sstrncpy(&p, color_to_asciirgb(bg), sizeof(buf)-(p-buf));
+ }
+
+ buf[sizeof(buf)-1] = '\0';
+
+ return(buf);
+}
+
+
+int
+colorcmp(char *color1, char *color2)
+{
+ if(color1 && color2)
+ return(strcmp(color_to_asciirgb(color1), color_to_asciirgb(color2)));
+
+ /* if both NULL they're the same? */
+ return(!(color1 || color2));
+}
+
+
+
+struct quote_colors {
+ COLOR_PAIR *color;
+ struct quote_colors *next;
+};
+
+
+int
+color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg)
+{
+ int countem = 0;
+ struct variable *vars = ps_global->vars;
+ char *p;
+ struct quote_colors *colors = NULL, *cp, *next;
+ COLOR_PAIR *col = NULL;
+ int is_flowed = is_flowed_msg ? *((int *)is_flowed_msg) : 0;
+
+ p = line;
+ if(!is_flowed)
+ while(isspace((unsigned char)*p))
+ p++;
+
+ if(p[0] == '>'){
+ struct quote_colors *c;
+
+ /*
+ * We have a fixed number of quote level colors (3). If there are
+ * more levels of quoting than are defined, they repeat.
+ */
+ if(VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR &&
+ (col = new_color_pair(VAR_QUOTE1_FORE_COLOR,
+ VAR_QUOTE1_BACK_COLOR)) &&
+ pico_is_good_colorpair(col)){
+ c = (struct quote_colors *)fs_get(sizeof(*c));
+ memset(c, 0, sizeof(*c));
+ c->color = col;
+ col = NULL;
+ colors = c;
+ cp = c;
+ countem++;
+ if(VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR &&
+ (col = new_color_pair(VAR_QUOTE2_FORE_COLOR,
+ VAR_QUOTE2_BACK_COLOR)) &&
+ pico_is_good_colorpair(col)){
+ c = (struct quote_colors *)fs_get(sizeof(*c));
+ memset(c, 0, sizeof(*c));
+ c->color = col;
+ col = NULL;
+ cp->next = c;
+ cp = c;
+ countem++;
+ if(VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR &&
+ (col = new_color_pair(VAR_QUOTE3_FORE_COLOR,
+ VAR_QUOTE3_BACK_COLOR)) &&
+ pico_is_good_colorpair(col)){
+ c = (struct quote_colors *)fs_get(sizeof(*cp));
+ memset(c, 0, sizeof(*c));
+ c->color = col;
+ col = NULL;
+ cp->next = c;
+ cp = c;
+ countem++;
+ }
+ }
+ }
+ }
+
+ if(col)
+ free_color_pair(&col);
+
+ cp = NULL;
+ while(*p == '>'){
+ cp = (cp && cp->next) ? cp->next : colors;
+
+ if(countem > 0)
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(cp->color->fg, cp->color->bg),
+ (2 * RGBLEN) + 4);
+
+ countem = (countem == 1) ? 0 : countem;
+
+ p++;
+ if(!is_flowed)
+ for(; isspace((unsigned char)*p); p++)
+ ;
+ }
+
+ if(colors){
+ char fg[RGBLEN + 1], bg[RGBLEN + 1], rgbbuf[RGBLEN + 1];
+
+ strncpy(fg, color_to_asciirgb(VAR_NORM_FORE_COLOR), sizeof(fg));
+ strncpy(bg, color_to_asciirgb(VAR_NORM_BACK_COLOR), sizeof(bg));
+ fg[sizeof(fg)-1] = '\0';
+ bg[sizeof(bg)-1] = '\0';
+
+ /*
+ * Loop watching colors, and override with most recent
+ * quote color whenever the normal foreground and background
+ * colors are in force.
+ */
+ while(*p)
+ if(*p++ == TAG_EMBED){
+
+ switch(*p++){
+ case TAG_HANDLE :
+ p += *p + 1; /* skip handle key */
+ break;
+
+ case TAG_FGCOLOR :
+ snprintf(rgbbuf, sizeof(rgbbuf), "%s", p);
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_NORM_FORE_COLOR)
+ && !colorcmp(bg, VAR_NORM_BACK_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(cp->color->fg,NULL),
+ RGBLEN + 2);
+ break;
+
+ case TAG_BGCOLOR :
+ snprintf(rgbbuf, sizeof(rgbbuf), "%s", p);
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_NORM_BACK_COLOR)
+ && !colorcmp(fg, VAR_NORM_FORE_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(NULL,cp->color->bg),
+ RGBLEN + 2);
+
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_NORM_FORE_COLOR,
+ VAR_NORM_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ for(cp = colors; cp && cp->color; cp = next){
+ free_color_pair(&cp->color);
+ next = cp->next;
+ fs_give((void **)&cp);
+ }
+ }
+
+ return(0);
+}
+
+
+void
+free_spec_colors(SPEC_COLOR_S **colors)
+{
+ if(colors && *colors){
+ free_spec_colors(&(*colors)->next);
+ if((*colors)->spec)
+ fs_give((void **)&(*colors)->spec);
+ if((*colors)->fg)
+ fs_give((void **)&(*colors)->fg);
+ if((*colors)->bg)
+ fs_give((void **)&(*colors)->bg);
+ if((*colors)->val)
+ free_pattern(&(*colors)->val);
+
+ fs_give((void **)colors);
+ }
+}
diff --git a/pith/color.h b/pith/color.h
new file mode 100644
index 00000000..b90d82cf
--- /dev/null
+++ b/pith/color.h
@@ -0,0 +1,87 @@
+/*
+ * $Id: color.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_COLOR_INCLUDED
+#define PITH_COLOR_INCLUDED
+
+
+#include "../pith/filttype.h"
+#include "../pith/pattern.h"
+#include "../pith/osdep/color.h"
+
+
+typedef struct spec_color_s {
+ int inherit; /* this isn't a color, it is INHERIT */
+ char *spec;
+ char *fg;
+ char *bg;
+ PATTERN_S *val;
+ struct spec_color_s *next;
+} SPEC_COLOR_S;
+
+
+/*
+ * These are default colors that you'll get when you turn
+ * color on. The way color works is that the closest possible
+ * RGB value is chosen so these colors will produce different
+ * results in the different color models (the 8-color, 16-color,
+ * xterm 256-color, and PC-Alpine color).
+ * See init_color_table() for the RGB values we use.
+ * The 8-color model uses the 8 0 or 255 possibilities. So,
+ * for example, if the default color is "000,217,217" the
+ * closes 8-color version of that is going to be "000,255,255".
+ * In the 16-color terminal we use 000, 174, and 255 as the
+ * possible values. That means that a default value
+ * of "000,214,000" maps to "000,174,000" (a dull green)
+ * but "000,215,000" maps to "000,255,000" (a bright green).
+ *
+ * The colors which don't have defaults map to either the current
+ * setting of the Normal color or the current setting of the
+ * Reverse color, depending on what we thought was right long ago.
+ */
+#define DEFAULT_TITLE_FORE_RGB "000,000,000"
+#define DEFAULT_TITLE_BACK_RGB "255,255,000"
+#define DEFAULT_TITLECLOSED_FORE_RGB "255,255,255"
+#define DEFAULT_TITLECLOSED_BACK_RGB "255,000,000"
+#define DEFAULT_METAMSG_FORE_RGB "000,000,000"
+#define DEFAULT_METAMSG_BACK_RGB "255,255,000"
+#define DEFAULT_QUOTE1_FORE_RGB "000,000,000"
+#define DEFAULT_QUOTE1_BACK_RGB "000,217,217"
+#define DEFAULT_QUOTE2_FORE_RGB "000,000,000"
+#define DEFAULT_QUOTE2_BACK_RGB "204,214,000"
+#define DEFAULT_QUOTE3_FORE_RGB "000,000,000"
+#define DEFAULT_QUOTE3_BACK_RGB "000,214,000"
+#define DEFAULT_SIGNATURE_FORE_RGB "000,000,255"
+#define DEFAULT_SIGNATURE_BACK_RGB "255,255,255"
+#define DEFAULT_IND_PLUS_FORE_RGB "000,000,000"
+#define DEFAULT_IND_PLUS_BACK_RGB "000,174,174"
+#define DEFAULT_IND_IMP_FORE_RGB "240,240,240"
+#define DEFAULT_IND_IMP_BACK_RGB "174,000,000"
+#define DEFAULT_IND_ANS_FORE_RGB "255,000,000"
+#define DEFAULT_IND_ANS_BACK_RGB "174,174,000"
+#define DEFAULT_IND_NEW_FORE_RGB "240,240,240"
+#define DEFAULT_IND_NEW_BACK_RGB "174,000,174"
+#define DEFAULT_IND_OP_FORE_RGB "192,192,192"
+#define DEFAULT_IND_OP_BACK_RGB "255,255,255"
+
+
+/* exported protoypes */
+char *color_embed(char *, char *);
+int colorcmp(char *, char *);
+int color_a_quote(long, char *, LT_INS_S **, void *);
+void free_spec_colors(SPEC_COLOR_S **);
+
+
+#endif /* PITH_COLOR_INCLUDED */
diff --git a/pith/conf.c b/pith/conf.c
new file mode 100644
index 00000000..1375b8dc
--- /dev/null
+++ b/pith/conf.c
@@ -0,0 +1,8241 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: conf.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ conf.c
+ Implements the Pine configuration management routines
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/state.h"
+#include "../pith/remote.h"
+#include "../pith/keyword.h"
+#include "../pith/mailview.h"
+#include "../pith/list.h"
+#include "../pith/status.h"
+#include "../pith/ldap.h"
+#include "../pith/folder.h"
+#include "../pith/thread.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/pattern.h"
+#include "../pith/color.h"
+#include "../pith/options.h"
+#include "../pith/busy.h"
+#include "../pith/readfile.h"
+#include "../pith/hist.h"
+#include "../pith/mailindx.h"
+#include "../pith/tempfile.h"
+#include "../pith/icache.h"
+#include "../pith/sort.h"
+#include "../pith/smime.h"
+#include "../pith/charconv/utf8.h"
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+#define TO_BAIL_THRESHOLD 60
+
+
+/*
+ * Internal prototypes
+ */
+void convert_configvars_to_utf8(struct variable *, char *);
+void convert_configvar_to_utf8(struct variable *, char *);
+void set_current_pattern_vals(struct pine *);
+void convert_pattern_data(void);
+void convert_filts_pattern_data(void);
+void convert_scores_pattern_data(void);
+void convert_pinerc_patterns(long);
+void convert_pinerc_filts_patterns(long);
+void convert_pinerc_scores_patterns(long);
+void set_old_growth_bits(struct pine *, int);
+int var_is_in_rest_of_file(char *, char *);
+char *skip_over_this_var(char *, char *);
+char *native_nl(char *);
+void set_color_val(struct variable *, int);
+int copy_localfile_to_remotefldr(RemType, char *, char *, char *, char **);
+char *backcompat_convert_from_utf8(char *, size_t, char *);
+#ifdef _WINDOWS
+char *transformed_color(char *);
+int convert_pc_gray_names(struct pine *, PINERC_S *, EditWhich);
+int unix_color_style_in_pinerc(PINERC_S *);
+char *pcpine_general_help(char *);
+char *pcpine_help(HelpType); /* defined in alpine/help */
+#endif /* _WINDOWS */
+
+
+/* hook too allow caller to decide what to do about failure */
+int (*pith_opt_remote_pinerc_failure)(void);
+
+
+/*------------------------------------
+Some definitions to keep the static "variable" array below
+a bit more readable...
+ ----*/
+CONF_TXT_T cf_text_comment[] = "#\n# Alpine configuration file\n#\n# This file sets the configuration options used by Alpine and PC-Alpine. These\n# options are usually set from within Alpine or PC-Alpine. There may be a\n# system-wide configuration file which sets the defaults for some of the\n# variables. On Unix, run alpine -conf to see how system defaults have been set.\n# For variables that accept multiple values, list elements are\
+ separated by\n# commas. A line beginning with a space or tab is considered to be a\n# continuation of the previous line. For a variable to be unset its value must\n# be blank. To set a variable to the empty string its value should be \"\".\n# You can override system defaults by setting a variable to the empty string.\n# Lines beginning with \"#\" are comments, and ignored by Alpine.\n";
+
+
+CONF_TXT_T cf_text_personal_name[] = "Over-rides your full name from Unix password file. Required for PC-Alpine.";
+
+CONF_TXT_T cf_text_user_id[] = "Your login/e-mail user name";
+
+CONF_TXT_T cf_text_user_domain[] = "Sets domain part of From: and local addresses in outgoing mail.";
+
+CONF_TXT_T cf_text_smtp_server[] = "List of SMTP servers for sending mail. If blank: Unix Alpine uses sendmail.";
+
+CONF_TXT_T cf_text_nntp_server[] = "NNTP server for posting news. Also sets news-collections for news reading.";
+
+#ifdef SMIME
+
+CONF_TXT_T cf_text_publiccertdir[] = "Public certificates are kept in files in this directory. The files should\n# contain certificates in PEM format. The name of each file should look\n# like <emailaddress>.crt. The default directory is .alpine-smime/public.";
+
+CONF_TXT_T cf_text_privatekeydir[] = "Private keys are kept in files in this directory. The files are in PEM format.\n# The name of a file should look like <emailaddress>.key.\n# The default directory is .alpine-smime/private.";
+
+CONF_TXT_T cf_text_cacertdir[] = "Certificate Authority certificates (in addition to the normal CACerts for the\n# system) are kept in files in this directory. The files are in PEM format.\n# Filenames should end with .crt. The default directory is .alpine-smime/ca.";
+
+CONF_TXT_T cf_text_publiccertcontainer[] = "If this option is set then public certificates are kept in a single container\n# \"file\" similar to a remote configuration file instead of in the\n# smime-publiccert-directory. The value can be a remote or local folder\n# specification like for a non-standard pinerc value. The default\n# is that it is not set.";
+
+CONF_TXT_T cf_text_privatekeycontainer[] = "If this option is set then private keys are kept in a single container\n# \"file\" similar to a remote configuration file instead of in the\n# private-key-directory. The value can be a remote or local folder\n# specification like for a non-standard pinerc value. The default\n# is that it is not set.";
+
+CONF_TXT_T cf_text_cacertcontainer[] = "If this option is set then CAcerts are kept in a single container\n# \"file\" similar to a remote configuration file instead of in the\n# ca-cert-directory. The value can be a remote or local folder\n# specification like for a non-standard pinerc value. The default\n# is that it is not set.";
+
+#endif /* SMIME */
+
+#ifdef ENABLE_LDAP
+CONF_TXT_T cf_text_ldap_server[] = "LDAP servers for looking up addresses.";
+#endif /* ENABLE_LDAP */
+
+CONF_TXT_T cf_text_rss_news[] = "RSS News feed";
+
+CONF_TXT_T cf_text_rss_weather[] = "RSS Weather feed";
+
+CONF_TXT_T cf_text_wp_indexheight[] = "Web Alpine index table row height";
+
+CONF_TXT_T cf_text_wp_indexlines[] = "Web Alpine number of index lines in table";
+
+CONF_TXT_T cf_text_wp_aggstate[] = "Web Alpine aggregate operations tab state";
+
+CONF_TXT_T cf_text_wp_state[] = "Web Alpine various aspects of cross-session state";
+
+CONF_TXT_T cf_text_wp_columns[] = "Web Alpine preferred width for message display in characters";
+
+CONF_TXT_T cf_text_inbox_path[] = "Path of (local or remote) INBOX, e.g. ={mail.somewhere.edu}inbox\n# Normal Unix default is the local INBOX (usually /usr/spool/mail/$USER).";
+
+CONF_TXT_T cf_text_incoming_folders[] = "List of incoming msg folders besides INBOX, e.g. ={host2}inbox, {host3}inbox\n# Syntax: optnl-label {optnl-imap-host-name}folder-path";
+
+CONF_TXT_T cf_text_folder_collections[] = "List of directories where saved-message folders may be. First one is\n# the default for Saves. Example: Main {host1}mail/[], Desktop mail\\[]\n# Syntax: optnl-label {optnl-imap-hostname}optnl-directory-path[]";
+
+CONF_TXT_T cf_text_news_collections[] = "List, only needed if nntp-server not set, or news is on a different host\n# than used for NNTP posting. Examples: News *[] or News *{host3/nntp}[]\n# Syntax: optnl-label *{news-host/protocol}[]";
+
+CONF_TXT_T cf_text_pruned_folders[] = "List of folders, assumed to be in first folder collection,\n# offered for pruning each month. For example: mumble";
+
+CONF_TXT_T cf_text_default_fcc[] = "Over-rides default path for sent-mail folder, e.g. =old-mail (using first\n# folder collection dir) or ={host2}sent-mail or =\"\" (to suppress saving).\n# Default: sent-mail (Unix) or SENTMAIL.MTX (PC) in default folder collection.";
+
+CONF_TXT_T cf_text_default_saved[] = "Over-rides default path for saved-msg folder, e.g. =saved-messages (using 1st\n# folder collection dir) or ={host2}saved-mail or =\"\" (to suppress saving).\n# Default: saved-messages (Unix) or SAVEMAIL.MTX (PC) in default collection.";
+
+CONF_TXT_T cf_text_postponed_folder[] = "Over-rides default path for postponed messages folder, e.g. =pm (which uses\n# first folder collection dir) or ={host4}pm (using home dir on host4).\n# Default: postponed-msgs (Unix) or POSTPOND.MTX (PC) in default fldr coltn.";
+
+CONF_TXT_T cf_text_mail_directory[] = "Alpine compares this value with the first folder collection directory.\n# If they match (or no folder collections are defined), and the directory\n# does not exist, Alpine will create and use it. Default: ~/mail";
+
+CONF_TXT_T cf_text_read_message_folder[] = "If set, specifies where already-read messages will be moved upon quitting.";
+
+CONF_TXT_T cf_text_form_letter_folder[] = "If set, specifies where form letters should be stored.";
+
+CONF_TXT_T cf_text_trash_folder[] = "If set, specifies where trash is moved to in Web Alpine.";
+
+CONF_TXT_T cf_text_signature_file[] = "Over-rides default path for signature file. Default is ~/.signature";
+
+CONF_TXT_T cf_text_literal_sig[] = "Contains the actual signature contents as opposed to the signature filename.\n# If defined, this overrides the signature-file. Default is undefined.";
+
+CONF_TXT_T cf_text_global_address_book[] = "List of file or path names for global/shared addressbook(s).\n# Default: none\n# Syntax: optnl-label path-name";
+
+CONF_TXT_T cf_text_address_book[] = "List of file or path names for personal addressbook(s).\n# Default: ~/.addressbook (Unix) or \\PINE\\ADDRBOOK (PC)\n# Syntax: optnl-label path-name";
+
+CONF_TXT_T cf_text_feature_list[] = "List of features; see Alpine's Setup/options menu for the current set.\n# e.g. feature-list= select-without-confirm, signature-at-bottom\n# Default condition for all of the features is no-.";
+
+CONF_TXT_T cf_text_initial_keystroke_list[] = "Alpine executes these keys upon startup (e.g. to view msg 13: i,j,1,3,CR,v)";
+
+CONF_TXT_T cf_text_default_composer_hdrs[] = "Only show these headers (by default) when composing messages";
+
+CONF_TXT_T cf_text_customized_hdrs[] = "Add these customized headers (and possible default values) when composing";
+
+CONF_TXT_T cf_text_view_headers[] = "When viewing messages, include this list of headers";
+
+CONF_TXT_T cf_text_view_margin_left[] = "When viewing messages, number of blank spaces between left display edge and text";
+
+CONF_TXT_T cf_text_view_margin_right[] = "When viewing messages, number of blank spaces between right display edge and text";
+
+CONF_TXT_T cf_text_quote_suppression[] = "When viewing messages, number of lines of quote displayed before suppressing";
+
+CONF_TXT_T cf_text_wordsep[] = "When these characters appear in the middle of a word in the composer\n# the forward word function will stop at the first text following (as happens\n# with SPACE characters by default)";
+
+CONF_TXT_T cf_text_color_style[] = "Controls display of color";
+
+CONF_TXT_T cf_text_current_indexline_style[] = "Controls display of color for current index line";
+
+CONF_TXT_T cf_text_titlebar_color_style[] = "Controls display of color for the titlebar at top of screen";
+
+CONF_TXT_T cf_text_view_hdr_color[] = "When viewing messages, these are the header colors";
+
+CONF_TXT_T cf_text_save_msg_name_rule[] = "Determines default folder name for Saves...\n# Choices: default-folder, by-sender, by-from, by-recipient, last-folder-used.\n# Default: \"default-folder\", i.e. \"saved-messages\" (Unix) or \"SAVEMAIL\" (PC).";
+
+CONF_TXT_T cf_text_fcc_name_rule[] = "Determines default name for Fcc...\n# Choices: default-fcc, by-recipient, last-fcc-used.\n# Default: \"default-fcc\" (see also \"default-fcc=\" variable.)";
+
+CONF_TXT_T cf_text_sort_key[] = "Sets presentation order of messages in Index. Choices:\n# Subject, From, Arrival, Date, Size, To, Cc, OrderedSubj, Score, and Thread.\n# Order may be reversed by appending /Reverse. Default: \"Arrival\".";
+
+CONF_TXT_T cf_text_addrbook_sort_rule[] = "Sets presentation order of address book entries. Choices: dont-sort,\n# fullname-with-lists-last, fullname, nickname-with-lists-last, nickname\n# Default: \"fullname-with-lists-last\".";
+
+CONF_TXT_T cf_text_folder_sort_rule[] = "Sets presentation order of folder list entries. Choices: alphabetical,\n# alpha-with-dirs-last, alpha-with-dirs-first.\n# Default: \"alpha-with-directories-last\".";
+
+CONF_TXT_T cf_text_old_char_set[] = "Character-set is obsolete, use display-character-set, keyboard-character-set,\n# and posting-character-set.";
+
+CONF_TXT_T cf_text_disp_char_set[] = "Reflects capabilities of the display you have.\n# If unset, the default is taken from your locale. That is usually the right\n# thing to use. Typical alternatives include UTF-8, ISO-8859-x, and EUC-JP\n# (where x is a number between 1 and 9).";
+
+CONF_TXT_T cf_text_key_char_set[] = "Reflects capabilities of the keyboard you have.\n# If unset, the default is to use the same value\n# used for the display-character-set.";
+
+CONF_TXT_T cf_text_post_character_set[] = "Defaults to UTF-8. This is used for outgoing messages.\n# It is usually correct to leave this unset.";
+
+CONF_TXT_T cf_text_unk_character_set[] = "Defaults to nothing, which is equivalent to US-ASCII. This is used for\n# unlabeled incoming messages. It is ok to leave this unset but if you receive\n# unlabeled mail that is usually in some known character set, set that here.";
+
+CONF_TXT_T cf_text_editor[] = "Specifies the program invoked by ^_ in the Composer,\n# or the \"enable-alternate-editor-implicitly\" feature.";
+
+CONF_TXT_T cf_text_speller[] = "Specifies the program invoked by ^T in the Composer.";
+
+CONF_TXT_T cf_text_deadlets[] = "Specifies the number of dead letter files to keep when canceling.";
+
+CONF_TXT_T cf_text_fillcol[] = "Specifies the column of the screen where the composer should wrap.";
+
+CONF_TXT_T cf_text_replystr[] = "Specifies the string to insert when replying to a message.";
+
+CONF_TXT_T cf_text_quotereplstr[] = "Specifies the string to replace quotes with when viewing a message.";
+
+CONF_TXT_T cf_text_replyintro[] = "Specifies the introduction to insert when replying to a message.";
+
+CONF_TXT_T cf_text_emptyhdr[] = "Specifies the string to use when sending a message with no to or cc.";
+
+CONF_TXT_T cf_text_image_viewer[] = "Program to view images (e.g. GIF or TIFF attachments).";
+
+CONF_TXT_T cf_text_browser[] = "List of programs to open Internet URLs (e.g. http or ftp references).";
+
+CONF_TXT_T cf_text_inc_startup[] = "Sets message which cursor begins on. Choices: first-unseen, first-recent,\n# first-important, first-important-or-unseen, first-important-or-recent,\n# first, last. Default: \"first-unseen\".";
+
+CONF_TXT_T cf_pruning_rule[] = "Allows a default answer for the prune folder questions. Choices: yes-ask,\n# yes-no, no-ask, no-no, ask-ask, ask-no. Default: \"ask-ask\".";
+
+CONF_TXT_T cf_reopen_rule[] = "Controls behavior when reopening an already open folder.";
+
+CONF_TXT_T cf_text_thread_disp_style[] = "Style that MESSAGE INDEX is displayed in when threading.";
+
+CONF_TXT_T cf_text_thread_index_style[] = "Style of THREAD INDEX or default MESSAGE INDEX when threading.";
+
+CONF_TXT_T cf_text_thread_more_char[] = "When threading, character used to indicate collapsed messages underneath.";
+
+CONF_TXT_T cf_text_thread_exp_char[] = "When threading, character used to indicate expanded messages underneath.";
+
+CONF_TXT_T cf_text_thread_lastreply_char[] = "When threading, character used to indicate this is the last reply\n# to the parent of this message.";
+
+CONF_TXT_T cf_text_use_only_domain_name[] = "If \"user-domain\" not set, strips hostname in FROM address. (Unix only)";
+
+CONF_TXT_T cf_text_printer[] = "Your default printer selection";
+
+CONF_TXT_T cf_text_personal_print_command[] = "List of special print commands";
+
+CONF_TXT_T cf_text_personal_print_cat[] = "Which category default print command is in";
+
+CONF_TXT_T cf_text_standard_printer[] = "The system wide standard printers";
+
+CONF_TXT_T cf_text_last_time_prune_quest[] = "Set by Alpine; controls beginning-of-month sent-mail pruning.";
+
+CONF_TXT_T cf_text_last_version_used[] = "Set by Alpine; controls display of \"new version\" message.";
+
+CONF_TXT_T cf_text_disable_drivers[] = "List of mail drivers to disable.";
+
+CONF_TXT_T cf_text_disable_auths[] = "List of SASL authenticators to disable.";
+
+CONF_TXT_T cf_text_remote_abook_metafile[] = "Set by Alpine; contains data for caching remote address books.";
+
+CONF_TXT_T cf_text_old_patterns[] = "Patterns is obsolete, use patterns-xxx";
+
+CONF_TXT_T cf_text_old_filters[] = "Patterns-filters is obsolete, use patterns-filters2";
+
+CONF_TXT_T cf_text_old_scores[] = "Patterns-scores is obsolete, use patterns-scores2";
+
+CONF_TXT_T cf_text_patterns[] = "Patterns and their actions are stored here.";
+
+CONF_TXT_T cf_text_remote_abook_history[] = "How many extra copies of remote address book should be kept. Default: 3";
+
+CONF_TXT_T cf_text_remote_abook_validity[] = "Minimum number of minutes between checks for remote address book changes.\n# 0 means never check except when opening a remote address book.\n# -1 means never check. Default: 5";
+
+CONF_TXT_T cf_text_bugs_fullname[] = "Full name for bug report address used by \"Report Bug\" command";
+
+CONF_TXT_T cf_text_bugs_address[] = "Email address used to send bug reports";
+
+CONF_TXT_T cf_text_bugs_extras[] = "Program/Script used by \"Report Bug\" command. No default.";
+
+CONF_TXT_T cf_text_suggest_fullname[] = "Full name for suggestion address used by \"Report Bug\" command";
+
+CONF_TXT_T cf_text_suggest_address[] = "Email address used to send suggestions";
+
+CONF_TXT_T cf_text_local_fullname[] = "Full name for \"local support\" address used by \"Report Bug\" command.\n# Default: Local Support";
+
+CONF_TXT_T cf_text_local_address[] = "Email address used to send to \"local support\".\n# Default: postmaster";
+
+CONF_TXT_T cf_text_forced_abook[] = "Force these address book entries into all writable personal address books.\n# Syntax is forced-abook-entry=nickname|fullname|address\n# This is a comma-separated list of entries, each with syntax above.\n# Existing entries with same nickname are not replaced.\n# Example: help|Help Desk|help@ourdomain.com";
+
+CONF_TXT_T cf_text_kblock_passwd[] = "This is a number between 1 and 5. It is the number of times a user will\n# have to enter a password when they run the keyboard lock command in the\n# main menu. Default is 1.";
+
+CONF_TXT_T cf_text_sendmail_path[] = "This names the path to an alternative program, and any necessary arguments,\n# to be used in posting mail messages. Example:\n# /usr/lib/sendmail -oem -t -oi\n# or,\n# /usr/local/bin/sendit.sh\n# The latter a script found in Alpine distribution's contrib/util directory.\n# NOTE: The program MUST read the message to be posted on standard input,\n# AND operate in the style of sendmail's \"-t\" option.";
+
+CONF_TXT_T cf_text_oper_dir[] = "This names the root of the tree to which the user is restricted when reading\n# and writing folders and files. For example, on Unix ~/work confines the\n# user to the subtree beginning with their work subdirectory.\n# (Note: this alone is not sufficient for preventing access. You will also\n# need to restrict shell access and so on, see Alpine Technical Notes.)\n# Default: not set (so no restriction)";
+
+CONF_TXT_T cf_text_in_fltr[] = "This variable takes a list of programs that message text is piped into\n# after MIME decoding, prior to display.";
+
+CONF_TXT_T cf_text_out_fltr[] = "This defines a program that message text is piped into before MIME\n# encoding, prior to sending";
+
+CONF_TXT_T cf_text_alt_addrs[] = "A list of alternate addresses the user is known by";
+
+CONF_TXT_T cf_text_keywords[] = "A list of keywords for use in categorizing messages";
+
+CONF_TXT_T cf_text_kw_colors[] = "Colors used to display keywords in the index";
+
+CONF_TXT_T cf_text_kw_braces[] = "Characters which surround keywords in SUBJKEY token.\n# Default is \"{\" \"} \"";
+
+CONF_TXT_T cf_text_opening_sep[] = "Characters between subject and opening text in SUBJECTTEXT token.\n# Default is \" - \"";
+
+CONF_TXT_T cf_text_abook_formats[] = "This is a list of formats for address books. Each entry in the list is made\n# up of space-delimited tokens telling which fields are displayed and in\n# which order. See help text";
+
+CONF_TXT_T cf_text_index_format[] = "This gives a format for displaying the index. It is made\n# up of space-delimited tokens telling which fields are displayed and in\n# which order. See help text";
+
+CONF_TXT_T cf_text_overlap[] = "The number of lines of overlap when scrolling through message text";
+
+CONF_TXT_T cf_text_maxremstreams[] = "The maximum number of non-stayopen remote connections that Alpine will use";
+
+CONF_TXT_T cf_text_permlocked[] = "A list of folders that should be left open once opened (INBOX is implicit)";
+
+CONF_TXT_T cf_text_margin[] = "Number of lines from top and bottom of screen where single\n# line scrolling occurs.";
+
+CONF_TXT_T cf_text_stat_msg_delay[] = "The number of seconds to sleep after writing a status message";
+
+CONF_TXT_T cf_text_busy_cue_rate[] = "Number of times per-second to update busy cue messages";
+
+CONF_TXT_T cf_text_mailcheck[] = "The approximate number of seconds between checks for new mail";
+
+CONF_TXT_T cf_text_mailchecknoncurr[] = "The approximate number of seconds between checks for new mail in folders\n# other than the current folder and inbox.\n# Default is same as mail-check-interval";
+
+CONF_TXT_T cf_text_maildropcheck[] = "The minimum number of seconds between checks for new mail in a Mail Drop.\n# This is always effectively at least as large as the mail-check-interval";
+
+CONF_TXT_T cf_text_nntprange[] = "For newsgroups accessed using NNTP, only messages numbered in the range\n# lastmsg-range+1 to lastmsg will be considered";
+
+CONF_TXT_T cf_text_news_active[] = "Path and filename of news configuration's active file.\n# The default is typically \"/usr/lib/news/active\".";
+
+CONF_TXT_T cf_text_news_spooldir[] = "Directory containing system's news data.\n# The default is typically \"/usr/spool/news\"";
+
+CONF_TXT_T cf_text_upload_cmd[] = "Path and filename of the program used to upload text from your terminal\n# emulator's into Alpine's composer.";
+
+CONF_TXT_T cf_text_upload_prefix[] = "Text sent to terminal emulator prior to invoking the program defined by\n# the upload-command variable.\n# Note: _FILE_ will be replaced with the temporary file used in the upload.";
+
+CONF_TXT_T cf_text_download_cmd[] = "Path and filename of the program used to download text via your terminal\n# emulator from Alpine's export and save commands.";
+
+CONF_TXT_T cf_text_download_prefix[] = "Text sent to terminal emulator prior to invoking the program defined by\n# the download-command variable.\n# Note: _FILE_ will be replaced with the temporary file used in the download.";
+
+CONF_TXT_T cf_text_goto_default[] = "Sets the default folder and collection offered at the Goto Command's prompt.";
+
+CONF_TXT_T cf_text_mailcap_path[] = "Sets the search path for the mailcap configuration file.\n# NOTE: colon delimited under UNIX, semi-colon delimited under DOS/Windows/OS2.";
+
+CONF_TXT_T cf_text_mimetype_path[] = "Sets the search path for the mimetypes configuration file.\n# NOTE: colon delimited under UNIX, semi-colon delimited under DOS/Windows/OS2.";
+
+CONF_TXT_T cf_text_newmail_fifo_path[] = "Sets the filename for the newmail fifo (named pipe). Unix only.";
+
+CONF_TXT_T cf_text_nmw_width[] = "Sets the width for the NewMail screen.";
+
+CONF_TXT_T cf_text_user_input_timeo[] = "If no user input for this many hours, Alpine will exit if in an idle loop\n# waiting for a new command. If set to zero (the default), then there will\n# be no timeout.";
+
+CONF_TXT_T cf_text_debug_mem[] = "Debug-memory is obsolete";
+
+CONF_TXT_T cf_text_tcp_open_timeo[] = "Sets the time in seconds that Alpine will attempt to open a network\n# connection. The default is 30, the minimum is 5, and the maximum is\n# system defined (typically 75).";
+
+CONF_TXT_T cf_text_tcp_read_timeo[] = "Network read warning timeout. The default is 15, the minimum is 5, and the\n# maximum is 1000.";
+
+CONF_TXT_T cf_text_tcp_write_timeo[] = "Network write warning timeout. The default is 0 (unset), the minimum\n# is 5 (if not 0), and the maximum is 1000.";
+
+CONF_TXT_T cf_text_tcp_query_timeo[] = "If this much time has elapsed at the time of a tcp read or write\n# timeout, Alpine will ask if you want to break the connection.\n# Default is 60 seconds, minimum is 5, maximum is 1000.";
+
+CONF_TXT_T cf_text_rsh_open_timeo[] = "Sets the time in seconds that Alpine will attempt to open a UNIX remote\n# shell connection. The default is 15, min is 5, and max is unlimited.\n# Zero disables rsh altogether.";
+
+CONF_TXT_T cf_text_rsh_path[] = "Sets the name of the command used to open a UNIX remote shell connection.\n# The default is typically /usr/ucb/rsh.";
+
+CONF_TXT_T cf_text_rsh_command[] = "Sets the format of the command used to open a UNIX remote\n# shell connection. The default is \"%s %s -l %s exec /etc/r%sd\"\n# NOTE: the 4 (four) \"%s\" entries MUST exist in the provided command\n# where the first is for the command's path, the second is for the\n# host to connect to, the third is for the user to connect as, and the\n# fourth is for the connection method (typically \"imap\")";
+
+CONF_TXT_T cf_text_ssh_open_timeo[] = "Sets the time in seconds that Alpine will attempt to open a UNIX secure\n# shell connection. The default is 15, min is 5, and max is unlimited.\n# Zero disables ssh altogether.";
+
+CONF_TXT_T cf_text_inc_check_timeo[] = "Sets the time in seconds that Alpine will attempt to open a network\n# connection when checking for new unseen messages in an incoming folder.\n# The default is 5.";
+
+CONF_TXT_T cf_text_inc_check_interval[] = "Sets the approximate number of seconds between checks for unseen messages\n# in incoming folders. The default is 180.";
+
+CONF_TXT_T cf_text_inc_second_check_interval[] = "Sets the approximate number of seconds between checks for unseen messages\n# for other than local or IMAP folders. The default is 180.";
+
+CONF_TXT_T cf_text_inc_check_list[] = "List of incoming folders to check for unseen messages. The default if left\n# blank is to check all incoming folders.";
+
+CONF_TXT_T cf_text_ssh_path[] = "Sets the name of the command used to open a UNIX secure shell connection.\n# Typically this is /usr/bin/ssh.";
+
+CONF_TXT_T cf_text_ssh_command[] = "Sets the format of the command used to open a UNIX secure\n# shell connection. The default is \"%s %s -l %s exec /etc/r%sd\"\n# NOTE: the 4 (four) \"%s\" entries MUST exist in the provided command\n# where the first is for the command's path, the second is for the\n# host to connect to, the third is for the user to connect as, and the\n# fourth is for the connection method (typically \"imap\")";
+
+CONF_TXT_T cf_text_version_threshold[] = "Sets the version number Alpine will use as a threshold for offering\n# its new version message on startup.";
+
+CONF_TXT_T cf_text_archived_folders[] = "List of folder pairs; the first indicates a folder to archive, and the\n# second indicates the folder read messages in the first should\n# be moved to.";
+
+CONF_TXT_T cf_text_elm_style_save[] = "Elm-style-save is obsolete, use saved-msg-name-rule";
+
+CONF_TXT_T cf_text_header_in_reply[] = "Header-in-reply is obsolete, use include-header-in-reply in feature-list";
+
+CONF_TXT_T cf_text_feature_level[] = "Feature-level is obsolete, use feature-list";
+
+CONF_TXT_T cf_text_old_style_reply[] = "Old-style-reply is obsolete, use signature-at-bottom in feature-list";
+
+CONF_TXT_T cf_text_compose_mime[] = "Compose-mime is obsolete";
+
+CONF_TXT_T cf_text_show_all_characters[] = "Show-all-characters is obsolete";
+
+CONF_TXT_T cf_text_save_by_sender[] = "Save-by-sender is obsolete, use saved-msg-name-rule";
+
+CONF_TXT_T cf_text_file_dir[] = "Default directory used for Attachment handling (attach and save)\n# and Export command output";
+
+CONF_TXT_T cf_text_folder_extension[] = "Folder-extension is obsolete";
+
+CONF_TXT_T cf_text_normal_foreground_color[] = "Choose: black, blue, green, cyan, red, magenta, yellow, or white.";
+
+CONF_TXT_T cf_text_window_position[] = "Window position in the format: CxR+X+Y\n# Where C and R are the window size in characters and X and Y are the\n# screen position of the top left corner of the window.\n# This is no longer used unless position is not set in registry.";
+
+CONF_TXT_T cf_text_newsrc_path[] = "Full path and name of NEWSRC file";
+
+
+/*----------------------------------------------------------------------
+These are the variables that control a number of pine functions. They
+come out of the .pinerc and the /usr/local/lib/pine.conf files. Some can
+be set by the user while in Alpine. Eventually all the local ones should
+be so and maybe the global ones too.
+
+Each variable can have a command-line, user, global, and current value.
+All of these values are malloc'd. The user value is the one read out of
+the user's .pinerc, the global value is the one from the system pine
+configuration file. There are often defaults for the global values, set
+at the start of init_vars(). Perhaps someday there will be group values.
+The current value is the one that is actually in use.
+ ----*/
+/* name is_changed_val
+ remove_quotes |
+ is_outermost | |
+ is_onlymain | | |
+ is_fixed | | | |
+ is_list | | | | |
+ is_global | | | | | |
+ is_user | | | | | | |
+ been_written | | | | | | | |
+ is_used | | | | | | | | |
+ is_obsolete | | | | | | | | | |
+ | | | | | | | | | | |
+ (on following line) description | | | | | | | | | | |
+ | | | | | | | | | | | |
+ | | | | | | | | | | | | */
+static struct variable variables[] = {
+{"personal-name", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_personal_name},
+{
+#if defined(DOS) || defined(OS2)
+ /* Have to have this on DOS, PC's, Macs, etc... */
+ "user-id", 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+#else /* Don't allow on UNIX machines for some security */
+ "user-id", 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+#endif
+ "User ID", cf_text_user_id},
+{"user-domain", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_user_domain},
+{"smtp-server", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ "SMTP Server (for sending)", cf_text_smtp_server},
+{"nntp-server", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ "NNTP Server (for news)", cf_text_nntp_server},
+{"inbox-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_inbox_path},
+{"incoming-archive-folders", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_archived_folders},
+{"pruned-folders", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_pruned_folders},
+{"default-fcc", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Default Fcc (File carbon copy)", cf_text_default_fcc},
+{"default-saved-msg-folder", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Default Saved Message Folder", cf_text_default_saved},
+{"postponed-folder", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_postponed_folder},
+{"read-message-folder", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_read_message_folder},
+{"form-letter-folder", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_form_letter_folder},
+{"trash-folder", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_trash_folder},
+{"literal-signature", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_literal_sig},
+{"signature-file", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_signature_file},
+{"feature-list", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_feature_list},
+{"initial-keystroke-list", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_initial_keystroke_list},
+{"default-composer-hdrs", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Default Composer Headers", cf_text_default_composer_hdrs},
+{"customized-hdrs", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Customized Headers", cf_text_customized_hdrs},
+{"viewer-hdrs", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Viewer Headers", cf_text_view_headers},
+{"viewer-margin-left", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_view_margin_left},
+{"viewer-margin-right", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_view_margin_right},
+{"quote-suppression-threshold", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_quote_suppression},
+{"saved-msg-name-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Saved Message Name Rule", cf_text_save_msg_name_rule},
+{"fcc-name-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_fcc_name_rule},
+{"sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_sort_key},
+{"addrbook-sort-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Address Book Sort Rule", cf_text_addrbook_sort_rule},
+{"folder-sort-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_folder_sort_rule},
+{"goto-default-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_goto_default},
+{"incoming-startup-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_inc_startup},
+{"pruning-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_pruning_rule},
+{"folder-reopen-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_reopen_rule},
+{"threading-display-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_thread_disp_style},
+{"threading-index-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_thread_index_style},
+{"threading-indicator-character", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_thread_more_char},
+{"threading-expanded-character", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_thread_exp_char},
+{"threading-lastreply-character", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Threading Last Reply Character", cf_text_thread_lastreply_char},
+#ifndef _WINDOWS
+{"display-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_disp_char_set},
+{"character-set", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_old_char_set},
+{"keyboard-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_key_char_set},
+#endif /* ! _WINDOWS */
+{"posting-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_post_character_set},
+{"unknown-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_unk_character_set},
+{"editor", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_editor},
+{"speller", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_speller},
+{"composer-wrap-column", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_fillcol},
+{"reply-indent-string", 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ NULL, cf_text_replystr},
+{"reply-leadin", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_replyintro},
+{"quote-replace-string", 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ NULL, cf_text_quotereplstr},
+{"composer-word-separators", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_wordsep},
+{"empty-header-message", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_emptyhdr},
+{"image-viewer", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_image_viewer},
+{"use-only-domain-name", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_use_only_domain_name},
+{"bugs-fullname", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_bugs_fullname},
+{"bugs-address", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_bugs_address},
+{"bugs-additional-data", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_bugs_extras},
+{"suggest-fullname", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_suggest_fullname},
+{"suggest-address", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_suggest_address},
+{"local-fullname", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_local_fullname},
+{"local-address", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_local_address},
+{"forced-abook-entry", 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_forced_abook},
+{"kblock-passwd-count", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_kblock_passwd},
+{"display-filters", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_in_fltr},
+{"sending-filters", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_out_fltr},
+{"alt-addresses", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Alternate Addresses", cf_text_alt_addrs},
+{"keywords", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_keywords},
+{"keyword-surrounding-chars", 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ "Keyword Surrounding Characters", cf_text_kw_braces},
+{"opening-text-separator-chars", 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ "Opening Text Separator Characters", cf_text_opening_sep},
+{"addressbook-formats", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Address Book Formats", cf_text_abook_formats},
+{"index-format", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_index_format},
+{"viewer-overlap", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_overlap},
+{"scroll-margin", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_margin},
+{"status-message-delay", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_stat_msg_delay},
+{"busy-cue-rate", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_busy_cue_rate},
+{"mail-check-interval", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_mailcheck},
+{"mail-check-interval-noncurrent", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_mailchecknoncurr},
+{"maildrop-check-minimum", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_maildropcheck},
+{"nntp-range", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "NNTP Range", cf_text_nntprange},
+{"newsrc-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_newsrc_path},
+{"news-active-file-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_news_active},
+{"news-spool-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_news_spooldir},
+{"upload-command", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_upload_cmd},
+{"upload-command-prefix", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_upload_prefix},
+{"download-command", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_download_cmd},
+{"download-command-prefix", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_download_prefix},
+{"mailcap-search-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_mailcap_path},
+{"mimetype-search-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_mimetype_path},
+{"url-viewers", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "URL-Viewers", cf_text_browser},
+{"max-remote-connections", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "Maximum Remote Connections", cf_text_maxremstreams},
+{"stay-open-folders", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Stayopen Folders", cf_text_permlocked},
+{"incoming-check-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_inc_check_timeo},
+{"incoming-check-interval", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_inc_check_interval},
+{"incoming-check-interval-secondary", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_inc_second_check_interval},
+{"incoming-check-list", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_inc_check_list},
+{"dead-letter-files", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_deadlets},
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+{"newmail-fifo-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "NewMail FIFO Path", cf_text_newmail_fifo_path},
+#endif
+{"newmail-window-width", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "NewMail Window Width", cf_text_nmw_width},
+/*
+ * Starting here, the variables are hidden in the Setup/Config screen.
+ * They are exposed if feature expose-hidden-config is set.
+ */
+{"incoming-folders", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_incoming_folders},
+{"mail-directory", 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_mail_directory},
+{"folder-collections", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_folder_collections},
+{"news-collections", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_news_collections},
+{"address-book", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_address_book},
+{"global-address-book", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_global_address_book},
+{"standard-printer", 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_standard_printer},
+{"last-time-prune-questioned", 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+ NULL, cf_text_last_time_prune_quest},
+{"last-version-used", 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
+ NULL, cf_text_last_version_used},
+{"sendmail-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_sendmail_path},
+{"operating-dir", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_oper_dir},
+{"user-input-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_user_input_timeo},
+/* OBSOLETE */
+#ifdef DEBUGJOURNAL
+{"debug-memory", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_debug_mem},
+#endif
+{"tcp-open-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "TCP Open Timeout", cf_text_tcp_open_timeo},
+{"tcp-read-warning-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "TCP Read Warning Timeout", cf_text_tcp_read_timeo},
+{"tcp-write-warning-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "TCP Write Warning Timeout", cf_text_tcp_write_timeo},
+{"tcp-query-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "TCP Query Timeout", cf_text_tcp_query_timeo},
+{"rsh-command", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_rsh_command},
+{"rsh-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_rsh_path},
+{"rsh-open-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_rsh_open_timeo},
+{"ssh-command", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_ssh_command},
+{"ssh-path", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_ssh_path},
+{"ssh-open-timeout", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_ssh_open_timeo},
+{"new-version-threshold", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_version_threshold},
+{"disable-these-drivers", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_disable_drivers},
+{"disable-these-authenticators", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_disable_auths},
+{"remote-abook-metafile", 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
+ NULL, cf_text_remote_abook_metafile},
+{"remote-abook-history", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_remote_abook_history},
+{"remote-abook-validity", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_remote_abook_validity},
+{"printer", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_printer},
+{"personal-print-command", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_personal_print_command},
+{"personal-print-category", 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_personal_print_cat},
+{"patterns", 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_old_patterns},
+{"patterns-roles", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_patterns},
+{"patterns-filters2", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Patterns Filters", cf_text_patterns},
+{"patterns-filters", 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_old_filters},
+{"patterns-scores2", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "Patterns Scores", cf_text_patterns},
+{"patterns-scores", 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_old_scores},
+{"patterns-indexcolors", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_patterns},
+{"patterns-other", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_patterns},
+{"patterns-search", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ NULL, cf_text_patterns},
+/* OBSOLETE VARS */
+{"elm-style-save", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_elm_style_save},
+{"header-in-reply", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_header_in_reply},
+{"feature-level", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_feature_level},
+{"old-style-reply", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_old_style_reply},
+{"compose-mime", 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_compose_mime},
+{"show-all-characters", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_show_all_characters},
+{"save-by-sender", 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_save_by_sender},
+#if defined(DOS) || defined(OS2)
+{"file-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_file_dir},
+{"folder-extension", 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_folder_extension},
+#endif
+#ifndef _WINDOWS
+{"color-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_color_style},
+#endif
+{"current-indexline-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_current_indexline_style},
+{"titlebar-color-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_titlebar_color_style},
+{"normal-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, cf_text_normal_foreground_color},
+{"normal-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"reverse-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"reverse-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"title-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"title-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"title-closed-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"title-closed-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"status-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"status-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"keylabel-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"keylabel-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"keyname-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"keyname-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"selectable-item-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"selectable-item-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"meta-message-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"meta-message-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote1-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote1-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote2-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote2-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote3-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"quote3-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"incoming-unseen-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"incoming-unseen-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"signature-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"signature-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"prompt-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"prompt-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"header-general-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"header-general-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-to-me-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-to-me-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-important-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-important-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-deleted-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-deleted-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-answered-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-answered-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-new-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-new-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-recent-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-recent-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-forward-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-forward-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-unseen-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-unseen-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-highpriority-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-highpriority-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-lowpriority-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-lowpriority-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-arrow-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-arrow-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-subject-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-subject-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-from-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-from-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-opening-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"index-opening-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0},
+{"viewer-hdr-colors", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ "Viewer Header Colors", cf_text_view_hdr_color},
+{"keyword-colors", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
+ NULL, cf_text_kw_colors},
+#ifdef _WINDOWS
+{"font-name", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, "name and size of font."},
+{"font-size", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"font-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"font-char-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"print-font-name", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ NULL, "name and size of printer font."},
+{"print-font-size", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"print-font-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"print-font-char-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+{"window-position", 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0,
+ NULL, cf_text_window_position},
+{"cursor-style", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL},
+#endif /* _WINDOWS */
+#ifdef SMIME
+{"smime-public-cert-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Public Cert Directory", cf_text_publiccertdir},
+{"smime-public-cert-container", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Public Cert Container", cf_text_publiccertcontainer},
+{"smime-private-key-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Private Key Directory", cf_text_privatekeydir},
+{"smime-private-key-container", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Private Key Container", cf_text_privatekeycontainer},
+{"smime-cacert-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Cert Authority Directory", cf_text_cacertdir},
+{"smime-cacert-container", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "S/MIME - Cert Authority Container", cf_text_cacertcontainer},
+#endif /* SMIME */
+#ifdef ENABLE_LDAP
+{"ldap-servers", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ "LDAP Servers", cf_text_ldap_server},
+#endif /* ENABLE_LDAP */
+{"rss-news", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - RSS News", cf_text_rss_news},
+{"rss-weather", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - RSS Weather", cf_text_rss_weather},
+{"wp-indexheight", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - Index Height", cf_text_wp_indexheight},
+{"wp-indexlines", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - Index Lines", cf_text_wp_indexlines},
+{"wp-aggstate", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - Aggregate State", cf_text_wp_aggstate},
+{"wp-state", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - Cross Session State", cf_text_wp_state},
+{"wp-columns", 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+ "WEB ALPINE - Columns", cf_text_wp_columns},
+{NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL,NULL}
+};
+
+
+struct variable *
+var_from_name(char *name)
+{
+ struct variable *v;
+ int i;
+
+ if(!(name && name[0]))
+ return(NULL);
+
+ for(i = 0; (v = &variables[i]) && v->name; i++)
+ if(!strucmp(v->name,name))
+ return(v);
+
+ return(NULL);
+}
+
+
+void
+init_init_vars(struct pine *ps)
+{
+ ps->vars = variables;
+}
+
+
+#define DSIZE (25000)
+/* this is just like dprint except it prints to a char * */
+#ifdef DEBUG
+#define mprint(n,x) { \
+ if(debug >= (n)){ \
+ snprintf x ; \
+ db += strlen(db); \
+ } \
+ }
+#else
+#define mprint(n,x)
+#endif
+
+/*
+ * this was split out from init_vars so we can get at the
+ * pinerc location sooner.
+ */
+void
+init_pinerc(struct pine *ps, char **debug_out)
+{
+ char buf[MAXPATH+1], *p, *db;
+#if defined(DOS) || defined(OS2)
+ char buf2[MAXPATH+1], l_pinerc[MAXPATH+1];
+ int nopinerc = 0, confregset = -1;
+ register struct variable *vars = ps->vars;
+#endif
+
+#ifdef DEBUG
+ /*
+ * Since this routine is called before we've had a chance to set up
+ * the debug file for output, we put the debugging into memory and
+ * pass it back to the caller for use after init_debug(). We just
+ * allocate plenty of space.
+ */
+ if(debug_out){
+ db = *debug_out = (char *)fs_get(DSIZE * sizeof(char));
+ db[0] = '\0';
+ }
+#endif
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "\n -- init_pinerc --\n\n"));
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * Rules for the config/support file locations under DOS are:
+ *
+ * 1) The location of the PINERC is searched for in the following
+ * order of precedence:
+ * - File pointed to by '-p' command line option
+ * - File pointed to by PINERC environment variable
+ * - $HOME\pine
+ * - same dir as argv[0]
+ *
+ * 2) The HOME environment variable, if not set, defaults to
+ * root of the current working drive (see alpine.c)
+ *
+ * 3) The default for external files (PINE.SIG and ADDRBOOK) is the
+ * same directory as the pinerc
+ *
+ * 4) The support files (PINE.HLP and PINE.NDX) are expected to be in
+ * the same directory as PINE.EXE.
+ */
+
+ if(ps->prc){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Personal config \"%.100s\" comes from command line\n",
+ (ps->prc && ps->prc->name) ? ps->prc->name : "<no name>"));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Personal config not set on cmdline, checking for $PINERC\n"));
+ }
+
+ /*
+ * First, if prc hasn't been set by a command-line -p, check to see
+ * if PINERC is in the environment. If so, treat it just like we
+ * would have treated it if it were a command-line arg.
+ */
+ if(!ps->prc && (p = getenv("PINERC")) && *p){
+ char path[MAXPATH], dir[MAXPATH];
+
+ if(IS_REMOTE(p) || is_absolute_path(p)){
+ strncpy(path, p, sizeof(path)-1);
+ path[sizeof(path)-1] = '\0';
+ }
+ else{
+ getcwd(dir, sizeof(dir));
+ build_path(path, dir, p, sizeof(path));
+ }
+
+ if(!IS_REMOTE(p))
+ ps->pinerc = cpystr(path);
+
+ ps->prc = new_pinerc_s(path);
+
+ if(ps->prc){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, personal config \"%.100s\" comes from $PINERC\n",
+ (ps->prc && ps->prc->name) ? ps->prc->name : "<no name>"));
+ }
+ }
+
+ /*
+ * Pinerc used to be the name of the pinerc file. Then we added
+ * the possibility of the pinerc file being remote, and we replaced
+ * the variable pinerc with the structure prc. Unfortunately, some
+ * parts of pine rely on the fact that pinerc is the name of the
+ * pinerc _file_, and use the directory that the pinerc file is located
+ * in for their own purposes. We want to preserve that so things will
+ * keep working. So, even if the real pinerc is remote, we need to
+ * put the name of a pinerc file in the pinerc variable so that the
+ * directory which contains that file is writable. The file itself
+ * doesn't have to exist for this purpose, since we are really only
+ * using the name of the directory containing the file. Twisted.
+ * (Alternatively, we could fix all of the code that uses the pinerc
+ * variable for this purpose to use a new variable which really is
+ * just a directory.) hubert 2000-sep
+ *
+ * There are 3 cases. If pinerc is already set that means that the user
+ * gave either a -p pinerc or an environment pinerc that is a local file,
+ * and we are done. If pinerc is not set, then either prc is set or not.
+ * If prc is set then the -p arg or PINERC value is a remote pinerc.
+ * In that case we need to find a local directory to use, and put that
+ * directory in the pinerc variable (with a fake filename tagged on).
+ * If prc is not set, then user hasn't told us anything so we have to
+ * try to find the default pinerc file by looking down the path of
+ * possibilities. When we find it, we'll also use that directory.
+ */
+ if(!ps->pinerc){
+ *l_pinerc = '\0';
+ *buf = '\0';
+
+ if(ps->prc){ /* remote pinerc case */
+ /*
+ * We don't give them an l_pinerc unless they tell us where
+ * to put it.
+ */
+ if(ps->aux_files_dir)
+ build_path(l_pinerc, ps->aux_files_dir, SYSTEM_PINERC,
+ sizeof(l_pinerc));
+ else{
+ /*
+ * Search for a writable directory.
+ * Mimic what happens in !prc for local case, except we
+ * don't need to look for the actual file.
+ */
+
+ /* check if $HOME\PINE is writable */
+ build_path(buf2, ps->home_dir, DF_PINEDIR, sizeof(buf2));
+ if(is_writable_dir(buf2) == 0)
+ build_path(l_pinerc, buf2, SYSTEM_PINERC, sizeof(l_pinerc));
+ else{ /* $HOME\PINE not a writable dir */
+ /* use this unless registry redirects us */
+ build_path(l_pinerc, ps->pine_dir, SYSTEM_PINERC,
+ sizeof(l_pinerc));
+#ifdef _WINDOWS
+ /* if in registry, use that value */
+ if(mswin_reg(MSWR_OP_GET, MSWR_PINE_RC, buf2, sizeof(buf2))
+ && !IS_REMOTE(buf2)){
+ strncpy(l_pinerc, buf2, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+ }
+#endif
+ }
+ }
+ }
+ else{ /* searching for pinerc file to use */
+ /*
+ * Buf2 is $HOME\PINE. If $HOME is not explicitly set,
+ * it defaults to the current working drive (often C:).
+ * See alpine.c to see how it is initially set.
+ */
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, searching...\n"));
+ build_path(buf2, ps->home_dir, DF_PINEDIR, sizeof(buf2));
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " checking for writable %.100s dir \"%.100s\" off of homedir\n",
+ DF_PINEDIR, buf2));
+ if(is_writable_dir(buf2) == 0){
+ /*
+ * $HOME\PINE exists and is writable.
+ * See if $HOME\PINE\PINERC exists.
+ */
+ build_path(buf, buf2, SYSTEM_PINERC, sizeof(buf));
+ strncpy(l_pinerc, buf, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " yes, now checking for file \"%.100s\"\n",
+ buf));
+ if(can_access(buf, ACCESS_EXISTS) == 0){ /* found it! */
+ /*
+ * Buf is what we were looking for.
+ * It is local and can be used for the directory, too.
+ */
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found it\n"));
+ }
+ else{
+ /*
+ * No $HOME\PINE\PINERC, look for
+ * one in same dir as PINE.EXE.
+ */
+ build_path(buf2, ps->pine_dir, SYSTEM_PINERC,
+ sizeof(buf2));
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " no, checking for \"%.100s\" in pine.exe dir\n",
+ buf2));
+ if(can_access(buf2, ACCESS_EXISTS) == 0){
+ /* found it! */
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found it\n"));
+ strncpy(buf, buf2, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ strncpy(l_pinerc, buf2, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+ }
+ else{
+#ifdef _WINDOWS
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, checking in registry\n"));
+ if(mswin_reg(MSWR_OP_GET, MSWR_PINE_RC,
+ buf2, sizeof(buf2))){
+ strncpy(buf, buf2, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ if(!IS_REMOTE(buf2)){
+ strncpy(l_pinerc, buf2, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+ }
+ /*
+ * Now buf is the pinerc to be used, l_pinerc is
+ * the directory, which may be either same as buf
+ * or it may be $HOME\PINE if registry gives us
+ * a remote pinerc.
+ */
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found \"%.100s\" in registry\n",
+ buf));
+ }
+ else{
+ nopinerc = 1;
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " not found, asking user\n"));
+ }
+#else
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " not found\n"));
+#endif
+ }
+ }
+
+ /*
+ * Buf is the pinerc (could be remote if from registry)
+ * and l_pinerc is the local pinerc, which may not exist.
+ */
+ }
+ else{ /* $HOME\PINE not a writable dir */
+ /*
+ * We notice that the order of checking in the registry
+ * and checking in the ALPINE.EXE directory are different
+ * in this case versus the is_writable_dir(buf2) case, and
+ * that does sort of look like a bug. However,
+ * we don't think this is a bug since we did it on purpose
+ * a long time ago. So even though we can't remember why
+ * it is this way, we think we would rediscover why if we
+ * changed it! So we won't change it.
+ */
+
+ /*
+ * Change the default to use to the ALPINE.EXE directory.
+ */
+ build_path(buf, ps->pine_dir, SYSTEM_PINERC, sizeof(buf));
+ strncpy(l_pinerc, buf, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+#ifdef _WINDOWS
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, not writable, checking in registry\n"));
+ /* if in registry, use that value */
+ if(mswin_reg(MSWR_OP_GET, MSWR_PINE_RC, buf2, sizeof(buf2))){
+ strncpy(buf, buf2, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found \"%.100s\" in registry\n",
+ buf));
+ if(!IS_REMOTE(buf)){
+ strncpy(l_pinerc, buf, sizeof(l_pinerc)-1);
+ l_pinerc[sizeof(l_pinerc)-1] = '\0';
+ }
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " no, checking for \"%.100s\" in alpine.exe dir\n",
+ buf));
+
+ if(can_access(buf, ACCESS_EXISTS) == 0){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found it\n"));
+ }
+ else{
+ nopinerc = 1;
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " not found, asking user\n"));
+ }
+ }
+#else
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " no, checking for \"%.100s\" in alpine.exe dir\n",
+ buf));
+
+ if(can_access(buf, ACCESS_EXISTS) == 0){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " found it\n"));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " not found, creating it\n"));
+ }
+#endif
+ }
+
+ /*
+ * When we get here we have buf set to the name of the
+ * pinerc, which could be local or remote. We have l_pinerc
+ * set to the same as buf if buf is local, and set to another
+ * name otherwise, hopefully contained in a writable directory.
+ */
+#ifdef _WINDOWS
+ if(nopinerc || ps_global->install_flag){
+ char buf3[MAXPATH+1];
+
+ confregset = 0;
+ strncpy(buf3, buf, MAXPATH);
+ buf3[MAXPATH] = '\0';
+ if(os_config_dialog(buf3, MAXPATH,
+ &confregset, nopinerc) == 0){
+ strncpy(buf, buf3, MAXPATH);
+ buf[MAXPATH] = '\0';
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " not found, creating it\n"));
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " user says use \"%.100s\"\n", buf));
+ if(!IS_REMOTE(buf)){
+ strncpy(l_pinerc, buf, MAXPATH);
+ l_pinerc[MAXPATH] = '\0';
+ }
+ }
+ else{
+ exit(-1);
+ }
+ }
+#endif
+ ps->prc = new_pinerc_s(buf);
+ }
+
+ ps->pinerc = cpystr(l_pinerc);
+ }
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * The goal here is to set the auxiliary directory in the pinerc variable.
+ * We are making the assumption that any reference to the pinerc variable
+ * after this point is used only as a directory in which to store things,
+ * with the prc variable being the preferred place to store pinerc location.
+ * If -aux isn't set, then there is no change. -jpf 08/2001
+ */
+ if(ps->aux_files_dir){
+ l_pinerc[0] = '\0';
+ build_path(l_pinerc, ps->aux_files_dir, SYSTEM_PINERC,
+ sizeof(l_pinerc));
+ if(ps->pinerc) fs_give((void **)&ps->pinerc);
+ ps->pinerc = cpystr(l_pinerc);
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Setting aux_files_dir to \"%.100s\"\n",
+ ps->aux_files_dir));
+ }
+#endif
+
+#ifdef _WINDOWS
+ if(confregset && (ps->update_registry != UREG_NEVER_SET))
+ mswin_reg(MSWR_OP_SET | ((ps->update_registry == UREG_ALWAYS_SET)
+ || confregset == 1 ? MSWR_OP_FORCE : 0),
+ MSWR_PINE_RC,
+ (ps->prc && ps->prc->name) ?
+ ps->prc->name : ps->pinerc, (size_t)NULL);
+#endif
+
+ /*
+ * Now that we know the default for the PINERC, build NEWSRC default.
+ * Backward compatibility makes this kind of funky. If what the
+ * c-client thinks the NEWSRC should be exists *AND* it doesn't
+ * already exist in the PINERC's dir, use c-client's default, otherwise
+ * use the one next to the PINERC...
+ */
+ p = last_cmpnt(ps->pinerc);
+ buf[0] = '\0';
+ if(p != NULL){
+ strncpy(buf, ps->pinerc, MIN(p - ps->pinerc, sizeof(buf)-1));
+ buf[MIN(p - ps->pinerc, sizeof(buf)-1)] = '\0';
+ }
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Using directory \"%.100s\" for auxiliary files\n", buf));
+ strncat(buf, "NEWSRC", sizeof(buf)-1-strlen(buf));
+
+ if(!(p = (void *) mail_parameters(NULL, GET_NEWSRC, (void *)NULL))
+ || can_access(p, ACCESS_EXISTS) < 0
+ || can_access(buf, ACCESS_EXISTS) == 0){
+ mail_parameters(NULL, SET_NEWSRC, (void *)buf);
+ GLO_NEWSRC_PATH = cpystr(buf);
+ }
+ else
+ GLO_NEWSRC_PATH = cpystr(p);
+
+ if(ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Global config \"%.100s\" comes from command line\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<no name>"));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Global config not set on cmdline, checking for $PINECONF\n"));
+ }
+
+ if(!ps->pconf && (p = getenv("PINECONF"))){
+ ps->pconf = new_pinerc_s(p);
+ if(ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, global config \"%.100s\" comes from $PINECONF\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<no name>"));
+ }
+ }
+#ifdef _WINDOWS
+ else if(!ps->pconf
+ && mswin_reg(MSWR_OP_GET, MSWR_PINE_CONF, buf2, sizeof(buf2))){
+ ps->pconf = new_pinerc_s(buf2);
+ if(ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, global config \"%.100s\" comes from Registry\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<no name>"));
+ }
+ }
+#endif
+ if(!ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, there is no global config\n"));
+ }
+#ifdef _WINDOWS
+ else if (ps->pconf && ps->pconf->name &&
+ (ps->update_registry != UREG_NEVER_SET)){
+ mswin_reg(MSWR_OP_SET | ((ps->update_registry == UREG_ALWAYS_SET)
+ ? MSWR_OP_FORCE : 0),
+ MSWR_PINE_CONF,
+ ps->pconf->name, (size_t)NULL);
+ }
+#endif
+
+ if(!ps->prc)
+ ps->prc = new_pinerc_s(ps->pinerc);
+
+ if(ps->exceptions){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Exceptions config \"%.100s\" comes from command line\n",
+ ps->exceptions));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Exceptions config not set on cmdline, checking for $PINERCEX\n"));
+ }
+
+ /*
+ * Exceptions is done slightly differently from pinerc. Instead of setting
+ * post_prc in args.c we just set the string and use it here. We do
+ * that so that we can put it in the same directory as the pinerc if
+ * exceptions is a relative name, and pinerc may not be set until here.
+ *
+ * First, just like for pinerc, check environment variable if it wasn't
+ * set on the command line.
+ */
+ if(!ps->exceptions && (p = getenv("PINERCEX")) && *p){
+ ps->exceptions = cpystr(p);
+ if(ps->exceptions){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, exceptions config \"%.100s\" comes from $PINERCEX\n",
+ ps->exceptions));
+ }
+ }
+
+ /*
+ * If still not set, try specific file in same dir as pinerc.
+ * Only use it if the file exists.
+ */
+ if(!ps->exceptions){
+ p = last_cmpnt(ps->pinerc);
+ buf[0] = '\0';
+ if(p != NULL){
+ strncpy(buf, ps->pinerc, MIN(p - ps->pinerc, sizeof(buf)-1));
+ buf[MIN(p - ps->pinerc, sizeof(buf)-1)] = '\0';
+ }
+
+ strncat(buf, "PINERCEX", sizeof(buf)-1-strlen(buf));
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " no, checking for default \"%.100s\" in pinerc dir\n", buf));
+ if(can_access(buf, ACCESS_EXISTS) == 0) /* found it! */
+ ps->exceptions = cpystr(buf);
+
+ if(ps->exceptions){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, exceptions config \"%.100s\" comes from default\n",
+ ps->exceptions));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, there is no exceptions config\n"));
+ }
+ }
+
+#else /* unix */
+
+ if(ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Global config \"%.100s\" comes from command line\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<no name>"));
+ }
+
+ if(!ps->pconf){
+ ps->pconf = new_pinerc_s(SYSTEM_PINERC);
+ if(ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Global config \"%.100s\" is default\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<no name>"));
+ }
+ }
+
+ if(!ps->pconf){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "No global config!\n"));
+ }
+
+ if(ps->prc){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Personal config \"%.100s\" comes from command line\n",
+ (ps->prc && ps->prc->name) ? ps->prc->name : "<no name>"));
+ }
+
+ if(!ps->pinerc){
+ build_path(buf, ps->home_dir, ".pinerc", sizeof(buf));
+ ps->pinerc = cpystr(buf);
+ }
+
+ if(!ps->prc){
+ ps->prc = new_pinerc_s(ps->pinerc);
+ if(ps->prc){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Personal config \"%.100s\" is default\n",
+ (ps->prc && ps->prc->name) ? ps->prc->name : "<no name>"));
+ }
+ }
+
+ if(!ps->prc){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "No personal config!\n"));
+ }
+
+ if(ps->exceptions){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ "Exceptions config \"%.100s\" comes from command line\n",
+ ps->exceptions));
+ }
+
+ /*
+ * If not set, try specific file in same dir as pinerc.
+ * Only use it if the file exists.
+ */
+ if(!ps->exceptions){
+ p = last_cmpnt(ps->pinerc);
+ buf[0] = '\0';
+ if(p != NULL){
+ strncpy(buf, ps->pinerc, MIN(p - ps->pinerc, sizeof(buf)-1));
+ buf[MIN(p - ps->pinerc, sizeof(buf)-1)] = '\0';
+ }
+
+ strncat(buf, ".pinercex", sizeof(buf)-1-strlen(buf));
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "Exceptions config not set on cmdline\n checking for default \"%.100s\" in pinerc dir\n", buf));
+
+ if(can_access(buf, ACCESS_EXISTS) == 0) /* found it! */
+ ps->exceptions = cpystr(buf);
+
+ if(ps->exceptions){
+ mprint(2, (db, DSIZE-(db-(*debug_out)),
+ " yes, exceptions config \"%.100s\" is default\n",
+ ps->exceptions));
+ }
+ else{
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " no, there is no exceptions config\n"));
+ }
+ }
+
+#endif /* unix */
+
+ if(ps->exceptions){
+
+ if(!IS_REMOTE(ps->exceptions) &&
+ !is_absolute_path(ps->exceptions)){
+#if defined(DOS) || defined(OS2)
+ p = last_cmpnt(ps->pinerc);
+ buf[0] = '\0';
+ if(p != NULL){
+ strncpy(buf, ps->pinerc, MIN(p - ps->pinerc, sizeof(buf)-1));
+ buf[MIN(p - ps->pinerc, sizeof(buf)-1)] = '\0';
+ }
+
+ strncat(buf, ps->exceptions, sizeof(buf)-1-strlen(buf));
+#else
+ build_path(buf, ps->home_dir, ps->exceptions, sizeof(buf));
+#endif
+ }
+ else{
+ strncpy(buf, ps->exceptions, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ ps->post_prc = new_pinerc_s(buf);
+
+ fs_give((void **)&ps->exceptions);
+ }
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "\n Global config: %.100s\n",
+ (ps->pconf && ps->pconf->name) ? ps->pconf->name : "<none>"));
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " Personal config: %.100s\n",
+ (ps->prc && ps->prc->name) ? ps->prc->name : "<none>"));
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " Exceptions config: %.100s\n",
+ (ps->post_prc && ps->post_prc->name) ? ps->post_prc->name
+ : "<none>"));
+#if !defined(DOS) && !defined(OS2)
+ if(SYSTEM_PINERC_FIXED){
+ mprint(2, (db, DSIZE-(db-(*debug_out)), " Fixed config: %.100s\n", SYSTEM_PINERC_FIXED));
+ }
+#endif
+
+ mprint(2, (db, DSIZE-(db-(*debug_out)), "\n"));
+}
+
+
+/*----------------------------------------------------------------------
+ Initialize the variables
+
+ Args: ps -- The usual pine structure
+
+ Result:
+
+ This reads the system pine configuration file and the user's pine
+configuration file ".pinerc" and places the results in the variables
+structure. It sorts out what was read and sets a few other variables
+based on the contents.
+ ----*/
+void
+init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **))
+{
+ char buf[MAXPATH+1], *p, *q, **s;
+ register struct variable *vars = ps->vars;
+ int obs_header_in_reply = 0, /* the obs_ variables are to */
+ obs_old_style_reply = 0, /* support backwards compatibility */
+ obs_save_by_sender, i, def_sort_rev;
+ long rvl;
+ PINERC_S *fixedprc = NULL;
+ FeatureLevel obs_feature_level;
+ char *fromcharset = NULL;
+ char *err = NULL;
+
+ dprint((5, "init_vars:\n"));
+
+ /*--- The defaults here are defined in os-xxx.h so they can vary
+ per machine ---*/
+
+ GLO_PRINTER = cpystr(DF_DEFAULT_PRINTER);
+ GLO_ELM_STYLE_SAVE = cpystr(DF_ELM_STYLE_SAVE);
+ GLO_SAVE_BY_SENDER = cpystr(DF_SAVE_BY_SENDER);
+ GLO_HEADER_IN_REPLY = cpystr(DF_HEADER_IN_REPLY);
+ GLO_INBOX_PATH = cpystr("inbox");
+ GLO_DEFAULT_FCC = cpystr(DF_DEFAULT_FCC);
+ GLO_DEFAULT_SAVE_FOLDER = cpystr(DEFAULT_SAVE);
+ GLO_POSTPONED_FOLDER = cpystr(POSTPONED_MSGS);
+ GLO_TRASH_FOLDER = cpystr(TRASH_FOLDER);
+ GLO_USE_ONLY_DOMAIN_NAME = cpystr(DF_USE_ONLY_DOMAIN_NAME);
+ GLO_FEATURE_LEVEL = cpystr("sappling");
+ GLO_OLD_STYLE_REPLY = cpystr(DF_OLD_STYLE_REPLY);
+ GLO_SORT_KEY = cpystr(DF_SORT_KEY);
+ GLO_SAVED_MSG_NAME_RULE = cpystr(DF_SAVED_MSG_NAME_RULE);
+ GLO_FCC_RULE = cpystr(DF_FCC_RULE);
+ GLO_AB_SORT_RULE = cpystr(DF_AB_SORT_RULE);
+ GLO_FLD_SORT_RULE = cpystr(DF_FLD_SORT_RULE);
+ GLO_SIGNATURE_FILE = cpystr(DF_SIGNATURE_FILE);
+ GLO_MAIL_DIRECTORY = cpystr(DF_MAIL_DIRECTORY);
+ GLO_REMOTE_ABOOK_HISTORY = cpystr(DF_REMOTE_ABOOK_HISTORY);
+ GLO_REMOTE_ABOOK_VALIDITY = cpystr(DF_REMOTE_ABOOK_VALIDITY);
+ GLO_GOTO_DEFAULT_RULE = cpystr(DF_GOTO_DEFAULT_RULE);
+ GLO_INCOMING_STARTUP = cpystr(DF_INCOMING_STARTUP);
+ GLO_PRUNING_RULE = cpystr(DF_PRUNING_RULE);
+ GLO_REOPEN_RULE = cpystr(DF_REOPEN_RULE);
+ GLO_THREAD_DISP_STYLE = cpystr(DF_THREAD_DISP_STYLE);
+ GLO_THREAD_INDEX_STYLE = cpystr(DF_THREAD_INDEX_STYLE);
+ GLO_THREAD_MORE_CHAR = cpystr(DF_THREAD_MORE_CHAR);
+ GLO_THREAD_EXP_CHAR = cpystr(DF_THREAD_EXP_CHAR);
+ GLO_THREAD_LASTREPLY_CHAR = cpystr(DF_THREAD_LASTREPLY_CHAR);
+ GLO_BUGS_FULLNAME = cpystr("Sorry No Address");
+ GLO_BUGS_ADDRESS = cpystr("nobody");
+ GLO_SUGGEST_FULLNAME = cpystr("Sorry No Address");
+ GLO_SUGGEST_ADDRESS = cpystr("nobody");
+ GLO_LOCAL_FULLNAME = cpystr(DF_LOCAL_FULLNAME);
+ GLO_LOCAL_ADDRESS = cpystr(DF_LOCAL_ADDRESS);
+ GLO_OVERLAP = cpystr(DF_OVERLAP);
+ GLO_MAXREMSTREAM = cpystr(DF_MAXREMSTREAM);
+ GLO_MARGIN = cpystr(DF_MARGIN);
+ GLO_FILLCOL = cpystr(DF_FILLCOL);
+ GLO_DEADLETS = cpystr(DF_DEADLETS);
+ GLO_NMW_WIDTH = cpystr(DF_NMW_WIDTH);
+ GLO_REPLY_STRING = cpystr("> ");
+ GLO_REPLY_INTRO = cpystr(DEFAULT_REPLY_INTRO);
+ GLO_EMPTY_HDR_MSG = cpystr("undisclosed-recipients");
+ GLO_STATUS_MSG_DELAY = cpystr("0");
+ GLO_ACTIVE_MSG_INTERVAL = cpystr("12");
+ GLO_USERINPUTTIMEO = cpystr("0");
+ GLO_INCCHECKTIMEO = cpystr("5");
+ GLO_INCCHECKINTERVAL = cpystr("180");
+ GLO_INC2NDCHECKINTERVAL = cpystr("180");
+ GLO_MAILCHECK = cpystr(DF_MAILCHECK);
+ GLO_MAILCHECKNONCURR = cpystr("0");
+ GLO_MAILDROPCHECK = cpystr(DF_MAILDROPCHECK);
+ GLO_NNTPRANGE = cpystr("0");
+ GLO_KBLOCK_PASSWD_COUNT = cpystr(DF_KBLOCK_PASSWD_COUNT);
+ GLO_INDEX_COLOR_STYLE = cpystr("flip-colors");
+ GLO_TITLEBAR_COLOR_STYLE = cpystr("default");
+ GLO_POST_CHAR_SET = cpystr("UTF-8");
+#ifdef DF_FOLDER_EXTENSION
+ GLO_FOLDER_EXTENSION = cpystr(DF_FOLDER_EXTENSION);
+#endif
+#ifdef DF_SMTP_SERVER
+ GLO_SMTP_SERVER = parse_list(DF_SMTP_SERVER, 1,
+ PL_REMSURRQUOT, NULL);
+#endif
+
+#ifdef DF_SSHPATH
+ GLO_SSHPATH = cpystr(DF_SSHPATH);
+#endif
+#ifdef DF_SSHCMD
+ GLO_SSHCMD = cpystr(DF_SSHCMD);
+#endif
+
+#ifndef _WINDOWS
+ GLO_COLOR_STYLE = cpystr("no-color");
+ GLO_NORM_FORE_COLOR = cpystr(DEFAULT_NORM_FORE_RGB);
+ GLO_NORM_BACK_COLOR = cpystr(DEFAULT_NORM_BACK_RGB);
+#endif
+ GLO_TITLE_FORE_COLOR = cpystr(DEFAULT_TITLE_FORE_RGB);
+ GLO_TITLE_BACK_COLOR = cpystr(DEFAULT_TITLE_BACK_RGB);
+ GLO_TITLECLOSED_FORE_COLOR = cpystr(DEFAULT_TITLECLOSED_FORE_RGB);
+ GLO_TITLECLOSED_BACK_COLOR = cpystr(DEFAULT_TITLECLOSED_BACK_RGB);
+ GLO_METAMSG_FORE_COLOR = cpystr(DEFAULT_METAMSG_FORE_RGB);
+ GLO_METAMSG_BACK_COLOR = cpystr(DEFAULT_METAMSG_BACK_RGB);
+ GLO_QUOTE1_FORE_COLOR = cpystr(DEFAULT_QUOTE1_FORE_RGB);
+ GLO_QUOTE1_BACK_COLOR = cpystr(DEFAULT_QUOTE1_BACK_RGB);
+ GLO_QUOTE2_FORE_COLOR = cpystr(DEFAULT_QUOTE2_FORE_RGB);
+ GLO_QUOTE2_BACK_COLOR = cpystr(DEFAULT_QUOTE2_BACK_RGB);
+ GLO_QUOTE3_FORE_COLOR = cpystr(DEFAULT_QUOTE3_FORE_RGB);
+ GLO_QUOTE3_BACK_COLOR = cpystr(DEFAULT_QUOTE3_BACK_RGB);
+ GLO_SIGNATURE_FORE_COLOR = cpystr(DEFAULT_SIGNATURE_FORE_RGB);
+ GLO_SIGNATURE_BACK_COLOR = cpystr(DEFAULT_SIGNATURE_BACK_RGB);
+ GLO_IND_PLUS_FORE_COLOR = cpystr(DEFAULT_IND_PLUS_FORE_RGB);
+ GLO_IND_PLUS_BACK_COLOR = cpystr(DEFAULT_IND_PLUS_BACK_RGB);
+ GLO_IND_IMP_FORE_COLOR = cpystr(DEFAULT_IND_IMP_FORE_RGB);
+ GLO_IND_IMP_BACK_COLOR = cpystr(DEFAULT_IND_IMP_BACK_RGB);
+ GLO_IND_ANS_FORE_COLOR = cpystr(DEFAULT_IND_ANS_FORE_RGB);
+ GLO_IND_ANS_BACK_COLOR = cpystr(DEFAULT_IND_ANS_BACK_RGB);
+ GLO_IND_NEW_FORE_COLOR = cpystr(DEFAULT_IND_NEW_FORE_RGB);
+ GLO_IND_NEW_BACK_COLOR = cpystr(DEFAULT_IND_NEW_BACK_RGB);
+ GLO_IND_OP_FORE_COLOR = cpystr(DEFAULT_IND_OP_FORE_RGB);
+ GLO_IND_OP_BACK_COLOR = cpystr(DEFAULT_IND_OP_BACK_RGB);
+ GLO_VIEW_MARGIN_LEFT = cpystr("0");
+ GLO_VIEW_MARGIN_RIGHT = cpystr(DF_VIEW_MARGIN_RIGHT);
+ GLO_QUOTE_SUPPRESSION = cpystr(DF_QUOTE_SUPPRESSION);
+ GLO_KW_BRACES = cpystr("\"{\" \"} \"");
+ GLO_OPENING_SEP = cpystr(" - ");
+ GLO_WP_INDEXHEIGHT = cpystr("24");
+ GLO_WP_AGGSTATE = cpystr("1");
+ GLO_WP_STATE = cpystr("");
+#ifdef DF_VAR_SPELLER
+ GLO_SPELLER = cpystr(DF_VAR_SPELLER);
+#endif
+#ifdef SMIME
+ GLO_PUBLICCERT_DIR = cpystr(DF_PUBLICCERT_DIR);
+ GLO_PRIVATEKEY_DIR = cpystr(DF_PRIVATEKEY_DIR);
+ GLO_CACERT_DIR = cpystr(DF_CACERT_DIR);
+#endif /* SMIME */
+
+ /*
+ * Default first value for addrbook list if none set.
+ * We also want to be sure to set global_val to the default
+ * if is_fixed, so that address-book= will cause the default to happen.
+ */
+ if(!GLO_ADDRESSBOOK && !FIX_ADDRESSBOOK)
+ GLO_ADDRESSBOOK = parse_list(DF_ADDRESSBOOK, 1, 0, NULL);
+
+ /*
+ * Default first value if none set.
+ */
+ if(!GLO_STANDARD_PRINTER && !FIX_STANDARD_PRINTER)
+ GLO_STANDARD_PRINTER = parse_list(DF_STANDARD_PRINTER, 1, 0, NULL);
+
+/*
+ * Defining this default sshpath should cause ssh to be preferred over rsh
+ * when attempting imapd preauth calls.
+ */
+#ifdef DF_SSHPATH
+ if(DF_SSHPATH
+ && is_absolute_path(DF_SSHPATH)
+ && can_access(DF_SSHPATH, EXECUTE_ACCESS) == 0){
+ mail_parameters(NULL, SET_SSHPATH, (void *) DF_SSHPATH);
+ }
+#endif
+/*
+ * It isn't usually necessary to define this.
+ */
+#ifdef DF_SSHCMD
+ if(DF_SSHCMD){
+ mail_parameters(NULL, SET_SSHCOMMAND, (void *) DF_SSHCMD);
+ }
+#endif
+
+#if !defined(DOS) && !defined(OS2)
+ /*
+ * This is here instead of in init_pinerc so that we can get by without
+ * having a global fixedprc, since we don't need it anymore after this.
+ */
+ fixedprc = new_pinerc_s(SYSTEM_PINERC_FIXED);
+#endif
+
+ if(ps->pconf){
+ read_pinerc(ps->pconf, vars, ParseGlobal);
+ if(ps->pconf->type != Loc)
+ rd_close_remote(ps->pconf->rd);
+ }
+
+ if(ps->prc){
+ read_pinerc(ps->prc, vars, ParsePers);
+ if(ps->prc->type != Loc)
+ rd_close_remote(ps->prc->rd);
+ }
+
+ if(ps->post_prc){
+ read_pinerc(ps->post_prc, vars, ParsePersPost);
+ if(ps->post_prc->type != Loc)
+ rd_close_remote(ps->post_prc->rd);
+ }
+
+ if(fixedprc){
+ read_pinerc(fixedprc, vars, ParseFixed);
+ free_pinerc_s(&fixedprc);
+ }
+
+ ps->ew_for_except_vars = ps->post_prc ? Post : Main;
+
+ if(ps->exit_if_no_pinerc && ps->first_time_user){
+
+ /* TRANSLATORS: -bail is a literal option name, don't change it. */
+ exceptional_exit(_("Exiting because -bail option is set and config file doesn't exist."), -1);
+ }
+
+ /*
+ * Convert everything having to do with the config to UTF-8
+ * in order to avoid having to worry about it all over the
+ * place.
+ * Set the character-set first so that we may use that in
+ * the conversion process.
+ */
+ set_collation(0, 1);
+
+#ifndef _WINDOWS
+#if (HAVE_LANGINFO_H && defined(CODESET))
+
+ if(output_charset_is_supported(nl_langinfo_codeset_wrapper()))
+ ps->GLO_CHAR_SET = cpystr(nl_langinfo_codeset_wrapper());
+ else{
+ ps->GLO_CHAR_SET = cpystr("UTF-8");
+ dprint((1,"nl_langinfo(CODESET) returns unrecognized value=\"%s\", using UTF-8 as default\n", (p=nl_langinfo(CODESET)) ? p : ""));
+ }
+#else
+ ps->GLO_CHAR_SET = cpystr("UTF-8");
+#endif
+
+ set_current_val(&vars[V_CHAR_SET], TRUE, TRUE);
+ set_current_val(&vars[V_OLD_CHAR_SET], TRUE, TRUE);
+ set_current_val(&vars[V_KEY_CHAR_SET], TRUE, TRUE);
+#endif /* ! _WINDOWS */
+
+ set_current_val(&vars[V_POST_CHAR_SET], TRUE, TRUE);
+
+ /*
+ * Also set up the feature list because we need the
+ * Use-System-Translation feature to set up the charmaps.
+ */
+
+ /* way obsolete, backwards compatibility */
+ set_current_val(&vars[V_FEATURE_LEVEL], TRUE, TRUE);
+ if(strucmp(VAR_FEATURE_LEVEL, "seedling") == 0)
+ obs_feature_level = Seedling;
+ else if(strucmp(VAR_FEATURE_LEVEL, "old-growth") == 0)
+ obs_feature_level = Seasoned;
+ else
+ obs_feature_level = Sapling;
+
+ /* obsolete, backwards compatibility */
+ set_current_val(&vars[V_OLD_STYLE_REPLY], TRUE, TRUE);
+ obs_old_style_reply = !strucmp(VAR_OLD_STYLE_REPLY, "yes");
+
+ set_feature_list_current_val(&vars[V_FEATURE_LIST]);
+ process_feature_list(ps, VAR_FEATURE_LIST,
+ (obs_feature_level == Seasoned) ? 1 : 0,
+ obs_header_in_reply, obs_old_style_reply);
+
+
+ /*
+ * Redo set_collation call with correct value for collation,
+ * but we're hardwiring ctype on now. That's because nl_langinfo()
+ * call needs it and system-dependent wcwidth and wcrtomb functions
+ * need it.
+ */
+ set_collation(F_OFF(F_DISABLE_SETLOCALE_COLLATE, ps_global), 1);
+
+ /*
+ * Set up to send the correct sequence of bytes to the display terminal.
+ */
+
+ if(reset_character_set_stuff(&err) == -1)
+ panic(err ? err : "trouble with character set setup");
+ else if(err){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, err);
+ fs_give((void **) &err);
+ }
+
+ /*
+ * Now we use the configvars from above to convert the rest
+ * to UTF-8. That should be ok because the ones above should
+ * be ASCII.
+ */
+ if(ps->keyboard_charmap && strucmp(ps->keyboard_charmap, "UTF-8")
+ && strucmp(ps->keyboard_charmap, "US-ASCII"))
+ fromcharset = ps->keyboard_charmap;
+ else if(ps->display_charmap && strucmp(ps->display_charmap, "UTF-8")
+ && strucmp(ps->display_charmap, "US-ASCII"))
+ fromcharset = ps->display_charmap;
+#ifndef _WINDOWS
+ else if(VAR_OLD_CHAR_SET && strucmp(VAR_OLD_CHAR_SET, "UTF-8")
+ && strucmp(VAR_OLD_CHAR_SET, "US-ASCII"))
+ fromcharset = VAR_OLD_CHAR_SET;
+#endif /* ! _WINDOWS */
+
+ convert_configvars_to_utf8(vars, fromcharset);
+
+ /*
+ * If we already set this while reading the remote pinerc, don't
+ * change it.
+ */
+ if(!VAR_REMOTE_ABOOK_METADATA || !VAR_REMOTE_ABOOK_METADATA[0])
+ set_current_val(&vars[V_REMOTE_ABOOK_METADATA], TRUE, TRUE);
+
+ /*
+ * mail-directory variable is obsolete, put its value in
+ * default folder-collection list
+ */
+ set_current_val(&vars[V_MAIL_DIRECTORY], TRUE, TRUE);
+ if(!GLO_FOLDER_SPEC){
+ build_path(tmp_20k_buf, VAR_MAIL_DIRECTORY, "[]", SIZEOF_20KBUF);
+ GLO_FOLDER_SPEC = parse_list(tmp_20k_buf, 1, 0, NULL);
+ }
+
+ set_current_val(&vars[V_FOLDER_SPEC], TRUE, TRUE);
+
+ set_current_val(&vars[V_NNTP_SERVER], TRUE, TRUE);
+ for(i = 0; VAR_NNTP_SERVER && VAR_NNTP_SERVER[i]; i++)
+ removing_quotes(VAR_NNTP_SERVER[i]);
+
+ set_news_spec_current_val(TRUE, TRUE);
+
+ set_current_val(&vars[V_INBOX_PATH], TRUE, TRUE);
+
+ set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
+ if(VAR_USER_DOMAIN
+ && VAR_USER_DOMAIN[0]
+ && (p = strrindex(VAR_USER_DOMAIN, '@'))){
+ if(*(++p)){
+ char *q;
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "User-domain (%s) cannot contain \"@\", using \"%s\"",
+ VAR_USER_DOMAIN, p);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ q = VAR_USER_DOMAIN;
+ while((*q++ = *p++) != '\0')
+ ;/* do nothing */
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "User-domain (%s) cannot contain \"@\", deleting",
+ VAR_USER_DOMAIN);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ if(ps->vars[V_USER_DOMAIN].post_user_val.p){
+ fs_give((void **)&ps->vars[V_USER_DOMAIN].post_user_val.p);
+ set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
+ }
+
+ if(VAR_USER_DOMAIN
+ && VAR_USER_DOMAIN[0]
+ && (p = strrindex(VAR_USER_DOMAIN, '@'))){
+ if(ps->vars[V_USER_DOMAIN].main_user_val.p){
+ fs_give((void **)&ps->vars[V_USER_DOMAIN].main_user_val.p);
+ set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
+ }
+ }
+ }
+ }
+
+ set_current_val(&vars[V_USE_ONLY_DOMAIN_NAME], TRUE, TRUE);
+ set_current_val(&vars[V_REPLY_STRING], TRUE, TRUE);
+ set_current_val(&vars[V_WORDSEPS], TRUE, TRUE);
+ set_current_val(&vars[V_QUOTE_REPLACE_STRING], TRUE, TRUE);
+ set_current_val(&vars[V_REPLY_INTRO], TRUE, TRUE);
+ set_current_val(&vars[V_EMPTY_HDR_MSG], TRUE, TRUE);
+
+#ifdef ENABLE_LDAP
+ set_current_val(&vars[V_LDAP_SERVERS], TRUE, TRUE);
+#endif /* ENABLE_LDAP */
+
+ /* obsolete, backwards compatibility */
+ set_current_val(&vars[V_HEADER_IN_REPLY], TRUE, TRUE);
+ obs_header_in_reply=!strucmp(VAR_HEADER_IN_REPLY, "yes");
+
+ set_current_val(&vars[V_PERSONAL_PRINT_COMMAND], TRUE, TRUE);
+ set_current_val(&vars[V_STANDARD_PRINTER], TRUE, TRUE);
+ set_current_val(&vars[V_PRINTER], TRUE, TRUE);
+ if(vars[V_PERSONAL_PRINT_COMMAND].is_fixed && !vars[V_PRINTER].is_fixed)
+ printer_value_check_and_adjust();
+
+ set_current_val(&vars[V_LAST_TIME_PRUNE_QUESTION], TRUE, TRUE);
+ if(VAR_LAST_TIME_PRUNE_QUESTION != NULL){
+ /* The month value in the file runs from 1-12, the variable here
+ runs from 0-11; the value in the file used to be 0-11, but we're
+ fixing it in January */
+ ps->last_expire_year = atoi(VAR_LAST_TIME_PRUNE_QUESTION);
+ ps->last_expire_month =
+ atoi(strindex(VAR_LAST_TIME_PRUNE_QUESTION, '.') + 1);
+ if(ps->last_expire_month == 0){
+ /* Fix for 0 because of old bug */
+ snprintf(buf, sizeof(buf), "%d.%d", ps_global->last_expire_year,
+ ps_global->last_expire_month + 1);
+ set_variable(V_LAST_TIME_PRUNE_QUESTION, buf, 1, 1, Main);
+ }else{
+ ps->last_expire_month--;
+ }
+ }else{
+ ps->last_expire_year = -1;
+ ps->last_expire_month = -1;
+ }
+
+ set_current_val(&vars[V_BUGS_FULLNAME], TRUE, TRUE);
+ set_current_val(&vars[V_BUGS_ADDRESS], TRUE, TRUE);
+ set_current_val(&vars[V_SUGGEST_FULLNAME], TRUE, TRUE);
+ set_current_val(&vars[V_SUGGEST_ADDRESS], TRUE, TRUE);
+ set_current_val(&vars[V_LOCAL_FULLNAME], TRUE, TRUE);
+ set_current_val(&vars[V_LOCAL_ADDRESS], TRUE, TRUE);
+ set_current_val(&vars[V_BUGS_EXTRAS], TRUE, TRUE);
+ set_current_val(&vars[V_KBLOCK_PASSWD_COUNT], TRUE, TRUE);
+ set_current_val(&vars[V_DEFAULT_FCC], TRUE, TRUE);
+ set_current_val(&vars[V_POSTPONED_FOLDER], TRUE, TRUE);
+ set_current_val(&vars[V_TRASH_FOLDER], TRUE, TRUE);
+ set_current_val(&vars[V_READ_MESSAGE_FOLDER], TRUE, TRUE);
+ set_current_val(&vars[V_FORM_FOLDER], TRUE, TRUE);
+ set_current_val(&vars[V_EDITOR], TRUE, TRUE);
+ set_current_val(&vars[V_SPELLER], TRUE, TRUE);
+ set_current_val(&vars[V_IMAGE_VIEWER], TRUE, TRUE);
+ set_current_val(&vars[V_BROWSER], TRUE, TRUE);
+ set_current_val(&vars[V_SMTP_SERVER], TRUE, TRUE);
+ set_current_val(&vars[V_COMP_HDRS], TRUE, TRUE);
+ set_current_val(&vars[V_CUSTOM_HDRS], TRUE, TRUE);
+ set_current_val(&vars[V_SENDMAIL_PATH], TRUE, TRUE);
+ set_current_val(&vars[V_DISPLAY_FILTERS], TRUE, TRUE);
+ set_current_val(&vars[V_SEND_FILTER], TRUE, TRUE);
+ set_current_val(&vars[V_ALT_ADDRS], TRUE, TRUE);
+ set_current_val(&vars[V_ABOOK_FORMATS], TRUE, TRUE);
+ set_current_val(&vars[V_KW_BRACES], TRUE, TRUE);
+ set_current_val(&vars[V_OPENING_SEP], TRUE, TRUE);
+ set_current_val(&vars[V_UNK_CHAR_SET], TRUE, TRUE);
+#ifdef SMIME
+ set_current_val(&vars[V_PUBLICCERT_DIR], TRUE, TRUE);
+ set_current_val(&vars[V_PUBLICCERT_CONTAINER], TRUE, TRUE);
+ set_current_val(&vars[V_PRIVATEKEY_DIR], TRUE, TRUE);
+ set_current_val(&vars[V_PRIVATEKEY_CONTAINER], TRUE, TRUE);
+ set_current_val(&vars[V_CACERT_DIR], TRUE, TRUE);
+ set_current_val(&vars[V_CACERT_CONTAINER], TRUE, TRUE);
+#endif /* SMIME */
+
+ set_current_val(&vars[V_KEYWORDS], TRUE, TRUE);
+ ps_global->keywords = init_keyword_list(VAR_KEYWORDS);
+
+ set_current_val(&vars[V_OPER_DIR], TRUE, TRUE);
+ if(VAR_OPER_DIR && !VAR_OPER_DIR[0]){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5,
+ "Setting operating-dir to the empty string is not allowed. Will be ignored.");
+ fs_give((void **)&VAR_OPER_DIR);
+ if(FIX_OPER_DIR)
+ fs_give((void **)&FIX_OPER_DIR);
+ if(GLO_OPER_DIR)
+ fs_give((void **)&GLO_OPER_DIR);
+ if(COM_OPER_DIR)
+ fs_give((void **)&COM_OPER_DIR);
+ if(ps_global->vars[V_OPER_DIR].post_user_val.p)
+ fs_give((void **)&ps_global->vars[V_OPER_DIR].post_user_val.p);
+ if(ps_global->vars[V_OPER_DIR].main_user_val.p)
+ fs_give((void **)&ps_global->vars[V_OPER_DIR].main_user_val.p);
+ }
+
+ set_current_val(&vars[V_PERSONAL_PRINT_CATEGORY], TRUE, TRUE);
+ ps->printer_category = -1;
+ if(VAR_PERSONAL_PRINT_CATEGORY != NULL)
+ ps->printer_category = atoi(VAR_PERSONAL_PRINT_CATEGORY);
+
+ if(ps->printer_category < 1 || ps->printer_category > 3){
+ char **tt;
+ char aname[100], wname[100];
+
+ strncpy(aname, ANSI_PRINTER, sizeof(aname));
+ aname[sizeof(aname)-1] = '\0';
+ strncat(aname, "-no-formfeed", sizeof(aname)-strlen(aname)-1);
+ strncpy(wname, WYSE_PRINTER, sizeof(wname));
+ wname[sizeof(wname)-1] = '\0';
+ strncat(wname, "-no-formfeed", sizeof(wname)-strlen(wname)-1);
+ if(strucmp(VAR_PRINTER, ANSI_PRINTER) == 0
+ || strucmp(VAR_PRINTER, aname) == 0
+ || strucmp(VAR_PRINTER, WYSE_PRINTER) == 0
+ || strucmp(VAR_PRINTER, wname) == 0)
+ ps->printer_category = 1;
+ else if(VAR_STANDARD_PRINTER && VAR_STANDARD_PRINTER[0]){
+ for(tt = VAR_STANDARD_PRINTER; *tt; tt++)
+ if(strucmp(VAR_PRINTER, *tt) == 0)
+ break;
+
+ if(*tt)
+ ps->printer_category = 2;
+ }
+
+ /* didn't find it yet */
+ if(ps->printer_category < 1 || ps->printer_category > 3){
+ if(VAR_PERSONAL_PRINT_COMMAND && VAR_PERSONAL_PRINT_COMMAND[0]){
+ for(tt = VAR_PERSONAL_PRINT_COMMAND; *tt; tt++)
+ if(strucmp(VAR_PRINTER, *tt) == 0)
+ break;
+
+ if(*tt)
+ ps->printer_category = 3;
+ }
+ }
+ }
+
+ set_current_val(&vars[V_OVERLAP], TRUE, TRUE);
+ ps->viewer_overlap = i = atoi(DF_OVERLAP);
+ if(SVAR_OVERLAP(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->viewer_overlap = i;
+
+ set_current_val(&vars[V_MARGIN], TRUE, TRUE);
+ ps->scroll_margin = i = atoi(DF_MARGIN);
+ if(SVAR_MARGIN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->scroll_margin = i;
+
+ set_current_val(&vars[V_FILLCOL], TRUE, TRUE);
+ ps->composer_fillcol = i = atoi(DF_FILLCOL);
+ if(SVAR_FILLCOL(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->composer_fillcol = i;
+
+ set_current_val(&vars[V_QUOTE_SUPPRESSION], TRUE, TRUE);
+ ps->quote_suppression_threshold = i = atoi(DF_QUOTE_SUPPRESSION);
+ if(SVAR_QUOTE_SUPPRESSION(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else{
+ if(i > 0 && i < Q_SUPP_LIMIT){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Ignoring Quote-Suppression-Threshold value of %.50s, see help",
+ VAR_QUOTE_SUPPRESSION);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ else{
+ if(i < 0 && i != Q_DEL_ALL)
+ ps->quote_suppression_threshold = -i;
+ else
+ ps->quote_suppression_threshold = i;
+ }
+ }
+
+ set_current_val(&vars[V_DEADLETS], TRUE, TRUE);
+ ps->deadlets = i = atoi(DF_DEADLETS);
+ if(SVAR_DEADLETS(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->deadlets = i;
+
+ set_current_val(&vars[V_STATUS_MSG_DELAY], TRUE, TRUE);
+ ps->status_msg_delay = i = 0;
+ if(SVAR_MSGDLAY(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->status_msg_delay = i;
+
+ set_current_val(&vars[V_ACTIVE_MSG_INTERVAL], TRUE, TRUE);
+ ps->active_status_interval = i = 8;
+ if(SVAR_ACTIVEINTERVAL(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->active_status_interval = i;
+
+ set_current_val(&vars[V_REMOTE_ABOOK_HISTORY], TRUE, TRUE);
+ ps->remote_abook_history = i = atoi(DF_REMOTE_ABOOK_HISTORY);
+ if(SVAR_AB_HIST(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->remote_abook_history = i;
+
+ set_current_val(&vars[V_REMOTE_ABOOK_VALIDITY], TRUE, TRUE);
+ ps->remote_abook_validity = i = atoi(DF_REMOTE_ABOOK_VALIDITY);
+ if(SVAR_AB_VALID(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->remote_abook_validity = i;
+
+ set_current_val(&vars[V_USERINPUTTIMEO], TRUE, TRUE);
+ ps->hours_to_timeout = i = 0;
+ if(SVAR_USER_INPUT(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->hours_to_timeout = i;
+
+ /* timeo is a regular extern int because it is referenced in pico */
+ set_current_val(&vars[V_MAILCHECK], TRUE, TRUE);
+ set_input_timeout(i = 15);
+ if(SVAR_MAILCHK(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ set_input_timeout(i);
+
+ set_current_val(&vars[V_MAILCHECKNONCURR], TRUE, TRUE);
+ ps->check_interval_for_noncurr = i = 0;
+ if(SVAR_MAILCHKNONCURR(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->check_interval_for_noncurr = i;
+
+#ifdef DEBUGJOURNAL
+ ps->debugmem = 1;
+#else
+ ps->debugmem = 0;
+#endif
+
+ i = 30;
+ set_current_val(&vars[V_TCPOPENTIMEO], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_TCPOPENTIMEO && SVAR_TCP_OPEN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ i = 15;
+ set_current_val(&vars[V_TCPREADWARNTIMEO], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_TCPREADWARNTIMEO && SVAR_TCP_READWARN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ i = 0;
+ set_current_val(&vars[V_TCPWRITEWARNTIMEO], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_TCPWRITEWARNTIMEO && SVAR_TCP_WRITEWARN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ i = 15;
+ set_current_val(&vars[V_RSHOPENTIMEO], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_RSHOPENTIMEO && SVAR_RSH_OPEN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ i = 15;
+ set_current_val(&vars[V_SSHOPENTIMEO], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_SSHOPENTIMEO && SVAR_SSH_OPEN(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ set_current_val(&vars[V_INCCHECKLIST], TRUE, TRUE);
+
+ set_current_val(&vars[V_INCCHECKTIMEO], TRUE, TRUE);
+ ps->inc_check_timeout = i = 5;
+ if(SVAR_INC_CHECK_TIMEO(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->inc_check_timeout = i;
+
+ set_current_val(&vars[V_INCCHECKINTERVAL], TRUE, TRUE);
+ ps->inc_check_interval = i = 180;
+ if(SVAR_INC_CHECK_INTERV(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->inc_check_interval = i;
+
+ set_current_val(&vars[V_INC2NDCHECKINTERVAL], TRUE, TRUE);
+ ps->inc_second_check_interval = i = 180;
+ if(SVAR_INC_2NDCHECK_INTERV(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->inc_second_check_interval = i;
+
+ rvl = 60L;
+ set_current_val(&vars[V_MAILDROPCHECK], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_MAILDROPCHECK && SVAR_MAILDCHK(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ rvl = 0L;
+ set_current_val(&vars[V_NNTPRANGE], TRUE, TRUE);
+ /* this is just for the error, we don't save the result */
+ if(VAR_NNTPRANGE && SVAR_NNTPRANGE(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+
+ set_current_val(&vars[V_TCPQUERYTIMEO], TRUE, TRUE);
+ ps->tcp_query_timeout = i = TO_BAIL_THRESHOLD;
+ if(VAR_TCPQUERYTIMEO && SVAR_TCP_QUERY(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->tcp_query_timeout = i;
+
+ set_current_val(&vars[V_NEWSRC_PATH], TRUE, TRUE);
+ if(VAR_NEWSRC_PATH && VAR_NEWSRC_PATH[0])
+ mail_parameters(NULL, SET_NEWSRC, (void *)VAR_NEWSRC_PATH);
+
+ set_current_val(&vars[V_NEWS_ACTIVE_PATH], TRUE, TRUE);
+ if(VAR_NEWS_ACTIVE_PATH)
+ mail_parameters(NULL, SET_NEWSACTIVE,
+ (void *)VAR_NEWS_ACTIVE_PATH);
+
+ set_current_val(&vars[V_NEWS_SPOOL_DIR], TRUE, TRUE);
+ if(VAR_NEWS_SPOOL_DIR)
+ mail_parameters(NULL, SET_NEWSSPOOL,
+ (void *)VAR_NEWS_SPOOL_DIR);
+
+ /* guarantee a save default */
+ set_current_val(&vars[V_DEFAULT_SAVE_FOLDER], TRUE, TRUE);
+ if(!VAR_DEFAULT_SAVE_FOLDER || !VAR_DEFAULT_SAVE_FOLDER[0])
+ set_variable(V_DEFAULT_SAVE_FOLDER,
+ (GLO_DEFAULT_SAVE_FOLDER && GLO_DEFAULT_SAVE_FOLDER[0])
+ ? GLO_DEFAULT_SAVE_FOLDER
+ : DEFAULT_SAVE, 1, 0, Main);
+
+ set_current_val(&vars[V_SIGNATURE_FILE], TRUE, TRUE);
+ set_current_val(&vars[V_LITERAL_SIG], TRUE, TRUE);
+ set_current_val(&vars[V_GLOB_ADDRBOOK], TRUE, TRUE);
+ set_current_val(&vars[V_ADDRESSBOOK], TRUE, TRUE);
+ set_current_val(&vars[V_FORCED_ABOOK_ENTRY], TRUE, TRUE);
+ set_current_val(&vars[V_DISABLE_DRIVERS], TRUE, TRUE);
+ set_current_val(&vars[V_DISABLE_AUTHS], TRUE, TRUE);
+
+ set_current_val(&vars[V_VIEW_HEADERS], TRUE, TRUE);
+ /* strip spaces and colons */
+ if(ps->VAR_VIEW_HEADERS){
+ for(s = ps->VAR_VIEW_HEADERS; (q = *s) != NULL; s++){
+ if(q[0]){
+ removing_leading_white_space(q);
+ /* look for colon or space or end */
+ for(p = q; *p && !isspace((unsigned char)*p) && *p != ':'; p++)
+ ;/* do nothing */
+
+ *p = '\0';
+ if(strucmp(q, ALL_EXCEPT) == 0)
+ ps->view_all_except = 1;
+ }
+ }
+ }
+
+ set_current_val(&vars[V_VIEW_MARGIN_LEFT], TRUE, TRUE);
+ set_current_val(&vars[V_VIEW_MARGIN_RIGHT], TRUE, TRUE);
+ set_current_val(&vars[V_UPLOAD_CMD], TRUE, TRUE);
+ set_current_val(&vars[V_UPLOAD_CMD_PREFIX], TRUE, TRUE);
+ set_current_val(&vars[V_DOWNLOAD_CMD], TRUE, TRUE);
+ set_current_val(&vars[V_DOWNLOAD_CMD_PREFIX], TRUE, TRUE);
+ set_current_val(&vars[V_MAILCAP_PATH], TRUE, TRUE);
+ set_current_val(&vars[V_MIMETYPE_PATH], TRUE, TRUE);
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ set_current_val(&vars[V_FIFOPATH], TRUE, TRUE);
+#endif
+
+ set_current_val(&vars[V_RSHPATH], TRUE, TRUE);
+ if(VAR_RSHPATH
+ && is_absolute_path(VAR_RSHPATH)
+ && can_access(VAR_RSHPATH, EXECUTE_ACCESS) == 0){
+ mail_parameters(NULL, SET_RSHPATH, (void *) VAR_RSHPATH);
+ }
+
+ set_current_val(&vars[V_RSHCMD], TRUE, TRUE);
+ if(VAR_RSHCMD){
+ mail_parameters(NULL, SET_RSHCOMMAND, (void *) VAR_RSHCMD);
+ }
+
+ set_current_val(&vars[V_SSHPATH], TRUE, TRUE);
+ if(VAR_SSHPATH) {
+ if(is_absolute_path(VAR_SSHPATH)
+ && can_access(VAR_SSHPATH, EXECUTE_ACCESS) == 0){
+ mail_parameters(NULL, SET_SSHPATH, (void *) VAR_SSHPATH);
+ }
+ else {
+ mail_parameters(NULL, SET_SSHPATH, (void *) NULL);
+ }
+ }
+
+ set_current_val(&vars[V_SSHCMD], TRUE, TRUE);
+ if(VAR_SSHCMD) {
+ if(VAR_SSHCMD[0]) {
+ mail_parameters(NULL, SET_SSHCOMMAND, (void *) VAR_SSHCMD);
+ }
+ else {
+ mail_parameters(NULL, SET_SSHCOMMAND, (void *) NULL);
+ }
+ }
+
+#if defined(DOS) || defined(OS2)
+
+ set_current_val(&vars[V_FILE_DIR], TRUE, TRUE);
+
+#ifdef _WINDOWS
+ set_current_val(&vars[V_FONT_NAME], TRUE, TRUE);
+ set_current_val(&vars[V_FONT_SIZE], TRUE, TRUE);
+ set_current_val(&vars[V_FONT_STYLE], TRUE, TRUE);
+ set_current_val(&vars[V_FONT_CHAR_SET], TRUE, TRUE);
+ set_current_val(&vars[V_CURSOR_STYLE], TRUE, TRUE);
+ set_current_val(&vars[V_WINDOW_POSITION], TRUE, TRUE);
+
+ if(F_OFF(F_STORE_WINPOS_IN_CONFIG, ps_global)){
+ /* if win position is in the registry, use it */
+ if(mswin_reg(MSWR_OP_GET, MSWR_PINE_POS, buf, sizeof(buf))){
+ if(VAR_WINDOW_POSITION)
+ fs_give((void **)&VAR_WINDOW_POSITION);
+
+ VAR_WINDOW_POSITION = cpystr(buf);
+ }
+ else if(VAR_WINDOW_POSITION
+ && (ps->update_registry != UREG_NEVER_SET)){
+ /* otherwise, put it there */
+ mswin_reg(MSWR_OP_SET | ((ps->update_registry == UREG_ALWAYS_SET)
+ ? MSWR_OP_FORCE : 0),
+ MSWR_PINE_POS,
+ VAR_WINDOW_POSITION, (size_t)NULL);
+ }
+ }
+
+ mswin_setwindow (VAR_FONT_NAME, VAR_FONT_SIZE,
+ VAR_FONT_STYLE, VAR_WINDOW_POSITION,
+ VAR_CURSOR_STYLE, VAR_FONT_CHAR_SET);
+
+ /* this is no longer used */
+ if(VAR_WINDOW_POSITION)
+ fs_give((void **)&VAR_WINDOW_POSITION);
+
+ set_current_val(&vars[V_PRINT_FONT_NAME], TRUE, TRUE);
+ set_current_val(&vars[V_PRINT_FONT_SIZE], TRUE, TRUE);
+ set_current_val(&vars[V_PRINT_FONT_STYLE], TRUE, TRUE);
+ set_current_val(&vars[V_PRINT_FONT_CHAR_SET], TRUE, TRUE);
+ mswin_setprintfont (VAR_PRINT_FONT_NAME,
+ VAR_PRINT_FONT_SIZE,
+ VAR_PRINT_FONT_STYLE,
+ VAR_PRINT_FONT_CHAR_SET);
+
+ mswin_setgenhelptextcallback(pcpine_general_help);
+
+ mswin_setclosetext ("Use the \"Q\" command to exit Alpine.");
+
+ {
+ char foreColor[64], backColor[64];
+
+ mswin_getwindow(NULL, 0, NULL, 0, NULL, 0, NULL, 0,
+ foreColor, sizeof(foreColor), backColor, sizeof(backColor),
+ NULL, 0, NULL, 0);
+ if(!GLO_NORM_FORE_COLOR)
+ GLO_NORM_FORE_COLOR = cpystr(foreColor);
+
+ if(!GLO_NORM_BACK_COLOR)
+ GLO_NORM_BACK_COLOR = cpystr(backColor);
+ }
+#endif /* _WINDOWS */
+#endif /* DOS */
+
+ /*
+ * We want the version number to start out as 1.0 for Alpine, but
+ * we also want to use the same old config file that was used
+ * with Pine. The Pine version numbers made it up to 4.64 and we
+ * want Alpine's 1.0 to be larger than 4.64 so we keep a separate
+ * internal version number which is the real version number
+ * plus 4. That's what gets written in LAST_VERS_USED.
+ */
+ strncpy(ps->vers_internal, ALPINE_VERSION, sizeof(ps->vers_internal));
+ ps->vers_internal[sizeof(ps->vers_internal)-1] = '\0';
+ if(isdigit(ps->vers_internal[0]) && ps->vers_internal[0] < '6')
+ ps->vers_internal[0] = ps->vers_internal[0] + 4;
+
+ set_current_val(&vars[V_LAST_VERS_USED], TRUE, TRUE);
+ /* Check for special cases first */
+ if(VAR_LAST_VERS_USED
+ && (isdigit(ps->vers_internal[0])
+ && ps->vers_internal[1] == '.'
+ && isdigit((unsigned char)ps->vers_internal[2])
+ && isdigit((unsigned char)ps->vers_internal[3])
+ && isalpha((unsigned char)ps->vers_internal[4])
+ && strncmp(VAR_LAST_VERS_USED, ps->vers_internal, 4) >= 0)){
+ ps->show_new_version = 0;
+ }
+ /* Otherwise just do lexicographic comparision... */
+ else if(VAR_LAST_VERS_USED
+ && strcmp(VAR_LAST_VERS_USED, ps->vers_internal) >= 0){
+ ps->show_new_version = 0;
+ }
+ else{
+#ifdef _WINDOWS
+ /*
+ * If this is the first time we've run a version > 4.40, and there
+ * is evidence that the config file has not been used by unix pine,
+ * then we convert color008 to colorlgr, color009 to colormgr, and
+ * color010 to colordgr. If the config file is being used by
+ * unix pine then color009 may really supposed to be red, etc.
+ * Same if we've already run 4.41 or higher. We don't have to do
+ * anything if we are new to alpine.
+ */
+ ps->pre441 = (VAR_LAST_VERS_USED
+ && strcmp(VAR_LAST_VERS_USED, "4.40") <= 0);
+#endif /* _WINDOWS */
+
+ /*
+ * Don't offer the new version message if we're told not to.
+ */
+ set_current_val(&vars[V_NEW_VER_QUELL], TRUE, TRUE);
+ ps->show_new_version = !(VAR_NEW_VER_QUELL
+ && strcmp(ps->vers_internal,
+ VAR_NEW_VER_QUELL) < 0);
+
+#ifdef _WINDOWS
+ if(!ps_global->install_flag)
+#endif /* _WINDOWS */
+ {
+ if(VAR_LAST_VERS_USED){
+ strncpy(ps_global->pine_pre_vers, VAR_LAST_VERS_USED,
+ sizeof(ps_global->pine_pre_vers));
+ ps_global->pine_pre_vers[sizeof(ps_global->pine_pre_vers)-1] = '\0';
+ }
+
+ set_variable(V_LAST_VERS_USED, ps->vers_internal, 1, 1,
+ ps_global->ew_for_except_vars);
+ }
+ }
+
+ /* Obsolete, backwards compatibility */
+ set_current_val(&vars[V_ELM_STYLE_SAVE], TRUE, TRUE);
+ /* Also obsolete */
+ set_current_val(&vars[V_SAVE_BY_SENDER], TRUE, TRUE);
+ if(!strucmp(VAR_ELM_STYLE_SAVE, "yes"))
+ set_variable(V_SAVE_BY_SENDER, "yes", 1, 1, Main);
+ obs_save_by_sender = !strucmp(VAR_SAVE_BY_SENDER, "yes");
+
+ set_current_pattern_vals(ps);
+
+ set_current_val(&vars[V_INDEX_FORMAT], TRUE, TRUE);
+ init_index_format(VAR_INDEX_FORMAT, &ps->index_disp_format);
+
+ /* this should come after pre441 is set or not */
+ set_current_color_vals(ps);
+
+ set_current_val(&vars[V_RSS_NEWS], TRUE, TRUE);
+ set_current_val(&vars[V_RSS_WEATHER], TRUE, TRUE);
+ set_current_val(&vars[V_WP_INDEXHEIGHT], TRUE, TRUE);
+ set_current_val(&vars[V_WP_INDEXLINES], TRUE, TRUE);
+ set_current_val(&vars[V_WP_AGGSTATE], TRUE, TRUE);
+ set_current_val(&vars[V_WP_STATE], TRUE, TRUE);
+ set_current_val(&vars[V_WP_COLUMNS], TRUE, TRUE);
+
+ set_current_val(&vars[V_PRUNED_FOLDERS], TRUE, TRUE);
+ set_current_val(&vars[V_ARCHIVED_FOLDERS], TRUE, TRUE);
+ set_current_val(&vars[V_INCOMING_FOLDERS], TRUE, TRUE);
+ set_current_val(&vars[V_SORT_KEY], TRUE, TRUE);
+ if(decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev) == -1){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Sort type \"%.200s\" is invalid", VAR_SORT_KEY);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ ps->def_sort = SortArrival;
+ ps->def_sort_rev = 0;
+ }
+ else
+ ps->def_sort_rev = def_sort_rev;
+
+ cur_rule_value(&vars[V_SAVED_MSG_NAME_RULE], TRUE, TRUE);
+ {NAMEVAL_S *v; int i;
+ for(i = 0; (v = save_msg_rules(i)); i++)
+ if(v->value == ps_global->save_msg_rule)
+ break;
+
+ /* if save_msg_rule is not default, or is explicitly set to default */
+ if((ps_global->save_msg_rule != SAV_RULE_DEFLT) ||
+ (v && v->name &&
+ (!strucmp(ps_global->vars[V_SAVED_MSG_NAME_RULE].post_user_val.p,
+ v->name) ||
+ !strucmp(ps_global->vars[V_SAVED_MSG_NAME_RULE].main_user_val.p,
+ v->name))))
+ obs_save_by_sender = 0; /* don't overwrite */
+ }
+
+ cur_rule_value(&vars[V_FCC_RULE], TRUE, TRUE);
+ cur_rule_value(&vars[V_AB_SORT_RULE], TRUE, TRUE);
+
+#ifndef _WINDOWS
+ cur_rule_value(&vars[V_COLOR_STYLE], TRUE, TRUE);
+#endif
+
+ cur_rule_value(&vars[V_INDEX_COLOR_STYLE], TRUE, TRUE);
+ cur_rule_value(&vars[V_TITLEBAR_COLOR_STYLE], TRUE, TRUE);
+ cur_rule_value(&vars[V_FLD_SORT_RULE], TRUE, TRUE);
+ cur_rule_value(&vars[V_INCOMING_STARTUP], TRUE, TRUE);
+ cur_rule_value(&vars[V_PRUNING_RULE], TRUE, TRUE);
+ cur_rule_value(&vars[V_REOPEN_RULE], TRUE, TRUE);
+ cur_rule_value(&vars[V_GOTO_DEFAULT_RULE], TRUE, TRUE);
+ cur_rule_value(&vars[V_THREAD_DISP_STYLE], TRUE, TRUE);
+ cur_rule_value(&vars[V_THREAD_INDEX_STYLE], TRUE, TRUE);
+
+ set_current_val(&vars[V_THREAD_MORE_CHAR], TRUE, TRUE);
+ if(VAR_THREAD_MORE_CHAR[0] && VAR_THREAD_MORE_CHAR[1]){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5,
+ _("Only using first character of threading-indicator-character option"));
+ VAR_THREAD_MORE_CHAR[1] = '\0';
+ }
+
+ set_current_val(&vars[V_THREAD_EXP_CHAR], TRUE, TRUE);
+ if(VAR_THREAD_EXP_CHAR[0] && VAR_THREAD_EXP_CHAR[1]){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5,
+ _("Only using first character of threading-expanded-character option"));
+ VAR_THREAD_EXP_CHAR[1] = '\0';
+ }
+
+ set_current_val(&vars[V_THREAD_LASTREPLY_CHAR], TRUE, TRUE);
+ if(!VAR_THREAD_LASTREPLY_CHAR[0])
+ VAR_THREAD_LASTREPLY_CHAR = cpystr(DF_THREAD_LASTREPLY_CHAR);
+
+ if(VAR_THREAD_LASTREPLY_CHAR[0] && VAR_THREAD_LASTREPLY_CHAR[1]){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5,
+ _("Only using first character of threading-lastreply-character option"));
+ VAR_THREAD_LASTREPLY_CHAR[1] = '\0';
+ }
+
+ set_current_val(&vars[V_MAXREMSTREAM], TRUE, TRUE);
+ ps->s_pool.max_remstream = i = atoi(DF_MAXREMSTREAM);
+ if(SVAR_MAXREMSTREAM(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->s_pool.max_remstream = i;
+
+ set_current_val(&vars[V_PERMLOCKED], TRUE, TRUE);
+
+ set_current_val(&vars[V_NMW_WIDTH], TRUE, TRUE);
+ ps->nmw_width = i = atoi(DF_NMW_WIDTH);
+ if(SVAR_NMW_WIDTH(ps, i, tmp_20k_buf, SIZEOF_20KBUF))
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ else
+ ps->nmw_width = i;
+
+ /* backwards compatibility */
+ if(obs_save_by_sender){
+ ps->save_msg_rule = SAV_RULE_FROM;
+ set_variable(V_SAVED_MSG_NAME_RULE, "by-from", 1, 1, Main);
+ }
+
+ /* this should come after process_feature_list because of use_fkeys */
+ if(!ps->start_in_index)
+ set_current_val(&vars[V_INIT_CMD_LIST], FALSE, TRUE);
+ if(VAR_INIT_CMD_LIST && VAR_INIT_CMD_LIST[0] && VAR_INIT_CMD_LIST[0][0])
+ if(cmds_f)
+ (*cmds_f)(ps, VAR_INIT_CMD_LIST);
+
+#ifdef _WINDOWS
+ mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global));
+#endif /* _WINDOWS */
+
+#ifdef DEBUG
+ dump_configuration(0);
+#endif /* DEBUG */
+}
+
+
+void
+convert_configvars_to_utf8(struct variable *vars, char *fromcharset)
+{
+ struct variable *v;
+
+ /*
+ * Make sure that everything is UTF-8.
+ */
+ for(v = vars; v->name; v++)
+ convert_configvar_to_utf8(v, fromcharset);
+}
+
+
+void
+convert_configvar_to_utf8(struct variable *v, char *fromcharset)
+{
+ char **p, *conv, **valptr;
+ int i;
+
+ /*
+ * Make sure that everything is UTF-8.
+ */
+ if(v->is_list){
+ for(i = 0; i < 7; i++){
+ switch(i){
+ case 1: valptr = v->current_val.l; break;
+ case 0: valptr = v->main_user_val.l; break;
+ case 2: valptr = v->changed_val.l; break;
+ case 3: valptr = v->post_user_val.l; break;
+ case 4: valptr = v->global_val.l; break;
+ case 5: valptr = v->fixed_val.l; break;
+ case 6: valptr = v->cmdline_val.l; break;
+ default: panic("bad case in convert_configvar");
+ }
+
+ if(valptr){
+ for(p = valptr; *p; p++){
+ if(**p){
+ conv = convert_to_utf8(*p, fromcharset, 0);
+ if(conv){
+ fs_give((void **) p);
+ *p = conv;
+ }
+ }
+ }
+ }
+ }
+ }
+ else{
+ for(i = 0; i < 7; i++){
+ switch(i){
+ case 1: valptr = &v->current_val.p; break;
+ case 0: valptr = &v->main_user_val.p; break;
+ case 2: valptr = &v->changed_val.p; break;
+ case 3: valptr = &v->post_user_val.p; break;
+ case 4: valptr = &v->global_val.p; break;
+ case 5: valptr = &v->fixed_val.p; break;
+ case 6: valptr = &v->cmdline_val.p; break;
+ default: panic("bad case in convert_configvar");
+ }
+
+ if(valptr && *valptr && (*valptr)[0]){
+ conv = convert_to_utf8(*valptr, fromcharset, 0);
+ if(conv){
+ fs_give((void **) valptr);
+ *valptr = conv;
+ }
+ }
+ }
+ }
+}
+
+
+
+/*
+ * Standard feature name sections
+ */
+char *
+feature_list_section(FEATURE_S *feature)
+{
+#define PREF_NONE -1
+ static char *feat_sect[] = {
+#define PREF_MISC 0
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Advanced User Preferences"),
+#define PREF_FLDR 1
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Folder Preferences"),
+#define PREF_ADDR 2
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Address Book Preferences"),
+#define PREF_COMP 3
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Composer Preferences"),
+#define PREF_NEWS 4
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("News Preferences"),
+#define PREF_VIEW 5
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Viewer Preferences"),
+#define PREF_ACMD 6
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Advanced Command Preferences"),
+#define PREF_PRNT 7
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Printer Preferences"),
+#define PREF_RPLY 8
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Reply Preferences"),
+#define PREF_SEND 9
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Sending Preferences"),
+#define PREF_INDX 10
+ /* TRANSLATORS: section heading in configuration screen */
+ N_("Message Index Preferences"),
+#define PREF_HIDDEN 11
+ HIDDEN_PREF
+};
+
+ return((feature && feature->section > PREF_NONE
+ && feature->section < (sizeof(feat_sect)/sizeof(feat_sect[0])))
+ ? _(feat_sect[feature->section]) : NULL);
+}
+
+
+/* any os-specific exclusions */
+#if defined(DOS) || defined(OS2)
+#define PREF_OS_LWSD PREF_NONE
+#define PREF_OS_LCLK PREF_NONE
+#define PREF_OS_STSP PREF_NONE
+#define PREF_OS_SPWN PREF_NONE
+#define PREF_OS_XNML PREF_NONE
+#define PREF_OS_USFK PREF_MISC
+#define PREF_OS_MOUSE PREF_NONE
+#else
+#define PREF_OS_LWSD PREF_MISC
+#define PREF_OS_LCLK PREF_COMP
+#define PREF_OS_STSP PREF_MISC
+#define PREF_OS_SPWN PREF_MISC
+#define PREF_OS_XNML PREF_MISC
+#define PREF_OS_USFK PREF_NONE
+#define PREF_OS_MOUSE PREF_MISC
+#endif
+
+
+/*
+ * Standard way to get at feature list members...
+ */
+FEATURE_S *
+feature_list(int index)
+{
+ /*
+ * This list is alphabatized by feature string, but the
+ * macro values need not be ordered.
+ */
+ static FEATURE_S feat_list[] = {
+/* Composer prefs */
+ {"allow-changing-from", NULL,
+ F_ALLOW_CHANGING_FROM, h_config_allow_chg_from, PREF_COMP, 1},
+ {"alternate-compose-menu", NULL,
+ F_ALT_COMPOSE_MENU, h_config_alt_compose_menu, PREF_COMP, 0},
+ {"alternate-role-menu", "Alternate Role (#) Menu",
+ F_ALT_ROLE_MENU, h_config_alt_role_menu, PREF_COMP, 0},
+ {"compose-cancel-confirm-uses-yes", NULL,
+ F_CANCEL_CONFIRM, h_config_cancel_confirm, PREF_COMP, 0},
+ {"compose-rejects-unqualified-addrs", "Compose Rejects Unqualified Addresses",
+ F_COMPOSE_REJECTS_UNQUAL, h_config_compose_rejects_unqual, PREF_COMP, 0},
+ {"compose-send-offers-first-filter", NULL,
+ F_FIRST_SEND_FILTER_DFLT, h_config_send_filter_dflt, PREF_COMP, 0},
+ {"compose-cut-from-cursor", "Ctrl-K Cuts From Cursor",
+ F_DEL_FROM_DOT, h_config_del_from_dot, PREF_COMP, 0},
+ {"compose-maps-delete-key-to-ctrl-d", "Delete Key Maps to Ctrl-D",
+ F_COMPOSE_MAPS_DEL, h_config_compose_maps_del, PREF_COMP, 0},
+ {"quell-dead-letter-on-cancel", "Do Not Save to Deadletter on Cancel",
+ F_QUELL_DEAD_LETTER, h_config_quell_dead_letter, PREF_COMP, 0},
+ {"enable-alternate-editor-cmd", "Enable Alternate Editor Command",
+ F_ENABLE_ALT_ED, h_config_enable_alt_ed, PREF_COMP, 1},
+ {"enable-alternate-editor-implicitly", NULL,
+ F_ALT_ED_NOW, h_config_alt_ed_now, PREF_COMP, 0},
+ {"enable-search-and-replace", "Enable Search and Replace",
+ F_ENABLE_SEARCH_AND_REPL, h_config_enable_search_and_repl, PREF_COMP, 1},
+ {"enable-sigdashes", NULL,
+ F_ENABLE_SIGDASHES, h_config_sigdashes, PREF_COMP, 0},
+ {"quell-mailchecks-composing-except-inbox", "Prevent Mailchecks While Composing Except for INBOX",
+ F_QUELL_PINGS_COMPOSING, h_config_quell_checks_comp, PREF_COMP, 0},
+ {"quell-mailchecks-composing-inbox", "Prevent Mailchecks While Composing for INBOX",
+ F_QUELL_PINGS_COMPOSING_INBOX, h_config_quell_checks_comp_inbox, PREF_COMP, 0},
+ {"quell-user-lookup-in-passwd-file", "Prevent User Lookup in Password File",
+ F_QUELL_LOCAL_LOOKUP, h_config_quell_local_lookup, PREF_OS_LCLK, 0},
+ {"spell-check-before-sending", NULL,
+ F_ALWAYS_SPELL_CHECK, h_config_always_spell_check, PREF_COMP, 0},
+
+/* Reply Prefs */
+ {"copy-to-address-to-from-if-it-is-us", "Copy To Address to From if it is Us",
+ F_COPY_TO_TO_FROM, h_config_copy_to_to_from, PREF_RPLY, 0},
+ {"enable-reply-indent-string-editing", NULL,
+ F_ENABLE_EDIT_REPLY_INDENT, h_config_prefix_editing, PREF_RPLY, 0},
+ {"include-attachments-in-reply", "Include Attachments in Reply",
+ F_ATTACHMENTS_IN_REPLY, h_config_attach_in_reply, PREF_RPLY, 0},
+ {"include-header-in-reply", "Include Header in Reply",
+ F_INCLUDE_HEADER, h_config_include_header, PREF_RPLY, 0},
+ {"include-text-in-reply", "Include Text in Reply",
+ F_AUTO_INCLUDE_IN_REPLY, h_config_auto_include_reply, PREF_RPLY, 0},
+ {"reply-always-uses-reply-to", "Reply Always Uses Reply-To",
+ F_AUTO_REPLY_TO, h_config_auto_reply_to, PREF_RPLY, 0},
+ {"signature-at-bottom", "Signature at Bottom",
+ F_SIG_AT_BOTTOM, h_config_sig_at_bottom, PREF_RPLY, 0},
+ {"strip-from-sigdashes-on-reply", "Strip From Sigdashes on Reply",
+ F_ENABLE_STRIP_SIGDASHES, h_config_strip_sigdashes, PREF_RPLY, 0},
+ {"forward-as-attachment", "Forward messages as attachments",
+ F_FORWARD_AS_ATTACHMENT, h_config_forward_as_attachment, PREF_RPLY, 0},
+
+/* Sending Prefs */
+ {"disable-sender", "Do Not Generate Sender Header",
+ F_DISABLE_SENDER, h_config_disable_sender, PREF_SEND, 0},
+ {"use-sender-not-x-sender", "Use Sender Instead of X-X-Sender",
+ F_USE_SENDER_NOT_X, h_config_use_sender_not_x, PREF_SEND, 0},
+ {"quell-flowed-text", "Do Not Send Flowed Text",
+ F_QUELL_FLOWED_TEXT, h_config_quell_flowed_text, PREF_SEND, 0},
+ {"downgrade-multipart-to-text", "Downgrade Multipart to Text",
+ F_COMPOSE_ALWAYS_DOWNGRADE, h_downgrade_multipart_to_text, PREF_SEND, 0},
+ {"enable-8bit-esmtp-negotiation", "Enable 8bit ESMTP Negotiation",
+ F_ENABLE_8BIT, h_config_8bit_smtp, PREF_SEND, 1},
+#ifdef BACKGROUND_POST
+ {"enable-background-sending", NULL,
+ F_BACKGROUND_POST, h_config_compose_bg_post, PREF_SEND, 0},
+#endif
+ {"enable-delivery-status-notification", NULL,
+ F_DSN, h_config_compose_dsn, PREF_SEND, 0},
+ {"enable-verbose-smtp-posting", "Enable Verbose SMTP Posting",
+ F_VERBOSE_POST, h_config_verbose_post, PREF_SEND, 0},
+ {"fcc-without-attachments", "Fcc Does Not Include Attachments",
+ F_NO_FCC_ATTACH, h_config_no_fcc_attach, PREF_SEND, 0},
+ {"fcc-on-bounce", "Include Fcc When Bouncing Messages",
+ F_FCC_ON_BOUNCE, h_config_fcc_on_bounce, PREF_SEND, 0},
+ {"mark-fcc-seen", NULL,
+ F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND, 0},
+ {"fcc-only-without-confirm", "Send to Fcc Only Without Confirming",
+ F_AUTO_FCC_ONLY, h_config_auto_fcc_only, PREF_SEND, 0},
+ {"send-without-confirm", "Send Without Confirming",
+ F_SEND_WO_CONFIRM, h_config_send_wo_confirm, PREF_SEND, 0},
+ {"strip-whitespace-before-send", "Strip Whitespace Before Sending",
+ F_STRIP_WS_BEFORE_SEND, h_config_strip_ws_before_send, PREF_SEND, 0},
+ {"warn-if-blank-fcc", "Warn if Blank Fcc",
+ F_WARN_ABOUT_NO_FCC, h_config_warn_if_fcc_blank, PREF_SEND, 0},
+ {"warn-if-blank-subject", "Warn if Blank Subject",
+ F_WARN_ABOUT_NO_SUBJECT, h_config_warn_if_subj_blank, PREF_SEND, 0},
+ {"warn-if-blank-to-and-cc-and-newsgroups", "Warn if Blank To and CC and Newsgroups",
+ F_WARN_ABOUT_NO_TO_OR_CC, h_config_warn_if_no_to_or_cc, PREF_SEND, 0},
+
+/* Folder */
+ {"combined-folder-display", NULL,
+ F_CMBND_FOLDER_DISP, h_config_combined_folder_display, PREF_FLDR, 0},
+ {"combined-subdirectory-display", NULL,
+ F_CMBND_SUBDIR_DISP, h_config_combined_subdir_display, PREF_FLDR, 0},
+ {"enable-lame-list-mode", "Compensate for Deficient IMAP servers",
+ F_FIX_BROKEN_LIST, h_config_lame_list_mode, PREF_FLDR, 0},
+ {"enable-dot-folders", "Enable Hidden Folders",
+ F_ENABLE_DOT_FOLDERS, h_config_enable_dot_folders, PREF_FLDR, 0},
+ {"enable-incoming-folders", "Enable Incoming Folders Collection",
+ F_ENABLE_INCOMING, h_config_enable_incoming, PREF_FLDR, 0},
+ {"enable-incoming-folders-checking", NULL,
+ F_ENABLE_INCOMING_CHECKING, h_config_enable_incoming_checking, PREF_FLDR, 0},
+ {"incoming-checking-includes-total", NULL,
+ F_INCOMING_CHECKING_TOTAL, h_config_incoming_checking_total, PREF_FLDR, 0},
+ {"incoming-checking-uses-recent", NULL,
+ F_INCOMING_CHECKING_RECENT, h_config_incoming_checking_recent, PREF_FLDR, 0},
+ {"expanded-view-of-folders", "Expanded View of Folders",
+ F_EXPANDED_FOLDERS, h_config_expanded_folders, PREF_FLDR, 0},
+ {"quell-empty-directories", "Hide Empty Directories",
+ F_QUELL_EMPTY_DIRS, h_config_quell_empty_dirs, PREF_FLDR, 0},
+ {"separate-folder-and-directory-entries", "Separate Folder and Directory Entries",
+ F_SEPARATE_FLDR_AS_DIR, h_config_separate_fold_dir_view, PREF_FLDR, 0},
+ {"single-column-folder-list", NULL,
+ F_SINGLE_FOLDER_LIST, h_config_single_list, PREF_FLDR, 0},
+ {"sort-default-fcc-alpha", "Sort Default Fcc Folder Alphabetically",
+ F_SORT_DEFAULT_FCC_ALPHA, h_config_sort_fcc_alpha, PREF_FLDR, 0},
+ {"sort-default-save-alpha", "Sort Default Save Folder Alphabetically",
+ F_SORT_DEFAULT_SAVE_ALPHA, h_config_sort_save_alpha, PREF_FLDR, 0},
+ {"vertical-folder-list", "Use Vertical Folder List",
+ F_VERTICAL_FOLDER_LIST, h_config_vertical_list, PREF_FLDR, 0},
+
+/* Addr book */
+ {"combined-addrbook-display", "Combined Address Book Display",
+ F_CMBND_ABOOK_DISP, h_config_combined_abook_display, PREF_ADDR, 0},
+ {"expanded-view-of-addressbooks", "Expanded View of Address Books",
+ F_EXPANDED_ADDRBOOKS, h_config_expanded_addrbooks, PREF_ADDR, 0},
+ {"expanded-view-of-distribution-lists", "Expanded View of Distribution Lists",
+ F_EXPANDED_DISTLISTS, h_config_expanded_distlists, PREF_ADDR, 0},
+#ifdef ENABLE_LDAP
+ {"ldap-result-to-addrbook-add", "LDAP Result to Addressbook Add",
+ F_ADD_LDAP_TO_ABOOK, h_config_add_ldap, PREF_ADDR, 0},
+#endif
+
+/* Index prefs */
+ {"auto-open-next-unread", NULL,
+ F_AUTO_OPEN_NEXT_UNREAD, h_config_auto_open_unread, PREF_INDX, 0},
+ {"continue-tab-without-confirm", "Continue NextNew Without Confirming",
+ F_TAB_NO_CONFIRM, h_config_tab_no_prompt, PREF_INDX, 0},
+ {"convert-dates-to-localtime", NULL,
+ F_DATES_TO_LOCAL, h_config_dates_to_local, PREF_INDX, 0},
+ {"delete-skips-deleted", NULL,
+ F_DEL_SKIPS_DEL, h_config_del_skips_del, PREF_INDX, 1},
+ {"disable-index-locale-dates", NULL,
+ F_DISABLE_INDEX_LOCALE_DATES, h_config_disable_index_locale_dates, PREF_INDX, 0},
+ {"enable-cruise-mode", NULL,
+ F_ENABLE_SPACE_AS_TAB, h_config_cruise_mode, PREF_INDX, 0},
+ {"enable-cruise-mode-delete", "Enable Cruise Mode With Deleting",
+ F_ENABLE_TAB_DELETES, h_config_cruise_mode_delete, PREF_INDX, 0},
+ {"mark-for-cc", "Mark for CC",
+ F_MARK_FOR_CC, h_config_mark_for_cc, PREF_INDX, 1},
+ {"next-thread-without-confirm", "Read Next Thread Without Confirming",
+ F_NEXT_THRD_WO_CONFIRM, h_config_next_thrd_wo_confirm, PREF_INDX, 0},
+ {"return-to-inbox-without-confirm", "Return to INBOX Without Confirming",
+ F_RET_INBOX_NO_CONFIRM, h_config_inbox_no_confirm, PREF_INDX, 0},
+ {"show-sort", "Show Sort in Titlebar",
+ F_SHOW_SORT, h_config_show_sort, PREF_INDX, 0},
+ {"tab-uses-unseen-for-next-folder", "Tab Uses Unseen for Next Folder",
+ F_TAB_USES_UNSEEN, h_config_tab_uses_unseen, PREF_INDX, 0},
+ {"tab-visits-next-new-message-only", NULL,
+ F_TAB_TO_NEW, h_config_tab_new_only, PREF_INDX, 0},
+ {"thread-index-shows-important-color", NULL,
+ F_COLOR_LINE_IMPORTANT, h_config_color_thrd_import, PREF_INDX, 0},
+ {"thread-sorts-by-arrival", "Thread Sorts by Arrival",
+ F_THREAD_SORTS_BY_ARRIVAL, h_config_thread_sorts_by_arrival, PREF_INDX, 0},
+
+/* Viewer prefs */
+ {"enable-msg-view-addresses", "Enable Message View Address Links",
+ F_SCAN_ADDR, h_config_enable_view_addresses, PREF_VIEW, 0},
+ {"enable-msg-view-attachments", "Enable Message View Attachment Links",
+ F_VIEW_SEL_ATTACH, h_config_enable_view_attach, PREF_VIEW, 0},
+ {"enable-msg-view-urls", "Enable Message View URL Links",
+ F_VIEW_SEL_URL, h_config_enable_view_url, PREF_VIEW, 1},
+ {"enable-msg-view-web-hostnames", "Enable Message View Web Hostname Links",
+ F_VIEW_SEL_URL_HOST, h_config_enable_view_web_host, PREF_VIEW, 1},
+ {"enable-msg-view-forced-arrows", "Enable Message View Forced Arrows",
+ F_FORCE_ARROWS, h_config_enable_view_arrows, PREF_VIEW, 0},
+ /* set to TRUE for windows */
+ {"pass-c1-control-characters-as-is", NULL,
+ F_PASS_C1_CONTROL_CHARS, h_config_pass_c1_control, PREF_VIEW, 0},
+ {"pass-control-characters-as-is", NULL,
+ F_PASS_CONTROL_CHARS, h_config_pass_control, PREF_VIEW, 0},
+ {"prefer-plain-text", NULL,
+ F_PREFER_PLAIN_TEXT, h_config_prefer_plain_text, PREF_VIEW, 0},
+ {"quell-charset-warning", "Suppress Character Set Warning",
+ F_QUELL_CHARSET_WARNING, h_config_quell_charset_warning, PREF_VIEW, 0},
+ {"quell-server-after-link-in-html", "Suppress Server After Link in HTML",
+ F_QUELL_HOST_AFTER_URL, h_config_quell_host_after_url, PREF_VIEW, 0},
+
+/* News */
+ {"compose-sets-newsgroup-without-confirm", "Compose Sets Newsgroup Without Confirming",
+ F_COMPOSE_TO_NEWSGRP, h_config_compose_news_wo_conf, PREF_NEWS, 0},
+ {"enable-8bit-nntp-posting", "Enable 8bit NNTP Posting",
+ F_ENABLE_8BIT_NNTP, h_config_8bit_nntp, PREF_NEWS, 0},
+ {"enable-multiple-newsrcs", NULL,
+ F_ENABLE_MULNEWSRCS, h_config_enable_mulnewsrcs, PREF_NEWS, 0},
+ {"mult-newsrc-hostnames-as-typed", "Multiple Newsrc Hostnames as Typed",
+ F_MULNEWSRC_HOSTNAMES_AS_TYPED, h_config_mulnews_as_typed, PREF_NEWS, 0},
+ {"hide-nntp-path", "Hide NNTP Path",
+ F_HIDE_NNTP_PATH, h_config_hide_nntp_path, PREF_NEWS, 0},
+ {"news-approximates-new-status", NULL,
+ F_FAKE_NEW_IN_NEWS, h_config_news_uses_recent, PREF_NEWS, 1},
+ {"news-deletes-across-groups", NULL,
+ F_NEWS_CROSS_DELETE, h_config_news_cross_deletes, PREF_NEWS, 0},
+ {"news-offers-catchup-on-close", "News Offers Catchup on Close",
+ F_NEWS_CATCHUP, h_config_news_catchup, PREF_NEWS, 0},
+ {"news-post-without-validation", NULL,
+ F_NO_NEWS_VALIDATION, h_config_post_wo_validation, PREF_NEWS, 0},
+ {"news-read-in-newsrc-order", "News Read in Newsrc Order",
+ F_READ_IN_NEWSRC_ORDER, h_config_read_in_newsrc_order, PREF_NEWS, 0},
+ {"nntp-search-uses-overview", "NNTP Search Uses Overview",
+ F_NNTP_SEARCH_USES_OVERVIEW, h_config_nntp_search_uses_overview, PREF_NEWS, 1},
+ {"predict-nntp-server", "Predict NNTP Server",
+ F_PREDICT_NNTP_SERVER, h_config_predict_nntp_server, PREF_NEWS, 0},
+ {"quell-extra-post-prompt", "Suppress Extra Posting Prompt",
+ F_QUELL_EXTRA_POST_PROMPT, h_config_quell_post_prompt, PREF_NEWS, 0},
+
+/* Print */
+ {"enable-print-via-y-command", NULL,
+ F_ENABLE_PRYNT, h_config_enable_y_print, PREF_PRNT, 0},
+ {"print-formfeed-between-messages", NULL,
+ F_AGG_PRINT_FF, h_config_ff_between_msgs, PREF_PRNT, 0},
+ {"print-includes-from-line", NULL,
+ F_FROM_DELIM_IN_PRINT, h_config_print_from, PREF_PRNT, 0},
+ {"print-index-enabled", NULL,
+ F_PRINT_INDEX, h_config_print_index, PREF_PRNT, 0},
+ {"print-offers-custom-cmd-prompt", "Print Offers Custom Command Prompt",
+ F_CUSTOM_PRINT, h_config_custom_print, PREF_PRNT, 0},
+
+/* adv cmd prefs */
+ {"enable-aggregate-command-set", NULL,
+ F_ENABLE_AGG_OPS, h_config_enable_agg_ops, PREF_ACMD, 1},
+ {"enable-arrow-navigation", NULL,
+ F_ARROW_NAV, h_config_arrow_nav, PREF_ACMD, 1},
+ {"enable-arrow-navigation-relaxed", NULL,
+ F_RELAXED_ARROW_NAV, h_config_relaxed_arrow_nav, PREF_ACMD, 1},
+ {"enable-bounce-cmd", "Enable Bounce Command",
+ F_ENABLE_BOUNCE, h_config_enable_bounce, PREF_ACMD, 1},
+ {"enable-exit-via-lessthan-command", NULL,
+ F_ENABLE_LESSTHAN_EXIT, h_config_enable_lessthan_exit, PREF_ACMD, 1},
+ {"enable-flag-cmd", "Enable Flag Command",
+ F_ENABLE_FLAG, h_config_enable_flag, PREF_ACMD, 1},
+ {"enable-flag-screen-implicitly", NULL,
+ F_FLAG_SCREEN_DFLT, h_config_flag_screen_default, PREF_ACMD, 0},
+ {"enable-flag-screen-keyword-shortcut", NULL,
+ F_FLAG_SCREEN_KW_SHORTCUT, h_config_flag_screen_kw_shortcut,PREF_ACMD, 1},
+ {"enable-full-header-and-text", "Enable Full Header and Text",
+ F_ENABLE_FULL_HDR_AND_TEXT, h_config_enable_full_hdr_and_text, PREF_ACMD, 0},
+ {"enable-full-header-cmd", "Enable Full Header Command",
+ F_ENABLE_FULL_HDR, h_config_enable_full_hdr, PREF_ACMD, 1},
+ {"enable-goto-in-file-browser", "Enable Goto in File Browser",
+ F_ALLOW_GOTO, h_config_allow_goto, PREF_ACMD, 1},
+ {"enable-jump-shortcut", NULL,
+ F_ENABLE_JUMP, h_config_enable_jump, PREF_ACMD, 1},
+ {"enable-partial-match-lists", NULL,
+ F_ENABLE_SUB_LISTS, h_config_sub_lists, PREF_ACMD, 1},
+ {"enable-tab-completion", NULL,
+ F_ENABLE_TAB_COMPLETE, h_config_enable_tab_complete, PREF_ACMD, 1},
+ {"enable-unix-pipe-cmd", "Enable Unix Pipe Command",
+ F_ENABLE_PIPE, h_config_enable_pipe, PREF_ACMD, 1},
+ {"quell-full-header-auto-reset", "Suppress Full Header Auto Reset",
+ F_QUELL_FULL_HDR_RESET, h_config_quell_full_hdr_reset, PREF_ACMD, 0},
+
+/* Adv user prefs */
+#if !defined(DOS) && !defined(OS2)
+ {"allow-talk", NULL,
+ F_ALLOW_TALK, h_config_allow_talk, PREF_MISC, 0},
+#endif
+ {"assume-slow-link", NULL,
+ F_FORCE_LOW_SPEED, h_config_force_low_speed, PREF_OS_LWSD, 0},
+ {"auto-move-read-msgs", "Auto Move Read Messages",
+ F_AUTO_READ_MSGS, h_config_auto_read_msgs, PREF_MISC, 0},
+ {"auto-unselect-after-apply", NULL,
+ F_AUTO_UNSELECT, h_config_auto_unselect, PREF_MISC, 0},
+ {"auto-unzoom-after-apply", NULL,
+ F_AUTO_UNZOOM, h_config_auto_unzoom, PREF_MISC, 1},
+ {"auto-zoom-after-select", NULL,
+ F_AUTO_ZOOM, h_config_auto_zoom, PREF_MISC, 1},
+ {"busy-cue-spinner-only", NULL,
+ F_USE_BORING_SPINNER, h_config_use_boring_spinner, PREF_MISC, 0},
+ {"check-newmail-when-quitting", NULL,
+ F_CHECK_MAIL_ONQUIT, h_config_check_mail_onquit, PREF_MISC, 0},
+ {"confirm-role-even-for-default", "Confirm Role Even for Default",
+ F_ROLE_CONFIRM_DEFAULT, h_config_confirm_role, PREF_MISC, 0},
+ {"disable-keymenu", NULL,
+ F_BLANK_KEYMENU, h_config_blank_keymenu, PREF_MISC, 0},
+ {"disable-password-caching", NULL,
+ F_DISABLE_PASSWORD_CACHING, h_config_disable_password_caching,
+ PREF_MISC, 0},
+ {"disable-regular-expression-matching-for-alternate-addresses", NULL,
+ F_DISABLE_REGEX, h_config_disable_regex, PREF_MISC, 0},
+ {"disable-save-input-history", NULL,
+ F_DISABLE_SAVE_INPUT_HISTORY, h_config_input_history, PREF_MISC, 0},
+ {"disable-take-fullname-in-addresses", "Disable Take Fullname in Addresses",
+ F_DISABLE_TAKE_FULLNAMES, h_config_take_fullname, PREF_MISC, 0},
+ {"disable-take-last-comma-first", NULL,
+ F_DISABLE_TAKE_LASTFIRST, h_config_take_lastfirst, PREF_MISC, 0},
+ {"disable-terminal-reset-for-display-filters", "Disable Terminal Reset for Display Filters",
+ F_DISABLE_TERM_RESET_DISP, h_config_disable_reset_disp, PREF_MISC, 0},
+ {"enable-dot-files", NULL,
+ F_ENABLE_DOT_FILES, h_config_enable_dot_files, PREF_MISC, 0},
+ {"enable-fast-recent-test", NULL,
+ F_ENABLE_FAST_RECENT, h_config_fast_recent, PREF_MISC, 0},
+ {"enable-mail-check-cue", NULL,
+ F_SHOW_DELAY_CUE, h_config_show_delay_cue, PREF_MISC, 0},
+ {"enable-mailcap-param-substitution", "Enable Mailcap Parameter Substitution",
+ F_DO_MAILCAP_PARAM_SUBST, h_config_mailcap_params, PREF_MISC, 0},
+ {"enable-mouse-in-xterm", "Enable Mouse in Xterm",
+ F_ENABLE_MOUSE, h_config_enable_mouse, PREF_OS_MOUSE, 0},
+ {"enable-newmail-in-xterm-icon", "Enable Newmail in Xterm Icon",
+ F_ENABLE_XTERM_NEWMAIL, h_config_enable_xterm_newmail, PREF_OS_XNML, 0},
+ {"enable-newmail-short-text-in-icon", "Enable Newmail Short Text in Icon",
+ F_ENABLE_NEWMAIL_SHORT_TEXT, h_config_enable_newmail_short_text, PREF_OS_XNML, 0},
+ {"enable-suspend", NULL,
+ F_CAN_SUSPEND, h_config_can_suspend, PREF_MISC, 0},
+ {"enable-take-export", NULL,
+ F_ENABLE_TAKE_EXPORT, h_config_enable_take_export, PREF_MISC, 0},
+ {"enable-rules-under-take", "Enable Take Rules",
+ F_ENABLE_ROLE_TAKE, h_config_enable_role_take, PREF_MISC, 0},
+#ifdef _WINDOWS
+ {"enable-tray-icon", NULL,
+ F_ENABLE_TRAYICON, h_config_tray_icon, PREF_MISC, 0},
+#endif
+ {"expose-hidden-config", NULL,
+ F_EXPOSE_HIDDEN_CONFIG, h_config_expose_hidden_config, PREF_MISC, 0},
+ {"expunge-only-manually", NULL,
+ F_EXPUNGE_MANUALLY, h_config_expunge_manually, PREF_MISC, 0},
+ {"expunge-without-confirm", "Expunge Without Confirming",
+ F_AUTO_EXPUNGE, h_config_auto_expunge, PREF_MISC, 0},
+ {"expunge-without-confirm-everywhere", "Expunge Without Confirming Everywhere",
+ F_FULL_AUTO_EXPUNGE, h_config_full_auto_expunge, PREF_MISC, 0},
+ {"force-arrow-cursor", NULL,
+ F_FORCE_ARROW, h_config_force_arrow, PREF_MISC, 0},
+ {"maildrops-preserve-state", NULL,
+ F_MAILDROPS_PRESERVE_STATE, h_config_maildrops_preserve_state,
+ PREF_MISC, 0},
+ {"offer-expunge-of-inbox", "Offer Expunge of INBOX",
+ F_EXPUNGE_INBOX, h_config_expunge_inbox, PREF_MISC, 0},
+ {"offer-expunge-of-stayopen-folders", "Offer Expunge of Stayopen Folders",
+ F_EXPUNGE_STAYOPENS, h_config_expunge_stayopens, PREF_MISC, 0},
+ {"preopen-stayopen-folders", NULL,
+ F_PREOPEN_STAYOPENS, h_config_preopen_stayopens, PREF_MISC, 0},
+ {"preserve-start-stop-characters", "Preserve Start/Stop Characters",
+ F_PRESERVE_START_STOP, h_config_preserve_start_stop, PREF_OS_STSP, 0},
+ {"quell-folder-internal-msg", "Prevent Folder Internal Message",
+ F_QUELL_INTERNAL_MSG, h_config_quell_folder_internal_msg, PREF_MISC, 0},
+ {"quell-partial-fetching", "Prevent Partial Fetching",
+ F_QUELL_PARTIAL_FETCH, h_config_quell_partial, PREF_MISC, 0},
+ {"prune-uses-yyyy-mm", "Prune Uses YYYY-MM",
+ F_PRUNE_USES_ISO, h_config_prune_uses_iso, PREF_MISC, 0},
+ {"quit-without-confirm", "Quit Without Confirming",
+ F_QUIT_WO_CONFIRM, h_config_quit_wo_confirm, PREF_MISC, 0},
+ {"quote-replace-nonflowed", NULL,
+ F_QUOTE_REPLACE_NOFLOW, h_config_quote_replace_noflow, PREF_MISC, 0},
+ {"save-aggregates-copy-sequence", "Save Combines Copies (may be out of order)",
+ F_AGG_SEQ_COPY, h_config_save_aggregates, PREF_MISC, 1},
+ {"save-partial-msg-without-confirm", "Save Partial Message Without Confirming",
+ F_SAVE_PARTIAL_WO_CONFIRM, h_config_save_part_wo_confirm, PREF_MISC, 0},
+ {"save-will-advance", NULL,
+ F_SAVE_ADVANCES, h_config_save_advances, PREF_MISC, 0},
+ {"save-will-not-delete", NULL,
+ F_SAVE_WONT_DELETE, h_config_save_wont_delete, PREF_MISC, 0},
+ {"save-will-quote-leading-froms", NULL,
+ F_QUOTE_ALL_FROMS, h_config_quote_all_froms, PREF_MISC, 0},
+ {"scramble-message-id", "Scramble the Message-ID When Sending",
+ F_ROT13_MESSAGE_ID, h_config_scramble_message_id, PREF_MISC, 0},
+ {"select-without-confirm", "Select Ctrl-T Foldername Without Confirming",
+ F_SELECT_WO_CONFIRM, h_config_select_wo_confirm, PREF_MISC, 0},
+ {"show-cursor", NULL,
+ F_SHOW_CURSOR, h_config_show_cursor, PREF_MISC, 0},
+ {"show-plain-text-internally", NULL,
+ F_SHOW_TEXTPLAIN_INT, h_config_textplain_int, PREF_MISC, 0},
+ {"show-selected-in-boldface", "Show Selected in Boldface",
+ F_SELECTED_SHOWN_BOLD, h_config_select_in_bold, PREF_MISC, 0},
+ {"slash-collapses-entire-thread", NULL,
+ F_SLASH_COLL_ENTIRE, h_config_slash_coll_entire, PREF_MISC, 0},
+#ifdef _WINDOWS
+ {"store-window-position-in-config", "Store Window Position in Config",
+ F_STORE_WINPOS_IN_CONFIG, h_config_winpos_in_config, PREF_MISC, 0},
+#endif
+ {"suppress-asterisks-in-password-prompt", "Suppress Asterisks in Password Prompt",
+ F_QUELL_ASTERISKS, h_config_quell_asterisks,
+ PREF_MISC, 0},
+ {"quell-attachment-extension-warn", "Suppress Attachment Extension Warning",
+ F_QUELL_ATTACH_EXT_WARN, h_config_quell_attach_ext_warn,
+ PREF_MISC, 0},
+ {"quell-attachment-extra-prompt", "Suppress Attachment Extra Prompt",
+ F_QUELL_ATTACH_EXTRA_PROMPT, h_config_quell_attach_extra_prompt,
+ PREF_MISC, 0},
+ {"quell-berkeley-format-timezone", "Suppress Berkeley Format Timezone",
+ F_QUELL_BEZERK_TIMEZONE, h_config_no_bezerk_zone, PREF_MISC, 0},
+ {"quell-content-id", "Suppress Content-ID",
+ F_QUELL_CONTENT_ID, h_config_quell_content_id, PREF_MISC, 0},
+ {"quell-filtering-done-message", "Suppress Filtering Done Message",
+ F_QUELL_FILTER_DONE_MSG, h_config_quell_filtering_done_message,
+ PREF_MISC, 0},
+ {"quell-filtering-messages", "Suppress Filtering Messages",
+ F_QUELL_FILTER_MSGS, h_config_quell_filtering_messages,
+ PREF_MISC, 0},
+ {"quell-imap-envelope-update", "Suppress IMAP Envelope Update",
+ F_QUELL_IMAP_ENV_CB, h_config_quell_imap_env, PREF_MISC, 0},
+ {"quell-lock-failure-warnings", "Suppress Lock Failure Warnings",
+ F_QUELL_LOCK_FAILURE_MSGS, h_config_quell_lock_failure_warnings,
+ PREF_MISC, 0},
+ {"quell-maildomain-warning", "Suppress Maildomain Warning",
+ F_QUELL_MAILDOMAIN_WARNING, h_config_quell_domain_warn, PREF_MISC, 0},
+ {"quell-news-envelope-update", "Suppress News Envelope Update",
+ F_QUELL_NEWS_ENV_CB, h_config_quell_news_env, PREF_MISC, 0},
+#ifdef _WINDOWS
+ {"quell-ssl-largeblocks", "Prevent SSL Largeblocks",
+ F_QUELL_SSL_LARGEBLOCKS, h_config_quell_ssl_largeblocks, PREF_MISC, 0},
+#endif
+ {"quell-status-message-beeping", "Suppress Status Message Beeping",
+ F_QUELL_BEEPS, h_config_quell_beeps, PREF_MISC, 0},
+ {"quell-timezone-comment-when-sending", "Suppress Timezone Comment When Sending",
+ F_QUELL_TIMEZONE, h_config_quell_tz_comment, PREF_MISC, 0},
+ {"suppress-user-agent-when-sending", NULL,
+ F_QUELL_USERAGENT, h_config_suppress_user_agent, PREF_MISC, 0},
+ {"tab-checks-recent", "Tab Checks for Recent Messages",
+ F_TAB_CHK_RECENT, h_config_tab_checks_recent, PREF_MISC, 0},
+ {"termdef-takes-precedence", NULL,
+ F_TCAP_WINS, h_config_termcap_wins, PREF_MISC, 0},
+ {"try-alternative-authentication-driver-first", NULL,
+ F_PREFER_ALT_AUTH, h_config_alt_auth, PREF_MISC, 0},
+ {"unselect-will-not-advance", NULL,
+ F_UNSELECT_WONT_ADVANCE, h_config_unsel_wont_advance, PREF_MISC, 0},
+ {"use-current-dir", "Use Current Directory",
+ F_USE_CURRENT_DIR, h_config_use_current_dir, PREF_MISC, 0},
+ {"use-function-keys", NULL,
+ F_USE_FK, h_config_use_fk, PREF_OS_USFK, 0},
+ {"use-regular-startup-rule-for-stayopen-folders", "Use Regular Startup Rule for Stayopen Folders",
+ F_STARTUP_STAYOPEN, h_config_use_reg_start_for_stayopen, PREF_MISC, 0},
+ {"use-resent-to-in-rules", "Use Resent-To in Rules",
+ F_USE_RESENTTO, h_config_use_resentto, PREF_MISC, 0},
+ {"use-subshell-for-suspend", "Use Subshell for Suspend",
+ F_SUSPEND_SPAWNS, h_config_suspend_spawns, PREF_OS_SPWN, 0},
+#ifndef _WINDOWS
+ {"use-system-translation", NULL,
+ F_USE_SYSTEM_TRANS, h_config_use_system_translation, PREF_MISC, 0},
+#endif
+
+/* Hidden Features */
+ {"old-growth", NULL,
+ F_OLD_GROWTH, NO_HELP, PREF_NONE, 0},
+ {"disable-config-cmd", NULL,
+ F_DISABLE_CONFIG_SCREEN, h_config_disable_config_cmd, PREF_HIDDEN, 0},
+ {"disable-keyboard-lock-cmd", NULL,
+ F_DISABLE_KBLOCK_CMD, h_config_disable_kb_lock, PREF_HIDDEN, 0},
+ {"disable-password-cmd", NULL,
+ F_DISABLE_PASSWORD_CMD, h_config_disable_password_cmd, PREF_HIDDEN, 0},
+ {"disable-pipes-in-sigs", NULL,
+ F_DISABLE_PIPES_IN_SIGS, h_config_disable_pipes_in_sigs, PREF_HIDDEN, 0},
+ {"disable-pipes-in-templates", NULL,
+ F_DISABLE_PIPES_IN_TEMPLATES, h_config_disable_pipes_in_templates,
+ PREF_HIDDEN, 0},
+ {"disable-roles-setup-cmd", NULL,
+ F_DISABLE_ROLES_SETUP, h_config_disable_roles_setup, PREF_HIDDEN, 0},
+ {"disable-roles-sig-edit", NULL,
+ F_DISABLE_ROLES_SIGEDIT, h_config_disable_roles_sigedit, PREF_HIDDEN, 0},
+ {"disable-roles-template-edit", NULL,
+ F_DISABLE_ROLES_TEMPLEDIT, h_config_disable_roles_templateedit,
+ PREF_HIDDEN, 0},
+ {"disable-setlocale-collate", NULL,
+ F_DISABLE_SETLOCALE_COLLATE, h_config_disable_collate, PREF_HIDDEN, 0},
+ {"disable-shared-namespaces", NULL,
+ F_DISABLE_SHARED_NAMESPACES, h_config_disable_shared, PREF_HIDDEN, 0},
+ {"disable-signature-edit-cmd", NULL,
+ F_DISABLE_SIGEDIT_CMD, h_config_disable_signature_edit, PREF_HIDDEN, 0},
+ {"new-thread-on-blank-subject", "New Thread on Blank Subject",
+ F_NEW_THREAD_ON_BLANK_SUBJECT, h_config_new_thread_blank_subject, PREF_HIDDEN, 1},
+ {"quell-personal-name-prompt", NULL,
+ F_QUELL_PERSONAL_NAME_PROMPT, h_config_quell_personal_name_prompt, PREF_HIDDEN, 0},
+ {"quell-user-id-prompt", "Quell User ID Prompt",
+ F_QUELL_USER_ID_PROMPT, h_config_quell_user_id_prompt, PREF_HIDDEN, 0},
+#ifdef SMIME
+ {"smime-dont-do-smime", "S/MIME -- Turn off S/MIME",
+ F_DONT_DO_SMIME, h_config_smime_dont_do_smime, PREF_HIDDEN, 0},
+ {"smime-encrypt-by-default", "S/MIME -- Encrypt by Default",
+ F_ENCRYPT_DEFAULT_ON, h_config_smime_encrypt_by_default, PREF_HIDDEN, 0},
+ {"smime-remember-passphrase", "S/MIME -- Remember S/MIME Passphrase",
+ F_REMEMBER_SMIME_PASSPHRASE, h_config_smime_remember_passphrase, PREF_HIDDEN, 0},
+ {"smime-sign-by-default", "S/MIME -- Sign by Default",
+ F_SIGN_DEFAULT_ON, h_config_smime_sign_by_default, PREF_HIDDEN, 0},
+#ifdef APPLEKEYCHAIN
+ {"publiccerts-in-keychain", "S/MIME -- Public Certs in MacOS Keychain",
+ F_PUBLICCERTS_IN_KEYCHAIN, h_config_smime_pubcerts_in_keychain, PREF_HIDDEN, 0},
+#endif
+#endif
+ {"selectable-item-nobold", NULL,
+ F_SLCTBL_ITEM_NOBOLD, NO_HELP, PREF_NONE, 0},
+ {"send-confirms-only-expanded", NULL, /* exposed in Web Alpine */
+ F_SEND_CONFIRM_ON_EXPAND, h_config_send_confirms_only_expanded, PREF_HIDDEN, 0},
+ {"enable-jump-cmd", NULL, /* exposed in Web Alpine */
+ F_ENABLE_JUMP_CMD, h_config_enable_jump_command, PREF_HIDDEN, 0},
+ {"enable-newmail-sound", NULL, /* exposed in Web Alpine */
+ F_ENABLE_NEWMAIL_SOUND, h_config_enable_newmail_sound, PREF_HIDDEN, 0},
+ {"render-html-internally", NULL, /* exposed in Web Alpine */
+ F_RENDER_HTML_INTERNALLY, h_config_render_html_internally, PREF_HIDDEN, 0}
+ };
+
+ return((index >= 0 && index < (sizeof(feat_list)/sizeof(feat_list[0])))
+ ? &feat_list[index] : NULL);
+}
+
+
+/*
+ * feature_list_index -- return index of given feature id in
+ * feature list
+ */
+int
+feature_list_index(int id)
+{
+ FEATURE_S *feature;
+ int i;
+
+ for(i = 0; (feature = feature_list(i)); i++)
+ if(id == feature->id)
+ return(i);
+
+ return(-1);
+}
+
+
+/*
+ * feature_list_name -- return the given feature id's corresponding name
+ */
+char *
+feature_list_name(int id)
+{
+ FEATURE_S *f;
+
+ return((f = feature_list(feature_list_index(id))) ? f->name : "");
+}
+
+
+int
+feature_list_id(char *name)
+{
+ FEATURE_S *f;
+ int i;
+
+ for(i = 0; (f = feature_list(i)); i++)
+ if(!strucmp(f->name, name))
+ return(f->id);
+
+ return(-1);
+}
+
+
+/*
+ * feature_list_help -- return the given feature id's corresponding help
+ */
+HelpType
+feature_list_help(int id)
+{
+ FEATURE_S *f;
+
+ return((f = feature_list(feature_list_index(id))) ? f->help : NO_HELP);
+}
+
+
+/*
+ * All the arguments past "list" are the backwards compatibility hacks.
+ */
+void
+process_feature_list(struct pine *ps, char **list, int old_growth, int hir, int osr)
+{
+ register char *q;
+ char **p,
+ *lvalue[BM_SIZE * 8];
+ int i,
+ yorn;
+ long l;
+ FEATURE_S *feat;
+
+
+ /* clear all previous settings and reset them to default */
+ for(i = 0; (feat = feature_list(i)) != NULL; i++)
+ F_SET(feat->id, ps, feat->defval);
+
+ /* backwards compatibility */
+ if(hir)
+ F_TURN_ON(F_INCLUDE_HEADER, ps);
+
+ /* ditto */
+ if(osr)
+ F_TURN_ON(F_SIG_AT_BOTTOM, ps);
+
+ /* ditto */
+ if(old_growth)
+ set_old_growth_bits(ps, 0);
+
+ /* now run through the list (global, user, and cmd_line lists are here) */
+ if(list){
+ for(p = list; (q = *p) != NULL; p++){
+ if(struncmp(q, "no-", 3) == 0){
+ yorn = 0;
+ q += 3;
+ }else{
+ yorn = 1;
+ }
+
+ for(i = 0; (feat = feature_list(i)) != NULL; i++){
+ if(strucmp(q, feat->name) == 0){
+ if(feat->id == F_OLD_GROWTH){
+ set_old_growth_bits(ps, yorn);
+ }else{
+ F_SET(feat->id, ps, yorn);
+ }
+ break;
+ }
+ }
+
+ /* if it wasn't in that list */
+ if(feat == NULL)
+ dprint((1,"Unrecognized feature in feature-list (%s%s)\n",
+ (yorn ? "" : "no-"), q ? q : "?"));
+ }
+ }
+
+ /*
+ * Turn on gratuitous '>From ' quoting, if requested...
+ */
+ mail_parameters(NULL, SET_FROMWIDGET,
+ F_ON(F_QUOTE_ALL_FROMS, ps) ? VOIDT : NIL);
+
+ /*
+ * Turn off .lock creation complaints...
+ */
+ if(F_ON(F_QUELL_LOCK_FAILURE_MSGS, ps))
+ mail_parameters(NULL, SET_LOCKEACCESERROR, (void *) 0);
+
+ /*
+ * Turn on quelling of pseudo message.
+ */
+ if(F_ON(F_QUELL_INTERNAL_MSG,ps_global))
+ mail_parameters(NULL, SET_USERHASNOLIFE, (void *) 1);
+
+ l = F_ON(F_MULNEWSRC_HOSTNAMES_AS_TYPED,ps_global) ? 0L : 1L;
+ mail_parameters(NULL, SET_NEWSRCCANONHOST, (void *) l);
+
+ ps->pass_ctrl_chars = F_ON(F_PASS_CONTROL_CHARS,ps_global) ? 1 : 0;
+ ps->pass_c1_ctrl_chars = F_ON(F_PASS_C1_CONTROL_CHARS,ps_global) ? 1 : 0;
+
+#ifndef _WINDOWS
+ if(F_ON(F_QUELL_BEZERK_TIMEZONE,ps_global))
+ mail_parameters(NULL, SET_NOTIMEZONES, (void *) 1);
+#endif
+
+ if(F_ON(F_USE_FK, ps))
+ ps->orig_use_fkeys = 1;
+
+ /* Will we have to build a new list? */
+ if(!(old_growth || hir || osr))
+ return;
+
+ /*
+ * Build a new list for feature-list. The only reason we ever need to
+ * do this is if one of the obsolete options is being converted
+ * into a feature-list item, and it isn't already included in the user's
+ * feature-list.
+ */
+ i = 0;
+ for(p = LVAL(&ps->vars[V_FEATURE_LIST], Main);
+ p && (q = *p); p++){
+ /* already have it or cancelled it, don't need to add later */
+ if(hir && (strucmp(q, "include-header-in-reply") == 0 ||
+ strucmp(q, "no-include-header-in-reply") == 0)){
+ hir = 0;
+ }else if(osr && (strucmp(q, "signature-at-bottom") == 0 ||
+ strucmp(q, "no-signature-at-bottom") == 0)){
+ osr = 0;
+ }else if(old_growth && (strucmp(q, "old-growth") == 0 ||
+ strucmp(q, "no-old-growth") == 0)){
+ old_growth = 0;
+ }
+ lvalue[i++] = cpystr(q);
+ }
+
+ /* check to see if we still need to build a new list */
+ if(!(old_growth || hir || osr))
+ return;
+
+ if(hir)
+ lvalue[i++] = "include-header-in-reply";
+ if(osr)
+ lvalue[i++] = "signature-at-bottom";
+ if(old_growth)
+ lvalue[i++] = "old-growth";
+ lvalue[i] = NULL;
+ set_variable_list(V_FEATURE_LIST, lvalue, TRUE, Main);
+}
+
+
+void
+set_current_pattern_vals(struct pine *ps)
+{
+ struct variable *vars = ps->vars;
+
+ set_current_val(&vars[V_PATTERNS], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_ROLES], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_FILTS], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_FILTS_OLD], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_SCORES], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_SCORES_OLD], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_INCOLS], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_OTHER], TRUE, TRUE);
+ set_current_val(&vars[V_PAT_SRCH], TRUE, TRUE);
+
+ /*
+ * If old pattern variable (V_PATTERNS) is set and the new ones aren't
+ * in the config file, then convert the old data into the new variables.
+ * It isn't quite that simple, though, because we don't store unset
+ * variables in remote pinercs. Check for the variables but if we
+ * don't find any of them, also check the version number. This change was
+ * made in version 4.30. We could just check that except that we're
+ * worried somebody will make an incompatible version number change in
+ * their local version, and will break this. So we check both the
+ * version # and the var_in_pinerc things to be safer.
+ */
+ if(vars[V_PATTERNS].current_val.l
+ && vars[V_PATTERNS].current_val.l[0]
+ && !var_in_pinerc(vars[V_PAT_ROLES].name)
+ && !var_in_pinerc(vars[V_PAT_FILTS].name)
+ && !var_in_pinerc(vars[V_PAT_FILTS_OLD].name)
+ && !var_in_pinerc(vars[V_PAT_SCORES].name)
+ && !var_in_pinerc(vars[V_PAT_SCORES_OLD].name)
+ && !var_in_pinerc(vars[V_PAT_INCOLS].name)
+ && isdigit((unsigned char) ps->pine_pre_vers[0])
+ && ps->pine_pre_vers[1] == '.'
+ && isdigit((unsigned char) ps->pine_pre_vers[2])
+ && isdigit((unsigned char) ps->pine_pre_vers[3])
+ && strncmp(ps->pine_pre_vers, "4.30", 4) < 0){
+ convert_pattern_data();
+ }
+
+ /*
+ * Otherwise, if FILTS_OLD is set and FILTS isn't in the config file,
+ * convert FILTS_OLD to FILTS. Same for SCORES.
+ * The reason FILTS was changed was so we could change the
+ * semantics of how rules work when there are pieces in the rule that
+ * we don't understand. At the same time as the FILTS change we added
+ * a rule to detect 8bitSubjects. So a user might have a filter that
+ * deletes messages with 8bitSubjects. The problem is that that same
+ * filter in a FILTS_OLD pine would match because it would ignore the
+ * 8bitSubject part of the pattern and match on the rest. So we changed
+ * the semantics so that rules with unknown bits would be ignored
+ * instead of used. We had to change variable names at the same time
+ * because we were adding the 8bit thing and the old pines are still
+ * out there. Filters and Scores can both be dangerous. Roles, Colors,
+ * and Other seem less dangerous so not worth adding a new variable.
+ * This was changed in 4.50.
+ */
+ else{
+ if(vars[V_PAT_FILTS_OLD].current_val.l
+ && vars[V_PAT_FILTS_OLD].current_val.l[0]
+ && !var_in_pinerc(vars[V_PAT_FILTS].name)
+ && !var_in_pinerc(vars[V_PAT_SCORES].name)
+ && isdigit((unsigned char) ps->pine_pre_vers[0])
+ && ps->pine_pre_vers[1] == '.'
+ && isdigit((unsigned char) ps->pine_pre_vers[2])
+ && isdigit((unsigned char) ps->pine_pre_vers[3])
+ && strncmp(ps->pine_pre_vers, "4.50", 4) < 0){
+ convert_filts_pattern_data();
+ }
+
+ if(vars[V_PAT_SCORES_OLD].current_val.l
+ && vars[V_PAT_SCORES_OLD].current_val.l[0]
+ && !var_in_pinerc(vars[V_PAT_FILTS].name)
+ && !var_in_pinerc(vars[V_PAT_SCORES].name)
+ && isdigit((unsigned char) ps->pine_pre_vers[0])
+ && ps->pine_pre_vers[1] == '.'
+ && isdigit((unsigned char) ps->pine_pre_vers[2])
+ && isdigit((unsigned char) ps->pine_pre_vers[3])
+ && strncmp(ps->pine_pre_vers, "4.50", 4) < 0){
+ convert_scores_pattern_data();
+ }
+ }
+
+ if(vars[V_PAT_ROLES].post_user_val.l)
+ ps_global->ew_for_role_take = Post;
+ else
+ ps_global->ew_for_role_take = Main;
+
+ if(vars[V_PAT_FILTS].post_user_val.l)
+ ps_global->ew_for_filter_take = Post;
+ else
+ ps_global->ew_for_filter_take = Main;
+
+ if(vars[V_PAT_SCORES].post_user_val.l)
+ ps_global->ew_for_score_take = Post;
+ else
+ ps_global->ew_for_score_take = Main;
+
+ if(vars[V_PAT_INCOLS].post_user_val.l)
+ ps_global->ew_for_incol_take = Post;
+ else
+ ps_global->ew_for_incol_take = Main;
+
+ if(vars[V_PAT_OTHER].post_user_val.l)
+ ps_global->ew_for_other_take = Post;
+ else
+ ps_global->ew_for_other_take = Main;
+
+ if(vars[V_PAT_SRCH].post_user_val.l)
+ ps_global->ew_for_srch_take = Post;
+ else
+ ps_global->ew_for_srch_take = Main;
+}
+
+
+/*
+ * Foreach of the config files;
+ * transfer the data to the new variables.
+ */
+void
+convert_pattern_data(void)
+{
+ convert_pinerc_patterns(PAT_USE_MAIN);
+ convert_pinerc_patterns(PAT_USE_POST);
+}
+
+
+void
+convert_filts_pattern_data(void)
+{
+ convert_pinerc_filts_patterns(PAT_USE_MAIN);
+ convert_pinerc_filts_patterns(PAT_USE_POST);
+}
+
+
+void
+convert_scores_pattern_data(void)
+{
+ convert_pinerc_scores_patterns(PAT_USE_MAIN);
+ convert_pinerc_scores_patterns(PAT_USE_POST);
+}
+
+
+/*
+ * Foreach of the four variables, transfer the data for this config file
+ * from the old patterns variable. We don't have to convert OTHER patterns
+ * or SRCH patterns because they didn't exist in pines without patterns-other.
+ *
+ * If the original variable had patlines with type File then we convert
+ * all of the individual patterns to type Lit, because each pattern can
+ * be of any category. Lit patterns are better tested, anyway.
+ */
+void
+convert_pinerc_patterns(long int use_flags)
+{
+ long old_rflags;
+ long rflags;
+ PAT_S *pat;
+ PAT_STATE pstate;
+ ACTION_S *act;
+
+ old_rflags = (ROLE_OLD_PAT | use_flags);
+
+ rflags = 0L;
+ if(any_patterns(old_rflags, &pstate)){
+ dprint((2, "converting old patterns to new (%s)\n", (use_flags == PAT_USE_MAIN) ? "Main" : "Post"));
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if((act = pat->action) != NULL){
+ if(act->is_a_role &&
+ add_to_pattern(pat, ROLE_DO_ROLES | use_flags))
+ rflags |= ROLE_DO_ROLES;
+ if(act->is_a_incol &&
+ add_to_pattern(pat, ROLE_DO_INCOLS | use_flags))
+ rflags |= ROLE_DO_INCOLS;
+ if(act->is_a_score &&
+ add_to_pattern(pat, ROLE_DO_SCORES | use_flags))
+ rflags |= ROLE_DO_SCORES;
+ if(act->is_a_filter &&
+ add_to_pattern(pat, ROLE_DO_FILTER | use_flags))
+ rflags |= ROLE_DO_FILTER;
+ }
+ }
+
+ if(rflags)
+ if(write_patterns(rflags | use_flags))
+ dprint((1,
+ "Trouble converting patterns to new variable\n"));
+ }
+}
+
+
+/*
+ * If the original variable had patlines with type File then we convert
+ * all of the individual patterns to type Lit, because each pattern can
+ * be of any category. Lit patterns are better tested, anyway.
+ */
+void
+convert_pinerc_filts_patterns(long int use_flags)
+{
+ long old_rflags;
+ long rflags;
+ PAT_S *pat;
+ PAT_STATE pstate;
+ ACTION_S *act;
+
+ old_rflags = (ROLE_OLD_FILT | use_flags);
+
+ rflags = 0L;
+ if(any_patterns(old_rflags, &pstate)){
+ dprint((2, "converting old filter patterns to new (%s)\n", (use_flags == PAT_USE_MAIN) ? "Main" : "Post"));
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if((act = pat->action) != NULL){
+ if(act->is_a_filter &&
+ add_to_pattern(pat, ROLE_DO_FILTER | use_flags))
+ rflags |= ROLE_DO_FILTER;
+ }
+ }
+
+ if(rflags)
+ if(write_patterns(rflags | use_flags))
+ dprint((1,
+ "Trouble converting filter patterns to new variable\n"));
+ }
+}
+
+
+/*
+ * If the original variable had patlines with type File then we convert
+ * all of the individual patterns to type Lit, because each pattern can
+ * be of any category. Lit patterns are better tested, anyway.
+ */
+void
+convert_pinerc_scores_patterns(long int use_flags)
+{
+ long old_rflags;
+ long rflags;
+ PAT_S *pat;
+ PAT_STATE pstate;
+ ACTION_S *act;
+
+ old_rflags = (ROLE_OLD_SCORE | use_flags);
+
+ rflags = 0L;
+ if(any_patterns(old_rflags, &pstate)){
+ dprint((2, "converting old scores patterns to new (%s)\n", (use_flags == PAT_USE_MAIN) ? "Main" : "Post"));
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if((act = pat->action) != NULL){
+ if(act->is_a_score &&
+ add_to_pattern(pat, ROLE_DO_SCORES | use_flags))
+ rflags |= ROLE_DO_SCORES;
+ }
+ }
+
+ if(rflags)
+ if(write_patterns(rflags | use_flags))
+ dprint((1,
+ "Trouble converting scores patterns to new variable\n"));
+ }
+}
+
+
+/*
+ * set_old_growth_bits - Command used to set or unset old growth set
+ * of features
+ */
+void
+set_old_growth_bits(struct pine *ps, int val)
+{
+ int i;
+
+ for(i = 1; i <= F_FEATURE_LIST_COUNT; i++)
+ if(test_old_growth_bits(ps, i))
+ F_SET(i, ps, val);
+}
+
+
+
+/*
+ * test_old_growth_bits - Test to see if all the old growth bits are on,
+ * *or* if a particular feature index is in the old
+ * growth set.
+ *
+ * WEIRD ALERT: if index == F_OLD_GROWTH bit values are tested
+ * otherwise a bits existence in the set is tested!!!
+ *
+ * BUG: this will break if an old growth feature number is ever >= 32.
+ */
+int
+test_old_growth_bits(struct pine *ps, int index)
+{
+ /*
+ * this list defines F_OLD_GROWTH set
+ */
+ static unsigned long old_growth_bits = ((1 << F_ENABLE_FULL_HDR) |
+ (1 << F_ENABLE_PIPE) |
+ (1 << F_ENABLE_TAB_COMPLETE) |
+ (1 << F_QUIT_WO_CONFIRM) |
+ (1 << F_ENABLE_JUMP) |
+ (1 << F_ENABLE_ALT_ED) |
+ (1 << F_ENABLE_BOUNCE) |
+ (1 << F_ENABLE_AGG_OPS) |
+ (1 << F_ENABLE_FLAG) |
+ (1 << F_CAN_SUSPEND));
+ if(index >= 32)
+ return(0);
+
+ if(index == F_OLD_GROWTH){
+ for(index = 1; index <= F_FEATURE_LIST_COUNT; index++)
+ if(((1 << index) & old_growth_bits) && F_OFF(index, ps))
+ return(0);
+
+ return(1);
+ }
+ else
+ return((1 << index) & old_growth_bits);
+}
+
+
+/*
+ * Side effect is that the appropriate global variable is set, and the
+ * appropriate current_val is set.
+ */
+void
+cur_rule_value(struct variable *var, int expand, int cmdline)
+{
+ int i;
+ NAMEVAL_S *v;
+
+ set_current_val(var, expand, cmdline);
+
+ if(var == &ps_global->vars[V_SAVED_MSG_NAME_RULE]){
+ if(ps_global->VAR_SAVED_MSG_NAME_RULE)
+ for(i = 0; (v = save_msg_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_SAVED_MSG_NAME_RULE, S_OR_L(v))){
+ ps_global->save_msg_rule = v->value;
+ break;
+ }
+ }
+#ifndef _WINDOWS
+ else if(var == &ps_global->vars[V_COLOR_STYLE]){
+ if(ps_global->VAR_COLOR_STYLE)
+ for(i = 0; (v = col_style(i)); i++)
+ if(!strucmp(ps_global->VAR_COLOR_STYLE, S_OR_L(v))){
+ ps_global->color_style = v->value;
+ break;
+ }
+ }
+#endif
+ else if(var == &ps_global->vars[V_INDEX_COLOR_STYLE]){
+ if(ps_global->VAR_INDEX_COLOR_STYLE)
+ for(i = 0; (v = index_col_style(i)); i++)
+ if(!strucmp(ps_global->VAR_INDEX_COLOR_STYLE, S_OR_L(v))){
+ ps_global->index_color_style = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_TITLEBAR_COLOR_STYLE]){
+ if(ps_global->VAR_TITLEBAR_COLOR_STYLE)
+ for(i = 0; (v = titlebar_col_style(i)); i++)
+ if(!strucmp(ps_global->VAR_TITLEBAR_COLOR_STYLE, S_OR_L(v))){
+ ps_global->titlebar_color_style = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_FCC_RULE]){
+ if(ps_global->VAR_FCC_RULE)
+ for(i = 0; (v = fcc_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_FCC_RULE, S_OR_L(v))){
+ ps_global->fcc_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_GOTO_DEFAULT_RULE]){
+ if(ps_global->VAR_GOTO_DEFAULT_RULE)
+ for(i = 0; (v = goto_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_GOTO_DEFAULT_RULE, S_OR_L(v))){
+ ps_global->goto_default_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_INCOMING_STARTUP]){
+ if(ps_global->VAR_INCOMING_STARTUP)
+ for(i = 0; (v = incoming_startup_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_INCOMING_STARTUP, S_OR_L(v))){
+ ps_global->inc_startup_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_PRUNING_RULE]){
+ if(ps_global->VAR_PRUNING_RULE)
+ for(i = 0; (v = pruning_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_PRUNING_RULE, S_OR_L(v))){
+ ps_global->pruning_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_REOPEN_RULE]){
+ if(ps_global->VAR_REOPEN_RULE)
+ for(i = 0; (v = reopen_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_REOPEN_RULE, S_OR_L(v))){
+ ps_global->reopen_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_FLD_SORT_RULE]){
+ if(ps_global->VAR_FLD_SORT_RULE)
+ for(i = 0; (v = fld_sort_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_FLD_SORT_RULE, S_OR_L(v))){
+ ps_global->fld_sort_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_AB_SORT_RULE]){
+ if(ps_global->VAR_AB_SORT_RULE)
+ for(i = 0; (v = ab_sort_rules(i)); i++)
+ if(!strucmp(ps_global->VAR_AB_SORT_RULE, S_OR_L(v))){
+ ps_global->ab_sort_rule = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_THREAD_DISP_STYLE]){
+ if(ps_global->VAR_THREAD_DISP_STYLE)
+ for(i = 0; (v = thread_disp_styles(i)); i++)
+ if(!strucmp(ps_global->VAR_THREAD_DISP_STYLE, S_OR_L(v))){
+ ps_global->thread_disp_style = v->value;
+ break;
+ }
+ }
+ else if(var == &ps_global->vars[V_THREAD_INDEX_STYLE]){
+ if(ps_global->VAR_THREAD_INDEX_STYLE)
+ for(i = 0; (v = thread_index_styles(i)); i++)
+ if(!strucmp(ps_global->VAR_THREAD_INDEX_STYLE, S_OR_L(v))){
+ ps_global->thread_index_style = v->value;
+ break;
+ }
+ }
+}
+
+
+/*
+ * Standard way to get at save message rules...
+ */
+NAMEVAL_S *
+save_msg_rules(int index)
+{
+ static NAMEVAL_S save_rules[] = {
+ {"by-from", NULL, SAV_RULE_FROM},
+ {"by-nick-of-from", NULL, SAV_RULE_NICK_FROM_DEF},
+ {"by-nick-of-from-then-from", NULL, SAV_RULE_NICK_FROM},
+ {"by-fcc-of-from", NULL, SAV_RULE_FCC_FROM_DEF},
+ {"by-fcc-of-from-then-from", NULL, SAV_RULE_FCC_FROM},
+ {"by-realname-of-from", NULL, SAV_RULE_RN_FROM_DEF},
+ {"by-realname-of-from-then-from", NULL, SAV_RULE_RN_FROM},
+ {"by-sender", NULL, SAV_RULE_SENDER},
+ {"by-nick-of-sender", NULL, SAV_RULE_NICK_SENDER_DEF},
+ {"by-nick-of-sender-then-sender", NULL, SAV_RULE_NICK_SENDER},
+ {"by-fcc-of-sender", NULL, SAV_RULE_FCC_SENDER_DEF},
+ {"by-fcc-of-sender-then-sender", NULL, SAV_RULE_FCC_SENDER},
+ {"by-realname-of-sender", NULL, SAV_RULE_RN_SENDER_DEF},
+ {"by-realname-of-sender-then-sender", NULL, SAV_RULE_RN_SENDER},
+ {"by-recipient", NULL, SAV_RULE_RECIP},
+ {"by-nick-of-recip", NULL, SAV_RULE_NICK_RECIP_DEF},
+ {"by-nick-of-recip-then-recip", NULL, SAV_RULE_NICK_RECIP},
+ {"by-fcc-of-recip", NULL, SAV_RULE_FCC_RECIP_DEF},
+ {"by-fcc-of-recip-then-recip", NULL, SAV_RULE_FCC_RECIP},
+ {"by-realname-of-recip", NULL, SAV_RULE_RN_RECIP_DEF},
+ {"by-realname-of-recip-then-recip", NULL, SAV_RULE_RN_RECIP},
+ {"by-replyto", NULL, SAV_RULE_REPLYTO},
+ {"by-nick-of-replyto", NULL, SAV_RULE_NICK_REPLYTO_DEF},
+ {"by-nick-of-replyto-then-replyto", NULL, SAV_RULE_NICK_REPLYTO},
+ {"by-fcc-of-replyto", NULL, SAV_RULE_FCC_REPLYTO_DEF},
+ {"by-fcc-of-replyto-then-replyto", NULL, SAV_RULE_FCC_REPLYTO},
+ {"by-realname-of-replyto", NULL, SAV_RULE_RN_REPLYTO_DEF},
+ {"by-realname-of-replyto-then-replyto", NULL, SAV_RULE_RN_REPLYTO},
+ {"last-folder-used", NULL, SAV_RULE_LAST},
+ {"default-folder", NULL, SAV_RULE_DEFLT}
+ };
+
+ return((index >= 0 && index < (sizeof(save_rules)/sizeof(save_rules[0])))
+ ? &save_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at fcc rules...
+ */
+NAMEVAL_S *
+fcc_rules(int index)
+{
+ static NAMEVAL_S f_rules[] = {
+ {"default-fcc", NULL, FCC_RULE_DEFLT},
+ {"last-fcc-used", NULL, FCC_RULE_LAST},
+ {"by-recipient", NULL, FCC_RULE_RECIP},
+ {"by-nickname", NULL, FCC_RULE_NICK},
+ {"by-nick-then-recip", NULL, FCC_RULE_NICK_RECIP},
+ {"current-folder", NULL, FCC_RULE_CURRENT}
+ };
+
+ return((index >= 0 && index < (sizeof(f_rules)/sizeof(f_rules[0])))
+ ? &f_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at addrbook sort rules...
+ */
+NAMEVAL_S *
+ab_sort_rules(int index)
+{
+ static NAMEVAL_S ab_rules[] = {
+ {"fullname-with-lists-last", NULL, AB_SORT_RULE_FULL_LISTS},
+ {"fullname", NULL, AB_SORT_RULE_FULL},
+ {"nickname-with-lists-last", NULL, AB_SORT_RULE_NICK_LISTS},
+ {"nickname", NULL, AB_SORT_RULE_NICK},
+ {"dont-sort", NULL, AB_SORT_RULE_NONE}
+ };
+
+ return((index >= 0 && index < (sizeof(ab_rules)/sizeof(ab_rules[0])))
+ ? &ab_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at color styles.
+ */
+NAMEVAL_S *
+col_style(int index)
+{
+ static NAMEVAL_S col_styles[] = {
+ {"no-color", NULL, COL_NONE},
+ {"use-termdef", NULL, COL_TERMDEF},
+ {"force-ansi-8color", NULL, COL_ANSI8},
+ {"force-ansi-16color", NULL, COL_ANSI16},
+ {"force-xterm-256color", NULL, COL_ANSI256}
+ };
+
+ return((index >= 0 && index < (sizeof(col_styles)/sizeof(col_styles[0])))
+ ? &col_styles[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at index color styles.
+ */
+NAMEVAL_S *
+index_col_style(int index)
+{
+ static NAMEVAL_S ind_col_styles[] = {
+ {"flip-colors", NULL, IND_COL_FLIP},
+ {"reverse", NULL, IND_COL_REV},
+ {"reverse-fg", NULL, IND_COL_FG},
+ {"reverse-fg-no-ambiguity", NULL, IND_COL_FG_NOAMBIG},
+ {"reverse-bg", NULL, IND_COL_BG},
+ {"reverse-bg-no-ambiguity", NULL, IND_COL_BG_NOAMBIG}
+ };
+
+ return((index >= 0 && index < (sizeof(ind_col_styles)/sizeof(ind_col_styles[0]))) ? &ind_col_styles[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at titlebar color styles.
+ */
+NAMEVAL_S *
+titlebar_col_style(int index)
+{
+ static NAMEVAL_S tbar_col_styles[] = {
+ {"default", NULL, TBAR_COLOR_DEFAULT},
+ {"indexline", NULL, TBAR_COLOR_INDEXLINE},
+ {"reverse-indexline", NULL, TBAR_COLOR_REV_INDEXLINE}
+ };
+
+ return((index >= 0 && index < (sizeof(tbar_col_styles)/sizeof(tbar_col_styles[0]))) ? &tbar_col_styles[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at folder sort rules...
+ */
+NAMEVAL_S *
+fld_sort_rules(int index)
+{
+ static NAMEVAL_S fdl_rules[] = {
+ {"alphabetical", NULL, FLD_SORT_ALPHA},
+ {"alpha-with-dirs-last", NULL, FLD_SORT_ALPHA_DIR_LAST},
+ {"alpha-with-dirs-first", NULL, FLD_SORT_ALPHA_DIR_FIRST}
+ };
+
+ return((index >= 0 && index < (sizeof(fdl_rules)/sizeof(fdl_rules[0])))
+ ? &fdl_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at incoming startup rules...
+ */
+NAMEVAL_S *
+incoming_startup_rules(int index)
+{
+ static NAMEVAL_S is_rules[] = {
+ {"first-unseen", NULL, IS_FIRST_UNSEEN},
+ {"first-recent", NULL, IS_FIRST_RECENT},
+ {"first-important", NULL, IS_FIRST_IMPORTANT},
+ {"first-important-or-unseen", NULL, IS_FIRST_IMPORTANT_OR_UNSEEN},
+ {"first-important-or-recent", NULL, IS_FIRST_IMPORTANT_OR_RECENT},
+ {"first", NULL, IS_FIRST},
+ {"last", NULL, IS_LAST}
+ };
+
+ return((index >= 0 && index < (sizeof(is_rules)/sizeof(is_rules[0])))
+ ? &is_rules[index] : NULL);
+}
+
+
+NAMEVAL_S *
+startup_rules(int index)
+{
+ static NAMEVAL_S is2_rules[] = {
+ {"first-unseen", NULL, IS_FIRST_UNSEEN},
+ {"first-recent", NULL, IS_FIRST_RECENT},
+ {"first-important", NULL, IS_FIRST_IMPORTANT},
+ {"first-important-or-unseen", NULL, IS_FIRST_IMPORTANT_OR_UNSEEN},
+ {"first-important-or-recent", NULL, IS_FIRST_IMPORTANT_OR_RECENT},
+ {"first", NULL, IS_FIRST},
+ {"last", NULL, IS_LAST},
+ {"default", NULL, IS_NOTSET}
+ };
+
+ return((index >= 0 && index < (sizeof(is2_rules)/sizeof(is2_rules[0])))
+ ? &is2_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at pruning-rule values.
+ */
+NAMEVAL_S *
+pruning_rules(int index)
+{
+ static NAMEVAL_S pr_rules[] = {
+ {"ask about rename, ask about deleting","ask-ask", PRUNE_ASK_AND_ASK},
+ {"ask about rename, don't delete", "ask-no", PRUNE_ASK_AND_NO},
+ {"always rename, ask about deleting", "yes-ask", PRUNE_YES_AND_ASK},
+ {"always rename, don't delete", "yes-no", PRUNE_YES_AND_NO},
+ {"don't rename, ask about deleting", "no-ask", PRUNE_NO_AND_ASK},
+ {"don't rename, don't delete", "no-no", PRUNE_NO_AND_NO}
+ };
+
+ return((index >= 0 && index < (sizeof(pr_rules)/sizeof(pr_rules[0])))
+ ? &pr_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at reopen-rule values.
+ */
+NAMEVAL_S *
+reopen_rules(int index)
+{
+ static NAMEVAL_S ro_rules[] = {
+ /* TRANSLATORS: short description of a feature option */
+ {"Always reopen", "yes-yes",
+ REOPEN_YES_YES},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Yes for POP/NNTP, Ask about other remote [Yes]", "yes-ask-y",
+ REOPEN_YES_ASK_Y},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Yes for POP/NNTP, Ask about other remote [No]", "yes-ask-n",
+ REOPEN_YES_ASK_N},
+ /* TRANSLATORS: short description of a feature option */
+ {"Yes for POP/NNTP, No for other remote", "yes-no",
+ REOPEN_YES_NO},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Always ask [Yes]", "ask-ask-y",
+ REOPEN_ASK_ASK_Y},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Always ask [No]", "ask-ask-n",
+ REOPEN_ASK_ASK_N},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Ask about POP/NNTP [Yes], No for other remote", "ask-no-y",
+ REOPEN_ASK_NO_Y},
+ /* TRANSLATORS: short description of a feature option, default in brackets */
+ {"Ask about POP/NNTP [No], No for other remote", "ask-no-n",
+ REOPEN_ASK_NO_N},
+ /* TRANSLATORS: short description of a feature option */
+ {"Never reopen", "no-no",
+ REOPEN_NO_NO},
+ };
+
+ return((index >= 0 && index < (sizeof(ro_rules)/sizeof(ro_rules[0])))
+ ? &ro_rules[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at thread_disp_style values.
+ */
+NAMEVAL_S *
+thread_disp_styles(int index)
+{
+ static NAMEVAL_S td_styles[] = {
+ {"none", "none", THREAD_NONE},
+ {"show-thread-structure", "struct", THREAD_STRUCT},
+ {"mutt-like", "mutt", THREAD_MUTTLIKE},
+ {"indent-subject-1", "subj1", THREAD_INDENT_SUBJ1},
+ {"indent-subject-2", "subj2", THREAD_INDENT_SUBJ2},
+ {"indent-from-1", "from1", THREAD_INDENT_FROM1},
+ {"indent-from-2", "from2", THREAD_INDENT_FROM2},
+ {"show-structure-in-from", "struct-from", THREAD_STRUCT_FROM}
+ };
+
+ return((index >= 0 && index < (sizeof(td_styles)/sizeof(td_styles[0])))
+ ? &td_styles[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at thread_index_style values.
+ */
+NAMEVAL_S *
+thread_index_styles(int index)
+{
+ static NAMEVAL_S ti_styles[] = {
+ {"regular-index-with-expanded-threads", "exp", THRDINDX_EXP},
+ {"regular-index-with-collapsed-threads","coll", THRDINDX_COLL},
+ {"separate-index-screen-always", "sep", THRDINDX_SEP},
+ {"separate-index-screen-except-for-single-messages","sep-auto",
+ THRDINDX_SEP_AUTO}
+ };
+
+ return((index >= 0 && index < (sizeof(ti_styles)/sizeof(ti_styles[0])))
+ ? &ti_styles[index] : NULL);
+}
+
+
+/*
+ * Standard way to get at goto default rules...
+ */
+NAMEVAL_S *
+goto_rules(int index)
+{
+ static NAMEVAL_S g_rules[] = {
+ {"folder-in-first-collection", NULL, GOTO_FIRST_CLCTN},
+ {"inbox-or-folder-in-first-collection", NULL, GOTO_INBOX_FIRST_CLCTN},
+ {"inbox-or-folder-in-recent-collection", NULL, GOTO_INBOX_RECENT_CLCTN},
+ {"first-collection-with-inbox-default", NULL, GOTO_FIRST_CLCTN_DEF_INBOX},
+ {"most-recent-folder", NULL, GOTO_LAST_FLDR}
+ };
+
+ return((index >= 0 && index < (sizeof(g_rules)/sizeof(g_rules[0])))
+ ? &g_rules[index] : NULL);
+}
+
+
+NAMEVAL_S *
+pat_fldr_types(int index)
+{
+ static NAMEVAL_S pat_fldr_list[] = {
+ {"Any", "ANY", FLDR_ANY},
+ {"News", "NEWS", FLDR_NEWS},
+ {"Email", "EMAIL", FLDR_EMAIL},
+ {"Specific (Enter Incoming Nicknames or use ^T)", "SPEC", FLDR_SPECIFIC}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(pat_fldr_list)/sizeof(pat_fldr_list[0])))
+ ? &pat_fldr_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+inabook_fldr_types(int indexarg)
+{
+ static NAMEVAL_S inabook_fldr_list[] = {
+ {"Don't care, always matches", "E", IAB_EITHER},
+ {"Yes, in any address book", "YES", IAB_YES},
+ {"No, not in any address book", "NO", IAB_NO},
+ {"Yes, in specific address books", "SYES", IAB_SPEC_YES},
+ {"No, not in any of specific address books", "SNO", IAB_SPEC_NO}
+ };
+
+ int index = indexarg & IAB_TYPE_MASK;
+
+ return((index >= 0 &&
+ index < (sizeof(inabook_fldr_list)/sizeof(inabook_fldr_list[0])))
+ ? &inabook_fldr_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+filter_types(int index)
+{
+ static NAMEVAL_S filter_type_list[] = {
+ {"Just Set Message Status", "NONE", FILTER_STATE},
+ {"Delete", "DEL", FILTER_KILL},
+ {"Move (Enter folder name(s) in primary collection, or use ^T)",
+ "FLDR", FILTER_FOLDER}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(filter_type_list)/sizeof(filter_type_list[0])))
+ ? &filter_type_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+role_repl_types(int index)
+{
+ static NAMEVAL_S role_repl_list[] = {
+ {"Never", "NO", ROLE_REPL_NO},
+ {"With confirmation", "YES", ROLE_REPL_YES},
+ {"Without confirmation", "NC", ROLE_REPL_NOCONF}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(role_repl_list)/sizeof(role_repl_list[0])))
+ ? &role_repl_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+role_forw_types(int index)
+{
+ static NAMEVAL_S role_forw_list[] = {
+ {"Never", "NO", ROLE_FORW_NO},
+ {"With confirmation", "YES", ROLE_FORW_YES},
+ {"Without confirmation", "NC", ROLE_FORW_NOCONF}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(role_forw_list)/sizeof(role_forw_list[0])))
+ ? &role_forw_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+role_comp_types(int index)
+{
+ static NAMEVAL_S role_comp_list[] = {
+ {"Never", "NO", ROLE_COMP_NO},
+ {"With confirmation", "YES", ROLE_COMP_YES},
+ {"Without confirmation", "NC", ROLE_COMP_NOCONF}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(role_comp_list)/sizeof(role_comp_list[0])))
+ ? &role_comp_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+role_status_types(int index)
+{
+ static NAMEVAL_S role_status_list[] = {
+ {"Don't care, always matches", "E", PAT_STAT_EITHER},
+ {"Yes", "YES", PAT_STAT_YES},
+ {"No", "NO", PAT_STAT_NO}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(role_status_list)/sizeof(role_status_list[0])))
+ ? &role_status_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+msg_state_types(int index)
+{
+ static NAMEVAL_S msg_state_list[] = {
+ {"Don't change it", "LV", ACT_STAT_LEAVE},
+ {"Set this state", "SET", ACT_STAT_SET},
+ {"Clear this state", "CLR", ACT_STAT_CLEAR}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(msg_state_list)/sizeof(msg_state_list[0])))
+ ? &msg_state_list[index] : NULL);
+}
+
+
+#ifdef ENABLE_LDAP
+NAMEVAL_S *
+ldap_search_rules(int index)
+{
+ static NAMEVAL_S ldap_search_list[] = {
+ {"contains", NULL, LDAP_SRCH_CONTAINS},
+ {"equals", NULL, LDAP_SRCH_EQUALS},
+ {"begins-with", NULL, LDAP_SRCH_BEGINS},
+ {"ends-with", NULL, LDAP_SRCH_ENDS}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(ldap_search_list)/sizeof(ldap_search_list[0])))
+ ? &ldap_search_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+ldap_search_types(int index)
+{
+ static NAMEVAL_S ldap_types_list[] = {
+ {"name", NULL, LDAP_TYPE_CN},
+ {"surname", NULL, LDAP_TYPE_SUR},
+ {"givenname", NULL, LDAP_TYPE_GIVEN},
+ {"email", NULL, LDAP_TYPE_EMAIL},
+ {"name-or-email", NULL, LDAP_TYPE_CN_EMAIL},
+ {"surname-or-givenname", NULL, LDAP_TYPE_SUR_GIVEN},
+ {"sur-or-given-or-name-or-email", NULL, LDAP_TYPE_SEVERAL}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(ldap_types_list)/sizeof(ldap_types_list[0])))
+ ? &ldap_types_list[index] : NULL);
+}
+
+
+NAMEVAL_S *
+ldap_search_scope(int index)
+{
+ static NAMEVAL_S ldap_scope_list[] = {
+ {"base", NULL, LDAP_SCOPE_BASE},
+ {"onelevel", NULL, LDAP_SCOPE_ONELEVEL},
+ {"subtree", NULL, LDAP_SCOPE_SUBTREE}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(ldap_scope_list)/sizeof(ldap_scope_list[0])))
+ ? &ldap_scope_list[index] : NULL);
+}
+#endif
+
+
+/*
+ * Choose from the global default, command line args, pinerc values to set
+ * the actual value of the variable that we will use. Start at the top
+ * and work down from higher to lower precedence.
+ * For lists, we may inherit values from lower precedence
+ * versions if that's the way the user specifies it.
+ * The user can put INHERIT_DEFAULT as the first entry in a list and that
+ * means it will inherit the current values, for example the values
+ * from the global_val, or the value from the main_user_val could be
+ * inherited in the post_user_val.
+ */
+void
+set_current_val(struct variable *var, int expand, int cmdline)
+{
+ int is_set[5], is_inherit[5];
+ int i, j, k, cnt, start;
+ char **tmp, **t, **list[5];
+ char *p;
+
+ dprint((9,
+ "set_current_val(var=%s%s, expand=%d, cmdline=%d)\n",
+ (var && var->name) ? var->name : "?",
+ (var && var->is_list) ? " (list)" : "",
+ expand, cmdline));
+
+ if(!var)
+ return;
+
+ if(var->is_list){ /* variable is a list */
+
+ for(j = 0; j < 5; j++){
+ t = j==0 ? var->global_val.l :
+ j==1 ? var->main_user_val.l :
+ j==2 ? var->post_user_val.l :
+ j==3 ? ((cmdline) ? var->cmdline_val.l : NULL) :
+ var->fixed_val.l;
+
+ is_set[j] = is_inherit[j] = 0;
+ list[j] = NULL;
+
+ if(t){
+ if(!expand){
+ is_set[j]++;
+ list[j] = t;
+ }
+ else{
+ for(i = 0; t[i]; i++){
+ if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i],
+ 0)){
+ /* successful expand */
+ is_set[j]++;
+ list[j] = t;
+ break;
+ }
+ }
+ }
+
+ if(list[j] && list[j][0] && !strcmp(list[j][0],INHERIT))
+ is_inherit[j]++;
+ }
+ }
+
+ cnt = 0;
+ start = 0;
+ /* count how many items in current_val list */
+ /* Admin wants default, which is global_val. */
+ if(var->is_fixed && var->fixed_val.l == NULL){
+ cnt = 0;
+ if(is_set[0]){
+ for(; list[0][cnt]; cnt++)
+ ;
+ }
+ }
+ else{
+ for(j = 0; j < 5; j++){
+ if(is_set[j]){
+ if(!is_inherit[j]){
+ cnt = 0; /* reset */
+ start = j;
+ }
+
+ for(i = is_inherit[j] ? 1 : 0; list[j][i]; i++)
+ cnt++;
+ }
+ }
+ }
+
+ free_list_array(&var->current_val.l); /* clean up any old values */
+
+ /* check to see if anything is set */
+ if(is_set[0] + is_set[1] + is_set[2] + is_set[3] + is_set[4] > 0){
+ var->current_val.l = (char **)fs_get((cnt+1)*sizeof(char *));
+ tmp = var->current_val.l;
+ if(var->is_fixed && var->fixed_val.l == NULL){
+ if(is_set[0]){
+ for(i = 0; list[0][i]; i++){
+ if(!expand)
+ *tmp++ = cpystr(list[0][i]);
+ else if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ list[0][i], 0))
+ *tmp++ = cpystr(tmp_20k_buf);
+ }
+ }
+ }
+ else{
+ for(j = start; j < 5; j++){
+ if(is_set[j]){
+ for(i = is_inherit[j] ? 1 : 0; list[j][i]; i++){
+ if(!expand)
+ *tmp++ = cpystr(list[j][i]);
+ else if(expand_variables(tmp_20k_buf,SIZEOF_20KBUF,
+ list[j][i], 0))
+ *tmp++ = cpystr(tmp_20k_buf);
+ }
+ }
+ }
+ }
+
+ *tmp = NULL;
+ }
+ else
+ var->current_val.l = NULL;
+ }
+ else{ /* variable is not a list */
+ char *strvar = NULL;
+
+ for(j = 0; j < 5; j++){
+
+ p = j==0 ? var->fixed_val.p :
+ j==1 ? ((cmdline) ? var->cmdline_val.p : NULL) :
+ j==2 ? var->post_user_val.p :
+ j==3 ? var->main_user_val.p :
+ var->global_val.p;
+
+ is_set[j] = 0;
+
+ if(p){
+ if(!expand){
+ is_set[j]++;
+ if(!strvar)
+ strvar = p;
+ }
+ else if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, p,
+ (var == &ps_global->vars[V_MAILCAP_PATH] ||
+ var == &ps_global->vars[V_MIMETYPE_PATH]))){
+ is_set[j]++;
+ if(!strvar)
+ strvar = p;
+ }
+ }
+ }
+
+ /* Admin wants default, which is global_val. */
+ if(var->is_fixed && var->fixed_val.p == NULL)
+ strvar = var->global_val.p;
+
+ if(var->current_val.p) /* free previous value */
+ fs_give((void **)&var->current_val.p);
+
+ if(strvar){
+ if(!expand)
+ var->current_val.p = cpystr(strvar);
+ else{
+ expand_variables(tmp_20k_buf, SIZEOF_20KBUF, strvar,
+ (var == &ps_global->vars[V_MAILCAP_PATH] ||
+ var == &ps_global->vars[V_MIMETYPE_PATH]));
+ var->current_val.p = cpystr(tmp_20k_buf);
+ }
+ }
+ else
+ var->current_val.p = NULL;
+ }
+
+ if(var->is_fixed && !is_inherit[4]){
+ char **flist;
+ int fixed_len, user_len;
+
+ /*
+ * sys mgr fixed this variable and user is trying to change it
+ */
+ for(k = 1; !(ps_global->give_fixed_warning &&
+ ps_global->fix_fixed_warning) && k <= 3; k++){
+ if(is_set[k]){
+ if(var->is_list){
+ t = k==1 ? ((cmdline) ? var->cmdline_val.l : NULL) :
+ k==2 ? var->post_user_val.l :
+ var->main_user_val.l;
+
+ /* If same length and same contents, don't warn. */
+ for(flist=var->fixed_val.l; flist && *flist; flist++)
+ ;/* just counting */
+
+ fixed_len = var->fixed_val.l ? (flist - var->fixed_val.l)
+ : 0;
+ for(flist=t; flist && *flist; flist++)
+ ;/* just counting */
+
+ user_len = t ? (flist - t) : 0;
+ if(user_len == fixed_len){
+ for(i=0; i < user_len; i++){
+ for(j=0; j < user_len; j++)
+ if(!strucmp(t[i], var->fixed_val.l[j]))
+ break;
+
+ if(j == user_len){
+ ps_global->give_fixed_warning = 1;
+ if(k != 1)
+ ps_global->fix_fixed_warning = 1;
+
+ break;
+ }
+ }
+ }
+ else{
+ ps_global->give_fixed_warning = 1;
+ if(k != 1)
+ ps_global->fix_fixed_warning = 1;
+ }
+ }
+ else{
+ p = k==1 ? ((cmdline) ? var->cmdline_val.p : NULL) :
+ k==2 ? var->post_user_val.p :
+ var->main_user_val.p;
+
+ if((var->fixed_val.p && !p) ||
+ (!var->fixed_val.p && p) ||
+ (var->fixed_val.p && p && strucmp(var->fixed_val.p, p))){
+ ps_global->give_fixed_warning = 1;
+ if(k != 1)
+ ps_global->fix_fixed_warning = 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void
+set_news_spec_current_val(int expand, int cmdline)
+{
+ struct variable *newsvar = &ps_global->vars[V_NEWS_SPEC];
+ struct variable *fvar = &ps_global->vars[V_FOLDER_SPEC];
+
+ /* check to see if it has a value */
+ set_current_val(newsvar, expand, cmdline);
+
+ /*
+ * If no value, we might want to fake a value. We'll do that if
+ * there is no news collection already defined in FOLDER_SPEC and if
+ * there is also an NNTP_SERVER defined.
+ */
+ if(!newsvar->current_val.l && ps_global->VAR_NNTP_SERVER &&
+ ps_global->VAR_NNTP_SERVER[0] && ps_global->VAR_NNTP_SERVER[0][0] &&
+ !news_in_folders(fvar)){
+ char buf[MAXPATH];
+
+ newsvar->global_val.l = (char **)fs_get(2 * sizeof(char *));
+ snprintf(buf, sizeof(buf), "{%.*s/nntp}#news.[]", sizeof(buf)-20,
+ ps_global->VAR_NNTP_SERVER[0]);
+ newsvar->global_val.l[0] = cpystr(buf);
+ newsvar->global_val.l[1] = NULL;
+ set_current_val(newsvar, expand, cmdline);
+ /*
+ * But we're going to get rid of the fake global_val in case
+ * things change.
+ */
+ free_list_array(&newsvar->global_val.l);
+ }
+}
+
+
+/*
+ * Feature-list has to be handled separately from the other variables
+ * because it is additive. The other variables choose one of command line,
+ * or pine.conf, or pinerc. Feature list adds them. This could easily be
+ * converted to a general purpose routine if we add more additive variables.
+ *
+ * This works by replacing earlier values with later ones. That is, command
+ * line settings have higher precedence than global settings and that is
+ * accomplished by putting the command line features after the global
+ * features in the list. When they are processed, the last one wins.
+ *
+ * Feature-list also has a backwards compatibility hack.
+ */
+void
+set_feature_list_current_val(struct variable *var)
+{
+ char **list;
+ char **list_fixed;
+ char no_allow[50];
+ int i, j, k, m,
+ elems = 0;
+
+ /* count the lists so we can allocate */
+ for(m = 0; m < 6; m++){
+ list = m==0 ? var->global_val.l :
+ m==1 ? var->main_user_val.l :
+ m==2 ? var->post_user_val.l :
+ m==3 ? ps_global->feat_list_back_compat :
+ m==4 ? var->cmdline_val.l :
+ var->fixed_val.l;
+ if(list)
+ for(i = 0; list[i]; i++)
+ elems++;
+ }
+
+ list_fixed = var->fixed_val.l;
+
+ if(var->current_val.l)
+ free_list_array(&var->current_val.l);
+
+ var->current_val.l = (char **)fs_get((elems+1) * sizeof(char *));
+
+ /*
+ * We need to warn the user if the sys mgr has restricted him or her
+ * from changing a feature that he or she is trying to change.
+ *
+ * We're not catching the old-growth macro since we're just comparing
+ * strings. That is, it works correctly, but the user won't be warned
+ * if the user old-growth and the mgr says no-quit-without-confirm.
+ */
+
+ j = 0;
+ strncpy(no_allow, "no-", 3);
+ strncpy(no_allow+3, feature_list_name(F_ALLOW_CHANGING_FROM), sizeof(no_allow)-3-1);
+ no_allow[sizeof(no_allow)-1] = '\0';
+
+ for(m = 0; m < 6; m++){
+ list = m==0 ? var->global_val.l :
+ m==1 ? var->main_user_val.l :
+ m==2 ? var->post_user_val.l :
+ m==3 ? ps_global->feat_list_back_compat :
+ m==4 ? var->cmdline_val.l :
+ var->fixed_val.l;
+ if(list)
+ for(i = 0; list[i]; i++){
+ var->current_val.l[j++] = cpystr(list[i]);
+
+ /* this is the warning section */
+ if(m >= 1 && m <= 4){
+ for(k = 0; list_fixed && list_fixed[k]; k++){
+ char *p, *q;
+ p = list[i];
+ q = list_fixed[k];
+ if(!struncmp(p, "no-", 3))
+ p += 3;
+ if(!struncmp(q, "no-", 3))
+ q += 3;
+ if(!strucmp(q, p) && strucmp(list[i], list_fixed[k])){
+ ps_global->give_fixed_warning = 1;
+ if(m <= 2)
+ ps_global->fix_fixed_warning = 1;
+ }
+ }
+ }
+ else if(m == 5 && !strucmp(list[i], no_allow))
+ ps_global->never_allow_changing_from = 1;
+ }
+ }
+
+#ifdef NEVER_ALLOW_CHANGING_FROM
+ ps_global->never_allow_changing_from = 1;
+#endif
+
+ var->current_val.l[j] = NULL;
+}
+
+
+
+/*----------------------------------------------------------------------
+
+ Expand Metacharacters/variables in file-names
+
+ Read input line and expand shell-variables/meta-characters
+
+ <input> <replaced by>
+ $variable getenv("variable")
+ ${variable} getenv("variable")
+ ${variable:-defvalue} is getenv("variable") if variable is defined and
+ is defvalue otherwise
+ ~ getenv("HOME")
+ \c c
+ <others> <just copied>
+
+NOTE handling of braces in ${name} doesn't check much or do error recovery
+
+ If colon_path is set, then we expand ~ not only at the start of linein,
+ but also after each : in the path.
+
+ ----*/
+#define is_allowed_envchar(C, S) ((S) == 0 ? !isspace((C)) && (C) != '/'\
+ : (((C) >= 'a' && (C) <= 'z') \
+ || ((C) >= 'A' && (C) <= 'Z') \
+ || ((C) >= '0' && (C) <= '9')))
+
+char *
+expand_variables(char *lineout, size_t lineoutlen, char *linein, int colon_path)
+{
+ char *src = linein, *dest = lineout, *p;
+ char *limit = lineout + lineoutlen;
+ int envexpand = 0, sp;
+
+ if(!linein)
+ return(NULL);
+
+ sp = strncmp(src,"LIT:pattern=\"/NICK=", strlen("LIT:pattern=\"/NICK=")) == 0;
+ while(*src ){ /* something in input string */
+ if(*src == '$' && *(src+1) == '$'){
+ /*
+ * $$ to escape chars we're interested in, else
+ * it's up to the user of the variable to handle the
+ * backslash...
+ */
+ if(dest < limit)
+ *dest++ = *++src; /* copy next as is */
+ }else
+#if !(defined(DOS) || defined(OS2))
+ if(*src == '\\' && *(src+1) == '$'){
+ /*
+ * backslash to escape chars we're interested in, else
+ * it's up to the user of the variable to handle the
+ * backslash...
+ */
+ if(dest < limit)
+ *dest++ = *++src; /* copy next as is */
+ }else if(*src == '~' &&
+ (src == linein || (colon_path && *(src-1) == ':'))){
+ char buf[MAXPATH];
+ int i;
+
+ for(i = 0; i < sizeof(buf)-1 && src[i] && src[i] != '/'; i++)
+ buf[i] = src[i];
+
+ src += i; /* advance src pointer */
+ buf[i] = '\0'; /* tie off buf string */
+ fnexpand(buf, sizeof(buf)); /* expand the path */
+
+ for(p = buf; dest < limit && (*dest = *p); p++, dest++)
+ ;
+
+ continue;
+ }else
+#endif
+ if(*src == '$'){ /* shell variable */
+ char word[128+1], *colon = NULL, *rbrace = NULL;
+
+ envexpand++; /* signal that we've expanded a var */
+ src++; /* skip dollar */
+ if(*src == '{'){ /* starts with brace? */
+ src++;
+ rbrace = strindex(src, '}');
+ if(rbrace){
+ /* look for default value */
+ colon = strstr(src, ":-");
+ if(colon && (rbrace < colon))
+ colon = NULL;
+ }
+ }
+
+ p = word;
+
+ /* put the env variable to be looked up in word */
+ if(rbrace){
+ while(*src
+ && (p-word < sizeof(word)-1)
+ && ((colon && src < colon) || (!colon && src < rbrace))){
+ if(isspace((unsigned char) *src)){
+ /*
+ * Illegal input. This should be an error of some
+ * sort but instead of that we'll just backup to the
+ * $ and treat it like it wasn't there.
+ */
+ while(*src != '$')
+ src--;
+
+ envexpand--;
+ goto just_copy;
+ }
+ else
+ *p++ = *src++;
+ }
+
+ /* adjust src for next char */
+ src = rbrace + 1;
+ }
+ else{
+ while(*src && is_allowed_envchar((unsigned char) *src, sp)
+ && (p-word < sizeof(word)-1))
+ *p++ = *src++;
+ }
+
+ *p = '\0';
+
+ if((p = getenv(word)) != NULL){ /* check for word in environment */
+ while(*p && dest < limit)
+ *dest++ = *p++;
+ }
+ else if(colon){ /* else possible default value */
+ p = colon + 2;
+ while(*p && p < rbrace && dest < limit)
+ *dest++ = *p++;
+ }
+
+ continue;
+ }else{ /* other cases: just copy */
+just_copy:
+ if(dest < limit)
+ *dest++ = *src;
+ }
+
+ if(*src) /* next character (if any) */
+ src++;
+ }
+
+ if(dest < limit)
+ *dest = '\0';
+ else
+ lineout[lineoutlen-1] = '\0';
+
+ return((envexpand && lineout[0] == '\0') ? NULL : lineout);
+}
+
+
+/*----------------------------------------------------------------------
+ Sets login, full_username and home_dir
+
+ Args: ps -- The Pine structure to put the user name, etc in
+
+ Result: sets the fullname, login and home_dir field of the pine structure
+ returns 0 on success, -1 if not.
+ ----*/
+#define MAX_INIT_ERRS 10
+void
+init_error(struct pine *ps, int flags, int min_time, int max_time, char *message)
+{
+ int i;
+
+ if(!ps->init_errs){
+ ps->init_errs = (INIT_ERR_S *)fs_get((MAX_INIT_ERRS + 1) *
+ sizeof(*ps->init_errs));
+ memset(ps->init_errs, 0, (MAX_INIT_ERRS + 1) * sizeof(*ps->init_errs));
+ }
+
+ for(i = 0; i < MAX_INIT_ERRS; i++)
+ if(!(ps->init_errs)[i].message){
+ (ps->init_errs)[i].message = cpystr(message);
+ (ps->init_errs)[i].min_time = min_time;
+ (ps->init_errs)[i].max_time = max_time;
+ (ps->init_errs)[i].flags = flags;
+ dprint((2, "%s\n", message ? message : "?"));
+ break;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Read and parse a pinerc file
+
+ Args: Filename -- name of the .pinerc file to open and read
+ vars -- The vars structure to store values in
+ which_vars -- Whether the local or global values are being read
+
+ Result:
+
+ This may be the local file or the global file. The values found are
+merged with the values currently in vars. All values are strings and
+are malloced; and existing values will be freed before the assignment.
+Those that are <unset> will be left unset; their values will be NULL.
+ ----*/
+void
+read_pinerc(PINERC_S *prc, struct variable *vars, ParsePinerc which_vars)
+{
+ char *filename, *file, *value, **lvalue, *line, *error;
+ char *p, *p1, *free_file = NULL;
+ struct variable *v;
+ PINERC_LINE *pline = NULL;
+ int line_count, was_quoted;
+ int i;
+
+ if(!prc)
+ return;
+
+ dprint((2, "reading_pinerc \"%s\"\n",
+ prc->name ? prc->name : "?"));
+
+ if(prc->type == Loc){
+ filename = prc->name ? prc->name : "";
+ file = free_file = read_file(filename, 0);
+
+ /*
+ * This is questionable. In case the user edits the pinerc
+ * in Windows and adds a UTF-8 BOM, we skip it here. If the
+ * user adds a Unicode BOM we're in trouble. We could write it
+ * with the BOM ourselves but so far we leave it BOMless in
+ * order that it's the same on Unix and Windows.
+ */
+ if(BOM_UTF8(file))
+ file += 3;
+ }
+ else{
+ if((file = read_remote_pinerc(prc, which_vars)) != NULL)
+ ps_global->c_client_error[0] = '\0';
+
+ free_file = file;
+ }
+
+ if(file == NULL || *file == '\0'){
+#ifdef DEBUG
+ if(file == NULL){
+ dprint((2, "Open failed: %s\n", error_description(errno)));
+ }
+ else{
+ if(prc->type == Loc){
+ dprint((1, "Read_pinerc: empty pinerc (new?)\n"));
+ }
+ else{
+ dprint((1, "Read_pinerc: new remote pinerc\n"));
+ }
+ }
+#endif /* DEBUG */
+
+ if(which_vars == ParsePers){
+ /* problems getting remote config */
+ if(file == NULL && prc->type == RemImap){
+ if(!pith_opt_remote_pinerc_failure
+ || !(*pith_opt_remote_pinerc_failure)())
+ exceptional_exit(_("Unable to read or write remote configuration"), -1);
+ }
+
+ ps_global->first_time_user = 1;
+ prc->outstanding_pinerc_changes = 1;
+ }
+
+ return;
+ }
+ else{
+ if(prc->type == Loc &&
+ (which_vars == ParseFixed || which_vars == ParseGlobal ||
+ (can_access(filename, ACCESS_EXISTS) == 0 &&
+ can_access(filename, EDIT_ACCESS) != 0))){
+ prc->readonly = 1;
+ if(prc == ps_global->prc)
+ ps_global->readonly_pinerc = 1;
+ }
+
+ /*
+ * accept CRLF or LF newlines
+ */
+ for(p = file; *p && *p != '\012'; p++)
+ ;
+
+ if(p > file && *p && *(p-1) == '\015') /* cvt crlf to lf */
+ for(p1 = p - 1; (*p1 = *p) != '\0'; p++)
+ if(!(*p == '\015' && *(p+1) == '\012'))
+ p1++;
+ }
+
+ dprint((2, "Read %d characters:\n", strlen(file)));
+
+ if(which_vars == ParsePers || which_vars == ParsePersPost){
+ /*--- Count up lines and allocate structures */
+ for(line_count = 0, p = file; *p != '\0'; p++)
+ if(*p == '\n')
+ line_count++;
+
+ prc->pinerc_lines = (PINERC_LINE *)
+ fs_get((3 + line_count) * sizeof(PINERC_LINE));
+ memset((void *)prc->pinerc_lines, 0,
+ (3 + line_count) * sizeof(PINERC_LINE));
+ pline = prc->pinerc_lines;
+ }
+
+ for(p = file, line = file; *p != '\0';){
+ /*----- Grab the line ----*/
+ line = p;
+ while(*p && *p != '\n')
+ p++;
+ if(*p == '\n'){
+ *p++ = '\0';
+ }
+
+ /*----- Comment Line -----*/
+ if(*line == '#'){
+ /* no comments in remote pinercs */
+ if(pline && prc->type == Loc){
+ pline->is_var = 0;
+ pline->line = cpystr(line);
+ pline++;
+ }
+ continue;
+ }
+
+ if(*line == '\0' || *line == '\t' || *line == ' '){
+ p1 = line;
+ while(*p1 == '\t' || *p1 == ' ')
+ p1++;
+ if(pline){
+ /*
+ * This could be a continuation line from some future
+ * version of pine, or it could be a continuation line
+ * from a PC-Pine variable we don't know about in unix.
+ */
+ if(*p1 != '\0')
+ pline->line = cpystr(line);
+ else
+ pline->line = cpystr("");
+ pline->is_var = 0;
+ pline++;
+ }
+ continue;
+ }
+
+ /*----- look up matching 'v' and leave "value" after '=' ----*/
+ for(v = vars; *line && v->name; v++)
+ if((i = strlen(v->name)) < strlen(line) && !struncmp(v->name,line,i)){
+ int j;
+
+ for(j = i; line[j] == ' ' || line[j] == '\t'; j++)
+ ;
+
+ if(line[j] == '='){ /* bingo! */
+ for(value = &line[j+1];
+ *value == ' ' || *value == '\t';
+ value++)
+ ;
+
+ break;
+ }
+ /* else either unrecognized var or bogus line */
+ }
+
+ /*----- Didn't match any variable or bogus format -----*/
+ /*
+ * This could be a variable from some future
+ * version of pine, or it could be a PC-Pine variable
+ * we don't know about in unix. Either way, we want to preserve
+ * it in the file.
+ */
+ if(!v->name){
+ if(pline){
+ pline->is_var = 0;
+ pline->line = cpystr(line);
+ pline++;
+ }
+ continue;
+ }
+
+ /*
+ * Previous versions have caused duplicate pinerc data to be
+ * written to pinerc files. This clause erases the duplicate
+ * information when we read it, and it will be removed from the file
+ * if we call write_pinerc. We test to see if the same variable
+ * appears later in the file, if so, we skip over it here.
+ * We don't care about duplicates if this isn't a pinerc we might
+ * write out, so include pline in the conditional.
+ * Note that we will leave all of the duplicate comments and blank
+ * lines in the file unless it is a remote pinerc. Luckily, the
+ * bug that caused the duplicates only applied to remote pinercs,
+ * so we should have that case covered.
+ *
+ * If we find a duplicate, we point p to the start
+ * of the next line that should be considered, and then skip back
+ * to the top of the loop.
+ */
+ if(pline && var_is_in_rest_of_file(v->name, p)){
+ if(v->is_list)
+ p = skip_over_this_var(line, p);
+
+ continue;
+ }
+
+
+ /*----- Obsolete variable, read it anyway below, might use it -----*/
+ if(v->is_obsolete){
+ if(pline){
+ pline->obsolete_var = 1;
+ pline->line = cpystr(line);
+ pline->var = v;
+ }
+ }
+
+ /*----- Variable is in the list but unused for some reason -----*/
+ if(!v->is_used){
+ if(pline){
+ pline->is_var = 0;
+ pline->line = cpystr(line);
+ pline++;
+ }
+ continue;
+ }
+
+ /*--- Var is not user controlled, leave it alone for back compat ---*/
+ if(!v->is_user && pline){
+ pline->is_var = 0;
+ pline->line = cpystr(line);
+ pline++;
+ continue;
+ }
+
+ if(which_vars == ParseFixed)
+ v->is_fixed = 1;
+
+ /*---- variable is unset, or it's global but expands to nothing ----*/
+ if(!*value
+ || (which_vars == ParseGlobal
+ && !expand_variables(tmp_20k_buf, SIZEOF_20KBUF, value,
+ (v == &ps_global->vars[V_MAILCAP_PATH] ||
+ v == &ps_global->vars[V_MIMETYPE_PATH])))){
+ if(v->is_user && pline){
+ pline->is_var = 1;
+ pline->var = v;
+ pline++;
+ }
+ continue;
+ }
+
+ /*--value is non-empty, store it handling quotes and trailing space--*/
+ if(*value == '"' && !v->is_list && v->del_quotes){
+ was_quoted = 1;
+ value++;
+ for(p1 = value; *p1 && *p1 != '"'; p1++);
+ if(*p1 == '"')
+ *p1 = '\0';
+ else
+ removing_trailing_white_space(value);
+ }else
+ was_quoted = 0;
+
+ /*
+ * List Entry Parsing
+ *
+ * The idea is to parse a comma separated list of
+ * elements, preserving quotes, and understanding
+ * continuation lines (that is ',' == "\n ").
+ * Quotes must be balanced within elements. Space
+ * within elements is preserved, but leading and trailing
+ * space is trimmed. This is a generic function, and it's
+ * left to the the functions that use the lists to make sure
+ * they contain valid data...
+ */
+ if(v->is_list){
+
+ was_quoted = 0;
+ line_count = 0;
+ p1 = value;
+ while(1){ /* generous count of list elements */
+ if(*p1 == '"') /* ignore ',' if quoted */
+ was_quoted = (was_quoted) ? 0 : 1 ;
+
+ if((*p1 == ',' && !was_quoted) || *p1 == '\n' || *p1 == '\0')
+ line_count++; /* count this element */
+
+ if(*p1 == '\0' || *p1 == '\n'){ /* deal with EOL */
+ if(p1 < p || *p1 == '\n'){
+ *p1++ = ','; /* fix null or newline */
+
+ if(*p1 != '\t' && *p1 != ' '){
+ *(p1-1) = '\0'; /* tie off list */
+ p = p1; /* reset p */
+ break;
+ }
+ }else{
+ p = p1; /* end of pinerc */
+ break;
+ }
+ }else
+ p1++;
+ }
+
+ error = NULL;
+ lvalue = parse_list(value, line_count,
+ v->del_quotes ? PL_REMSURRQUOT : PL_NONE,
+ &error);
+ if(error){
+ dprint((1,
+ "read_pinerc: ERROR: %s in %s = \"%s\"\n",
+ error ? error : "?",
+ v->name ? v->name : "?",
+ value ? value : "?"));
+ }
+ /*
+ * Special case: turn "" strings into empty strings.
+ * This allows users to turn off default lists. For example,
+ * if smtp-server is set then a user could override smtp-server
+ * with smtp-server="".
+ */
+ for(i = 0; lvalue[i]; i++)
+ if(lvalue[i][0] == '"' &&
+ lvalue[i][1] == '"' &&
+ lvalue[i][2] == '\0')
+ lvalue[i][0] = '\0';
+ }
+
+ if(pline){
+ if(v->is_user && (which_vars == ParsePers || !v->is_onlymain)){
+ if(v->is_list){
+ char ***l;
+
+ l = (which_vars == ParsePers) ? &v->main_user_val.l
+ : &v->post_user_val.l;
+ free_list_array(l);
+ *l = lvalue;
+ }
+ else{
+ char **p;
+
+ p = (which_vars == ParsePers) ? &v->main_user_val.p
+ : &v->post_user_val.p;
+ if(p && *p != NULL)
+ fs_give((void **)p);
+
+ *p = cpystr(value);
+ }
+
+ if(pline){
+ pline->is_var = 1;
+ pline->var = v;
+ pline->is_quoted = was_quoted;
+ pline++;
+ }
+ }
+ }
+ else if(which_vars == ParseGlobal){
+ if(v->is_global){
+ if(v->is_list){
+ free_list_array(&v->global_val.l);
+ v->global_val.l = lvalue;
+ }
+ else{
+ if(v->global_val.p != NULL)
+ fs_give((void **) &(v->global_val.p));
+
+ v->global_val.p = cpystr(value);
+ }
+ }
+ }
+ else{ /* which_vars == ParseFixed */
+ if(v->is_user || v->is_global){
+ if(v->is_list){
+ free_list_array(&v->fixed_val.l);
+ v->fixed_val.l = lvalue;
+ }
+ else{
+ if(v->fixed_val.p != NULL)
+ fs_give((void **) &(v->fixed_val.p));
+
+ v->fixed_val.p = cpystr(value);
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if(v->is_list){
+ char **l;
+ l = (which_vars == ParsePers) ? v->main_user_val.l :
+ (which_vars == ParsePersPost) ? v->post_user_val.l :
+ (which_vars == ParseGlobal) ? v->global_val.l :
+ v->fixed_val.l;
+ if(l && *l && **l){
+ dprint((5, " %20.20s : %s\n",
+ v->name ? v->name : "?",
+ *l ? *l : "?"));
+ while(++l && *l && **l)
+ dprint((5, " %20.20s : %s\n", "",
+ *l ? *l : "?"));
+ }
+ }else{
+ char *p;
+ p = (which_vars == ParsePers) ? v->main_user_val.p :
+ (which_vars == ParsePersPost) ? v->post_user_val.p :
+ (which_vars == ParseGlobal) ? v->global_val.p :
+ v->fixed_val.p;
+ if(p && *p)
+ dprint((5, " %20.20s : %s\n",
+ v->name ? v->name : "?",
+ p ? p : "?"));
+ }
+#endif /* DEBUG */
+ }
+
+ if(pline){
+ pline->line = NULL;
+ pline->is_var = 0;
+ if(!prc->pinerc_written && prc->type == Loc){
+ prc->pinerc_written = name_file_mtime(filename);
+ dprint((5, "read_pinerc: time_pinerc_written = %ld\n",
+ (long) prc->pinerc_written));
+ }
+ }
+
+ if(free_file)
+ fs_give((void **) &free_file);
+}
+
+
+/*
+ * Args varname The variable name we're looking for
+ * begin Begin looking here
+ *
+ * Returns 1 if variable varname appears in the rest of the file
+ * 0 if not
+ */
+int
+var_is_in_rest_of_file(char *varname, char *begin)
+{
+ char *p;
+
+ if(!(varname && *varname && begin && *begin))
+ return 0;
+
+ p = begin;
+
+ while((p = srchstr(p, varname)) != NULL){
+ /* beginning of a line? */
+ if(p > begin && (*(p-1) != '\n' && *(p-1) != '\r')){
+ p++;
+ continue;
+ }
+
+ /* followed by [ SPACE ] < = > ? */
+ p += strlen(varname);
+ while(*p == ' ' || *p == '\t')
+ p++;
+
+ if(*p == '=')
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Args begin Variable to skip starts here.
+ * nextline This is where the next line starts. We need to know this
+ * because the input has been mangled a little. A \0 has
+ * replaced the \n at the end of the first line, but we can
+ * use nextline to help us out of that quandry.
+ *
+ * Return a pointer to the start of the first line after this variable
+ * and all of its continuation lines.
+ */
+char *
+skip_over_this_var(char *begin, char *nextline)
+{
+ char *p;
+
+ p = begin;
+
+ while(1){
+ if(*p == '\0' || *p == '\n'){ /* EOL */
+ if(p < nextline || *p == '\n'){ /* there may be another line */
+ p++;
+ if(*p != ' ' && *p != '\t') /* no continuation line */
+ return(p);
+ }
+ else /* end of file */
+ return(p);
+ }
+ else
+ p++;
+ }
+}
+
+
+static char quotes[3] = {'"', '"', '\0'};
+/*----------------------------------------------------------------------
+ Write out the .pinerc state information
+
+ Args: ps -- The pine structure to take state to be written from
+ which -- Which pinerc to write
+ flags -- If bit WRP_NOUSER is set, then assume that there is
+ not a user present to answer questions.
+
+ This writes to a temporary file first, and then renames that to
+ be the new .pinerc file to protect against disk error. This has the
+ problem of possibly messing up file protections, ownership and links.
+ ----*/
+int
+write_pinerc(struct pine *ps, EditWhich which, int flags)
+{
+ char *p, *dir, *tmp = NULL, *pinrc;
+ char *pval, **lval;
+ int bc = 1;
+ PINERC_LINE *pline;
+ struct variable *var;
+ time_t mtime;
+ char *filename;
+ REMDATA_S *rd = NULL;
+ PINERC_S *prc = NULL;
+ STORE_S *so = NULL;
+
+ dprint((2,"---- write_pinerc(%s) ----\n",
+ (which == Main) ? "Main" : "Post"));
+
+ switch(which){
+ case Main:
+ prc = ps ? ps->prc : NULL;
+ break;
+ case Post:
+ prc = ps ? ps->post_prc : NULL;
+ break;
+ default:
+ break;
+ }
+
+ if(!prc)
+ return(-1);
+
+ if(prc->quit_to_edit){
+ if(!(flags & WRP_NOUSER))
+ quit_to_edit_msg(prc);
+
+ return(-1);
+ }
+
+ if(prc->type != Loc && !prc->readonly){
+
+ bc = 0; /* don't do backcompat conversion */
+ rd = prc->rd;
+ if(!rd)
+ return(-1);
+
+ rd_check_remvalid(rd, -10L);
+
+ if(rd->flags & REM_OUTOFDATE){
+ if((flags & WRP_NOUSER) || unexpected_pinerc_change()){
+ prc->outstanding_pinerc_changes = 1;
+ if(!(flags & WRP_NOUSER))
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Pinerc \"%.200s\" NOT saved",
+ prc->name ? prc->name : "");
+ dprint((2, "write_pinerc: remote pinerc changed\n"));
+ return(-1);
+ }
+ else
+ rd->flags &= ~REM_OUTOFDATE;
+ }
+
+ rd_open_remote(rd);
+
+ if(rd->access == ReadWrite){
+ int ro;
+
+ if((ro=rd_remote_is_readonly(rd)) || rd->flags & REM_OUTOFDATE){
+ if(ro == 1){
+ if(!(flags & WRP_NOUSER))
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ _("Can't access remote config, changes NOT saved!"));
+ dprint((1,
+ "write_pinerc: Can't write to remote pinerc %s, aborting write\n",
+ rd->rn ? rd->rn : "?"));
+ }
+ else if(ro == 2){
+ if(!(rd->flags & NO_META_UPDATE)){
+ unsigned long save_chk_nmsgs;
+
+ switch(rd->type){
+ case RemImap:
+ save_chk_nmsgs = rd->t.i.chk_nmsgs;
+ rd->t.i.chk_nmsgs = 0;
+ rd_write_metadata(rd, 0);
+ rd->t.i.chk_nmsgs = save_chk_nmsgs;
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ "Write_pinerc: Type not supported");
+ break;
+ }
+ }
+
+ if(!(flags & WRP_NOUSER))
+ q_status_message1(SM_ORDER | SM_DING, 5, 15,
+ _("No write permission for remote config %.200s, changes NOT saved!"),
+ rd->rn);
+ }
+ else{
+ if(!(flags & WRP_NOUSER))
+ q_status_message(SM_ORDER | SM_DING, 5, 15,
+ _("Remote config changed, aborting our change to avoid damage..."));
+ dprint((1,
+ "write_pinerc: remote config %s changed since we started pine, aborting write\n",
+ prc->name ? prc->name : "?"));
+ }
+
+ rd->flags &= ~DO_REMTRIM;
+ return(-1);
+ }
+
+ filename = rd->lf;
+ }
+ else{
+ prc->readonly = 1;
+ if(prc == ps->prc)
+ ps->readonly_pinerc = 1;
+ }
+ }
+ else
+ filename = prc->name ? prc->name : "";
+
+ pinrc = prc->name ? prc->name : "";
+
+ if(prc->type == Loc){
+ mtime = name_file_mtime(filename);
+ if(prc->pinerc_written
+ && prc->pinerc_written != mtime
+ && ((flags & WRP_NOUSER) || unexpected_pinerc_change())){
+ prc->outstanding_pinerc_changes = 1;
+
+ if(!(flags & WRP_NOUSER))
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Pinerc \"%.200s\" NOT saved", pinrc);
+
+ dprint((2,"write_pinerc: mtime mismatch: \"%s\": %ld != %ld\n",
+ filename ? filename : "?",
+ (long) prc->pinerc_written, (long) mtime));
+ return(-1);
+ }
+ }
+
+ /* don't write if pinerc is read-only */
+ if(prc->readonly ||
+ (filename &&
+ can_access(filename, ACCESS_EXISTS) == 0 &&
+ can_access(filename, EDIT_ACCESS) != 0)){
+ prc->readonly = 1;
+ if(prc == ps->prc)
+ ps->readonly_pinerc = 1;
+
+ if(!(flags & WRP_NOUSER))
+ q_status_message1(SM_ORDER | SM_DING, 0, 5,
+ _("Can't modify configuration file \"%.200s\": ReadOnly"),
+ pinrc);
+ dprint((2, "write_pinerc: fail because can't access pinerc\n"));
+
+ if(rd)
+ rd->flags &= ~DO_REMTRIM;
+
+ return(-1);
+ }
+
+ if(rd && rd->flags & NO_FILE){
+ so = rd->sonofile;
+ so_truncate(rd->sonofile, 0L); /* reset storage object */
+ }
+ else{
+ dir = ".";
+ if((p = last_cmpnt(filename)) != NULL){
+ *--p = '\0';
+ dir = filename;
+ }
+
+#if defined(DOS) || defined(OS2)
+ if(!(isalpha((unsigned char)dir[0]) && dir[1] == ':' && dir[2] == '\0')
+ && (can_access(dir, EDIT_ACCESS) < 0 &&
+ our_mkdir(dir, 0700) < 0))
+ {
+ if(!(flags & WRP_NOUSER))
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: first argument is a filename, second
+ arg is the text of the error message */
+ _("Error creating \"%.200s\" : %.200s"), dir,
+ error_description(errno));
+ if(rd)
+ rd->flags &= ~DO_REMTRIM;
+
+ return(-1);
+ }
+
+ tmp = temp_nam(dir, "rc");
+
+ if(*dir && tmp && !in_dir(dir, tmp)){
+ our_unlink(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ if(p)
+ *p = '\\';
+
+ if(tmp == NULL)
+ goto io_err;
+
+#else /* !DOS */
+ tmp = temp_nam((*dir) ? dir : "/", "pinerc");
+
+ /*
+ * If temp_nam can't write in dir it puts the temp file in a
+ * temp directory, which won't help us when we go to rename.
+ */
+ if(*dir && tmp && !in_dir(dir, tmp)){
+ our_unlink(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ if(p)
+ *p = '/';
+
+ if(tmp == NULL)
+ goto io_err;
+
+#endif /* !DOS */
+
+ if((so = so_get(FileStar, tmp, WRITE_ACCESS)) == NULL)
+ goto io_err;
+ }
+
+ if(!(flags & WRP_PRESERV_WRITTEN))
+ for(var = ps->vars; var->name != NULL; var++)
+ var->been_written = 0;
+
+ if(prc->type == Loc && ps->first_time_user &&
+ !so_puts(so, native_nl(cf_text_comment)))
+ goto io_err;
+
+ /* Write out what was in the .pinerc */
+ for(pline = prc->pinerc_lines;
+ pline && (pline->is_var || pline->line); pline++){
+ if(pline->is_var){
+ var = pline->var;
+
+ if(var->is_list)
+ lval = LVAL(var, which);
+ else
+ pval = PVAL(var, which);
+
+ /* variable is not set */
+ if((var->is_list && (!lval || !lval[0])) ||
+ (!var->is_list && !pval)){
+ /* leave null variables out of remote pinerc */
+ if(prc->type == Loc &&
+ (!so_puts(so, var->name) || !so_puts(so, "=") ||
+ !so_puts(so, NEWLINE)))
+ goto io_err;
+ }
+ /* var is set to empty string */
+ else if((var->is_list && lval[0][0] == '\0') ||
+ (!var->is_list && pval[0] == '\0')){
+ if(!so_puts(so, var->name) || !so_puts(so, "=") ||
+ !so_puts(so, quotes) || !so_puts(so, NEWLINE))
+ goto io_err;
+ }
+ else{
+ if(var->is_list){
+ int i = 0;
+
+ for(i = 0; lval[i]; i++){
+ snprintf(tmp_20k_buf, 10000, "%s%s%s%s%s",
+ (i) ? "\t" : var->name,
+ (i) ? "" : "=",
+ lval[i][0] ? lval[i] : quotes,
+ lval[i+1] ? "," : "", NEWLINE);
+ tmp_20k_buf[10000-1] = '\0';
+ if(!so_puts(so, bc ? backcompat_convert_from_utf8(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, tmp_20k_buf) : tmp_20k_buf))
+ goto io_err;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, 10000, "%s=%s%s%s%s",
+ var->name,
+ (pline->is_quoted && pval[0] != '\"')
+ ? "\"" : "",
+ pval,
+ (pline->is_quoted && pval[0] != '\"')
+ ? "\"" : "", NEWLINE);
+ tmp_20k_buf[10000-1] = '\0';
+ if(!so_puts(so, bc ? backcompat_convert_from_utf8(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, tmp_20k_buf) : tmp_20k_buf))
+ goto io_err;
+ }
+ }
+
+ var->been_written = 1;
+
+ }else{
+ /*
+ * The description text should be changed into a message
+ * about the variable being obsolete when a variable is
+ * moved to obsolete status. We add that message before
+ * the variable unless it is already there. However, we
+ * leave the variable itself in case the user runs an old
+ * version of pine again. Note that we have read in the
+ * value of the variable in read_pinerc and translated it
+ * into a new variable if appropriate.
+ */
+ if(pline->obsolete_var && prc->type == Loc){
+ if(pline <= prc->pinerc_lines || (pline-1)->line == NULL ||
+ strlen((pline-1)->line) < 3 ||
+ strucmp((pline-1)->line+2, pline->var->descrip) != 0)
+ if(!so_puts(so, "# ") ||
+ !so_puts(so, native_nl(pline->var->descrip)) ||
+ !so_puts(so, NEWLINE))
+ goto io_err;
+ }
+
+ /* remove comments from remote pinercs */
+ if((prc->type == Loc ||
+ (pline->line[0] != '#' && pline->line[0] != '\0')) &&
+ (!so_puts(so, pline->line) || !so_puts(so, NEWLINE)))
+ goto io_err;
+ }
+ }
+
+ /* Now write out all the variables not in the .pinerc */
+ for(var = ps->vars; var->name != NULL; var++){
+ if(!var->is_user || var->been_written || !var->is_used ||
+ var->is_obsolete || (var->is_onlymain && which != Main))
+ continue;
+
+ if(var->is_list)
+ lval = LVAL(var, which);
+ else
+ pval = PVAL(var, which);
+
+ /*
+ * set description to NULL to eliminate preceding
+ * blank and comment line.
+ */
+ if(prc->type == Loc && var->descrip && *var->descrip &&
+ (!so_puts(so, NEWLINE) || !so_puts(so, "# ") ||
+ !so_puts(so, native_nl(var->descrip)) || !so_puts(so, NEWLINE)))
+ goto io_err;
+
+ /* variable is not set */
+ /** Don't know what the global_val thing is for. SH, Mar 00 **/
+ if((var->is_list && (!lval || (!lval[0] && !var->global_val.l))) ||
+ (!var->is_list && !pval)){
+ /* leave null variables out of remote pinerc */
+ if(prc->type == Loc &&
+ (!so_puts(so, var->name) || !so_puts(so, "=") ||
+ !so_puts(so, NEWLINE)))
+ goto io_err;
+ }
+ /* var is set to empty string */
+ else if((var->is_list && (!lval[0] || !lval[0][0]))
+ || (!var->is_list && pval[0] == '\0')){
+ if(!so_puts(so, var->name) || !so_puts(so, "=") ||
+ !so_puts(so, quotes) || !so_puts(so, NEWLINE))
+ goto io_err;
+ }
+ else if(var->is_list){
+ int i = 0;
+
+ for(i = 0; lval[i] ; i++){
+ snprintf(tmp_20k_buf, 10000, "%s%s%s%s%s",
+ (i) ? "\t" : var->name,
+ (i) ? "" : "=",
+ lval[i],
+ lval[i+1] ? "," : "", NEWLINE);
+ tmp_20k_buf[10000-1] = '\0';
+ if(!so_puts(so, bc ? backcompat_convert_from_utf8(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, tmp_20k_buf) : tmp_20k_buf))
+ goto io_err;
+ }
+ }
+ else{
+ char *pconverted;
+
+ pconverted = bc ? backcompat_convert_from_utf8(tmp_20k_buf, SIZEOF_20KBUF, pval) : pval;
+
+ if(!so_puts(so, var->name) || !so_puts(so, "=") ||
+ !so_puts(so, pconverted) || !so_puts(so, NEWLINE))
+ goto io_err;
+ }
+ }
+
+ if(!(rd && rd->flags & NO_FILE)){
+ if(so_give(&so))
+ goto io_err;
+
+ file_attrib_copy(tmp, filename);
+ if(rename_file(tmp, filename) < 0)
+ goto io_err;
+ }
+
+ if(prc->type != Loc){
+ int e, we_cancel;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+
+ if(!(flags & WRP_NOUSER))
+ we_cancel = busy_cue(_("Copying to remote config"), NULL, 1);
+
+ if((e = rd_update_remote(rd, datebuf)) != 0){
+ dprint((1,
+ "write_pinerc: error copying from %s to %s\n",
+ rd->lf ? rd->lf : "<memory>", rd->rn ? rd->rn : "?"));
+ if(!(flags & WRP_NOUSER)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %.200s: %.200s"),
+ rd->rn, error_description(errno));
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of config to remote folder failed, changes NOT saved remotely"));
+ }
+ }
+ else{
+ rd_update_metadata(rd, datebuf);
+ rd->read_status = 'W';
+ rd_trim_remdata(&rd);
+ rd_close_remote(rd);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+
+ prc->outstanding_pinerc_changes = 0;
+
+ if(prc->type == Loc){
+ prc->pinerc_written = name_file_mtime(filename);
+ dprint((2, "wrote pinerc: %s: time_pinerc_written = %ld\n",
+ pinrc ? pinrc : "?", (long) prc->pinerc_written));
+ }
+ else{
+ dprint((2, "wrote pinerc: %s\n", pinrc ? pinrc : "?"));
+ }
+
+ if(tmp){
+ our_unlink(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ return(0);
+
+ io_err:
+ if(!(flags & WRP_NOUSER))
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error saving configuration in \"%.200s\": %.200s"),
+ pinrc, error_description(errno));
+
+ dprint((1, "Error writing %s : %s\n", pinrc ? pinrc : "?",
+ error_description(errno)));
+ if(rd)
+ rd->flags &= ~DO_REMTRIM;
+ if(tmp){
+ our_unlink(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ return(-1);
+}
+
+
+/*
+ * The srcstr is UTF-8. In order to help the user with
+ * running this pine and an old pre-alpine pine on the same config
+ * file we attempt to convert the values of the config variables
+ * to the user's character set before writing.
+ */
+char *
+backcompat_convert_from_utf8(char *buf, size_t buflen, char *srcstr)
+{
+ char *converted = NULL;
+ char *p;
+ int its_ascii = 1;
+
+
+ for(p = srcstr; *p && its_ascii; p++)
+ if(*p & 0x80)
+ its_ascii = 0;
+
+ /* if it is ascii, go with that */
+ if(its_ascii)
+ converted = srcstr;
+ else{
+ char *trythischarset = NULL;
+
+ /*
+ * If it is possible to translate the UTF-8
+ * string into the user's character set then
+ * do that. For backwards compatibility with
+ * old pines.
+ */
+ if(ps_global->keyboard_charmap && ps_global->keyboard_charmap[0])
+ trythischarset = ps_global->keyboard_charmap;
+ else if(ps_global->display_charmap && ps_global->display_charmap[0])
+ trythischarset = ps_global->display_charmap;
+
+ if(trythischarset){
+ SIZEDTEXT src, dst;
+
+ src.data = (unsigned char *) srcstr;
+ src.size = strlen(srcstr);
+ memset(&dst, 0, sizeof(dst));
+ if(utf8_cstext(&src, trythischarset, &dst, 0)){
+ if(dst.data){
+ strncpy(buf, (char *) dst.data, buflen);
+ buf[buflen-1] = '\0';
+ fs_give((void **) &dst.data);
+ converted = buf;
+ }
+ }
+ }
+
+ if(!converted)
+ converted = srcstr;
+ }
+
+ return(converted);
+}
+
+
+/*
+ * Given a unix-style source string which may contain LFs,
+ * convert those to CRLFs if appropriate.
+ *
+ * Returns a pointer to the converted string. This will be a string
+ * stored in tmp_20k_buf.
+ *
+ * This is just used for the variable descriptions in the pinerc file. It
+ * could certainly be fancier. It simply converts all \n to NEWLINE.
+ */
+char *
+native_nl(char *src)
+{
+ char *q, *p;
+
+ tmp_20k_buf[0] = '\0';
+
+ if(src){
+ for(q = (char *)tmp_20k_buf; *src; src++){
+ if(*src == '\n'){
+ for(p = NEWLINE; *p; p++)
+ *q++ = *p;
+ }
+ else
+ *q++ = *src;
+ }
+
+ *q = '\0';
+ }
+
+ return((char *)tmp_20k_buf);
+}
+
+
+void
+quit_to_edit_msg(PINERC_S *prc)
+{
+ /* TRANSLATORS: The %s is either "Postload " or nothing. A Postload config file
+ is a type of config file. */
+ q_status_message1(SM_ORDER, 3, 4, _("Must quit Alpine to change %sconfig file."),
+ (prc == ps_global->post_prc) ? "Postload " : "");
+}
+
+
+/*------------------------------------------------------------
+ Return TRUE if the given string was a feature name present in the
+ pinerc as it was when pine was started...
+ ----*/
+int
+var_in_pinerc(char *s)
+{
+ PINERC_LINE *pline;
+
+ for(pline = ps_global->prc ? ps_global->prc->pinerc_lines : NULL;
+ pline && (pline->var || pline->line); pline++)
+ if(pline->var && pline->var->name && !strucmp(s, pline->var->name))
+ return(1);
+
+ for(pline = ps_global->post_prc ? ps_global->post_prc->pinerc_lines : NULL;
+ pline && (pline->var || pline->line); pline++)
+ if(pline->var && pline->var->name && !strucmp(s, pline->var->name))
+ return(1);
+
+ return(0);
+}
+
+
+/*------------------------------------------------------------
+ Free resources associated with pinerc_lines data
+ ----*/
+void
+free_pinerc_lines(PINERC_LINE **pinerc_lines)
+{
+ PINERC_LINE *pline;
+
+ if(pinerc_lines && *pinerc_lines){
+ for(pline = *pinerc_lines; pline->var || pline->line; pline++)
+ if(pline->line)
+ fs_give((void **)&pline->line);
+
+ fs_give((void **)pinerc_lines);
+ }
+}
+
+
+/*------------------------------------------------------------
+ Dump out a global pine.conf on the standard output with fresh
+ comments. Preserves variables currently set in SYSTEM_PINERC, if any.
+ ----*/
+void
+dump_global_conf(void)
+{
+ FILE *f;
+ struct variable *var;
+ PINERC_S *prc;
+
+ prc = new_pinerc_s(SYSTEM_PINERC);
+ read_pinerc(prc, variables, ParseGlobal);
+ if(prc)
+ free_pinerc_s(&prc);
+
+ f = stdout;
+ if(f == NULL)
+ goto io_err;
+
+ fprintf(f, "# %s -- system wide pine configuration\n#\n",
+ SYSTEM_PINERC);
+ fprintf(f, "# Values here affect all pine users unless they've overridden the values\n");
+ fprintf(f, "# in their .pinerc files. A copy of this file with current comments may\n");
+ fprintf(f, "# be obtained by running \"pine -conf\". It will be printed to standard output.\n#\n");
+ fprintf(f,"# For a variable to be unset its value must be null/blank. This is not the\n");
+ fprintf(f,"# same as the value of \"empty string\", which can be used to effectively\n");
+ fprintf(f,"# \"unset\" a variable that has a default or previously assigned value.\n");
+ fprintf(f,"# To set a variable to the empty string its value should be \"\".\n");
+ fprintf(f,"# Switch variables are set to either \"yes\" or \"no\", and default to \"no\".\n");
+ fprintf(f,"# Except for feature-list items, which are additive, values set in the\n");
+ fprintf(f,"# .pinerc file replace those in pine.conf, and those in pine.conf.fixed\n");
+ fprintf(f,"# over-ride all others. Features can be over-ridden in .pinerc or\n");
+ fprintf(f,"# pine.conf.fixed by pre-pending the feature name with \"no-\".\n#\n");
+ fprintf(f,"# (These comments are automatically inserted.)\n");
+
+ for(var = variables; var->name != NULL; var++){
+ if(!var->is_global || !var->is_used || var->is_obsolete)
+ continue;
+
+ if(var->descrip && *var->descrip){
+ if(fprintf(f, "\n# %s\n", var->descrip) == EOF)
+ goto io_err;
+ }
+
+ if(var->is_list){
+ if(var->global_val.l == NULL){
+ if(fprintf(f, "%s=\n", var->name) == EOF)
+ goto io_err;
+ }else{
+ int i;
+
+ for(i=0; var->global_val.l[i]; i++)
+ if(fprintf(f, "%s%s%s%s\n", (i) ? "\t" : var->name,
+ (i) ? "" : "=", var->global_val.l[i],
+ var->global_val.l[i+1] ? ",":"") == EOF)
+ goto io_err;
+ }
+ }else{
+ if(var->global_val.p == NULL){
+ if(fprintf(f, "%s=\n", var->name) == EOF)
+ goto io_err;
+ }else if(strlen(var->global_val.p) == 0){
+ if(fprintf(f, "%s=\"\"\n", var->name) == EOF)
+ goto io_err;
+ }else{
+ if(fprintf(f,"%s=%s\n",var->name,var->global_val.p) == EOF)
+ goto io_err;
+ }
+ }
+ }
+ exit(0);
+
+
+ io_err:
+ fprintf(stderr, "Error writing config to stdout: %s\n",
+ error_description(errno));
+ exit(-1);
+}
+
+
+/*------------------------------------------------------------
+ Dump out a pinerc to filename with fresh
+ comments. Preserves variables currently set in pinerc, if any.
+ ----*/
+void
+dump_new_pinerc(char *filename)
+{
+ FILE *f;
+ struct variable *var;
+ char buf[MAXPATH], *p;
+ PINERC_S *prc;
+
+
+ p = ps_global->pinerc;
+
+#if defined(DOS) || defined(OS2)
+ if(!ps_global->pinerc){
+ char *p;
+
+ if(p = getenv("PINERC")){
+ ps_global->pinerc = cpystr(p);
+ }else{
+ char buf2[MAXPATH];
+ build_path(buf2, ps_global->home_dir, DF_PINEDIR, sizeof(buf2));
+ build_path(buf, buf2, SYSTEM_PINERC, sizeof(buf));
+ }
+
+ p = buf;
+ }
+#else /* !DOS */
+ if(!ps_global->pinerc){
+ build_path(buf, ps_global->home_dir, ".pinerc", sizeof(buf));
+ p = buf;
+ }
+#endif /* !DOS */
+
+ prc = new_pinerc_s(p);
+ read_pinerc(prc, variables, ParsePers);
+ if(prc)
+ free_pinerc_s(&prc);
+
+ f = NULL;;
+ if(filename[0] == '\0'){
+ fprintf(stderr, "Missing argument to \"-pinerc\".\n");
+ }else if(!strcmp(filename, "-")){
+ f = stdout;
+ }else{
+ f = our_fopen(filename, "wb");
+ }
+
+ if(f == NULL)
+ goto io_err;
+
+ if(fprintf(f, "%s", cf_text_comment) == EOF)
+ goto io_err;
+
+ for(var = variables; var->name != NULL; var++){
+ dprint((7,"write_pinerc: %s = %s\n",
+ var->name ? var->name : "?",
+ var->main_user_val.p ? var->main_user_val.p : "<not set>"));
+ if(!var->is_user || !var->is_used || var->is_obsolete)
+ continue;
+
+ /*
+ * set description to NULL to eliminate preceding
+ * blank and comment line.
+ */
+ if(var->descrip && *var->descrip){
+ if(fprintf(f, "\n# %s\n", var->descrip) == EOF)
+ goto io_err;
+ }
+
+ if(var->is_list){
+ if(var->main_user_val.l == NULL){
+ if(fprintf(f, "%s=\n", var->name) == EOF)
+ goto io_err;
+ }else{
+ int i;
+
+ for(i=0; var->main_user_val.l[i]; i++)
+ if(fprintf(f, "%s%s%s%s\n", (i) ? "\t" : var->name,
+ (i) ? "" : "=", var->main_user_val.l[i],
+ var->main_user_val.l[i+1] ? ",":"") == EOF)
+ goto io_err;
+ }
+ }else{
+ if(var->main_user_val.p == NULL){
+ if(fprintf(f, "%s=\n", var->name) == EOF)
+ goto io_err;
+ }else if(strlen(var->main_user_val.p) == 0){
+ if(fprintf(f, "%s=\"\"\n", var->name) == EOF)
+ goto io_err;
+ }else{
+ if(fprintf(f,"%s=%s\n",var->name,var->main_user_val.p) == EOF)
+ goto io_err;
+ }
+ }
+ }
+ exit(0);
+
+
+io_err:
+ snprintf(buf, sizeof(buf), "Error writing config to %s: %s\n",
+ filename, error_description(errno));
+ exceptional_exit(buf, -1);
+}
+
+
+/*----------------------------------------------------------------------
+ Set a user variable and save the .pinerc
+
+ Args: var -- The index of the variable to set from conftype.h (V_....)
+ value -- The string to set the value to
+
+ Result: -1 is returned on failure and 0 is returned on success
+
+ The vars data structure is updated and the pinerc saved.
+ ----*/
+int
+set_variable(int var, char *value, int expand, int commit, EditWhich which)
+{
+ struct variable *v;
+ char **apval;
+ PINERC_S *prc;
+
+ v = &ps_global->vars[var];
+
+ if(!v->is_user)
+ panic1("Trying to set non-user variable %s", v->name);
+
+ /* Override value of which, at most one of these should be set */
+ if(v->is_onlymain)
+ which = Main;
+ else if(v->is_outermost)
+ which = ps_global->ew_for_except_vars;
+
+ apval = APVAL(v, which);
+
+ if(!apval)
+ return(-1);
+
+ if(*apval)
+ fs_give((void **)apval);
+
+ *apval = value ? cpystr(value) : NULL;
+ set_current_val(v, expand, FALSE);
+
+ switch(which){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ if(prc)
+ prc->outstanding_pinerc_changes = 1;
+
+ return(commit ? write_pinerc(ps_global, which, WRP_NONE) : 0);
+}
+
+
+/*----------------------------------------------------------------------
+ Set a user variable list and save the .pinerc
+
+ Args: var -- The index of the variable to set from conftype.h (V_....)
+ lvalue -- The list to set the value to
+
+ Result: -1 is returned on failure and 0 is returned on success
+
+ The vars data structure is updated and if write_it, the pinerc is saved.
+ ----*/
+int
+set_variable_list(int var, char **lvalue, int write_it, EditWhich which)
+{
+ char ***alval;
+ int i;
+ struct variable *v = &ps_global->vars[var];
+ PINERC_S *prc;
+
+ if(!v->is_user || !v->is_list)
+ panic1("BOTCH: Trying to set non-user or non-list variable %s", v->name);
+
+ /* Override value of which, at most one of these should be set */
+ if(v->is_onlymain)
+ which = Main;
+ else if(v->is_outermost)
+ which = ps_global->ew_for_except_vars;
+
+ alval = ALVAL(v, which);
+ if(!alval)
+ return(-1);
+
+ if(*alval)
+ free_list_array(alval);
+
+ if(lvalue){
+ for(i = 0; lvalue[i] ; i++) /* count elements */
+ ;
+
+ *alval = (char **) fs_get((i+1) * sizeof(char *));
+
+ for(i = 0; lvalue[i] ; i++)
+ (*alval)[i] = cpystr(lvalue[i]);
+
+ (*alval)[i] = NULL;
+ }
+
+ set_current_val(v, TRUE, FALSE);
+
+ switch(which){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ if(prc)
+ prc->outstanding_pinerc_changes = 1;
+
+ return(write_it ? write_pinerc(ps_global, which, WRP_NONE) : 0);
+}
+
+
+void
+set_current_color_vals(struct pine *ps)
+{
+ struct variable *vars = ps->vars;
+ int later_color_is_set = 0;
+
+ set_current_val(&vars[V_NORM_FORE_COLOR], TRUE, TRUE);
+ set_current_val(&vars[V_NORM_BACK_COLOR], TRUE, TRUE);
+ pico_nfcolor(VAR_NORM_FORE_COLOR);
+ pico_nbcolor(VAR_NORM_BACK_COLOR);
+
+ set_current_val(&vars[V_REV_FORE_COLOR], TRUE, TRUE);
+ set_current_val(&vars[V_REV_BACK_COLOR], TRUE, TRUE);
+ pico_rfcolor(VAR_REV_FORE_COLOR);
+ pico_rbcolor(VAR_REV_BACK_COLOR);
+
+ set_color_val(&vars[V_TITLE_FORE_COLOR], 1);
+ set_color_val(&vars[V_TITLECLOSED_FORE_COLOR], 0);
+ set_color_val(&vars[V_STATUS_FORE_COLOR], 1);
+ set_color_val(&vars[V_KEYLABEL_FORE_COLOR], 1);
+ set_color_val(&vars[V_KEYNAME_FORE_COLOR], 1);
+ set_color_val(&vars[V_SLCTBL_FORE_COLOR], 1);
+ set_color_val(&vars[V_METAMSG_FORE_COLOR], 1);
+ set_color_val(&vars[V_PROMPT_FORE_COLOR], 1);
+ set_color_val(&vars[V_HEADER_GENERAL_FORE_COLOR], 1);
+ set_color_val(&vars[V_IND_PLUS_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_IMP_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_DEL_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_HIPRI_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_LOPRI_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_ANS_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_NEW_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_REC_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_FWD_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_UNS_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_ARR_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_SUBJ_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_FROM_FORE_COLOR], 0);
+ set_color_val(&vars[V_IND_OP_FORE_COLOR], 0);
+ set_color_val(&vars[V_INCUNSEEN_FORE_COLOR], 0);
+ set_color_val(&vars[V_SIGNATURE_FORE_COLOR], 0);
+
+ set_current_val(&ps->vars[V_VIEW_HDR_COLORS], TRUE, TRUE);
+ set_current_val(&ps->vars[V_KW_COLORS], TRUE, TRUE);
+ set_custom_spec_colors(ps);
+
+ /*
+ * Set up the quoting colors. If a later color is set but not an earlier
+ * color we set the earlier color to Normal to make it easier when
+ * we go to use the colors. However, if the only quote colors set are
+ * Normal that is the same as no settings, so delete them.
+ */
+ set_color_val(&vars[V_QUOTE1_FORE_COLOR], 0);
+ set_color_val(&vars[V_QUOTE2_FORE_COLOR], 0);
+ set_color_val(&vars[V_QUOTE3_FORE_COLOR], 0);
+
+ if((!(VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR) ||
+ (!strucmp(VAR_QUOTE3_FORE_COLOR, VAR_NORM_FORE_COLOR) &&
+ !strucmp(VAR_QUOTE3_BACK_COLOR, VAR_NORM_BACK_COLOR))) &&
+ (!(VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR) ||
+ (!strucmp(VAR_QUOTE2_FORE_COLOR, VAR_NORM_FORE_COLOR) &&
+ !strucmp(VAR_QUOTE2_BACK_COLOR, VAR_NORM_BACK_COLOR))) &&
+ (!(VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR) ||
+ (!strucmp(VAR_QUOTE1_FORE_COLOR, VAR_NORM_FORE_COLOR) &&
+ !strucmp(VAR_QUOTE1_BACK_COLOR, VAR_NORM_BACK_COLOR)))){
+ /*
+ * They are all either Normal or not set. Delete them all.
+ */
+ if(VAR_QUOTE3_FORE_COLOR)
+ fs_give((void **)&VAR_QUOTE3_FORE_COLOR);
+ if(VAR_QUOTE3_BACK_COLOR)
+ fs_give((void **)&VAR_QUOTE3_BACK_COLOR);
+ if(VAR_QUOTE2_FORE_COLOR)
+ fs_give((void **)&VAR_QUOTE2_FORE_COLOR);
+ if(VAR_QUOTE2_BACK_COLOR)
+ fs_give((void **)&VAR_QUOTE2_BACK_COLOR);
+ if(VAR_QUOTE1_FORE_COLOR)
+ fs_give((void **)&VAR_QUOTE1_FORE_COLOR);
+ if(VAR_QUOTE1_BACK_COLOR)
+ fs_give((void **)&VAR_QUOTE1_BACK_COLOR);
+ }
+ else{ /* something is non-Normal */
+ if(VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR)
+ later_color_is_set++;
+
+ /* if 3 is set but not 2, set 2 to Normal */
+ if(VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR)
+ later_color_is_set++;
+ else if(later_color_is_set)
+ set_color_val(&vars[V_QUOTE2_FORE_COLOR], 1);
+
+ /* if 3 or 2 is set but not 1, set 1 to Normal */
+ if(VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR)
+ later_color_is_set++;
+ else if(later_color_is_set)
+ set_color_val(&vars[V_QUOTE1_FORE_COLOR], 1);
+ }
+
+#ifdef _WINDOWS
+ if(ps->pre441){
+ int conv_main = 0, conv_post = 0;
+
+ ps->pre441 = 0;
+ if(ps->prc && !unix_color_style_in_pinerc(ps->prc)){
+ conv_main = convert_pc_gray_names(ps, ps->prc, Main);
+ if(conv_main)
+ ps->prc->outstanding_pinerc_changes = 1;
+ }
+
+
+ if(ps->post_prc && !unix_color_style_in_pinerc(ps->post_prc)){
+ conv_post = convert_pc_gray_names(ps, ps->post_prc, Post);
+ if(conv_post)
+ ps->post_prc->outstanding_pinerc_changes = 1;
+ }
+
+ if(conv_main || conv_post){
+ if(conv_main)
+ write_pinerc(ps, Main, WRP_NONE);
+
+ if(conv_post)
+ write_pinerc(ps, Post, WRP_NONE);
+
+ set_current_color_vals(ps);
+ }
+ }
+#endif /* _WINDOWS */
+
+ pico_set_normal_color();
+}
+
+
+/*
+ * Set current_val for the foreground and background color vars, which
+ * are assumed to be in order. If a set_current_val on them doesn't
+ * produce current_vals, then use the colors from defvar to set those
+ * current_vals.
+ */
+void
+set_color_val(struct variable *v, int use_default)
+{
+ set_current_val(v, TRUE, TRUE);
+ set_current_val(v+1, TRUE, TRUE);
+
+ if(!(v->current_val.p && v->current_val.p[0] &&
+ (v+1)->current_val.p && (v+1)->current_val.p[0])){
+ struct variable *defvar;
+
+ if(v->current_val.p)
+ fs_give((void **)&v->current_val.p);
+ if((v+1)->current_val.p)
+ fs_give((void **)&(v+1)->current_val.p);
+
+ if(!use_default)
+ return;
+
+ if(var_defaults_to_rev(v))
+ defvar = &ps_global->vars[V_REV_FORE_COLOR];
+ else
+ defvar = &ps_global->vars[V_NORM_FORE_COLOR];
+
+ /* use default vars values instead */
+ if(defvar && defvar->current_val.p && defvar->current_val.p[0] &&
+ (defvar+1)->current_val.p && (defvar+1)->current_val.p[0]){
+ v->current_val.p = cpystr(defvar->current_val.p);
+ (v+1)->current_val.p = cpystr((defvar+1)->current_val.p);
+ }
+ }
+}
+
+
+int
+var_defaults_to_rev(struct variable *v)
+{
+ return(v == &ps_global->vars[V_REV_FORE_COLOR] ||
+ v == &ps_global->vars[V_TITLE_FORE_COLOR] ||
+ v == &ps_global->vars[V_STATUS_FORE_COLOR] ||
+ v == &ps_global->vars[V_KEYNAME_FORE_COLOR] ||
+ v == &ps_global->vars[V_PROMPT_FORE_COLOR]);
+}
+
+
+
+/*
+ * Each item in the list looks like:
+ *
+ * /HDR=<header>/FG=<foreground color>/BG=<background color>
+ *
+ * We separate the three pieces into an array of structures to make
+ * it easier to deal with later.
+ */
+void
+set_custom_spec_colors(struct pine *ps)
+{
+ if(ps->hdr_colors)
+ free_spec_colors(&ps->hdr_colors);
+
+ ps->hdr_colors = spec_colors_from_varlist(ps->VAR_VIEW_HDR_COLORS, 1);
+
+ /* fit keyword colors into the same structures for code re-use */
+ if(ps->kw_colors)
+ free_spec_colors(&ps->kw_colors);
+
+ ps->kw_colors = spec_colors_from_varlist(ps->VAR_KW_COLORS, 1);
+}
+
+
+/*
+ * Input is one item from config variable.
+ *
+ * Return value must be freed by caller. The return is a single SPEC_COLOR_S,
+ * not a list.
+ */
+SPEC_COLOR_S *
+spec_color_from_var(char *t, int already_expanded)
+{
+ char *p, *spec, *fg, *bg;
+ PATTERN_S *val;
+ SPEC_COLOR_S *new_hcolor = NULL;
+
+ if(t && t[0] && !strcmp(t, INHERIT)){
+ new_hcolor = (SPEC_COLOR_S *)fs_get(sizeof(*new_hcolor));
+ memset((void *)new_hcolor, 0, sizeof(*new_hcolor));
+ new_hcolor->inherit = 1;
+ }
+ else if(t && t[0]){
+ char tbuf[10000];
+
+ if(!already_expanded){
+ tbuf[0] = '\0';
+ if(expand_variables(tbuf, sizeof(tbuf), t, 0))
+ t = tbuf;
+ }
+
+ spec = fg = bg = NULL;
+ val = NULL;
+ if((p = srchstr(t, "/HDR=")) != NULL)
+ spec = remove_backslash_escapes(p+5);
+ if((p = srchstr(t, "/FG=")) != NULL)
+ fg = remove_backslash_escapes(p+4);
+ if((p = srchstr(t, "/BG=")) != NULL)
+ bg = remove_backslash_escapes(p+4);
+ val = parse_pattern("VAL", t, 0);
+
+ if(spec && *spec){
+ /* remove colons */
+ if((p = strindex(spec, ':')) != NULL)
+ *p = '\0';
+
+ new_hcolor = (SPEC_COLOR_S *)fs_get(sizeof(*new_hcolor));
+ memset((void *)new_hcolor, 0, sizeof(*new_hcolor));
+ new_hcolor->spec = spec;
+ new_hcolor->fg = fg;
+ new_hcolor->bg = bg;
+ new_hcolor->val = val;
+ }
+ else{
+ if(spec)
+ fs_give((void **)&spec);
+ if(fg)
+ fs_give((void **)&fg);
+ if(bg)
+ fs_give((void **)&bg);
+ if(val)
+ free_pattern(&val);
+ }
+ }
+
+ return(new_hcolor);
+}
+
+
+/*
+ * Input is a list from config file.
+ *
+ * Return value may be a list of SPEC_COLOR_S and must be freed by caller.
+ */
+SPEC_COLOR_S *
+spec_colors_from_varlist(char **varlist, int already_expanded)
+{
+ char **s, *t;
+ SPEC_COLOR_S *new_hc = NULL;
+ SPEC_COLOR_S *new_hcolor, **nexthc;
+
+ nexthc = &new_hc;
+ if(varlist){
+ for(s = varlist; (t = *s) != NULL; s++){
+ if(t[0]){
+ new_hcolor = spec_color_from_var(t, already_expanded);
+ if(new_hcolor){
+ *nexthc = new_hcolor;
+ nexthc = &new_hcolor->next;
+ }
+ }
+ }
+ }
+
+ return(new_hc);
+}
+
+
+/*
+ * Returns allocated charstar suitable for config var for a single
+ * SPEC_COLOR_S.
+ */
+char *
+var_from_spec_color(SPEC_COLOR_S *hc)
+{
+ char *ret_val = NULL;
+ char *p, *spec = NULL, *fg = NULL, *bg = NULL, *val = NULL;
+ size_t len;
+
+ if(hc && hc->inherit)
+ ret_val = cpystr(INHERIT);
+ else if(hc){
+ if(hc->spec)
+ spec = add_viewerhdr_escapes(hc->spec);
+ if(hc->fg)
+ fg = add_viewerhdr_escapes(hc->fg);
+ if(hc->bg)
+ bg = add_viewerhdr_escapes(hc->bg);
+ if(hc->val){
+ p = pattern_to_string(hc->val);
+ if(p){
+ val = add_viewerhdr_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ len = strlen("/HDR=/FG=/BG=") + strlen(spec ? spec : "") +
+ strlen(fg ? fg : "") + strlen(bg ? bg : "") +
+ strlen(val ? "/VAL=" : "") + strlen(val ? val : "");
+ ret_val = (char *) fs_get(len + 1);
+ snprintf(ret_val, len+1, "/HDR=%s/FG=%s/BG=%s%s%s",
+ spec ? spec : "", fg ? fg : "", bg ? bg : "",
+ val ? "/VAL=" : "", val ? val : "");
+
+ if(spec)
+ fs_give((void **)&spec);
+ if(fg)
+ fs_give((void **)&fg);
+ if(bg)
+ fs_give((void **)&bg);
+ if(val)
+ fs_give((void **)&val);
+ }
+
+ return(ret_val);
+}
+
+
+/*
+ * Returns allocated charstar suitable for config var for a single
+ * SPEC_COLOR_S.
+ */
+char **
+varlist_from_spec_colors(SPEC_COLOR_S *hcolors)
+{
+ SPEC_COLOR_S *hc;
+ char **ret_val = NULL;
+ int i;
+
+ /* count how many */
+ for(hc = hcolors, i = 0; hc; hc = hc->next, i++)
+ ;
+
+ ret_val = (char **)fs_get((i+1) * sizeof(*ret_val));
+ memset((void *)ret_val, 0, (i+1) * sizeof(*ret_val));
+ for(hc = hcolors, i = 0; hc; hc = hc->next, i++)
+ ret_val[i] = var_from_spec_color(hc);
+
+ return(ret_val);
+}
+
+
+void
+update_posting_charset(struct pine *ps, int revert)
+{
+#ifndef _WINDOWS
+ if(F_ON(F_USE_SYSTEM_TRANS, ps)){
+ if(!revert)
+ q_status_message(SM_ORDER, 5, 5, _("This change has no effect because feature Use-System-Translation is on"));
+ }
+ else{
+#endif /* ! _WINDOWS */
+ if(ps->posting_charmap)
+ fs_give((void **) &ps->posting_charmap);
+
+ if(ps->VAR_POST_CHAR_SET){
+ ps->posting_charmap = cpystr(ps->VAR_POST_CHAR_SET);
+ if(!posting_charset_is_supported(ps->posting_charmap)){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _("Posting-Character set \"%s\" is unsupported, using UTF-8"),
+ ps->posting_charmap);
+ q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ fs_give((void **) &ps->posting_charmap);
+ ps->posting_charmap = cpystr("UTF-8");
+ }
+ }
+ else
+ ps->posting_charmap = cpystr("UTF-8");
+#ifndef _WINDOWS
+ }
+#endif /* ! _WINDOWS */
+}
+
+
+#define FIXED_COMMENT _("(fixed)")
+#define DEFAULT_COMMENT _("(default)")
+#define OVERRIDE_COMMENT _("(overridden)")
+
+int
+feature_gets_an_x(struct pine *ps, struct variable *var, FEATURE_S *feature,
+ char **comment, EditWhich ew)
+{
+ char **lval, **lvalexc, **lvalnorm;
+ char *def = DEFAULT_COMMENT;
+ int j, done = 0;
+ int feature_fixed_on = 0, feature_fixed_off = 0;
+
+ if(comment)
+ *comment = NULL;
+
+ lval = LVAL(var, ew);
+ lvalexc = LVAL(var, ps->ew_for_except_vars);
+ lvalnorm = LVAL(var, Main);
+
+ /* feature value is administratively fixed */
+ if((j = feature_in_list(var->fixed_val.l, feature->name)) != 0){
+ if(j == 1)
+ feature_fixed_on++;
+ else if(j == -1)
+ feature_fixed_off++;
+
+ done++;
+ if(comment)
+ *comment = FIXED_COMMENT;
+ }
+
+ /*
+ * We have an exceptions config setting which overrides anything
+ * we do here, in the normal config.
+ */
+ if(!done &&
+ ps->ew_for_except_vars != Main && ew == Main &&
+ feature_in_list(lvalexc, feature->name)){
+ done++;
+ if(comment)
+ *comment = OVERRIDE_COMMENT;
+ }
+
+ /*
+ * Feature is set On in default but not set here.
+ */
+ if(!done &&
+ !feature_in_list(lval, feature->name) &&
+ ((feature_in_list(var->global_val.l, feature->name) == 1) ||
+ ((ps->ew_for_except_vars != Main &&
+ ew == ps->ew_for_except_vars &&
+ feature_in_list(lvalnorm, feature->name) == 1)))){
+ done = 17;
+ if(comment)
+ *comment = def;
+ }
+
+ if(!done &&
+ feature->defval &&
+ !feature_in_list(lval, feature->name) &&
+ !feature_in_list(var->global_val.l, feature->name) &&
+ (ps->ew_for_except_vars == Main ||
+ ew != ps->ew_for_except_vars ||
+ !feature_in_list(lvalnorm, feature->name))){
+ done = 17;
+ if(comment)
+ *comment = def;
+ }
+
+ return(feature_fixed_on ||
+ (!feature_fixed_off &&
+ (done == 17 ||
+ test_feature(lval, feature->name,
+ test_old_growth_bits(ps, feature->id)))));
+}
+
+
+int
+longest_feature_comment(struct pine *ps, EditWhich ew)
+{
+ int lc = 0;
+
+ lc = MAX(lc, utf8_width(FIXED_COMMENT));
+ lc = MAX(lc, utf8_width(DEFAULT_COMMENT));
+ if(ps->ew_for_except_vars != Main && ew == Main)
+ lc = MAX(lc, utf8_width(OVERRIDE_COMMENT));
+
+ return(lc);
+}
+
+
+void
+toggle_feature(struct pine *ps, struct variable *var, FEATURE_S *f,
+ int just_flip_value, EditWhich ew)
+{
+ char **vp, *p, **lval, ***alval;
+ int og, on_before, was_set;
+ char *err;
+ long l;
+
+ og = test_old_growth_bits(ps, f->id);
+
+ /*
+ * if this feature is in the fixed set, or old-growth is in the fixed
+ * set and this feature is in the old-growth set, don't alter it...
+ */
+ for(vp = var->fixed_val.l; vp && *vp; vp++){
+ p = (struncmp(*vp, "no-", 3)) ? *vp : *vp + 3;
+ if(!strucmp(p, f->name) || (og && !strucmp(p, "old-growth"))){
+ q_status_message(SM_ORDER, 3, 3,
+ /* TRANSLATORS: In the configuration screen, telling the user we
+ can't change this option because the system administrator
+ prohibits it. */
+ _("Can't change value fixed by sys-admin."));
+ return;
+ }
+ }
+
+ on_before = F_ON(f->id, ps);
+
+ lval = LVAL(var, ew);
+ alval = ALVAL(var, ew);
+ if(just_flip_value)
+ was_set = test_feature(lval, f->name, og);
+ else
+ was_set = feature_gets_an_x(ps, var, f, NULL, ew);
+
+ if(alval)
+ set_feature(alval, f->name, !was_set);
+
+ set_feature_list_current_val(var);
+ process_feature_list(ps, var->current_val.l, 0, 0, 0);
+
+ /*
+ * Handle any features that need special attention here...
+ */
+ if(on_before != F_ON(f->id, ps))
+ switch(f->id){
+ case F_QUOTE_ALL_FROMS :
+ mail_parameters(NULL,SET_FROMWIDGET,F_ON(f->id,ps) ? VOIDT : NIL);
+ break;
+
+ case F_FAKE_NEW_IN_NEWS :
+ if(IS_NEWS(ps->mail_stream))
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "news-approximates-new-status won't affect current newsgroup until next open");
+
+ break;
+
+ case F_COLOR_LINE_IMPORTANT :
+ case F_DATES_TO_LOCAL :
+ clear_index_cache(ps->mail_stream, 0);
+ break;
+
+ case F_DISABLE_INDEX_LOCALE_DATES :
+ reset_index_format();
+ clear_index_cache(ps->mail_stream, 0);
+ break;
+
+ case F_MARK_FOR_CC :
+ clear_index_cache(ps->mail_stream, 0);
+ if(THREADING() && sp_viewing_a_thread(ps->mail_stream))
+ unview_thread(ps, ps->mail_stream, ps->msgmap);
+
+ break;
+
+ case F_HIDE_NNTP_PATH :
+ mail_parameters(NULL, SET_NNTPHIDEPATH,
+ F_ON(f->id, ps) ? VOIDT : NIL);
+ break;
+
+ case F_MAILDROPS_PRESERVE_STATE :
+ mail_parameters(NULL, SET_SNARFPRESERVE,
+ F_ON(f->id, ps) ? VOIDT : NIL);
+ break;
+
+ case F_DISABLE_SHARED_NAMESPACES :
+ mail_parameters(NULL, SET_DISABLEAUTOSHAREDNS,
+ F_ON(f->id, ps) ? VOIDT : NIL);
+ break;
+
+ case F_QUELL_LOCK_FAILURE_MSGS :
+ mail_parameters(NULL, SET_LOCKEACCESERROR,
+ F_ON(f->id, ps) ? VOIDT : NIL);
+ break;
+
+ case F_MULNEWSRC_HOSTNAMES_AS_TYPED :
+ l = F_ON(f->id, ps) ? 0L : 1L;
+ mail_parameters(NULL, SET_NEWSRCCANONHOST, (void *) l);
+ break;
+
+ case F_QUELL_INTERNAL_MSG :
+ mail_parameters(NULL, SET_USERHASNOLIFE,
+ F_ON(f->id, ps) ? VOIDT : NIL);
+ break;
+
+ case F_DISABLE_SETLOCALE_COLLATE :
+ set_collation(F_OFF(F_DISABLE_SETLOCALE_COLLATE, ps), 1);
+ break;
+
+#ifndef _WINDOWS
+ case F_USE_SYSTEM_TRANS :
+ err = NULL;
+ reset_character_set_stuff(&err);
+ if(err){
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ fs_give((void **) &err);
+ }
+
+ break;
+#endif /* ! _WINDOWS */
+
+ case F_ENABLE_INCOMING_CHECKING :
+ if(!on_before && F_OFF(F_ENABLE_INCOMING, ps))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders"));
+
+ clear_incoming_valid_bits();
+ break;
+
+ case F_INCOMING_CHECKING_TOTAL :
+ case F_INCOMING_CHECKING_RECENT :
+ if(!on_before && F_OFF(F_ENABLE_INCOMING_CHECKING, ps))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
+
+ clear_incoming_valid_bits();
+ break;
+
+ case F_THREAD_SORTS_BY_ARRIVAL :
+ clear_index_cache(ps->mail_stream, 0);
+ refresh_sort(ps->mail_stream, sp_msgmap(ps->mail_stream), SRT_NON);
+ break;
+
+#ifdef SMIME
+ case F_DONT_DO_SMIME :
+ smime_deinit();
+ break;
+
+#ifdef APPLEKEYCHAIN
+ case F_PUBLICCERTS_IN_KEYCHAIN :
+ smime_deinit();
+ break;
+#endif
+#endif
+
+ default :
+ break;
+ }
+}
+
+
+/*
+ * Returns 1 -- Feature is in the list and positive
+ * 0 -- Feature is not in the list at all
+ * -1 -- Feature is in the list and negative (no-)
+ */
+int
+feature_in_list(char **l, char *f)
+{
+ char *p;
+ int rv = 0, forced_off;
+
+ for(; l && *l; l++){
+ p = (forced_off = !struncmp(*l, "no-", 3)) ? *l + 3 : *l;
+ if(!strucmp(p, f))
+ rv = forced_off ? -1 : 1;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * test_feature - runs thru a feature list, and returns:
+ * 1 if feature explicitly set and matches 'v'
+ * 0 if feature not explicitly set *or* doesn't match 'v'
+ */
+int
+test_feature(char **l, char *f, int g)
+{
+ char *p;
+ int rv = 0, forced_off;
+
+ for(; l && *l; l++){
+ p = (forced_off = !struncmp(*l, "no-", 3)) ? *l + 3 : *l;
+ if(!strucmp(p, f))
+ rv = !forced_off;
+ else if(g && !strucmp(p, "old-growth"))
+ rv = !forced_off;
+ }
+
+ return(rv);
+}
+
+
+void
+set_feature(char ***l, char *f, int v)
+{
+ char **list = l ? *l : NULL, newval[256];
+ int count = 0;
+
+ snprintf(newval, sizeof(newval), "%s%s", v ? "" : "no-", f);
+ for(; list && *list; list++, count++)
+ if((**list == '\0') /* anything can replace an empty value */
+ || !strucmp(((!struncmp(*list, "no-", 3)) ? *list + 3 : *list), f)){
+ fs_give((void **)list); /* replace with new value */
+ *list = cpystr(newval);
+ return;
+ }
+
+ /*
+ * if we got here, we didn't find it in the list, so grow the list
+ * and add it..
+ */
+ if(!*l)
+ *l = (char **)fs_get((count + 2) * sizeof(char *));
+ else
+ fs_resize((void **)l, (count + 2) * sizeof(char *));
+
+ (*l)[count] = cpystr(newval);
+ (*l)[count + 1] = NULL;
+}
+
+
+int
+reset_character_set_stuff(char **err)
+{
+ int use_system = 0;
+ char buf[1000];
+
+ if(err)
+ *err = NULL;
+
+ if(ps_global->display_charmap)
+ fs_give((void **) &ps_global->display_charmap);
+
+ if(ps_global->keyboard_charmap)
+ fs_give((void **) &ps_global->keyboard_charmap);
+
+ if(ps_global->posting_charmap)
+ fs_give((void **) &ps_global->posting_charmap);
+
+#ifdef _WINDOWS
+ ps_global->display_charmap = cpystr("UTF-8");
+#else /* UNIX */
+ if(ps_global->VAR_CHAR_SET)
+ ps_global->display_charmap = cpystr(ps_global->VAR_CHAR_SET);
+ else{
+#if HAVE_LANGINFO_H && defined(CODESET)
+ ps_global->display_charmap = cpystr(nl_langinfo_codeset_wrapper());
+#else
+ ps_global->display_charmap = cpystr("UTF-8");
+#endif
+ }
+#endif /* UNIX */
+
+ if(!ps_global->display_charmap)
+ ps_global->display_charmap = cpystr("US-ASCII");
+
+#ifdef _WINDOWS
+ ps_global->keyboard_charmap = cpystr("UTF-8");
+#else /* UNIX */
+ if(ps_global->VAR_KEY_CHAR_SET)
+ ps_global->keyboard_charmap = cpystr(ps_global->VAR_KEY_CHAR_SET);
+ else
+ ps_global->keyboard_charmap = cpystr(ps_global->display_charmap);
+
+ if(!ps_global->keyboard_charmap)
+ ps_global->keyboard_charmap = cpystr("US-ASCII");
+
+ if(F_ON(F_USE_SYSTEM_TRANS, ps_global)){
+#if PREREQ_FOR_SYS_TRANSLATION
+ use_system++;
+ /* This modifies its arguments */
+ if(setup_for_input_output(use_system, &ps_global->display_charmap,
+ &ps_global->keyboard_charmap,
+ &ps_global->input_cs, (err && *err) ? NULL : err) == -1)
+ return -1;
+#endif
+ }
+#endif /* UNIX */
+
+ if(!use_system){
+ if(setup_for_input_output(use_system, &ps_global->display_charmap,
+ &ps_global->keyboard_charmap,
+ &ps_global->input_cs, (err && *err) ? NULL : err) == -1)
+ return -1;
+ }
+
+ if(!use_system && ps_global->VAR_POST_CHAR_SET){
+ ps_global->posting_charmap = cpystr(ps_global->VAR_POST_CHAR_SET);
+ if(!posting_charset_is_supported(ps_global->posting_charmap)){
+ if(err && !*err){
+ snprintf(buf, sizeof(buf),
+ _("Posting-Character-Set \"%s\" is unsupported, using UTF-8"),
+ ps_global->posting_charmap);
+ *err = cpystr(buf);
+ }
+
+ fs_give((void **) &ps_global->posting_charmap);
+ ps_global->posting_charmap = cpystr("UTF-8");
+ }
+ }
+ else{
+ if(use_system && ps_global->VAR_POST_CHAR_SET
+ && strucmp(ps_global->VAR_POST_CHAR_SET, "UTF-8"))
+ if(err && !*err)
+ *err = cpystr(_("Posting-Character-Set is ignored with Use-System-Translation turned on"));
+
+ ps_global->posting_charmap = cpystr("UTF-8");
+ }
+
+ set_locale_charmap(ps_global->keyboard_charmap);
+
+ return(0);
+}
+
+
+/*
+ * Given a single printer string from the config file, returns pointers
+ * to alloc'd strings containing the printer nickname, the command,
+ * the init string, the trailer string, everything but the nickname string,
+ * and everything but the command string. All_but_cmd includes the trailing
+ * space at the end (the one before the command) but all_but_nick does not
+ * include the leading space (the one before the [).
+ * If you pass in a pointer it is guaranteed to come back pointing to an
+ * allocated string, even if it is just an empty string. It is ok to pass
+ * NULL for any of the six return strings.
+ */
+void
+parse_printer(char *input, char **nick, char **cmd, char **init, char **trailer,
+ char **all_but_nick, char **all_but_cmd)
+{
+ char *p, *q, *start, *saved_options = NULL;
+ int tmpsave, cnt;
+
+ if(!input)
+ input = "";
+
+ if(nick || all_but_nick){
+ if((p = srchstr(input, " [")) != NULL){
+ if(all_but_nick)
+ *all_but_nick = cpystr(p+1);
+
+ if(nick){
+ while(p-1 > input && isspace((unsigned char)*(p-1)))
+ p--;
+
+ tmpsave = *p;
+ *p = '\0';
+ *nick = cpystr(input);
+ *p = tmpsave;
+ }
+ }
+ else{
+ if(nick)
+ *nick = cpystr("");
+
+ if(all_but_nick)
+ *all_but_nick = cpystr(input);
+ }
+ }
+
+ if((p = srchstr(input, "] ")) != NULL){
+ do{
+ ++p;
+ }while(isspace((unsigned char)*p));
+
+ tmpsave = *p;
+ *p = '\0';
+ saved_options = cpystr(input);
+ *p = tmpsave;
+ }
+ else
+ p = input;
+
+ if(cmd)
+ *cmd = cpystr(p);
+
+ if(init){
+ if(saved_options && (p = srchstr(saved_options, "INIT="))){
+ start = p + strlen("INIT=");
+ for(cnt=0, p = start; *p && *(p+1) && isxpair(p); p += 2)
+ cnt++;
+
+ q = *init = (char *)fs_get((cnt + 1) * sizeof(char));
+ for(p = start; *p && *(p+1) && isxpair(p); p += 2)
+ *q++ = read_hex(p);
+
+ *q = '\0';
+ }
+ else
+ *init = cpystr("");
+ }
+
+ if(trailer){
+ if(saved_options && (p = srchstr(saved_options, "TRAILER="))){
+ start = p + strlen("TRAILER=");
+ for(cnt=0, p = start; *p && *(p+1) && isxpair(p); p += 2)
+ cnt++;
+
+ q = *trailer = (char *)fs_get((cnt + 1) * sizeof(char));
+ for(p = start; *p && *(p+1) && isxpair(p); p += 2)
+ *q++ = read_hex(p);
+
+ *q = '\0';
+ }
+ else
+ *trailer = cpystr("");
+ }
+
+ if(all_but_cmd){
+ if(saved_options)
+ *all_but_cmd = saved_options;
+ else
+ *all_but_cmd = cpystr("");
+ }
+ else if(saved_options)
+ fs_give((void **)&saved_options);
+}
+
+
+int
+copy_pinerc(char *local, char *remote, char **err_msg)
+{
+ return(copy_localfile_to_remotefldr(RemImap, local, remote,
+ REMOTE_PINERC_SUBTYPE,
+ err_msg));
+}
+
+
+int
+copy_abook(char *local, char *remote, char **err_msg)
+{
+ return(copy_localfile_to_remotefldr(RemImap, local, remote,
+ REMOTE_ABOOK_SUBTYPE,
+ err_msg));
+}
+
+
+/*
+ * Copy local file to remote folder.
+ *
+ * Args remotetype -- type of remote folder
+ * local -- name of local file
+ * remote -- name of remote folder
+ * subtype --
+ *
+ * Returns 0 on success.
+ */
+int
+copy_localfile_to_remotefldr(RemType remotetype, char *local, char *remote,
+ char *subtype, char **err_msg)
+{
+ int retfail = -1;
+ unsigned flags;
+ REMDATA_S *rd;
+
+ dprint((9, "copy_localfile_to_remotefldr(%s,%s)\n",
+ local ? local : "<null>",
+ remote ? remote : "<null>"));
+
+ *err_msg = (char *)fs_get(MAXPATH * sizeof(char));
+
+ if(!local || !*local){
+ snprintf(*err_msg, MAXPATH, _("No local file specified"));
+ return(retfail);
+ }
+
+ if(!remote || !*remote){
+ snprintf(*err_msg, MAXPATH, _("No remote folder specified"));
+ return(retfail);
+ }
+
+ if(!IS_REMOTE(remote)){
+ snprintf(*err_msg, MAXPATH, _("Remote folder name \"%s\" %s"), remote,
+ (*remote != '{') ? _("must begin with \"{\"") : _("not valid"));
+ return(retfail);
+ }
+
+ if(IS_REMOTE(local)){
+ snprintf(*err_msg, MAXPATH, _("First argument \"%s\" must be a local filename"),
+ local);
+ return(retfail);
+ }
+
+ if(can_access(local, ACCESS_EXISTS) != 0){
+ snprintf(*err_msg, MAXPATH, _("Local file \"%s\" does not exist"), local);
+ return(retfail);
+ }
+
+ if(can_access(local, READ_ACCESS) != 0){
+ snprintf(*err_msg, MAXPATH, _("Can't read local file \"%s\": %s"),
+ local, error_description(errno));
+ return(retfail);
+ }
+
+ /*
+ * Check if remote folder exists and create it if it doesn't.
+ */
+ flags = 0;
+ rd = rd_create_remote(remotetype, remote, subtype,
+ &flags, _("Error: "), _("Can't copy to remote folder."));
+
+ if(!rd || rd->access == NoExists){
+ snprintf(*err_msg, MAXPATH, _("Can't create \"%s\""), remote);
+ if(rd)
+ rd_free_remdata(&rd);
+
+ return(retfail);
+ }
+
+ if(rd->access == MaybeRorW)
+ rd->access = ReadWrite;
+
+ rd->flags |= (NO_META_UPDATE | DO_REMTRIM);
+ rd->lf = cpystr(local);
+
+ rd_open_remote(rd);
+ if(!rd_stream_exists(rd)){
+ snprintf(*err_msg, MAXPATH, _("Can't open remote folder \"%s\""), rd->rn);
+ rd_free_remdata(&rd);
+ return(retfail);
+ }
+
+ if(rd_remote_is_readonly(rd)){
+ snprintf(*err_msg, MAXPATH, _("Remote folder \"%s\" is readonly"), rd->rn);
+ rd_free_remdata(&rd);
+ return(retfail);
+ }
+
+ switch(rd->type){
+ case RemImap:
+ /*
+ * Empty folder, add a header msg.
+ */
+ if(rd->t.i.stream->nmsgs == 0){
+ if(rd_init_remote(rd, 1) != 0){
+ snprintf(*err_msg, MAXPATH,
+ _("Failed initializing remote folder \"%s\", check debug file"),
+ rd->rn);
+ rd_free_remdata(&rd);
+ return(retfail);
+ }
+ }
+
+ fs_give((void **)err_msg);
+ *err_msg = NULL;
+ if(rd_chk_for_hdr_msg(&(rd->t.i.stream), rd, err_msg)){
+ rd_free_remdata(&rd);
+ return(retfail);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if(rd_update_remote(rd, NULL) != 0){
+ snprintf(*err_msg, MAXPATH, _("Error copying to remote folder \"%s\""), rd->rn);
+ rd_free_remdata(&rd);
+ return(retfail);
+ }
+
+ rd_update_metadata(rd, NULL);
+ rd_close_remdata(&rd);
+
+ fs_give((void **)err_msg);
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Panic pine - call on detected programmatic errors to exit pine, with arg
+
+ Input: message -- printf styule string for panic message (see above)
+ arg -- argument for printf string
+
+ Result: The various tty modes are restored
+ If debugging is active a core dump will be generated
+ Exits Pine
+ ----*/
+void
+panic1(char *message, char *arg)
+{
+ char buf1[1001], buf2[1001];
+
+ snprintf(buf1, sizeof(buf1), "%.*s", MAX(sizeof(buf1) - 1 - strlen(message), 0), arg);
+ snprintf(buf2, sizeof(buf2), message, buf1);
+ panic(buf2);
+}
+
+
+/*
+ *
+ */
+HelpType
+config_help(int var, int feature)
+{
+ switch(var){
+ case V_FEATURE_LIST :
+ return(feature_list_help(feature));
+ break;
+
+ case V_PERSONAL_NAME :
+ return(h_config_pers_name);
+ case V_USER_ID :
+ return(h_config_user_id);
+ case V_USER_DOMAIN :
+ return(h_config_user_dom);
+ case V_SMTP_SERVER :
+ return(h_config_smtp_server);
+ case V_NNTP_SERVER :
+ return(h_config_nntp_server);
+ case V_INBOX_PATH :
+ return(h_config_inbox_path);
+ case V_PRUNED_FOLDERS :
+ return(h_config_pruned_folders);
+ case V_DEFAULT_FCC :
+ return(h_config_default_fcc);
+ case V_DEFAULT_SAVE_FOLDER :
+ return(h_config_def_save_folder);
+ case V_POSTPONED_FOLDER :
+ return(h_config_postponed_folder);
+ case V_READ_MESSAGE_FOLDER :
+ return(h_config_read_message_folder);
+ case V_FORM_FOLDER :
+ return(h_config_form_folder);
+ case V_ARCHIVED_FOLDERS :
+ return(h_config_archived_folders);
+ case V_SIGNATURE_FILE :
+ return(h_config_signature_file);
+ case V_LITERAL_SIG :
+ return(h_config_literal_sig);
+ case V_INIT_CMD_LIST :
+ return(h_config_init_cmd_list);
+ case V_COMP_HDRS :
+ return(h_config_comp_hdrs);
+ case V_CUSTOM_HDRS :
+ return(h_config_custom_hdrs);
+ case V_VIEW_HEADERS :
+ return(h_config_viewer_headers);
+ case V_VIEW_MARGIN_LEFT :
+ return(h_config_viewer_margin_left);
+ case V_VIEW_MARGIN_RIGHT :
+ return(h_config_viewer_margin_right);
+ case V_QUOTE_SUPPRESSION :
+ return(h_config_quote_suppression);
+ case V_SAVED_MSG_NAME_RULE :
+ return(h_config_saved_msg_name_rule);
+ case V_FCC_RULE :
+ return(h_config_fcc_rule);
+ case V_SORT_KEY :
+ return(h_config_sort_key);
+ case V_AB_SORT_RULE :
+ return(h_config_ab_sort_rule);
+ case V_FLD_SORT_RULE :
+ return(h_config_fld_sort_rule);
+ case V_POST_CHAR_SET :
+ return(h_config_post_char_set);
+ case V_UNK_CHAR_SET :
+ return(h_config_unk_char_set);
+#ifndef _WINDOWS
+ case V_KEY_CHAR_SET :
+ return(h_config_key_char_set);
+ case V_CHAR_SET :
+ return(h_config_char_set);
+#endif /* ! _WINDOWS */
+ case V_EDITOR :
+ return(h_config_editor);
+ case V_SPELLER :
+ return(h_config_speller);
+ case V_DISPLAY_FILTERS :
+ return(h_config_display_filters);
+ case V_SEND_FILTER :
+ return(h_config_sending_filter);
+ case V_ALT_ADDRS :
+ return(h_config_alt_addresses);
+ case V_KEYWORDS :
+ return(h_config_keywords);
+ case V_KW_BRACES :
+ return(h_config_kw_braces);
+ case V_OPENING_SEP :
+ return(h_config_opening_sep);
+ case V_KW_COLORS :
+ return(h_config_kw_color);
+ case V_ABOOK_FORMATS :
+ return(h_config_abook_formats);
+ case V_INDEX_FORMAT :
+ return(h_config_index_format);
+ case V_INCCHECKTIMEO :
+ return(h_config_incoming_timeo);
+ case V_INCCHECKINTERVAL :
+ return(h_config_incoming_interv);
+ case V_INC2NDCHECKINTERVAL :
+ return(h_config_incoming_second_interv);
+ case V_INCCHECKLIST :
+ return(h_config_incoming_list);
+ case V_OVERLAP :
+ return(h_config_viewer_overlap);
+ case V_MAXREMSTREAM :
+ return(h_config_maxremstream);
+ case V_PERMLOCKED :
+ return(h_config_permlocked);
+ case V_MARGIN :
+ return(h_config_scroll_margin);
+ case V_DEADLETS :
+ return(h_config_deadlets);
+ case V_FILLCOL :
+ return(h_config_composer_wrap_column);
+ case V_TCPOPENTIMEO :
+ return(h_config_tcp_open_timeo);
+ case V_TCPREADWARNTIMEO :
+ return(h_config_tcp_readwarn_timeo);
+ case V_TCPWRITEWARNTIMEO :
+ return(h_config_tcp_writewarn_timeo);
+ case V_TCPQUERYTIMEO :
+ return(h_config_tcp_query_timeo);
+ case V_RSHOPENTIMEO :
+ return(h_config_rsh_open_timeo);
+ case V_SSHOPENTIMEO :
+ return(h_config_ssh_open_timeo);
+ case V_USERINPUTTIMEO :
+ return(h_config_user_input_timeo);
+ case V_REMOTE_ABOOK_VALIDITY :
+ return(h_config_remote_abook_validity);
+ case V_REMOTE_ABOOK_HISTORY :
+ return(h_config_remote_abook_history);
+ case V_INCOMING_FOLDERS :
+ return(h_config_incoming_folders);
+ case V_FOLDER_SPEC :
+ return(h_config_folder_spec);
+ case V_NEWS_SPEC :
+ return(h_config_news_spec);
+ case V_ADDRESSBOOK :
+ return(h_config_address_book);
+ case V_GLOB_ADDRBOOK :
+ return(h_config_glob_addrbook);
+ case V_LAST_VERS_USED :
+ return(h_config_last_vers);
+ case V_SENDMAIL_PATH :
+ return(h_config_sendmail_path);
+ case V_OPER_DIR :
+ return(h_config_oper_dir);
+ case V_RSHPATH :
+ return(h_config_rshpath);
+ case V_RSHCMD :
+ return(h_config_rshcmd);
+ case V_SSHPATH :
+ return(h_config_sshpath);
+ case V_SSHCMD :
+ return(h_config_sshcmd);
+ case V_NEW_VER_QUELL :
+ return(h_config_new_ver_quell);
+ case V_DISABLE_DRIVERS :
+ return(h_config_disable_drivers);
+ case V_DISABLE_AUTHS :
+ return(h_config_disable_auths);
+ case V_REMOTE_ABOOK_METADATA :
+ return(h_config_abook_metafile);
+ case V_REPLY_STRING :
+ return(h_config_reply_indent_string);
+ case V_WORDSEPS :
+ return(h_config_wordseps);
+ case V_QUOTE_REPLACE_STRING :
+ return(h_config_quote_replace_string);
+ case V_REPLY_INTRO :
+ return(h_config_reply_intro);
+ case V_EMPTY_HDR_MSG :
+ return(h_config_empty_hdr_msg);
+ case V_STATUS_MSG_DELAY :
+ return(h_config_status_msg_delay);
+ case V_ACTIVE_MSG_INTERVAL :
+ return(h_config_active_msg_interval);
+ case V_MAILCHECK :
+ return(h_config_mailcheck);
+ case V_MAILCHECKNONCURR :
+ return(h_config_mailchecknoncurr);
+ case V_MAILDROPCHECK :
+ return(h_config_maildropcheck);
+ case V_NNTPRANGE :
+ return(h_config_nntprange);
+ case V_NEWS_ACTIVE_PATH :
+ return(h_config_news_active);
+ case V_NEWS_SPOOL_DIR :
+ return(h_config_news_spool);
+ case V_IMAGE_VIEWER :
+ return(h_config_image_viewer);
+ case V_USE_ONLY_DOMAIN_NAME :
+ return(h_config_domain_name);
+ case V_LAST_TIME_PRUNE_QUESTION :
+ return(h_config_prune_date);
+ case V_UPLOAD_CMD:
+ return(h_config_upload_cmd);
+ case V_UPLOAD_CMD_PREFIX:
+ return(h_config_upload_prefix);
+ case V_DOWNLOAD_CMD:
+ return(h_config_download_cmd);
+ case V_DOWNLOAD_CMD_PREFIX:
+ return(h_config_download_prefix);
+ case V_GOTO_DEFAULT_RULE:
+ return(h_config_goto_default);
+ case V_INCOMING_STARTUP:
+ return(h_config_inc_startup);
+ case V_PRUNING_RULE:
+ return(h_config_pruning_rule);
+ case V_REOPEN_RULE:
+ return(h_config_reopen_rule);
+ case V_THREAD_DISP_STYLE:
+ return(h_config_thread_disp_style);
+ case V_THREAD_INDEX_STYLE:
+ return(h_config_thread_index_style);
+ case V_THREAD_MORE_CHAR:
+ return(h_config_thread_indicator_char);
+ case V_THREAD_EXP_CHAR:
+ return(h_config_thread_exp_char);
+ case V_THREAD_LASTREPLY_CHAR:
+ return(h_config_thread_lastreply_char);
+ case V_MAILCAP_PATH :
+ return(h_config_mailcap_path);
+ case V_MIMETYPE_PATH :
+ return(h_config_mimetype_path);
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ case V_FIFOPATH :
+ return(h_config_fifopath);
+#endif
+ case V_NMW_WIDTH :
+ return(h_config_newmailwidth);
+ case V_NEWSRC_PATH :
+ return(h_config_newsrc_path);
+ case V_BROWSER :
+ return(h_config_browser);
+#if defined(DOS) || defined(OS2)
+ case V_FILE_DIR :
+ return(h_config_file_dir);
+#endif
+ case V_NORM_FORE_COLOR :
+ case V_NORM_BACK_COLOR :
+ return(h_config_normal_color);
+ case V_REV_FORE_COLOR :
+ case V_REV_BACK_COLOR :
+ return(h_config_reverse_color);
+ case V_TITLE_FORE_COLOR :
+ case V_TITLE_BACK_COLOR :
+ return(h_config_title_color);
+ case V_TITLECLOSED_FORE_COLOR :
+ case V_TITLECLOSED_BACK_COLOR :
+ return(h_config_titleclosed_color);
+ case V_STATUS_FORE_COLOR :
+ case V_STATUS_BACK_COLOR :
+ return(h_config_status_color);
+ case V_SLCTBL_FORE_COLOR :
+ case V_SLCTBL_BACK_COLOR :
+ return(h_config_slctbl_color);
+ case V_QUOTE1_FORE_COLOR :
+ case V_QUOTE2_FORE_COLOR :
+ case V_QUOTE3_FORE_COLOR :
+ case V_QUOTE1_BACK_COLOR :
+ case V_QUOTE2_BACK_COLOR :
+ case V_QUOTE3_BACK_COLOR :
+ return(h_config_quote_color);
+ case V_INCUNSEEN_FORE_COLOR :
+ case V_INCUNSEEN_BACK_COLOR :
+ return(h_config_incunseen_color);
+ case V_SIGNATURE_FORE_COLOR :
+ case V_SIGNATURE_BACK_COLOR :
+ return(h_config_signature_color);
+ case V_PROMPT_FORE_COLOR :
+ case V_PROMPT_BACK_COLOR :
+ return(h_config_prompt_color);
+ case V_HEADER_GENERAL_FORE_COLOR :
+ case V_HEADER_GENERAL_BACK_COLOR :
+ return(h_config_header_general_color);
+ case V_IND_PLUS_FORE_COLOR :
+ case V_IND_IMP_FORE_COLOR :
+ case V_IND_DEL_FORE_COLOR :
+ case V_IND_ANS_FORE_COLOR :
+ case V_IND_NEW_FORE_COLOR :
+ case V_IND_UNS_FORE_COLOR :
+ case V_IND_REC_FORE_COLOR :
+ case V_IND_FWD_FORE_COLOR :
+ case V_IND_PLUS_BACK_COLOR :
+ case V_IND_IMP_BACK_COLOR :
+ case V_IND_DEL_BACK_COLOR :
+ case V_IND_ANS_BACK_COLOR :
+ case V_IND_NEW_BACK_COLOR :
+ case V_IND_UNS_BACK_COLOR :
+ case V_IND_REC_BACK_COLOR :
+ case V_IND_FWD_BACK_COLOR :
+ return(h_config_index_color);
+ case V_IND_OP_FORE_COLOR :
+ case V_IND_OP_BACK_COLOR :
+ return(h_config_index_opening_color);
+ case V_IND_SUBJ_FORE_COLOR :
+ case V_IND_SUBJ_BACK_COLOR :
+ return(h_config_index_subject_color);
+ case V_IND_FROM_FORE_COLOR :
+ case V_IND_FROM_BACK_COLOR :
+ return(h_config_index_from_color);
+ case V_IND_HIPRI_FORE_COLOR :
+ case V_IND_HIPRI_BACK_COLOR :
+ case V_IND_LOPRI_FORE_COLOR :
+ case V_IND_LOPRI_BACK_COLOR :
+ return(h_config_index_pri_color);
+ case V_IND_ARR_FORE_COLOR :
+ case V_IND_ARR_BACK_COLOR :
+ return(h_config_index_arrow_color);
+ case V_KEYLABEL_FORE_COLOR :
+ case V_KEYLABEL_BACK_COLOR :
+ return(h_config_keylabel_color);
+ case V_KEYNAME_FORE_COLOR :
+ case V_KEYNAME_BACK_COLOR :
+ return(h_config_keyname_color);
+ case V_METAMSG_FORE_COLOR :
+ case V_METAMSG_BACK_COLOR :
+ return(h_config_metamsg_color);
+ case V_VIEW_HDR_COLORS :
+ return(h_config_customhdr_color);
+ case V_PRINTER :
+ return(h_config_printer);
+ case V_PERSONAL_PRINT_CATEGORY :
+ return(h_config_print_cat);
+ case V_PERSONAL_PRINT_COMMAND :
+ return(h_config_print_command);
+ case V_PAT_ROLES :
+ return(h_config_pat_roles);
+ case V_PAT_FILTS :
+ return(h_config_pat_filts);
+ case V_PAT_SCORES :
+ return(h_config_pat_scores);
+ case V_PAT_INCOLS :
+ return(h_config_pat_incols);
+ case V_PAT_OTHER :
+ return(h_config_pat_other);
+ case V_PAT_SRCH :
+ return(h_config_pat_srch);
+ case V_INDEX_COLOR_STYLE :
+ return(h_config_index_color_style);
+ case V_TITLEBAR_COLOR_STYLE :
+ return(h_config_titlebar_color_style);
+#ifdef _WINDOWS
+ case V_FONT_NAME :
+ return(h_config_font_name);
+ case V_FONT_SIZE :
+ return(h_config_font_size);
+ case V_FONT_STYLE :
+ return(h_config_font_style);
+ case V_FONT_CHAR_SET :
+ return(h_config_font_char_set);
+ case V_PRINT_FONT_NAME :
+ return(h_config_print_font_name);
+ case V_PRINT_FONT_SIZE :
+ return(h_config_print_font_size);
+ case V_PRINT_FONT_STYLE :
+ return(h_config_print_font_style);
+ case V_PRINT_FONT_CHAR_SET :
+ return(h_config_print_font_char_set);
+ case V_WINDOW_POSITION :
+ return(h_config_window_position);
+ case V_CURSOR_STYLE :
+ return(h_config_cursor_style);
+#else
+ case V_COLOR_STYLE :
+ return(h_config_color_style);
+#endif
+#ifdef ENABLE_LDAP
+ case V_LDAP_SERVERS :
+ return(h_config_ldap_servers);
+#endif
+#ifdef SMIME
+ case V_PUBLICCERT_DIR :
+ return(h_config_smime_pubcertdir);
+ case V_PUBLICCERT_CONTAINER :
+ return(h_config_smime_pubcertcon);
+ case V_PRIVATEKEY_DIR :
+ return(h_config_smime_privkeydir);
+ case V_PRIVATEKEY_CONTAINER :
+ return(h_config_smime_privkeycon);
+ case V_CACERT_DIR :
+ return(h_config_smime_cacertdir);
+ case V_CACERT_CONTAINER :
+ return(h_config_smime_cacertcon);
+#endif
+ case V_RSS_NEWS :
+ return(h_config_rss_news);
+ case V_RSS_WEATHER :
+ return(h_config_rss_weather);
+ case V_WP_INDEXHEIGHT :
+ return(h_config_wp_indexheight);
+ case V_WP_INDEXLINES :
+ return(h_config_wp_indexlines);
+ case V_WP_AGGSTATE :
+ return(h_config_wp_aggstate);
+ case V_WP_STATE :
+ return(h_config_wp_state);
+ case V_WP_COLUMNS :
+ return(h_config_wp_columns);
+ default :
+ return(NO_HELP);
+ }
+}
+
+
+/*
+ * We don't want the user to be able to edit their pinerc and set
+ * printer to whatever they want if personal-print-command is fixed.
+ * So make sure printer is set to something legitimate. If it isn't,
+ * set it to something standard and return non-zero.
+ */
+int
+printer_value_check_and_adjust(void)
+{
+ char **tt;
+ char aname[100], wname[100];
+ int ok = 0;
+ struct variable *vars = ps_global->vars;
+
+ if(vars[V_PERSONAL_PRINT_COMMAND].is_fixed && !vars[V_PRINTER].is_fixed){
+ strncpy(aname, ANSI_PRINTER, sizeof(aname));
+ aname[sizeof(aname)-1] = '\0';
+ strncat(aname, "-no-formfeed", sizeof(aname)-strlen(aname)-1);
+ strncpy(wname, WYSE_PRINTER, sizeof(wname));
+ wname[sizeof(wname)-1] = '\0';
+ strncat(wname, "-no-formfeed", sizeof(wname)-strlen(wname)-1);
+ if(strucmp(VAR_PRINTER, ANSI_PRINTER) == 0
+ || strucmp(VAR_PRINTER, aname) == 0
+ || strucmp(VAR_PRINTER, WYSE_PRINTER) == 0
+ || strucmp(VAR_PRINTER, wname) == 0)
+ ok++;
+ else if(VAR_STANDARD_PRINTER && VAR_STANDARD_PRINTER[0]){
+ for(tt = VAR_STANDARD_PRINTER; *tt; tt++)
+ if(strucmp(VAR_PRINTER, *tt) == 0)
+ break;
+
+ if(*tt)
+ ok++;
+ }
+
+ if(!ok){
+ char *val;
+ struct variable *v;
+
+ if(VAR_STANDARD_PRINTER && VAR_STANDARD_PRINTER[0])
+ val = VAR_STANDARD_PRINTER[0];
+ else
+ val = ANSI_PRINTER;
+
+ v = &vars[V_PRINTER];
+ if(v->main_user_val.p)
+ fs_give((void **)&v->main_user_val.p);
+ if(v->post_user_val.p)
+ fs_give((void **)&v->post_user_val.p);
+ if(v->current_val.p)
+ fs_give((void **)&v->current_val.p);
+
+ v->main_user_val.p = cpystr(val);
+ v->current_val.p = cpystr(val);
+ }
+ }
+
+ return(!ok);
+}
+
+
+char **
+get_supported_options(void)
+{
+ char **config;
+ DRIVER *d;
+ AUTHENTICATOR *a;
+ char *title = _("Supported features in this Alpine");
+ char sbuf[MAX_SCREEN_COLS+1];
+ int cnt, alcnt, len, cols, disabled, any_disabled = 0;;
+
+ /*
+ * Line count:
+ * Title + blank = 2
+ * SSL Title + SSL lines + blank = 4
+ * Auth title + blank = 2
+ * Driver title + blank = 2
+ * LDAP title + LDAP line = 2
+ * Disabled explanation + blank line = 4
+ * end = 1
+ */
+ cnt = 17;
+ for(a = mail_lookup_auth(1); a; a = a->next)
+ cnt++;
+ for(d = (DRIVER *)mail_parameters(NIL, GET_DRIVERS, NIL);
+ d; d = d->next)
+ cnt++;
+
+ alcnt = cnt;
+ config = (char **) fs_get(alcnt * sizeof(char *));
+ memset(config, 0, alcnt * sizeof(char *));
+
+ cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 0;
+ len = utf8_width(title);
+ snprintf(sbuf, sizeof(sbuf), "%*s%s", cols > len ? (cols-len)/2 : 0, "", title);
+
+ cnt = 0;
+ if(cnt < alcnt)
+ config[cnt] = cpystr(sbuf);
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr("");
+
+ if(++cnt < alcnt)
+ /* TRANSLATORS: headings */
+ config[cnt] = cpystr(_("Encryption:"));
+
+ if(++cnt < alcnt && mail_parameters(NIL, GET_SSLDRIVER, NIL))
+ config[cnt] = cpystr(_(" TLS and SSL"));
+ else
+ config[cnt] = cpystr(_(" None (no TLS or SSL)"));
+#ifdef SMIME
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(" S/MIME");
+#endif
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr("");
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("Authenticators:"));
+
+ for(a = mail_lookup_auth(1); a; a = a->next){
+ disabled = (a->client == NULL && a->server == NULL);
+ any_disabled += disabled;
+ snprintf(sbuf, sizeof(sbuf), " %s%s", a->name, disabled ? " (disabled)" : "");
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(sbuf);
+ }
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr("");
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("Mailbox drivers:"));
+
+ for(d = (DRIVER *)mail_parameters(NIL, GET_DRIVERS, NIL);
+ d; d = d->next){
+ disabled = (d->flags & DR_DISABLE);
+ any_disabled += disabled;
+ snprintf(sbuf, sizeof(sbuf), " %s%s", d->name, disabled ? " (disabled)" : "");
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(sbuf);
+ }
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr("");
+
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("Directories:"));
+
+#ifdef ENABLE_LDAP
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(" LDAP");
+#else
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(" None (no LDAP)");
+#endif
+
+ if(any_disabled){
+ if(++cnt < alcnt)
+ config[cnt] = cpystr("");
+
+ if(ps_global->ttyo){
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("Authenticators may be disabled because of the \"disable-these-authenticators\" hidden config option. Mailbox drivers may be disabled because of the \"disable-these-drivers\" hidden config option."));
+ }
+ else{
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("Authenticators may be disabled because of the \"disable-these-authenticators\""));
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("hidden config option. Mailbox drivers may be disabled because of the"));
+ if(++cnt < alcnt)
+ config[cnt] = cpystr(_("\"disable-these-drivers\" hidden config option."));
+ }
+ }
+
+ if(++cnt < alcnt)
+ config[cnt] = NULL;
+
+ return(config);
+}
+
+
+unsigned
+reset_startup_rule(MAILSTREAM *stream)
+{
+ long rflags = ROLE_DO_OTHER;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ unsigned startup_rule;
+
+ startup_rule = IS_NOTSET;
+
+ if(stream && nonempty_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(match_pattern(pat->patgrp, stream, NULL, NULL, NULL,
+ SE_NOSERVER|SE_NOPREFETCH))
+ break;
+ }
+
+ if(pat && pat->action && !pat->action->bogus)
+ startup_rule = pat->action->startup_rule;
+ }
+
+ return(startup_rule);
+}
+
+
+#ifdef _WINDOWS
+
+char *
+transformed_color(old)
+ char *old;
+{
+ if(!old)
+ return("");
+
+ if(!struncmp(old, "color008", 8))
+ return("colorlgr");
+ else if(!struncmp(old, "color009", 8))
+ return("colormgr");
+ else if(!struncmp(old, "color010", 8))
+ return("colordgr");
+
+ return("");
+}
+
+
+/*
+ * If this is the first time we've run a version > 4.40, and there
+ * is evidence that the config file has not been used by unix pine,
+ * then we convert color008 to colorlgr, color009 to colormgr, and
+ * color010 to colordgr. If the config file is being used by
+ * unix pine then color008 may really supposed to be color008, color009
+ * may really supposed to be red, and color010 may really supposed to be
+ * green. Same if we've already run 4.41 or higher previously.
+ *
+ * Returns 0 if no changes, > 0 if something was changed.
+ */
+int
+convert_pc_gray_names(ps, prc, which)
+ struct pine *ps;
+ PINERC_S *prc;
+ EditWhich which;
+{
+ struct variable *v;
+ int ret = 0, ic = 0;
+ char **s, *t, *p, *pstr, *new, *pval, **apval, **lval;
+
+ for(v = ps->vars; v->name; v++){
+ if(!color_holding_var(ps, v) || v == &ps->vars[V_KW_COLORS])
+ continue;
+
+ if(v == &ps->vars[V_VIEW_HDR_COLORS]){
+
+ if((lval = LVAL(v,which)) != NULL){
+ /* fix these in place */
+ for(s = lval; (t = *s) != NULL; s++){
+ if((p = srchstr(t, "FG=color008")) ||
+ (p = srchstr(t, "FG=color009")) ||
+ (p = srchstr(t, "FG=color010"))){
+ strncpy(p+3, transformed_color(p+3), 8);
+ ret++;
+ }
+
+ if((p = srchstr(t, "BG=color008")) ||
+ (p = srchstr(t, "BG=color009")) ||
+ (p = srchstr(t, "BG=color010"))){
+ strncpy(p+3, transformed_color(p+3), 8);
+ ret++;
+ }
+ }
+ }
+ }
+ else{
+ if((pval = PVAL(v,which)) != NULL){
+ apval = APVAL(v,which);
+ if(apval && (!strucmp(pval, "color008") ||
+ !strucmp(pval, "color009") ||
+ !strucmp(pval, "color010"))){
+ new = transformed_color(pval);
+ if(*apval)
+ fs_give((void **)apval);
+
+ *apval = cpystr(new);
+ ret++;
+ }
+ }
+ }
+ }
+
+ v = &ps->vars[V_PAT_INCOLS];
+ if((lval = LVAL(v,which)) != NULL){
+ for(s = lval; (t = *s) != NULL; s++){
+ if((pstr = srchstr(t, "action=")) != NULL){
+ if((p = srchstr(pstr, "FG=color008")) ||
+ (p = srchstr(pstr, "FG=color009")) ||
+ (p = srchstr(pstr, "FG=color010"))){
+ strncpy(p+3, transformed_color(p+3), 8);
+ ic++;
+ }
+
+ if((p = srchstr(pstr, "BG=color008")) ||
+ (p = srchstr(pstr, "BG=color009")) ||
+ (p = srchstr(pstr, "BG=color010"))){
+ strncpy(p+3, transformed_color(p+3), 8);
+ ic++;
+ }
+ }
+ }
+ }
+
+ if(ic)
+ set_current_val(&ps->vars[V_PAT_INCOLS], TRUE, TRUE);
+
+ return(ret+ic);
+}
+
+
+int
+unix_color_style_in_pinerc(prc)
+ PINERC_S *prc;
+{
+ PINERC_LINE *pline;
+
+ for(pline = prc ? prc->pinerc_lines : NULL;
+ pline && (pline->var || pline->line); pline++)
+ if(pline->line && !struncmp("color-style=", pline->line, 12))
+ return(1);
+
+ return(0);
+}
+
+char *
+pcpine_general_help(titlebuf)
+ char *titlebuf;
+{
+ if(titlebuf)
+ strcpy(titlebuf, "PC Alpine For Windows");
+
+ return(pcpine_help(h_pine_for_windows));
+}
+
+#endif /* _WINDOWS */
diff --git a/pith/conf.h b/pith/conf.h
new file mode 100644
index 00000000..4c6bae4a
--- /dev/null
+++ b/pith/conf.h
@@ -0,0 +1,895 @@
+/*
+ * $Id: conf.h 1155 2008-08-21 18:33:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CONFIG_INCLUDED
+#define PITH_CONFIG_INCLUDED
+
+
+#include "../pith/conftype.h"
+#include "../pith/remtype.h"
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/color.h"
+
+
+#ifndef DF_REMOTE_ABOOK_VALIDITY
+#define DF_REMOTE_ABOOK_VALIDITY "5"
+#endif
+#ifndef DF_GOTO_DEFAULT_RULE
+#define DF_GOTO_DEFAULT_RULE "inbox-or-folder-in-recent-collection"
+#endif
+#ifndef DF_INCOMING_STARTUP
+#define DF_INCOMING_STARTUP "first-unseen"
+#endif
+#ifndef DF_PRUNING_RULE
+#define DF_PRUNING_RULE "ask-ask"
+#endif
+#ifndef DF_REOPEN_RULE
+#define DF_REOPEN_RULE "ask-no-n"
+#endif
+#ifndef DF_THREAD_DISP_STYLE
+#define DF_THREAD_DISP_STYLE "struct"
+#endif
+#ifndef DF_THREAD_INDEX_STYLE
+#define DF_THREAD_INDEX_STYLE "exp"
+#endif
+#ifndef DF_THREAD_MORE_CHAR
+#define DF_THREAD_MORE_CHAR ">"
+#endif
+#ifndef DF_THREAD_EXP_CHAR
+#define DF_THREAD_EXP_CHAR "."
+#endif
+#ifndef DF_THREAD_LASTREPLY_CHAR
+#define DF_THREAD_LASTREPLY_CHAR "\\"
+#endif
+#ifndef DF_MAILDROPCHECK
+#define DF_MAILDROPCHECK "60"
+#endif
+#ifndef DF_MAXREMSTREAM
+#define DF_MAXREMSTREAM "3"
+#endif
+#ifndef DF_VIEW_MARGIN_RIGHT
+#define DF_VIEW_MARGIN_RIGHT "4"
+#endif
+#ifndef DF_QUOTE_SUPPRESSION
+#define DF_QUOTE_SUPPRESSION "0"
+#endif
+#ifndef DF_DEADLETS
+#define DF_DEADLETS "1"
+#endif
+#ifndef DF_NMW_WIDTH
+#define DF_NMW_WIDTH "80"
+#endif
+
+
+#define VAR_PERSONAL_NAME vars[V_PERSONAL_NAME].current_val.p
+#define FIX_PERSONAL_NAME vars[V_PERSONAL_NAME].fixed_val.p
+#define COM_PERSONAL_NAME vars[V_PERSONAL_NAME].cmdline_val.p
+#define VAR_USER_ID vars[V_USER_ID].current_val.p
+#define COM_USER_ID vars[V_USER_ID].cmdline_val.p
+#define VAR_USER_DOMAIN vars[V_USER_DOMAIN].current_val.p
+#define VAR_SMTP_SERVER vars[V_SMTP_SERVER].current_val.l
+#define FIX_SMTP_SERVER vars[V_SMTP_SERVER].fixed_val.l
+#define COM_SMTP_SERVER vars[V_SMTP_SERVER].cmdline_val.l
+#define GLO_SMTP_SERVER vars[V_SMTP_SERVER].global_val.l
+#define VAR_INBOX_PATH vars[V_INBOX_PATH].current_val.p
+#define GLO_INBOX_PATH vars[V_INBOX_PATH].global_val.p
+#define VAR_INCOMING_FOLDERS vars[V_INCOMING_FOLDERS].current_val.l
+#define VAR_FOLDER_SPEC vars[V_FOLDER_SPEC].current_val.l
+#define GLO_FOLDER_SPEC vars[V_FOLDER_SPEC].global_val.l
+#define VAR_NEWS_SPEC vars[V_NEWS_SPEC].current_val.l
+#define VAR_ARCHIVED_FOLDERS vars[V_ARCHIVED_FOLDERS].current_val.l
+#define VAR_PRUNED_FOLDERS vars[V_PRUNED_FOLDERS].current_val.l
+#define GLO_PRUNED_FOLDERS vars[V_PRUNED_FOLDERS].global_val.l
+#define VAR_DEFAULT_FCC vars[V_DEFAULT_FCC].current_val.p
+#define GLO_DEFAULT_FCC vars[V_DEFAULT_FCC].global_val.p
+#define VAR_DEFAULT_SAVE_FOLDER vars[V_DEFAULT_SAVE_FOLDER].current_val.p
+#define GLO_DEFAULT_SAVE_FOLDER vars[V_DEFAULT_SAVE_FOLDER].global_val.p
+#define VAR_POSTPONED_FOLDER vars[V_POSTPONED_FOLDER].current_val.p
+#define GLO_POSTPONED_FOLDER vars[V_POSTPONED_FOLDER].global_val.p
+#define VAR_MAIL_DIRECTORY vars[V_MAIL_DIRECTORY].current_val.p
+#define GLO_MAIL_DIRECTORY vars[V_MAIL_DIRECTORY].global_val.p
+#define VAR_READ_MESSAGE_FOLDER vars[V_READ_MESSAGE_FOLDER].current_val.p
+#define GLO_READ_MESSAGE_FOLDER vars[V_READ_MESSAGE_FOLDER].global_val.p
+#define VAR_FORM_FOLDER vars[V_FORM_FOLDER].current_val.p
+#define GLO_FORM_FOLDER vars[V_FORM_FOLDER].global_val.p
+#define VAR_TRASH_FOLDER vars[V_TRASH_FOLDER].current_val.p
+#define GLO_TRASH_FOLDER vars[V_TRASH_FOLDER].global_val.p
+#define VAR_SIGNATURE_FILE vars[V_SIGNATURE_FILE].current_val.p
+#define GLO_SIGNATURE_FILE vars[V_SIGNATURE_FILE].global_val.p
+#define VAR_LITERAL_SIG vars[V_LITERAL_SIG].current_val.p
+#define VAR_GLOB_ADDRBOOK vars[V_GLOB_ADDRBOOK].current_val.l
+#define VAR_ADDRESSBOOK vars[V_ADDRESSBOOK].current_val.l
+#define GLO_ADDRESSBOOK vars[V_ADDRESSBOOK].global_val.l
+#define FIX_ADDRESSBOOK vars[V_ADDRESSBOOK].fixed_val.l
+#define VAR_FEATURE_LIST vars[V_FEATURE_LIST].current_val.l
+#define VAR_INIT_CMD_LIST vars[V_INIT_CMD_LIST].current_val.l
+#define GLO_INIT_CMD_LIST vars[V_INIT_CMD_LIST].global_val.l
+#define COM_INIT_CMD_LIST vars[V_INIT_CMD_LIST].cmdline_val.l
+#define VAR_COMP_HDRS vars[V_COMP_HDRS].current_val.l
+#define GLO_COMP_HDRS vars[V_COMP_HDRS].global_val.l
+#define VAR_CUSTOM_HDRS vars[V_CUSTOM_HDRS].current_val.l
+#define VAR_VIEW_HEADERS vars[V_VIEW_HEADERS].current_val.l
+#define VAR_VIEW_MARGIN_LEFT vars[V_VIEW_MARGIN_LEFT].current_val.p
+#define GLO_VIEW_MARGIN_LEFT vars[V_VIEW_MARGIN_LEFT].global_val.p
+#define VAR_VIEW_MARGIN_RIGHT vars[V_VIEW_MARGIN_RIGHT].current_val.p
+#define GLO_VIEW_MARGIN_RIGHT vars[V_VIEW_MARGIN_RIGHT].global_val.p
+#define VAR_QUOTE_SUPPRESSION vars[V_QUOTE_SUPPRESSION].current_val.p
+#define GLO_QUOTE_SUPPRESSION vars[V_QUOTE_SUPPRESSION].global_val.p
+#ifndef _WINDOWS
+#define VAR_COLOR_STYLE vars[V_COLOR_STYLE].current_val.p
+#define GLO_COLOR_STYLE vars[V_COLOR_STYLE].global_val.p
+#endif
+#define VAR_INDEX_COLOR_STYLE vars[V_INDEX_COLOR_STYLE].current_val.p
+#define GLO_INDEX_COLOR_STYLE vars[V_INDEX_COLOR_STYLE].global_val.p
+#define VAR_TITLEBAR_COLOR_STYLE vars[V_TITLEBAR_COLOR_STYLE].current_val.p
+#define GLO_TITLEBAR_COLOR_STYLE vars[V_TITLEBAR_COLOR_STYLE].global_val.p
+#define VAR_SAVED_MSG_NAME_RULE vars[V_SAVED_MSG_NAME_RULE].current_val.p
+#define GLO_SAVED_MSG_NAME_RULE vars[V_SAVED_MSG_NAME_RULE].global_val.p
+#define VAR_FCC_RULE vars[V_FCC_RULE].current_val.p
+#define GLO_FCC_RULE vars[V_FCC_RULE].global_val.p
+#define VAR_SORT_KEY vars[V_SORT_KEY].current_val.p
+#define GLO_SORT_KEY vars[V_SORT_KEY].global_val.p
+#define COM_SORT_KEY vars[V_SORT_KEY].cmdline_val.p
+#define VAR_AB_SORT_RULE vars[V_AB_SORT_RULE].current_val.p
+#define GLO_AB_SORT_RULE vars[V_AB_SORT_RULE].global_val.p
+#define VAR_FLD_SORT_RULE vars[V_FLD_SORT_RULE].current_val.p
+#define GLO_FLD_SORT_RULE vars[V_FLD_SORT_RULE].global_val.p
+#ifndef _WINDOWS
+#define VAR_CHAR_SET vars[V_CHAR_SET].current_val.p
+#define GLO_CHAR_SET vars[V_CHAR_SET].global_val.p
+#define VAR_OLD_CHAR_SET vars[V_OLD_CHAR_SET].current_val.p
+#define VAR_KEY_CHAR_SET vars[V_KEY_CHAR_SET].current_val.p
+#endif /* ! _WINDOWS */
+#define VAR_POST_CHAR_SET vars[V_POST_CHAR_SET].current_val.p
+#define GLO_POST_CHAR_SET vars[V_POST_CHAR_SET].global_val.p
+#define VAR_UNK_CHAR_SET vars[V_UNK_CHAR_SET].current_val.p
+#define VAR_EDITOR vars[V_EDITOR].current_val.l
+#define GLO_EDITOR vars[V_EDITOR].global_val.l
+#define VAR_SPELLER vars[V_SPELLER].current_val.p
+#define GLO_SPELLER vars[V_SPELLER].global_val.p
+#define VAR_FILLCOL vars[V_FILLCOL].current_val.p
+#define GLO_FILLCOL vars[V_FILLCOL].global_val.p
+#define VAR_DEADLETS vars[V_DEADLETS].current_val.p
+#define GLO_DEADLETS vars[V_DEADLETS].global_val.p
+#define VAR_REPLY_STRING vars[V_REPLY_STRING].current_val.p
+#define GLO_REPLY_STRING vars[V_REPLY_STRING].global_val.p
+#define VAR_WORDSEPS vars[V_WORDSEPS].current_val.l
+#define VAR_QUOTE_REPLACE_STRING vars[V_QUOTE_REPLACE_STRING].current_val.p
+#define GLO_QUOTE_REPLACE_STRING vars[V_QUOTE_REPLACE_STRING].global_val.p
+#define VAR_REPLY_INTRO vars[V_REPLY_INTRO].current_val.p
+#define GLO_REPLY_INTRO vars[V_REPLY_INTRO].global_val.p
+#define VAR_EMPTY_HDR_MSG vars[V_EMPTY_HDR_MSG].current_val.p
+#define GLO_EMPTY_HDR_MSG vars[V_EMPTY_HDR_MSG].global_val.p
+#define VAR_IMAGE_VIEWER vars[V_IMAGE_VIEWER].current_val.p
+#define GLO_IMAGE_VIEWER vars[V_IMAGE_VIEWER].global_val.p
+#define VAR_USE_ONLY_DOMAIN_NAME vars[V_USE_ONLY_DOMAIN_NAME].current_val.p
+#define GLO_USE_ONLY_DOMAIN_NAME vars[V_USE_ONLY_DOMAIN_NAME].global_val.p
+#define VAR_PRINTER vars[V_PRINTER].current_val.p
+#define GLO_PRINTER vars[V_PRINTER].global_val.p
+#define VAR_PERSONAL_PRINT_COMMAND vars[V_PERSONAL_PRINT_COMMAND].current_val.l
+#define GLO_PERSONAL_PRINT_COMMAND vars[V_PERSONAL_PRINT_COMMAND].global_val.l
+#define VAR_PERSONAL_PRINT_CATEGORY vars[V_PERSONAL_PRINT_CATEGORY].current_val.p
+#define GLO_PERSONAL_PRINT_CATEGORY vars[V_PERSONAL_PRINT_CATEGORY].global_val.p
+#define VAR_STANDARD_PRINTER vars[V_STANDARD_PRINTER].current_val.l
+#define GLO_STANDARD_PRINTER vars[V_STANDARD_PRINTER].global_val.l
+#define FIX_STANDARD_PRINTER vars[V_STANDARD_PRINTER].fixed_val.l
+#define VAR_LAST_TIME_PRUNE_QUESTION vars[V_LAST_TIME_PRUNE_QUESTION].current_val.p
+#define VAR_LAST_VERS_USED vars[V_LAST_VERS_USED].current_val.p
+#define VAR_BUGS_FULLNAME vars[V_BUGS_FULLNAME].current_val.p
+#define GLO_BUGS_FULLNAME vars[V_BUGS_FULLNAME].global_val.p
+#define VAR_BUGS_ADDRESS vars[V_BUGS_ADDRESS].current_val.p
+#define GLO_BUGS_ADDRESS vars[V_BUGS_ADDRESS].global_val.p
+#define VAR_BUGS_EXTRAS vars[V_BUGS_EXTRAS].current_val.p
+#define GLO_BUGS_EXTRAS vars[V_BUGS_EXTRAS].global_val.p
+#define VAR_SUGGEST_FULLNAME vars[V_SUGGEST_FULLNAME].current_val.p
+#define GLO_SUGGEST_FULLNAME vars[V_SUGGEST_FULLNAME].global_val.p
+#define VAR_SUGGEST_ADDRESS vars[V_SUGGEST_ADDRESS].current_val.p
+#define GLO_SUGGEST_ADDRESS vars[V_SUGGEST_ADDRESS].global_val.p
+#define VAR_LOCAL_FULLNAME vars[V_LOCAL_FULLNAME].current_val.p
+#define GLO_LOCAL_FULLNAME vars[V_LOCAL_FULLNAME].global_val.p
+#define VAR_LOCAL_ADDRESS vars[V_LOCAL_ADDRESS].current_val.p
+#define GLO_LOCAL_ADDRESS vars[V_LOCAL_ADDRESS].global_val.p
+#define VAR_FORCED_ABOOK_ENTRY vars[V_FORCED_ABOOK_ENTRY].current_val.l
+#define VAR_KBLOCK_PASSWD_COUNT vars[V_KBLOCK_PASSWD_COUNT].current_val.p
+#define GLO_KBLOCK_PASSWD_COUNT vars[V_KBLOCK_PASSWD_COUNT].global_val.p
+#define VAR_STATUS_MSG_DELAY vars[V_STATUS_MSG_DELAY].current_val.p
+#define GLO_STATUS_MSG_DELAY vars[V_STATUS_MSG_DELAY].global_val.p
+#define VAR_ACTIVE_MSG_INTERVAL vars[V_ACTIVE_MSG_INTERVAL].current_val.p
+#define GLO_ACTIVE_MSG_INTERVAL vars[V_ACTIVE_MSG_INTERVAL].global_val.p
+#define GLO_SENDMAIL_PATH vars[V_SENDMAIL_PATH].global_val.p
+#define FIX_SENDMAIL_PATH vars[V_SENDMAIL_PATH].fixed_val.p
+#define COM_SENDMAIL_PATH vars[V_SENDMAIL_PATH].cmdline_val.p
+#define VAR_OPER_DIR vars[V_OPER_DIR].current_val.p
+#define GLO_OPER_DIR vars[V_OPER_DIR].global_val.p
+#define FIX_OPER_DIR vars[V_OPER_DIR].fixed_val.p
+#define COM_OPER_DIR vars[V_OPER_DIR].cmdline_val.p
+#define VAR_DISPLAY_FILTERS vars[V_DISPLAY_FILTERS].current_val.l
+#define VAR_SEND_FILTER vars[V_SEND_FILTER].current_val.l
+#define GLO_SEND_FILTER vars[V_SEND_FILTER].global_val.l
+#define VAR_ALT_ADDRS vars[V_ALT_ADDRS].current_val.l
+#define VAR_KEYWORDS vars[V_KEYWORDS].current_val.l
+#define VAR_KW_COLORS vars[V_KW_COLORS].current_val.l
+#define VAR_KW_BRACES vars[V_KW_BRACES].current_val.p
+#define GLO_KW_BRACES vars[V_KW_BRACES].global_val.p
+#define VAR_OPENING_SEP vars[V_OPENING_SEP].current_val.p
+#define GLO_OPENING_SEP vars[V_OPENING_SEP].global_val.p
+#define VAR_ABOOK_FORMATS vars[V_ABOOK_FORMATS].current_val.l
+#define VAR_INDEX_FORMAT vars[V_INDEX_FORMAT].current_val.p
+#define VAR_OVERLAP vars[V_OVERLAP].current_val.p
+#define GLO_OVERLAP vars[V_OVERLAP].global_val.p
+#define VAR_MAXREMSTREAM vars[V_MAXREMSTREAM].current_val.p
+#define GLO_MAXREMSTREAM vars[V_MAXREMSTREAM].global_val.p
+#define VAR_PERMLOCKED vars[V_PERMLOCKED].current_val.l
+#define VAR_MARGIN vars[V_MARGIN].current_val.p
+#define GLO_MARGIN vars[V_MARGIN].global_val.p
+#define VAR_MAILCHECK vars[V_MAILCHECK].current_val.p
+#define GLO_MAILCHECK vars[V_MAILCHECK].global_val.p
+#define VAR_MAILCHECKNONCURR vars[V_MAILCHECKNONCURR].current_val.p
+#define GLO_MAILCHECKNONCURR vars[V_MAILCHECKNONCURR].global_val.p
+#define VAR_MAILDROPCHECK vars[V_MAILDROPCHECK].current_val.p
+#define GLO_MAILDROPCHECK vars[V_MAILDROPCHECK].global_val.p
+#define VAR_NNTPRANGE vars[V_NNTPRANGE].current_val.p
+#define GLO_NNTPRANGE vars[V_NNTPRANGE].global_val.p
+#define VAR_NEWSRC_PATH vars[V_NEWSRC_PATH].current_val.p
+#define GLO_NEWSRC_PATH vars[V_NEWSRC_PATH].global_val.p
+#define VAR_NEWS_ACTIVE_PATH vars[V_NEWS_ACTIVE_PATH].current_val.p
+#define GLO_NEWS_ACTIVE_PATH vars[V_NEWS_ACTIVE_PATH].global_val.p
+#define VAR_NEWS_SPOOL_DIR vars[V_NEWS_SPOOL_DIR].current_val.p
+#define GLO_NEWS_SPOOL_DIR vars[V_NEWS_SPOOL_DIR].global_val.p
+#define VAR_DISABLE_DRIVERS vars[V_DISABLE_DRIVERS].current_val.l
+#define VAR_DISABLE_AUTHS vars[V_DISABLE_AUTHS].current_val.l
+#define VAR_REMOTE_ABOOK_METADATA vars[V_REMOTE_ABOOK_METADATA].current_val.p
+#define VAR_REMOTE_ABOOK_HISTORY vars[V_REMOTE_ABOOK_HISTORY].current_val.p
+#define GLO_REMOTE_ABOOK_HISTORY vars[V_REMOTE_ABOOK_HISTORY].global_val.p
+#define VAR_REMOTE_ABOOK_VALIDITY vars[V_REMOTE_ABOOK_VALIDITY].current_val.p
+#define GLO_REMOTE_ABOOK_VALIDITY vars[V_REMOTE_ABOOK_VALIDITY].global_val.p
+ /* Elm style save is obsolete in Pine 3.81 (see saved msg name rule) */
+#define VAR_ELM_STYLE_SAVE vars[V_ELM_STYLE_SAVE].current_val.p
+#define GLO_ELM_STYLE_SAVE vars[V_ELM_STYLE_SAVE].global_val.p
+ /* Header in reply is obsolete in Pine 3.83 (see feature list) */
+#define VAR_HEADER_IN_REPLY vars[V_HEADER_IN_REPLY].current_val.p
+#define GLO_HEADER_IN_REPLY vars[V_HEADER_IN_REPLY].global_val.p
+ /* Feature level is obsolete in Pine 3.83 (see feature list) */
+#define VAR_FEATURE_LEVEL vars[V_FEATURE_LEVEL].current_val.p
+#define GLO_FEATURE_LEVEL vars[V_FEATURE_LEVEL].global_val.p
+ /* Old style reply is obsolete in Pine 3.83 (see feature list) */
+#define VAR_OLD_STYLE_REPLY vars[V_OLD_STYLE_REPLY].current_val.p
+#define GLO_OLD_STYLE_REPLY vars[V_OLD_STYLE_REPLY].global_val.p
+ /* Save by sender is obsolete in Pine 3.83 (see saved msg name rule) */
+#define VAR_SAVE_BY_SENDER vars[V_SAVE_BY_SENDER].current_val.p
+#define GLO_SAVE_BY_SENDER vars[V_SAVE_BY_SENDER].global_val.p
+#define VAR_NNTP_SERVER vars[V_NNTP_SERVER].current_val.l
+#define FIX_NNTP_SERVER vars[V_NNTP_SERVER].fixed_val.l
+#ifdef ENABLE_LDAP
+#define VAR_LDAP_SERVERS vars[V_LDAP_SERVERS].current_val.l
+#endif
+#define VAR_RSS_NEWS vars[V_RSS_NEWS].current_val.p
+#define VAR_RSS_WEATHER vars[V_RSS_WEATHER].current_val.p
+#define VAR_WP_INDEXHEIGHT vars[V_WP_INDEXHEIGHT].current_val.p
+#define VAR_WP_INDEXLINES vars[V_WP_INDEXLINES].current_val.p
+#define GLO_WP_INDEXHEIGHT vars[V_WP_INDEXHEIGHT].global_val.p
+#define VAR_WP_AGGSTATE vars[V_WP_AGGSTATE].current_val.p
+#define GLO_WP_AGGSTATE vars[V_WP_AGGSTATE].global_val.p
+#define VAR_WP_STATE vars[V_WP_STATE].current_val.p
+#define GLO_WP_STATE vars[V_WP_STATE].global_val.p
+#define VAR_WP_COLUMNS vars[V_WP_COLUMNS].current_val.p
+#define VAR_UPLOAD_CMD vars[V_UPLOAD_CMD].current_val.p
+#define VAR_UPLOAD_CMD_PREFIX vars[V_UPLOAD_CMD_PREFIX].current_val.p
+#define VAR_DOWNLOAD_CMD vars[V_DOWNLOAD_CMD].current_val.p
+#define VAR_DOWNLOAD_CMD_PREFIX vars[V_DOWNLOAD_CMD_PREFIX].current_val.p
+#define VAR_GOTO_DEFAULT_RULE vars[V_GOTO_DEFAULT_RULE].current_val.p
+#define GLO_GOTO_DEFAULT_RULE vars[V_GOTO_DEFAULT_RULE].global_val.p
+#define VAR_MAILCAP_PATH vars[V_MAILCAP_PATH].current_val.p
+#define VAR_MIMETYPE_PATH vars[V_MIMETYPE_PATH].current_val.p
+#define VAR_FIFOPATH vars[V_FIFOPATH].current_val.p
+#define VAR_NMW_WIDTH vars[V_NMW_WIDTH].current_val.p
+#define GLO_NMW_WIDTH vars[V_NMW_WIDTH].global_val.p
+#define VAR_USERINPUTTIMEO vars[V_USERINPUTTIMEO].current_val.p
+#define GLO_USERINPUTTIMEO vars[V_USERINPUTTIMEO].global_val.p
+#define VAR_TCPOPENTIMEO vars[V_TCPOPENTIMEO].current_val.p
+#define VAR_TCPREADWARNTIMEO vars[V_TCPREADWARNTIMEO].current_val.p
+#define VAR_TCPWRITEWARNTIMEO vars[V_TCPWRITEWARNTIMEO].current_val.p
+#define VAR_TCPQUERYTIMEO vars[V_TCPQUERYTIMEO].current_val.p
+#define VAR_RSHOPENTIMEO vars[V_RSHOPENTIMEO].current_val.p
+#define VAR_RSHPATH vars[V_RSHPATH].current_val.p
+#define VAR_RSHCMD vars[V_RSHCMD].current_val.p
+#define VAR_SSHOPENTIMEO vars[V_SSHOPENTIMEO].current_val.p
+#define VAR_INCCHECKTIMEO vars[V_INCCHECKTIMEO].current_val.p
+#define GLO_INCCHECKTIMEO vars[V_INCCHECKTIMEO].global_val.p
+#define VAR_INCCHECKINTERVAL vars[V_INCCHECKINTERVAL].current_val.p
+#define GLO_INCCHECKINTERVAL vars[V_INCCHECKINTERVAL].global_val.p
+#define VAR_INC2NDCHECKINTERVAL vars[V_INC2NDCHECKINTERVAL].current_val.p
+#define GLO_INC2NDCHECKINTERVAL vars[V_INC2NDCHECKINTERVAL].global_val.p
+#define VAR_INCCHECKLIST vars[V_INCCHECKLIST].current_val.l
+#define VAR_SSHPATH vars[V_SSHPATH].current_val.p
+#define GLO_SSHPATH vars[V_SSHPATH].global_val.p
+#define VAR_SSHCMD vars[V_SSHCMD].current_val.p
+#define GLO_SSHCMD vars[V_SSHCMD].global_val.p
+#define VAR_NEW_VER_QUELL vars[V_NEW_VER_QUELL].current_val.p
+#define VAR_BROWSER vars[V_BROWSER].current_val.l
+#define VAR_INCOMING_STARTUP vars[V_INCOMING_STARTUP].current_val.p
+#define GLO_INCOMING_STARTUP vars[V_INCOMING_STARTUP].global_val.p
+#define VAR_PRUNING_RULE vars[V_PRUNING_RULE].current_val.p
+#define GLO_PRUNING_RULE vars[V_PRUNING_RULE].global_val.p
+#define VAR_REOPEN_RULE vars[V_REOPEN_RULE].current_val.p
+#define GLO_REOPEN_RULE vars[V_REOPEN_RULE].global_val.p
+#define VAR_THREAD_DISP_STYLE vars[V_THREAD_DISP_STYLE].current_val.p
+#define GLO_THREAD_DISP_STYLE vars[V_THREAD_DISP_STYLE].global_val.p
+#define VAR_THREAD_INDEX_STYLE vars[V_THREAD_INDEX_STYLE].current_val.p
+#define GLO_THREAD_INDEX_STYLE vars[V_THREAD_INDEX_STYLE].global_val.p
+#define VAR_THREAD_MORE_CHAR vars[V_THREAD_MORE_CHAR].current_val.p
+#define GLO_THREAD_MORE_CHAR vars[V_THREAD_MORE_CHAR].global_val.p
+#define VAR_THREAD_EXP_CHAR vars[V_THREAD_EXP_CHAR].current_val.p
+#define GLO_THREAD_EXP_CHAR vars[V_THREAD_EXP_CHAR].global_val.p
+#define VAR_THREAD_LASTREPLY_CHAR vars[V_THREAD_LASTREPLY_CHAR].current_val.p
+#define GLO_THREAD_LASTREPLY_CHAR vars[V_THREAD_LASTREPLY_CHAR].global_val.p
+
+#if defined(DOS) || defined(OS2)
+#define VAR_FILE_DIR vars[V_FILE_DIR].current_val.p
+#define GLO_FILE_DIR vars[V_FILE_DIR].current_val.p
+#define VAR_FOLDER_EXTENSION vars[V_FOLDER_EXTENSION].current_val.p
+#define GLO_FOLDER_EXTENSION vars[V_FOLDER_EXTENSION].global_val.p
+
+#ifdef _WINDOWS
+#define VAR_FONT_NAME vars[V_FONT_NAME].current_val.p
+#define VAR_FONT_SIZE vars[V_FONT_SIZE].current_val.p
+#define VAR_FONT_STYLE vars[V_FONT_STYLE].current_val.p
+#define VAR_FONT_CHAR_SET vars[V_FONT_CHAR_SET].current_val.p
+#define VAR_PRINT_FONT_NAME vars[V_PRINT_FONT_NAME].current_val.p
+#define VAR_PRINT_FONT_SIZE vars[V_PRINT_FONT_SIZE].current_val.p
+#define VAR_PRINT_FONT_STYLE vars[V_PRINT_FONT_STYLE].current_val.p
+#define VAR_PRINT_FONT_CHAR_SET vars[V_PRINT_FONT_CHAR_SET].current_val.p
+#define VAR_WINDOW_POSITION vars[V_WINDOW_POSITION].current_val.p
+#define VAR_CURSOR_STYLE vars[V_CURSOR_STYLE].current_val.p
+#endif /* _WINDOWS */
+#endif /* DOS or OS2 */
+
+#define VAR_NORM_FORE_COLOR vars[V_NORM_FORE_COLOR].current_val.p
+#define GLO_NORM_FORE_COLOR vars[V_NORM_FORE_COLOR].global_val.p
+#define VAR_NORM_BACK_COLOR vars[V_NORM_BACK_COLOR].current_val.p
+#define GLO_NORM_BACK_COLOR vars[V_NORM_BACK_COLOR].global_val.p
+#define VAR_REV_FORE_COLOR vars[V_REV_FORE_COLOR].current_val.p
+#define GLO_REV_FORE_COLOR vars[V_REV_FORE_COLOR].global_val.p
+#define VAR_REV_BACK_COLOR vars[V_REV_BACK_COLOR].current_val.p
+#define GLO_REV_BACK_COLOR vars[V_REV_BACK_COLOR].global_val.p
+#define VAR_TITLE_FORE_COLOR vars[V_TITLE_FORE_COLOR].current_val.p
+#define GLO_TITLE_FORE_COLOR vars[V_TITLE_FORE_COLOR].global_val.p
+#define VAR_TITLE_BACK_COLOR vars[V_TITLE_BACK_COLOR].current_val.p
+#define GLO_TITLE_BACK_COLOR vars[V_TITLE_BACK_COLOR].global_val.p
+#define VAR_TITLECLOSED_FORE_COLOR vars[V_TITLECLOSED_FORE_COLOR].current_val.p
+#define GLO_TITLECLOSED_FORE_COLOR vars[V_TITLECLOSED_FORE_COLOR].global_val.p
+#define VAR_TITLECLOSED_BACK_COLOR vars[V_TITLECLOSED_BACK_COLOR].current_val.p
+#define GLO_TITLECLOSED_BACK_COLOR vars[V_TITLECLOSED_BACK_COLOR].global_val.p
+#define VAR_STATUS_FORE_COLOR vars[V_STATUS_FORE_COLOR].current_val.p
+#define VAR_STATUS_BACK_COLOR vars[V_STATUS_BACK_COLOR].current_val.p
+#define VAR_HEADER_GENERAL_FORE_COLOR vars[V_HEADER_GENERAL_FORE_COLOR].current_val.p
+#define VAR_HEADER_GENERAL_BACK_COLOR vars[V_HEADER_GENERAL_BACK_COLOR].current_val.p
+#define VAR_IND_PLUS_FORE_COLOR vars[V_IND_PLUS_FORE_COLOR].current_val.p
+#define GLO_IND_PLUS_FORE_COLOR vars[V_IND_PLUS_FORE_COLOR].global_val.p
+#define VAR_IND_PLUS_BACK_COLOR vars[V_IND_PLUS_BACK_COLOR].current_val.p
+#define GLO_IND_PLUS_BACK_COLOR vars[V_IND_PLUS_BACK_COLOR].global_val.p
+#define VAR_IND_IMP_FORE_COLOR vars[V_IND_IMP_FORE_COLOR].current_val.p
+#define GLO_IND_IMP_FORE_COLOR vars[V_IND_IMP_FORE_COLOR].global_val.p
+#define VAR_IND_IMP_BACK_COLOR vars[V_IND_IMP_BACK_COLOR].current_val.p
+#define GLO_IND_IMP_BACK_COLOR vars[V_IND_IMP_BACK_COLOR].global_val.p
+#define VAR_IND_DEL_FORE_COLOR vars[V_IND_DEL_FORE_COLOR].current_val.p
+#define VAR_IND_DEL_BACK_COLOR vars[V_IND_DEL_BACK_COLOR].current_val.p
+#define VAR_IND_ANS_FORE_COLOR vars[V_IND_ANS_FORE_COLOR].current_val.p
+#define GLO_IND_ANS_FORE_COLOR vars[V_IND_ANS_FORE_COLOR].global_val.p
+#define VAR_IND_ANS_BACK_COLOR vars[V_IND_ANS_BACK_COLOR].current_val.p
+#define GLO_IND_ANS_BACK_COLOR vars[V_IND_ANS_BACK_COLOR].global_val.p
+#define VAR_IND_NEW_FORE_COLOR vars[V_IND_NEW_FORE_COLOR].current_val.p
+#define GLO_IND_NEW_FORE_COLOR vars[V_IND_NEW_FORE_COLOR].global_val.p
+#define VAR_IND_NEW_BACK_COLOR vars[V_IND_NEW_BACK_COLOR].current_val.p
+#define GLO_IND_NEW_BACK_COLOR vars[V_IND_NEW_BACK_COLOR].global_val.p
+#define VAR_IND_REC_FORE_COLOR vars[V_IND_REC_FORE_COLOR].current_val.p
+#define VAR_IND_REC_BACK_COLOR vars[V_IND_REC_BACK_COLOR].current_val.p
+#define VAR_IND_FWD_FORE_COLOR vars[V_IND_FWD_FORE_COLOR].current_val.p
+#define VAR_IND_FWD_BACK_COLOR vars[V_IND_FWD_BACK_COLOR].current_val.p
+#define VAR_IND_UNS_FORE_COLOR vars[V_IND_UNS_FORE_COLOR].current_val.p
+#define VAR_IND_UNS_BACK_COLOR vars[V_IND_UNS_BACK_COLOR].current_val.p
+#define VAR_IND_OP_FORE_COLOR vars[V_IND_OP_FORE_COLOR].current_val.p
+#define GLO_IND_OP_FORE_COLOR vars[V_IND_OP_FORE_COLOR].global_val.p
+#define VAR_IND_OP_BACK_COLOR vars[V_IND_OP_BACK_COLOR].current_val.p
+#define GLO_IND_OP_BACK_COLOR vars[V_IND_OP_BACK_COLOR].global_val.p
+#define VAR_IND_SUBJ_FORE_COLOR vars[V_IND_SUBJ_FORE_COLOR].current_val.p
+#define VAR_IND_SUBJ_BACK_COLOR vars[V_IND_SUBJ_BACK_COLOR].current_val.p
+#define VAR_IND_FROM_FORE_COLOR vars[V_IND_FROM_FORE_COLOR].current_val.p
+#define VAR_IND_FROM_BACK_COLOR vars[V_IND_FROM_BACK_COLOR].current_val.p
+#define VAR_IND_HIPRI_FORE_COLOR vars[V_IND_HIPRI_FORE_COLOR].current_val.p
+#define VAR_IND_HIPRI_BACK_COLOR vars[V_IND_HIPRI_BACK_COLOR].current_val.p
+#define VAR_IND_LOPRI_FORE_COLOR vars[V_IND_LOPRI_FORE_COLOR].current_val.p
+#define VAR_IND_LOPRI_BACK_COLOR vars[V_IND_LOPRI_BACK_COLOR].current_val.p
+#define VAR_IND_ARR_FORE_COLOR vars[V_IND_ARR_FORE_COLOR].current_val.p
+#define VAR_IND_ARR_BACK_COLOR vars[V_IND_ARR_BACK_COLOR].current_val.p
+#define VAR_KEYLABEL_FORE_COLOR vars[V_KEYLABEL_FORE_COLOR].current_val.p
+#define VAR_KEYLABEL_BACK_COLOR vars[V_KEYLABEL_BACK_COLOR].current_val.p
+#define VAR_KEYNAME_FORE_COLOR vars[V_KEYNAME_FORE_COLOR].current_val.p
+#define VAR_KEYNAME_BACK_COLOR vars[V_KEYNAME_BACK_COLOR].current_val.p
+#define VAR_SLCTBL_FORE_COLOR vars[V_SLCTBL_FORE_COLOR].current_val.p
+#define VAR_SLCTBL_BACK_COLOR vars[V_SLCTBL_BACK_COLOR].current_val.p
+#define VAR_METAMSG_FORE_COLOR vars[V_METAMSG_FORE_COLOR].current_val.p
+#define GLO_METAMSG_FORE_COLOR vars[V_METAMSG_FORE_COLOR].global_val.p
+#define VAR_METAMSG_BACK_COLOR vars[V_METAMSG_BACK_COLOR].current_val.p
+#define GLO_METAMSG_BACK_COLOR vars[V_METAMSG_BACK_COLOR].global_val.p
+#define VAR_QUOTE1_FORE_COLOR vars[V_QUOTE1_FORE_COLOR].current_val.p
+#define GLO_QUOTE1_FORE_COLOR vars[V_QUOTE1_FORE_COLOR].global_val.p
+#define VAR_QUOTE1_BACK_COLOR vars[V_QUOTE1_BACK_COLOR].current_val.p
+#define GLO_QUOTE1_BACK_COLOR vars[V_QUOTE1_BACK_COLOR].global_val.p
+#define VAR_QUOTE2_FORE_COLOR vars[V_QUOTE2_FORE_COLOR].current_val.p
+#define GLO_QUOTE2_FORE_COLOR vars[V_QUOTE2_FORE_COLOR].global_val.p
+#define VAR_QUOTE2_BACK_COLOR vars[V_QUOTE2_BACK_COLOR].current_val.p
+#define GLO_QUOTE2_BACK_COLOR vars[V_QUOTE2_BACK_COLOR].global_val.p
+#define VAR_QUOTE3_FORE_COLOR vars[V_QUOTE3_FORE_COLOR].current_val.p
+#define GLO_QUOTE3_FORE_COLOR vars[V_QUOTE3_FORE_COLOR].global_val.p
+#define VAR_QUOTE3_BACK_COLOR vars[V_QUOTE3_BACK_COLOR].current_val.p
+#define GLO_QUOTE3_BACK_COLOR vars[V_QUOTE3_BACK_COLOR].global_val.p
+#define VAR_INCUNSEEN_FORE_COLOR vars[V_INCUNSEEN_FORE_COLOR].current_val.p
+#define VAR_INCUNSEEN_BACK_COLOR vars[V_INCUNSEEN_BACK_COLOR].current_val.p
+#define VAR_SIGNATURE_FORE_COLOR vars[V_SIGNATURE_FORE_COLOR].current_val.p
+#define GLO_SIGNATURE_FORE_COLOR vars[V_SIGNATURE_FORE_COLOR].global_val.p
+#define VAR_SIGNATURE_BACK_COLOR vars[V_SIGNATURE_BACK_COLOR].current_val.p
+#define GLO_SIGNATURE_BACK_COLOR vars[V_SIGNATURE_BACK_COLOR].global_val.p
+#define VAR_PROMPT_FORE_COLOR vars[V_PROMPT_FORE_COLOR].current_val.p
+#define VAR_PROMPT_BACK_COLOR vars[V_PROMPT_BACK_COLOR].current_val.p
+#define VAR_VIEW_HDR_COLORS vars[V_VIEW_HDR_COLORS].current_val.l
+#ifdef SMIME
+#define VAR_PUBLICCERT_DIR vars[V_PUBLICCERT_DIR].current_val.p
+#define GLO_PUBLICCERT_DIR vars[V_PUBLICCERT_DIR].global_val.p
+#define VAR_PRIVATEKEY_DIR vars[V_PRIVATEKEY_DIR].current_val.p
+#define GLO_PRIVATEKEY_DIR vars[V_PRIVATEKEY_DIR].global_val.p
+#define VAR_CACERT_DIR vars[V_CACERT_DIR].current_val.p
+#define GLO_CACERT_DIR vars[V_CACERT_DIR].global_val.p
+#define VAR_PUBLICCERT_CONTAINER vars[V_PUBLICCERT_CONTAINER].current_val.p
+#define VAR_PRIVATEKEY_CONTAINER vars[V_PRIVATEKEY_CONTAINER].current_val.p
+#define VAR_CACERT_CONTAINER vars[V_CACERT_CONTAINER].current_val.p
+#endif /* SMIME */
+
+
+/*
+ * Define some bitmap operations for manipulating features.
+ */
+
+#define _BITCHAR(bit) ((bit) / 8)
+#define _BITBIT(bit) (1 << ((bit) % 8))
+
+/* is bit set? */
+#define bitnset(bit,map) (((map)[_BITCHAR(bit)] & _BITBIT(bit)) ? 1 : 0)
+/* set bit */
+#define setbitn(bit,map) ((map)[_BITCHAR(bit)] |= _BITBIT(bit))
+/* clear bit */
+#define clrbitn(bit,map) ((map)[_BITCHAR(bit)] &= ~_BITBIT(bit))
+
+
+/* Feature list support */
+/* Is feature "feature" turned on? */
+#define F_ON(feature,ps) (bitnset((feature),(ps)->feature_list))
+#define F_OFF(feature,ps) (!F_ON(feature,ps))
+#define F_TURN_ON(feature,ps) (setbitn((feature),(ps)->feature_list))
+#define F_TURN_OFF(feature,ps) (clrbitn((feature),(ps)->feature_list))
+/* turn off or on depending on value */
+#define F_SET(feature,ps,value) ((value) ? F_TURN_ON((feature),(ps)) \
+ : F_TURN_OFF((feature),(ps)))
+
+/*
+ * Save message rules. if these grow, widen pine
+ * struct's save_msg_rule...
+ */
+#define SAV_RULE_DEFLT 0
+#define SAV_RULE_LAST 1
+#define SAV_RULE_FROM 2
+#define SAV_RULE_NICK_FROM_DEF 3
+#define SAV_RULE_NICK_FROM 4
+#define SAV_RULE_FCC_FROM_DEF 5
+#define SAV_RULE_FCC_FROM 6
+#define SAV_RULE_RN_FROM_DEF 7
+#define SAV_RULE_RN_FROM 8
+#define SAV_RULE_SENDER 9
+#define SAV_RULE_NICK_SENDER_DEF 10
+#define SAV_RULE_NICK_SENDER 11
+#define SAV_RULE_FCC_SENDER_DEF 12
+#define SAV_RULE_FCC_SENDER 13
+#define SAV_RULE_RN_SENDER_DEF 14
+#define SAV_RULE_RN_SENDER 15
+#define SAV_RULE_RECIP 16
+#define SAV_RULE_NICK_RECIP_DEF 17
+#define SAV_RULE_NICK_RECIP 18
+#define SAV_RULE_FCC_RECIP_DEF 19
+#define SAV_RULE_FCC_RECIP 20
+#define SAV_RULE_RN_RECIP_DEF 21
+#define SAV_RULE_RN_RECIP 22
+#define SAV_RULE_REPLYTO 23
+#define SAV_RULE_NICK_REPLYTO_DEF 24
+#define SAV_RULE_NICK_REPLYTO 25
+#define SAV_RULE_FCC_REPLYTO_DEF 26
+#define SAV_RULE_FCC_REPLYTO 27
+#define SAV_RULE_RN_REPLYTO_DEF 28
+#define SAV_RULE_RN_REPLYTO 29
+
+/*
+ * Fcc rules. if these grow, widen pine
+ * struct's fcc_rule...
+ */
+#define FCC_RULE_DEFLT 0
+#define FCC_RULE_RECIP 1
+#define FCC_RULE_LAST 2
+#define FCC_RULE_NICK 3
+#define FCC_RULE_NICK_RECIP 4
+#define FCC_RULE_CURRENT 5
+
+/*
+ * Addrbook sorting rules. if these grow, widen pine
+ * struct's ab_sort_rule...
+ */
+#define AB_SORT_RULE_FULL_LISTS 0
+#define AB_SORT_RULE_FULL 1
+#define AB_SORT_RULE_NICK_LISTS 2
+#define AB_SORT_RULE_NICK 3
+#define AB_SORT_RULE_NONE 4
+
+/*
+ * Incoming startup rules. if these grow, widen pine
+ * struct's inc_startup_rule and reset_startup_rule().
+ */
+#define IS_FIRST_UNSEEN 0
+#define IS_FIRST_RECENT 1
+#define IS_FIRST_IMPORTANT 2
+#define IS_FIRST_IMPORTANT_OR_UNSEEN 3
+#define IS_FIRST_IMPORTANT_OR_RECENT 4
+#define IS_FIRST 5
+#define IS_LAST 6
+#define IS_NOTSET 7 /* for reset version */
+
+/*
+ * Pruning rules. If these grow, widen pruning_rule.
+ */
+#define PRUNE_ASK_AND_ASK 0
+#define PRUNE_ASK_AND_NO 1
+#define PRUNE_YES_AND_ASK 2
+#define PRUNE_YES_AND_NO 3
+#define PRUNE_NO_AND_ASK 4
+#define PRUNE_NO_AND_NO 5
+
+/*
+ * Folder reopen rules. If these grow, widen reopen_rule.
+ */
+#define REOPEN_YES_YES 0
+#define REOPEN_YES_ASK_Y 1
+#define REOPEN_YES_ASK_N 2
+#define REOPEN_YES_NO 3
+#define REOPEN_ASK_ASK_Y 4
+#define REOPEN_ASK_ASK_N 5
+#define REOPEN_ASK_NO_Y 6
+#define REOPEN_ASK_NO_N 7
+#define REOPEN_NO_NO 8
+
+/*
+ * Goto default rules.
+ */
+#define GOTO_INBOX_RECENT_CLCTN 0
+#define GOTO_INBOX_FIRST_CLCTN 1
+#define GOTO_LAST_FLDR 2
+#define GOTO_FIRST_CLCTN 3
+#define GOTO_FIRST_CLCTN_DEF_INBOX 4
+
+/*
+ * Thread display styles. If these grow, widen thread_disp_rule.
+ */
+#define THREAD_NONE 0
+#define THREAD_STRUCT 1
+#define THREAD_MUTTLIKE 2
+#define THREAD_INDENT_SUBJ1 3
+#define THREAD_INDENT_SUBJ2 4
+#define THREAD_INDENT_FROM1 5
+#define THREAD_INDENT_FROM2 6
+#define THREAD_STRUCT_FROM 7
+
+/*
+ * Thread index styles. If these grow, widen thread_index_rule.
+ */
+#define THRDINDX_EXP 0
+#define THRDINDX_COLL 1
+#define THRDINDX_SEP 2
+#define THRDINDX_SEP_AUTO 3
+
+/*
+ * Titlebar color styles. If these grow, widen titlebar_color_style.
+ */
+#define TBAR_COLOR_DEFAULT 0
+#define TBAR_COLOR_INDEXLINE 1
+#define TBAR_COLOR_REV_INDEXLINE 2
+
+/*
+ * PC-Pine update registry modes
+ */
+#ifdef _WINDOWS
+#define UREG_NORMAL 0
+#define UREG_ALWAYS_SET 1
+#define UREG_NEVER_SET 2
+#endif
+
+
+/*
+ * Folder list sort rules
+ */
+#define FLD_SORT_ALPHA 0
+#define FLD_SORT_ALPHA_DIR_LAST 1
+#define FLD_SORT_ALPHA_DIR_FIRST 2
+
+
+/*
+ * Color styles
+ */
+#define COL_NONE 0
+#define COL_TERMDEF 1
+#define COL_ANSI8 2
+#define COL_ANSI16 3
+#define COL_ANSI256 4
+
+
+/* styles for apply_rev_color() */
+#define IND_COL_FLIP 0
+#define IND_COL_REV 1
+#define IND_COL_FG 2
+#define IND_COL_FG_NOAMBIG 3
+#define IND_COL_BG 4
+#define IND_COL_BG_NOAMBIG 5
+
+
+
+/*
+ * Macros to help set numeric pinerc variables. Defined here are the
+ * allowed min and max values as well as the name of the var as it
+ * should be displayed in error messages.
+ */
+#define Q_SUPP_LIMIT (4)
+#define Q_DEL_ALL (-10)
+#define SVAR_OVERLAP(ps,n,e,el) strtoval((ps)->VAR_OVERLAP, \
+ &(n), 0, 20, 0, (e), \
+ (el), \
+ "Viewer-Overlap")
+#define SVAR_MAXREMSTREAM(ps,n,e,el) strtoval((ps)->VAR_MAXREMSTREAM, \
+ &(n), 0, 15, 0, (e), \
+ (el), \
+ "Max-Remote-Connections")
+#define SVAR_MARGIN(ps,n,e,el) strtoval((ps)->VAR_MARGIN, \
+ &(n), 0, 20, 0, (e), \
+ (el), \
+ "Scroll-Margin")
+#define SVAR_FILLCOL(ps,n,e,el) strtoval((ps)->VAR_FILLCOL, \
+ &(n), 0, MAX_FILLCOL, 0, (e), \
+ (el), \
+ "Composer-Wrap-Column")
+#define SVAR_QUOTE_SUPPRESSION(ps,n,e,el) strtoval((ps)->VAR_QUOTE_SUPPRESSION, \
+ &(n), -(Q_SUPP_LIMIT-1), \
+ 1000, Q_DEL_ALL, (e), \
+ (el), \
+ "Quote-Suppression-Threshold")
+#define SVAR_DEADLETS(ps,n,e,el) strtoval((ps)->VAR_DEADLETS, \
+ &(n), 0, 9, 0, (e), \
+ (el), \
+ "Dead-Letter-Files")
+#define SVAR_MSGDLAY(ps,n,e,el) strtoval((ps)->VAR_STATUS_MSG_DELAY, \
+ &(n), -10, 30, 0, (e), \
+ (el), \
+ "Status-Message-Delay")
+#define SVAR_ACTIVEINTERVAL(ps,n,e,el) strtoval((ps)->VAR_ACTIVE_MSG_INTERVAL, \
+ &(n), 0, 20, 0, (e), \
+ (el), \
+ "Active-Msg-Updates-Per-Second")
+#define SVAR_MAILCHK(ps,n,e,el) strtoval((ps)->VAR_MAILCHECK, \
+ &(n), 15, 30000, 0, (e), \
+ (el), \
+ "Mail-Check-Interval")
+#define SVAR_MAILCHKNONCURR(ps,n,e,el) strtoval((ps)->VAR_MAILCHECKNONCURR, \
+ &(n), 15, 30000, 0, (e), \
+ (el), \
+ "Mail-Check-Interval-Noncurrent")
+#define SVAR_AB_HIST(ps,n,e,el) strtoval((ps)->VAR_REMOTE_ABOOK_HISTORY, \
+ &(n), 0, 100, 0, (e), \
+ (el), \
+ "Remote-Abook-History")
+#define SVAR_AB_VALID(ps,n,e,el) strtoval((ps)->VAR_REMOTE_ABOOK_VALIDITY, \
+ &(n), -1, 30000, 0, (e), \
+ (el), \
+ "Remote-Abook-Validity")
+#define SVAR_USER_INPUT(ps,n,e,el) strtoval((ps)->VAR_USERINPUTTIMEO, \
+ &(n), 0, 1000, 0, (e), \
+ (el), \
+ "User-Input-Timeout")
+#define SVAR_TCP_OPEN(ps,n,e,el) strtoval((ps)->VAR_TCPOPENTIMEO, \
+ &(n), 5, 30000, 5, (e), \
+ (el), \
+ "Tcp-Open-Timeout")
+#define SVAR_TCP_READWARN(ps,n,e,el) strtoval((ps)->VAR_TCPREADWARNTIMEO, \
+ &(n), 5, 30000, 5, (e), \
+ (el), \
+ "Tcp-Read-Warning-Timeout")
+#define SVAR_TCP_WRITEWARN(ps,n,e,el) strtoval((ps)->VAR_TCPWRITEWARNTIMEO, \
+ &(n), 5, 30000, 0, (e), \
+ (el), \
+ "Tcp-Write-Warning-Timeout")
+#define SVAR_TCP_QUERY(ps,n,e,el) strtoval((ps)->VAR_TCPQUERYTIMEO, \
+ &(n), 5, 30000, 0, (e), \
+ (el), \
+ "Tcp-Query-Timeout")
+#define SVAR_RSH_OPEN(ps,n,e,el) strtoval((ps)->VAR_RSHOPENTIMEO, \
+ &(n), 5, 30000, 0, (e), \
+ (el), \
+ "Rsh-Open-Timeout")
+#define SVAR_SSH_OPEN(ps,n,e,el) strtoval((ps)->VAR_SSHOPENTIMEO, \
+ &(n), 5, 30000, 0, (e), \
+ (el), \
+ "Ssh-Open-Timeout")
+#define SVAR_INC_CHECK_TIMEO(ps,n,e,el) strtoval((ps)->VAR_INCCHECKTIMEO, \
+ &(n), 1, 30000, 1, (e), \
+ (el), \
+ "Incoming-Check-Timeout")
+#define SVAR_INC_CHECK_INTERV(ps,n,e,el) strtoval((ps)->VAR_INCCHECKINTERVAL, \
+ &(n), 15, 30000, 0, (e), \
+ (el), \
+ "Incoming-Check-Interval")
+
+#define SVAR_INC_2NDCHECK_INTERV(ps,n,e,el) strtoval((ps)->VAR_INC2NDCHECKINTERVAL, \
+ &(n), 15, 30000, 0, (e), \
+ (el), \
+ "Incoming-Secondary-Check-Interval")
+
+#define SVAR_NNTPRANGE(ps,n,e,el) strtolval((ps)->VAR_NNTPRANGE, \
+ &(n), 0L, 30000L, 0L, (e), \
+ (el), \
+ "Nntp-Range")
+#define SVAR_MAILDCHK(ps,n,e,el) strtolval((ps)->VAR_MAILDROPCHECK, \
+ &(n), 60L, 30000L, 0L, (e), \
+ (el), \
+ "Maildrop-Check-Minimum")
+#define SVAR_NMW_WIDTH(ps,n,e,el) strtoval((ps)->VAR_NMW_WIDTH, \
+ &(n), 20, 170, 0, (e), \
+ (el), \
+ "NewMail-Window-Width")
+
+
+/*----------------------------------------------------------------------
+ struct for pruning old Fcc, usually "sent-mail" folders.
+ ----*/
+struct sm_folder {
+ char *name;
+ int month_num;
+};
+
+
+#define PVAL(v,w) ((v) ? (((w) == Main) ? (v)->main_user_val.p : \
+ (v)->post_user_val.p) : NULL)
+#define APVAL(v,w) ((v) ? (((w) == Main) ? &(v)->main_user_val.p : \
+ &(v)->post_user_val.p) : NULL)
+#define LVAL(v,w) ((v) ? (((w) == Main) ? (v)->main_user_val.l : \
+ (v)->post_user_val.l) : NULL)
+#define ALVAL(v,w) ((v) ? (((w) == Main) ? &(v)->main_user_val.l : \
+ &(v)->post_user_val.l) : NULL)
+
+
+/* use shortname if present, else regular long name */
+#define S_OR_L(v) (((v) && (v)->shortname) ? (v)->shortname : \
+ ((v) ? (v)->name : NULL))
+
+
+/*
+ * Flags for write_pinerc
+ */
+#define WRP_NONE 0
+#define WRP_NOUSER 0x01
+#define WRP_PRESERV_WRITTEN 0x02
+
+
+#define ALL_EXCEPT "all-except"
+#define INHERIT "INHERIT"
+#define HIDDEN_PREF "Normally Hidden Preferences"
+#define WYSE_PRINTER "attached-to-wyse"
+#define METASTR "\nremote-abook-metafile="
+
+
+#define CONF_TXT_T char
+
+
+/* exported protoypes */
+void init_init_vars(struct pine *);
+void init_pinerc(struct pine *, char **);
+void init_vars(struct pine *, void (*)(struct pine *, char **));
+char *feature_list_section(FEATURE_S *);
+FEATURE_S *feature_list(int);
+int feature_list_index(int);
+int feature_list_id(char *);
+char *feature_list_name(int);
+struct variable *var_from_name(char *);
+HelpType feature_list_help(int);
+void process_feature_list(struct pine *, char **, int , int, int);
+void cur_rule_value(struct variable *, int, int);
+NAMEVAL_S *save_msg_rules(int);
+NAMEVAL_S *fcc_rules(int);
+NAMEVAL_S *ab_sort_rules(int);
+NAMEVAL_S *col_style(int);
+NAMEVAL_S *index_col_style(int);
+NAMEVAL_S *titlebar_col_style(int);
+NAMEVAL_S *fld_sort_rules(int);
+NAMEVAL_S *incoming_startup_rules(int);
+NAMEVAL_S *startup_rules(int);
+NAMEVAL_S *pruning_rules(int);
+NAMEVAL_S *reopen_rules(int);
+NAMEVAL_S *thread_disp_styles(int);
+NAMEVAL_S *thread_index_styles(int);
+NAMEVAL_S *goto_rules(int);
+NAMEVAL_S *pat_fldr_types(int);
+NAMEVAL_S *inabook_fldr_types(int);
+NAMEVAL_S *filter_types(int);
+NAMEVAL_S *role_repl_types(int);
+NAMEVAL_S *role_forw_types(int);
+NAMEVAL_S *role_comp_types(int);
+NAMEVAL_S *role_status_types(int);
+NAMEVAL_S *msg_state_types(int);
+#ifdef ENABLE_LDAP
+NAMEVAL_S *ldap_search_rules(int);
+NAMEVAL_S *ldap_search_types(int);
+NAMEVAL_S *ldap_search_scope(int);
+#endif
+void set_current_val(struct variable *, int, int);
+void set_news_spec_current_val(int, int);
+void set_feature_list_current_val(struct variable *);
+char *expand_variables(char *, size_t, char *, int);
+void init_error(struct pine *, int, int, int, char *);
+void read_pinerc(PINERC_S *, struct variable *, ParsePinerc);
+int write_pinerc(struct pine *, EditWhich, int);
+void quit_to_edit_msg(PINERC_S *);
+int var_in_pinerc(char *);
+void dump_global_conf(void);
+void dump_new_pinerc(char *);
+int set_variable(int, char *, int, int, EditWhich);
+int set_variable_list(int, char **, int, EditWhich);
+void set_current_color_vals(struct pine *);
+int var_defaults_to_rev(struct variable *);
+void set_custom_spec_colors(struct pine *);
+SPEC_COLOR_S *spec_color_from_var(char *, int);
+SPEC_COLOR_S *spec_colors_from_varlist(char **, int);
+char *var_from_spec_color(SPEC_COLOR_S *);
+char **varlist_from_spec_colors(SPEC_COLOR_S *);
+void update_posting_charset(struct pine *, int);
+int test_old_growth_bits(struct pine *, int);
+int feature_gets_an_x(struct pine *, struct variable *, FEATURE_S *, char **, EditWhich);
+int longest_feature_comment(struct pine *, EditWhich);
+void toggle_feature(struct pine *, struct variable *, FEATURE_S *, int, EditWhich);
+int feature_in_list(char **, char *);
+int test_feature(char **, char *, int);
+void set_feature(char ***, char *, int);
+int reset_character_set_stuff(char **);
+void parse_printer(char *, char **, char **, char **, char **, char **, char **);
+int copy_pinerc(char *, char *, char **);
+int copy_abook(char *, char *, char **);
+HelpType config_help(int, int);
+int printer_value_check_and_adjust(void);
+char **get_supported_options(void);
+unsigned reset_startup_rule(MAILSTREAM *);
+void free_pinerc_lines(PINERC_LINE **);
+void panic1(char *, char *);
+
+/* mandatory to implement prototypes */
+int set_input_timeout(int);
+int get_input_timeout();
+
+/* decide what to do: return 0 to continue, nonzero to abort pinerc write */
+int unexpected_pinerc_change();
+
+
+#endif /* PITH_CONFIG_INCLUDED */
diff --git a/pith/conftype.h b/pith/conftype.h
new file mode 100644
index 00000000..d1fddddb
--- /dev/null
+++ b/pith/conftype.h
@@ -0,0 +1,709 @@
+/*
+ * $Id: conftype.h 1155 2008-08-21 18:33:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CONFTYPE_INCLUDED
+#define PITH_CONFTYPE_INCLUDED
+
+
+#include "../pith/helptext.h"
+#include "../pith/remtype.h"
+
+
+typedef enum {Sapling, Seedling, Seasoned} FeatureLevel;
+
+
+/*
+ * The array is initialized in pith/conf.c so the order of that initialization
+ * must correspond to the order of the values here. The order is
+ * significant in that it determines the order that the variables
+ * are written into the pinerc file and the order they show up in in the
+ * config screen.
+ */
+typedef enum { V_PERSONAL_NAME = 0
+ , V_USER_ID
+ , V_USER_DOMAIN
+ , V_SMTP_SERVER
+ , V_NNTP_SERVER
+ , V_INBOX_PATH
+ , V_ARCHIVED_FOLDERS
+ , V_PRUNED_FOLDERS
+ , V_DEFAULT_FCC
+ , V_DEFAULT_SAVE_FOLDER
+ , V_POSTPONED_FOLDER
+ , V_READ_MESSAGE_FOLDER
+ , V_FORM_FOLDER
+ , V_TRASH_FOLDER
+ , V_LITERAL_SIG
+ , V_SIGNATURE_FILE
+ , V_FEATURE_LIST
+ , V_INIT_CMD_LIST
+ , V_COMP_HDRS
+ , V_CUSTOM_HDRS
+ , V_VIEW_HEADERS
+ , V_VIEW_MARGIN_LEFT
+ , V_VIEW_MARGIN_RIGHT
+ , V_QUOTE_SUPPRESSION
+ , V_SAVED_MSG_NAME_RULE
+ , V_FCC_RULE
+ , V_SORT_KEY
+ , V_AB_SORT_RULE
+ , V_FLD_SORT_RULE
+ , V_GOTO_DEFAULT_RULE
+ , V_INCOMING_STARTUP
+ , V_PRUNING_RULE
+ , V_REOPEN_RULE
+ , V_THREAD_DISP_STYLE
+ , V_THREAD_INDEX_STYLE
+ , V_THREAD_MORE_CHAR
+ , V_THREAD_EXP_CHAR
+ , V_THREAD_LASTREPLY_CHAR
+#ifndef _WINDOWS
+ , V_CHAR_SET
+ , V_OLD_CHAR_SET
+ , V_KEY_CHAR_SET
+#endif /* ! _WINDOWS */
+ , V_POST_CHAR_SET
+ , V_UNK_CHAR_SET
+ , V_EDITOR
+ , V_SPELLER
+ , V_FILLCOL
+ , V_REPLY_STRING
+ , V_REPLY_INTRO
+ , V_QUOTE_REPLACE_STRING
+ , V_WORDSEPS
+ , V_EMPTY_HDR_MSG
+ , V_IMAGE_VIEWER
+ , V_USE_ONLY_DOMAIN_NAME
+ , V_BUGS_FULLNAME
+ , V_BUGS_ADDRESS
+ , V_BUGS_EXTRAS
+ , V_SUGGEST_FULLNAME
+ , V_SUGGEST_ADDRESS
+ , V_LOCAL_FULLNAME
+ , V_LOCAL_ADDRESS
+ , V_FORCED_ABOOK_ENTRY
+ , V_KBLOCK_PASSWD_COUNT
+ , V_DISPLAY_FILTERS
+ , V_SEND_FILTER
+ , V_ALT_ADDRS
+ , V_KEYWORDS
+ , V_KW_BRACES
+ , V_OPENING_SEP
+ , V_ABOOK_FORMATS
+ , V_INDEX_FORMAT
+ , V_OVERLAP
+ , V_MARGIN
+ , V_STATUS_MSG_DELAY
+ , V_ACTIVE_MSG_INTERVAL
+ , V_MAILCHECK
+ , V_MAILCHECKNONCURR
+ , V_MAILDROPCHECK
+ , V_NNTPRANGE
+ , V_NEWSRC_PATH
+ , V_NEWS_ACTIVE_PATH
+ , V_NEWS_SPOOL_DIR
+ , V_UPLOAD_CMD
+ , V_UPLOAD_CMD_PREFIX
+ , V_DOWNLOAD_CMD
+ , V_DOWNLOAD_CMD_PREFIX
+ , V_MAILCAP_PATH
+ , V_MIMETYPE_PATH
+ , V_BROWSER
+ , V_MAXREMSTREAM
+ , V_PERMLOCKED
+ , V_INCCHECKTIMEO
+ , V_INCCHECKINTERVAL
+ , V_INC2NDCHECKINTERVAL
+ , V_INCCHECKLIST
+ , V_DEADLETS
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ , V_FIFOPATH
+#endif
+ , V_NMW_WIDTH
+ /*
+ * Starting here, the rest of the variables are hidden by default in config
+ * screen. They are exposed with expose-hidden-config feature.
+ */
+ , V_INCOMING_FOLDERS
+ , V_MAIL_DIRECTORY
+ , V_FOLDER_SPEC
+ , V_NEWS_SPEC
+ , V_ADDRESSBOOK
+ , V_GLOB_ADDRBOOK
+ , V_STANDARD_PRINTER
+ , V_LAST_TIME_PRUNE_QUESTION
+ , V_LAST_VERS_USED
+ , V_SENDMAIL_PATH
+ , V_OPER_DIR
+ , V_USERINPUTTIMEO
+#ifdef DEBUGJOURNAL
+ , V_DEBUGMEM /* obsolete */
+#endif
+ , V_TCPOPENTIMEO
+ , V_TCPREADWARNTIMEO
+ , V_TCPWRITEWARNTIMEO
+ , V_TCPQUERYTIMEO
+ , V_RSHCMD
+ , V_RSHPATH
+ , V_RSHOPENTIMEO
+ , V_SSHCMD
+ , V_SSHPATH
+ , V_SSHOPENTIMEO
+ , V_NEW_VER_QUELL
+ , V_DISABLE_DRIVERS
+ , V_DISABLE_AUTHS
+ , V_REMOTE_ABOOK_METADATA
+ , V_REMOTE_ABOOK_HISTORY
+ , V_REMOTE_ABOOK_VALIDITY
+ , V_PRINTER
+ , V_PERSONAL_PRINT_COMMAND
+ , V_PERSONAL_PRINT_CATEGORY
+ , V_PATTERNS /* obsolete */
+ , V_PAT_ROLES
+ , V_PAT_FILTS
+ , V_PAT_FILTS_OLD /* obsolete */
+ , V_PAT_SCORES
+ , V_PAT_SCORES_OLD /* obsolete */
+ , V_PAT_INCOLS
+ , V_PAT_OTHER
+ , V_PAT_SRCH
+ , V_ELM_STYLE_SAVE /* obsolete */
+ , V_HEADER_IN_REPLY /* obsolete */
+ , V_FEATURE_LEVEL /* obsolete */
+ , V_OLD_STYLE_REPLY /* obsolete */
+ , V_COMPOSE_MIME /* obsolete */
+ , V_SHOW_ALL_CHARACTERS /* obsolete */
+ , V_SAVE_BY_SENDER /* obsolete */
+#if defined(DOS) || defined(OS2)
+ , V_FILE_DIR
+ , V_FOLDER_EXTENSION
+#endif
+#ifndef _WINDOWS
+ , V_COLOR_STYLE
+#endif
+ , V_INDEX_COLOR_STYLE
+ , V_TITLEBAR_COLOR_STYLE
+ , V_NORM_FORE_COLOR
+ , V_NORM_BACK_COLOR
+ , V_REV_FORE_COLOR
+ , V_REV_BACK_COLOR
+ , V_TITLE_FORE_COLOR
+ , V_TITLE_BACK_COLOR
+ , V_TITLECLOSED_FORE_COLOR
+ , V_TITLECLOSED_BACK_COLOR
+ , V_STATUS_FORE_COLOR
+ , V_STATUS_BACK_COLOR
+ , V_KEYLABEL_FORE_COLOR
+ , V_KEYLABEL_BACK_COLOR
+ , V_KEYNAME_FORE_COLOR
+ , V_KEYNAME_BACK_COLOR
+ , V_SLCTBL_FORE_COLOR
+ , V_SLCTBL_BACK_COLOR
+ , V_METAMSG_FORE_COLOR
+ , V_METAMSG_BACK_COLOR
+ , V_QUOTE1_FORE_COLOR
+ , V_QUOTE1_BACK_COLOR
+ , V_QUOTE2_FORE_COLOR
+ , V_QUOTE2_BACK_COLOR
+ , V_QUOTE3_FORE_COLOR
+ , V_QUOTE3_BACK_COLOR
+ , V_INCUNSEEN_FORE_COLOR
+ , V_INCUNSEEN_BACK_COLOR
+ , V_SIGNATURE_FORE_COLOR
+ , V_SIGNATURE_BACK_COLOR
+ , V_PROMPT_FORE_COLOR
+ , V_PROMPT_BACK_COLOR
+ , V_HEADER_GENERAL_FORE_COLOR
+ , V_HEADER_GENERAL_BACK_COLOR
+ , V_IND_PLUS_FORE_COLOR
+ , V_IND_PLUS_BACK_COLOR
+ , V_IND_IMP_FORE_COLOR
+ , V_IND_IMP_BACK_COLOR
+ , V_IND_DEL_FORE_COLOR
+ , V_IND_DEL_BACK_COLOR
+ , V_IND_ANS_FORE_COLOR
+ , V_IND_ANS_BACK_COLOR
+ , V_IND_NEW_FORE_COLOR
+ , V_IND_NEW_BACK_COLOR
+ , V_IND_REC_FORE_COLOR
+ , V_IND_REC_BACK_COLOR
+ , V_IND_FWD_FORE_COLOR
+ , V_IND_FWD_BACK_COLOR
+ , V_IND_UNS_FORE_COLOR
+ , V_IND_UNS_BACK_COLOR
+ , V_IND_HIPRI_FORE_COLOR
+ , V_IND_HIPRI_BACK_COLOR
+ , V_IND_LOPRI_FORE_COLOR
+ , V_IND_LOPRI_BACK_COLOR
+ , V_IND_ARR_FORE_COLOR
+ , V_IND_ARR_BACK_COLOR
+ , V_IND_SUBJ_FORE_COLOR
+ , V_IND_SUBJ_BACK_COLOR
+ , V_IND_FROM_FORE_COLOR
+ , V_IND_FROM_BACK_COLOR
+ , V_IND_OP_FORE_COLOR
+ , V_IND_OP_BACK_COLOR
+ , V_VIEW_HDR_COLORS
+ , V_KW_COLORS
+#if defined(DOS) || defined(OS2)
+#ifdef _WINDOWS
+ , V_FONT_NAME
+ , V_FONT_SIZE
+ , V_FONT_STYLE
+ , V_FONT_CHAR_SET
+ , V_PRINT_FONT_NAME
+ , V_PRINT_FONT_SIZE
+ , V_PRINT_FONT_STYLE
+ , V_PRINT_FONT_CHAR_SET
+ , V_WINDOW_POSITION
+ , V_CURSOR_STYLE
+#endif
+#endif
+#ifdef SMIME
+ , V_PUBLICCERT_DIR
+ , V_PUBLICCERT_CONTAINER
+ , V_PRIVATEKEY_DIR
+ , V_PRIVATEKEY_CONTAINER
+ , V_CACERT_DIR
+ , V_CACERT_CONTAINER
+#endif
+#ifdef ENABLE_LDAP
+ , V_LDAP_SERVERS /* should be last so make will work right */
+#endif
+ , V_RSS_NEWS
+ , V_RSS_WEATHER
+ , V_WP_INDEXHEIGHT
+ , V_WP_INDEXLINES
+ , V_WP_AGGSTATE
+ , V_WP_STATE
+ , V_WP_COLUMNS
+ , V_DUMMY
+} VariableIndex;
+
+#define V_LAST_VAR (V_DUMMY - 1)
+
+
+/*
+ * The list of feature numbers (which bit goes with which feature).
+ * The order of the features is not significant.
+ */
+typedef enum {
+ F_OLD_GROWTH = 0,
+ F_ENABLE_FULL_HDR,
+ F_ENABLE_PIPE,
+ F_ENABLE_TAB_COMPLETE,
+ F_QUIT_WO_CONFIRM,
+ F_ENABLE_JUMP,
+ F_ENABLE_ALT_ED,
+ F_ENABLE_BOUNCE,
+ F_ENABLE_AGG_OPS,
+ F_ENABLE_FLAG,
+ F_CAN_SUSPEND,
+ F_USE_FK,
+ F_INCLUDE_HEADER,
+ F_SIG_AT_BOTTOM,
+ F_DEL_SKIPS_DEL,
+ F_AUTO_EXPUNGE,
+ F_FULL_AUTO_EXPUNGE,
+ F_EXPUNGE_MANUALLY,
+ F_AUTO_READ_MSGS,
+ F_AUTO_FCC_ONLY,
+ F_READ_IN_NEWSRC_ORDER,
+ F_SELECT_WO_CONFIRM,
+ F_SAVE_PARTIAL_WO_CONFIRM,
+ F_NEXT_THRD_WO_CONFIRM,
+ F_USE_CURRENT_DIR,
+ F_STARTUP_STAYOPEN,
+ F_USE_RESENTTO,
+ F_SAVE_WONT_DELETE,
+ F_SAVE_ADVANCES,
+ F_UNSELECT_WONT_ADVANCE,
+ F_FORCE_LOW_SPEED,
+ F_FORCE_ARROW,
+ F_PRUNE_USES_ISO,
+ F_ALT_ED_NOW,
+ F_SHOW_DELAY_CUE,
+ F_CANCEL_CONFIRM,
+ F_AUTO_OPEN_NEXT_UNREAD,
+ F_DISABLE_INDEX_LOCALE_DATES,
+ F_SELECTED_SHOWN_BOLD,
+ F_QUOTE_ALL_FROMS,
+ F_AUTO_INCLUDE_IN_REPLY,
+ F_DISABLE_CONFIG_SCREEN,
+ F_DISABLE_PASSWORD_CACHING,
+ F_DISABLE_REGEX,
+ F_DISABLE_PASSWORD_CMD,
+ F_DISABLE_UPDATE_CMD,
+ F_DISABLE_KBLOCK_CMD,
+ F_DISABLE_SIGEDIT_CMD,
+ F_DISABLE_ROLES_SETUP,
+ F_DISABLE_ROLES_SIGEDIT,
+ F_DISABLE_ROLES_TEMPLEDIT,
+ F_DISABLE_PIPES_IN_SIGS,
+ F_DISABLE_PIPES_IN_TEMPLATES,
+ F_ATTACHMENTS_IN_REPLY,
+ F_ENABLE_INCOMING,
+ F_ENABLE_INCOMING_CHECKING,
+ F_INCOMING_CHECKING_TOTAL,
+ F_INCOMING_CHECKING_RECENT,
+ F_NO_NEWS_VALIDATION,
+ F_QUELL_EXTRA_POST_PROMPT,
+ F_DISABLE_TAKE_LASTFIRST,
+ F_DISABLE_TAKE_FULLNAMES,
+ F_DISABLE_TERM_RESET_DISP,
+ F_DISABLE_SENDER,
+ F_ROT13_MESSAGE_ID,
+ F_QUELL_LOCAL_LOOKUP,
+ F_COMPOSE_TO_NEWSGRP,
+ F_PRESERVE_START_STOP,
+ F_COMPOSE_REJECTS_UNQUAL,
+ F_FAKE_NEW_IN_NEWS,
+ F_SUSPEND_SPAWNS,
+ F_ENABLE_8BIT,
+ F_COMPOSE_MAPS_DEL,
+ F_ENABLE_8BIT_NNTP,
+ F_ENABLE_MOUSE,
+ F_SHOW_CURSOR,
+ F_PASS_CONTROL_CHARS,
+ F_PASS_C1_CONTROL_CHARS,
+ F_SINGLE_FOLDER_LIST,
+ F_VERTICAL_FOLDER_LIST,
+ F_TAB_CHK_RECENT,
+ F_AUTO_REPLY_TO,
+ F_VERBOSE_POST,
+ F_FCC_ON_BOUNCE,
+ F_SEND_WO_CONFIRM,
+ F_USE_SENDER_NOT_X,
+ F_BLANK_KEYMENU,
+ F_DISABLE_SAVE_INPUT_HISTORY,
+ F_CUSTOM_PRINT,
+ F_DEL_FROM_DOT,
+ F_AUTO_ZOOM,
+ F_AUTO_UNZOOM,
+ F_PRINT_INDEX,
+ F_ALLOW_TALK,
+ F_AGG_PRINT_FF,
+ F_ENABLE_DOT_FILES,
+ F_ENABLE_DOT_FOLDERS,
+ F_FIRST_SEND_FILTER_DFLT,
+ F_ALWAYS_LAST_FLDR_DFLT,
+ F_TAB_TO_NEW,
+ F_MARK_FOR_CC,
+ F_WARN_ABOUT_NO_SUBJECT,
+ F_WARN_ABOUT_NO_FCC,
+ F_WARN_ABOUT_NO_TO_OR_CC,
+ F_QUELL_DEAD_LETTER,
+ F_QUELL_BEEPS,
+ F_QUELL_LOCK_FAILURE_MSGS,
+ F_ENABLE_SPACE_AS_TAB,
+ F_USE_BORING_SPINNER,
+ F_ENABLE_TAB_DELETES,
+ F_FLAG_SCREEN_KW_SHORTCUT,
+ F_FLAG_SCREEN_DFLT,
+ F_ENABLE_XTERM_NEWMAIL,
+ F_ENABLE_NEWMAIL_SHORT_TEXT,
+ F_EXPANDED_DISTLISTS,
+ F_AGG_SEQ_COPY,
+ F_DISABLE_ALARM,
+ F_DISABLE_SETLOCALE_COLLATE,
+ F_FROM_DELIM_IN_PRINT,
+ F_BACKGROUND_POST,
+ F_ALLOW_GOTO,
+ F_DSN,
+ F_ENABLE_SEARCH_AND_REPL,
+ F_ARROW_NAV,
+ F_RELAXED_ARROW_NAV,
+ F_TCAP_WINS,
+ F_ENABLE_SIGDASHES,
+ F_ENABLE_STRIP_SIGDASHES,
+ F_NEW_THREAD_ON_BLANK_SUBJECT,
+ F_QUELL_PARTIAL_FETCH,
+ F_QUELL_PERSONAL_NAME_PROMPT,
+ F_QUELL_USER_ID_PROMPT,
+ F_VIEW_SEL_ATTACH,
+ F_VIEW_SEL_URL,
+ F_VIEW_SEL_URL_HOST,
+ F_SCAN_ADDR,
+ F_FORCE_ARROWS,
+ F_PREFER_PLAIN_TEXT,
+ F_QUELL_CHARSET_WARNING,
+ F_COPY_TO_TO_FROM,
+ F_ENABLE_EDIT_REPLY_INDENT,
+ F_ENABLE_PRYNT,
+ F_ALLOW_CHANGING_FROM,
+ F_ENABLE_SUB_LISTS,
+ F_ENABLE_LESSTHAN_EXIT,
+ F_ENABLE_FAST_RECENT,
+ F_TAB_USES_UNSEEN,
+ F_ENABLE_ROLE_TAKE,
+ F_ENABLE_TAKE_EXPORT,
+ F_QUELL_ATTACH_EXTRA_PROMPT,
+ F_QUELL_ASTERISKS,
+ F_QUELL_ATTACH_EXT_WARN,
+ F_QUELL_FILTER_MSGS,
+ F_QUELL_FILTER_DONE_MSG,
+ F_SHOW_SORT,
+ F_FIX_BROKEN_LIST,
+ F_ENABLE_MULNEWSRCS,
+ F_PREDICT_NNTP_SERVER,
+ F_NEWS_CROSS_DELETE,
+ F_NEWS_CATCHUP,
+ F_QUELL_INTERNAL_MSG,
+ F_QUELL_IMAP_ENV_CB,
+ F_QUELL_NEWS_ENV_CB,
+ F_SEPARATE_FLDR_AS_DIR,
+ F_CMBND_ABOOK_DISP,
+ F_CMBND_FOLDER_DISP,
+ F_CMBND_SUBDIR_DISP,
+ F_EXPANDED_ADDRBOOKS,
+ F_EXPANDED_FOLDERS,
+ F_QUELL_EMPTY_DIRS,
+ F_SHOW_TEXTPLAIN_INT,
+ F_ROLE_CONFIRM_DEFAULT,
+ F_TAB_NO_CONFIRM,
+ F_DATES_TO_LOCAL,
+ F_RET_INBOX_NO_CONFIRM,
+ F_CHECK_MAIL_ONQUIT,
+ F_PREOPEN_STAYOPENS,
+ F_EXPUNGE_STAYOPENS,
+ F_EXPUNGE_INBOX,
+ F_NO_FCC_ATTACH,
+ F_DO_MAILCAP_PARAM_SUBST,
+ F_PREFER_ALT_AUTH,
+ F_SLCTBL_ITEM_NOBOLD,
+ F_QUELL_PINGS_COMPOSING,
+ F_QUELL_PINGS_COMPOSING_INBOX,
+ F_QUELL_BEZERK_TIMEZONE,
+ F_QUELL_CONTENT_ID,
+ F_QUELL_MAILDOMAIN_WARNING,
+ F_DISABLE_SHARED_NAMESPACES,
+ F_HIDE_NNTP_PATH,
+ F_MAILDROPS_PRESERVE_STATE,
+ F_EXPOSE_HIDDEN_CONFIG,
+ F_ALT_COMPOSE_MENU,
+ F_ALT_ROLE_MENU,
+ F_ALWAYS_SPELL_CHECK,
+ F_QUELL_TIMEZONE,
+ F_QUELL_USERAGENT,
+ F_COLOR_LINE_IMPORTANT,
+ F_SLASH_COLL_ENTIRE,
+ F_ENABLE_FULL_HDR_AND_TEXT,
+ F_QUELL_FULL_HDR_RESET,
+ F_MARK_FCC_SEEN,
+ F_MULNEWSRC_HOSTNAMES_AS_TYPED,
+ F_STRIP_WS_BEFORE_SEND,
+ F_QUELL_FLOWED_TEXT,
+ F_COMPOSE_ALWAYS_DOWNGRADE,
+ F_SORT_DEFAULT_FCC_ALPHA,
+ F_SORT_DEFAULT_SAVE_ALPHA,
+ F_QUOTE_REPLACE_NOFLOW,
+ F_AUTO_UNSELECT,
+ F_SEND_CONFIRM_ON_EXPAND,
+ F_ENABLE_NEWMAIL_SOUND,
+ F_RENDER_HTML_INTERNALLY,
+ F_ENABLE_JUMP_CMD,
+ F_FORWARD_AS_ATTACHMENT,
+#ifndef _WINDOWS
+ F_USE_SYSTEM_TRANS,
+#endif /* ! _WINDOWS */
+ F_QUELL_HOST_AFTER_URL,
+ F_NNTP_SEARCH_USES_OVERVIEW,
+ F_THREAD_SORTS_BY_ARRIVAL,
+#ifdef _WINDOWS
+ F_ENABLE_TRAYICON,
+ F_QUELL_SSL_LARGEBLOCKS,
+ F_STORE_WINPOS_IN_CONFIG,
+#endif
+#ifdef ENABLE_LDAP
+ F_ADD_LDAP_TO_ABOOK,
+#endif
+#ifdef SMIME
+ F_DONT_DO_SMIME,
+ F_SIGN_DEFAULT_ON,
+ F_ENCRYPT_DEFAULT_ON,
+ F_REMEMBER_SMIME_PASSPHRASE,
+#ifdef APPLEKEYCHAIN
+ F_PUBLICCERTS_IN_KEYCHAIN,
+#endif
+#endif
+ F_FEATURE_LIST_COUNT /* Number of features */
+} FeatureList;
+
+
+typedef struct init_err {
+ int flags, min_time, max_time;
+ char *message;
+} INIT_ERR_S;
+
+
+struct variable {
+ char *name;
+ unsigned is_obsolete:1; /* variable read in, not written unless set */
+ unsigned is_used:1; /* Some variables are disabled */
+ unsigned been_written:1;
+ unsigned is_user:1;
+ unsigned is_global:1;
+ unsigned is_list:1; /* flag indicating variable is a list */
+ unsigned is_fixed:1; /* sys mgr has fixed this variable */
+ unsigned is_onlymain:1; /* read and written from main_user_val */
+ unsigned is_outermost:1; /* read and written from outermost pinerc */
+ unsigned del_quotes:1; /* remove double quotes */
+ unsigned is_changed_val:1; /* WP: use the changed val instead of cur val */
+ char *dname; /* display name */
+ char *descrip; /* description */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } current_val;
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } main_user_val; /* from pinerc */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } changed_val; /* currently different from pinerc */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } post_user_val; /* from pinerc */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } global_val; /* from default or pine.conf */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } fixed_val; /* fixed value assigned in pine.conf.fixed */
+ union {
+ char *p; /* pointer to single string value */
+ char **l; /* pointer to list of string values */
+ } cmdline_val; /* user typed as cmdline arg */
+};
+
+
+typedef struct feature_entry {
+ char *name;
+ char *dname; /* display name, same as name if NULL */
+ int id;
+ HelpType help;
+ int section; /* for grouping in config screen */
+ int defval; /* default value, 0 or 1 */
+} FEATURE_S;
+
+
+typedef struct pinerc_line {
+ char *line;
+ struct variable *var;
+ unsigned int is_var:1;
+ unsigned int is_quoted:1;
+ unsigned int obsolete_var:1;
+} PINERC_LINE;
+
+
+/*
+ * Each pinerc has one of these.
+ */
+typedef struct pinerc_s {
+ RemType type; /* type of pinerc, remote or local */
+ char *name; /* file name or remote name */
+ REMDATA_S *rd; /* remote data structure */
+ time_t pinerc_written;
+ unsigned readonly:1;
+ unsigned outstanding_pinerc_changes:1;
+ unsigned quit_to_edit:1;
+ PINERC_LINE *pinerc_lines;
+} PINERC_S;
+
+
+typedef enum {ParsePers, ParsePersPost, ParseGlobal, ParseFixed} ParsePinerc;
+
+
+/* data stored in a line in the metadata file */
+typedef struct remote_data_meta {
+ char *local_cache_file;
+ imapuid_t uidvalidity;
+ imapuid_t uidnext;
+ imapuid_t uid;
+ unsigned long nmsgs;
+ char read_status; /* 'R' for readonly, 'W' for readwrite */
+ char *date;
+} REMDATA_META_S;
+
+
+/*
+ * Generic name/value pair structure
+ */
+typedef struct nameval {
+ char *name; /* the name that goes on the screen */
+ char *shortname; /* if non-NULL, name that goes in config file */
+ int value; /* the internal bit number */
+} NAMEVAL_S;
+
+
+typedef enum {Main, Post, None} EditWhich;
+
+
+#ifdef SMIME
+
+typedef enum {Directory, Container, Keychain, Nada} SmimeHolderType;
+
+typedef struct certlist {
+ char *name;
+ void *x509_cert; /* this is type (X509 *) */
+ struct certlist *next;
+}CertList;
+
+typedef struct smime_stuff {
+ unsigned inited:1;
+ unsigned do_sign:1; /* set true if signing */
+ unsigned do_encrypt:1; /* set true if encrypting */
+ unsigned need_passphrase:1; /* set true if loading a key failed due to lack of passphrase */
+ unsigned entered_passphrase:1; /* user entered a passphrase */
+ unsigned already_auto_asked:1; /* asked for passphrase automatically, not again */
+ volatile char passphrase[100]; /* storage for the entered passphrase */
+ char *passphrase_emailaddr; /* pointer to allocated storage */
+
+ /*
+ * If we are using the Container type it is easiest if we
+ * read in and maintain a list of certs and then write them
+ * out all at once. For Directory type we just leave the data
+ * in the individual files and read or write the individual
+ * files when needed, so we don't have a list of all the certs.
+ */
+ SmimeHolderType publictype;
+ char *publicpath;
+ char *publiccontent;
+ CertList *publiccertlist;
+
+ SmimeHolderType privatetype;
+ char *privatepath;
+ char *privatecontent;
+ void *personal_certs; /* this is type (PERSONAL_CERT *) */
+
+ SmimeHolderType catype;
+ char *capath;
+ char *cacontent;
+
+} SMIME_STUFF_S;
+
+#endif /* SMIME */
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_CONFTYPE_INCLUDED */
diff --git a/pith/context.c b/pith/context.c
new file mode 100644
index 00000000..05f2d301
--- /dev/null
+++ b/pith/context.c
@@ -0,0 +1,788 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: context.c 1144 2008-08-14 16:53:34Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/conf.h"
+#include "../pith/context.h"
+#include "../pith/state.h"
+#include "../pith/status.h"
+#include "../pith/stream.h"
+#include "../pith/folder.h"
+#include "../pith/util.h"
+#include "../pith/tempfile.h"
+
+
+/*
+ * Internal prototypes
+ */
+char *context_percent_quote(char *);
+
+
+/* Context Manager context format digester
+ * Accepts: context string and buffers for sprintf-suitable context,
+ * remote host (for display), remote context (for display),
+ * and view string
+ * Returns: NULL if successful, else error string
+ *
+ * Comments: OK, here's what we expect a fully qualified context to
+ * look like:
+ *
+ * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
+ *
+ * 2) It's understood that double "[" or "]" are used to
+ * quote a literal '[' or ']' in a context name.
+ *
+ * 3) an empty view in context implies a view of '*', so that's
+ * what get's put in the view string
+ *
+ * The 2nd,3rd,4th, and 5th args all have length at least len.
+ *
+ */
+char *
+context_digest(char *context, char *scontext, char *host, char *rcontext,
+ char *view, size_t len)
+{
+ char *p, *viewp = view;
+ char *scontextp = scontext;
+ int i = 0;
+
+ if((p = context) == NULL || *p == '\0'){
+ if(scontext) /* so the caller can apply */
+ strncpy(scontext, "%s", len); /* folder names as is. */
+
+ scontext[len-1] = '\0';
+ return(NULL); /* no error, just empty context */
+ }
+
+ /* find hostname if requested and exists */
+ if(*p == '{' || (*p == '*' && *++p == '{')){
+ for(p++; *p && *p != '}' ; p++)
+ if(host && p-context < len-1)
+ *host++ = *p; /* copy host if requested */
+
+ while(*p && *p != '}') /* find end of imap host */
+ p++;
+
+ if(*p == '\0')
+ return("Unbalanced '}'"); /* bogus. */
+ else
+ p++; /* move into next field */
+ }
+
+ for(; *p ; p++){ /* get thru context */
+ if(rcontext && i < len-1)
+ rcontext[i] = *p; /* copy if requested */
+
+ i++;
+
+ if(*p == '['){ /* done? */
+ if(*++p == '\0') /* look ahead */
+ return("Unbalanced '['");
+
+ if(*p != '[') /* not quoted: "[[" */
+ break;
+ }
+ else if(*p == ']' && *(p+1) == ']') /* these may be quoted, too */
+ p++;
+ }
+
+ if(*p == '\0')
+ return("No '[' in context");
+
+ for(; *p ; p++){ /* possibly in view portion ? */
+ if(*p == ']'){
+ if(*(p+1) == ']') /* is quoted */
+ p++;
+ else
+ break;
+ }
+
+ if(viewp && viewp-view < len-1)
+ *viewp++ = *p;
+ }
+
+ if(*p != ']')
+ return("No ']' in context");
+
+ for(; *p ; p++){ /* trailing context ? */
+ if(rcontext && i < len-1)
+ rcontext[i] = *p;
+ i++;
+ }
+
+ if(host) *host = '\0';
+ if(rcontext && i < len) rcontext[i] = '\0';
+ if(viewp) {
+/* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
+ if(viewp == view && viewp-view < len-1)
+ *viewp++ = '*';
+*/
+
+ *viewp = '\0';
+ }
+
+ if(scontextp){ /* sprint'able context request ? */
+ if(*context == '*'){
+ if(scontextp-scontext < len-1)
+ *scontextp++ = *context;
+
+ context++;
+ }
+
+ if(*context == '{'){
+ while(*context && *context != '}'){
+ if(scontextp-scontext < len-1)
+ *scontextp++ = *context;
+
+ context++;
+ }
+
+ *scontextp++ = '}';
+ }
+
+ for(p = rcontext; *p ; p++){
+ if(*p == '[' && *(p+1) == ']'){
+ if(scontextp-scontext < len-2){
+ *scontextp++ = '%'; /* replace "[]" with "%s" */
+ *scontextp++ = 's';
+ }
+
+ p++; /* skip ']' */
+ }
+ else if(scontextp-scontext < len-1)
+ *scontextp++ = *p;
+ }
+
+ *scontextp = '\0';
+ }
+
+ return(NULL); /* no problems to report... */
+}
+
+
+/* Context Manager apply name to context
+ * Accepts: buffer to write, context to apply, ambiguous folder name
+ * Returns: buffer filled with fully qualified name in context
+ * No context applied if error
+ */
+char *
+context_apply(char *b, CONTEXT_S *c, char *name, size_t len)
+{
+ if(!c || IS_REMOTE(name) ||
+ (!IS_REMOTE(c->context) && is_absolute_path(name))){
+ strncpy(b, name, len-1); /* no context! */
+ }
+ else if(name[0] == '#'){
+ if(IS_REMOTE(c->context)){
+ char *p = strchr(c->context, '}'); /* name specifies namespace */
+ snprintf(b, len, "%.*s", MIN(p - c->context + 1, len-1), c->context);
+ b[MIN(p - c->context + 1, len-1)] = '\0';
+ snprintf(b+strlen(b), len-strlen(b), "%.*s", len-1-strlen(b), name);
+ }
+ else{
+ strncpy(b, name, len-1);
+ }
+ }
+ else if(c->dir && c->dir->ref){ /* has reference string! */
+ snprintf(b, len, "%.*s", len-1, c->dir->ref);
+ b[len-1] = '\0';
+ snprintf(b+strlen(b), len-strlen(b), "%.*s", len-1-strlen(b), name);
+ }
+ else{ /* no ref, apply to context */
+ char *pq = NULL;
+
+ /*
+ * Have to quote %s for the sprintf because we're using context
+ * as a format string.
+ */
+ pq = context_percent_quote(c->context);
+
+ if(strlen(c->context) + strlen(name) < len)
+ snprintf(b, len, pq, name);
+ else{
+ char *t;
+ size_t l;
+
+ l = strlen(pq)+strlen(name);
+ t = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(t, l+1, pq, name);
+ strncpy(b, t, len-1);
+ fs_give((void **)&t);
+ }
+
+ if(pq)
+ fs_give((void **) &pq);
+ }
+
+ b[len-1] = '\0';
+ return(b);
+}
+
+
+/*
+ * Insert % before existing %'s so printf will print a real %.
+ * This is a special routine just for contexts. It only does the % stuffing
+ * for %'s inside of the braces of the context name, not for %'s to
+ * the right of the braces, which we will be using for printf format strings.
+ * Returns a malloced string which the caller is responsible for.
+ */
+char *
+context_percent_quote(char *context)
+{
+ char *pq = NULL;
+
+ if(!context || !*context)
+ pq = cpystr("");
+ else{
+ if(IS_REMOTE(context)){
+ char *end, *p, *q;
+
+ /* don't worry about size efficiency, just allocate double */
+ pq = (char *) fs_get((2*strlen(context) + 1) * sizeof(char));
+
+ end = strchr(context, '}');
+ p = context;
+ q = pq;
+ while(*p){
+ if(*p == '%' && p < end)
+ *q++ = '%';
+
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+ }
+ else
+ pq = cpystr(context);
+ }
+
+ return(pq);
+}
+
+
+/* Context Manager check if name is ambiguous
+ * Accepts: candidate string
+ * Returns: T if ambiguous, NIL if fully-qualified
+ */
+
+int
+context_isambig (char *s)
+{
+ return(!(*s == '{' || *s == '#'));
+}
+
+
+/*----------------------------------------------------------------------
+ Check to see if user is allowed to read or write this folder.
+
+ Args: s -- the name to check
+
+ Result: Returns 1 if OK
+ Returns 0 and posts an error message if access is denied
+ ----*/
+int
+context_allowed(char *s)
+{
+ struct variable *vars = ps_global ? ps_global->vars : NULL;
+ int retval = 1;
+ MAILSTREAM stream; /* fake stream for error message in mm_notify */
+
+ if(ps_global
+ && ps_global->restricted
+ && (strindex("./~", s[0]) || srchstr(s, "/../"))){
+ stream.mailbox = s;
+ mm_notify(&stream, "Restricted mode doesn't allow operation", WARN);
+ retval = 0;
+ }
+ else if(vars && VAR_OPER_DIR
+ && s[0] != '{' && !(s[0] == '*' && s[1] == '{')
+ && strucmp(s,ps_global->inbox_name) != 0
+ && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){
+ char *p, *free_this = NULL;
+
+ p = s;
+ if(strindex(s, '~')){
+ p = strindex(s, '~');
+ free_this = (char *)fs_get(strlen(p) + 200);
+ strncpy(free_this, p, strlen(p)+200);
+ fnexpand(free_this, strlen(p)+200);
+ p = free_this;
+ }
+ else if(p[0] != '/'){ /* add home dir to relative paths */
+ free_this = p = (char *)fs_get(strlen(s)
+ + strlen(ps_global->home_dir) + 2);
+ build_path(p, ps_global->home_dir, s,
+ strlen(s)+strlen(ps_global->home_dir)+2);
+ }
+
+ if(!in_dir(VAR_OPER_DIR, p)){
+ char err[200];
+
+ /* TRANSLATORS: User is restricted to operating within a certain directory */
+ snprintf(err, sizeof(err), _("Not allowed outside of %.150s"), VAR_OPER_DIR);
+ stream.mailbox = p;
+ mm_notify(&stream, err, WARN);
+ retval = 0;
+ }
+ else if(srchstr(p, "/../")){ /* check for .. in path */
+ stream.mailbox = p;
+ mm_notify(&stream, "\"..\" not allowed in name", WARN);
+ retval = 0;
+ }
+
+ if(free_this)
+ fs_give((void **)&free_this);
+ }
+
+ return retval;
+}
+
+
+
+/* Context Manager create mailbox
+ * Accepts: context
+ * mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_create (CONTEXT_S *context, MAILSTREAM *stream, char *mailbox)
+{
+ char tmp[MAILTMPLEN]; /* must be within context */
+
+ return(context_allowed(context_apply(tmp, context, mailbox, sizeof(tmp)))
+ ? pine_mail_create(stream,tmp) : 0L);
+}
+
+
+/* Context Manager open
+ * Accepts: context
+ * candidate stream for recycling
+ * mailbox name
+ * open options
+ * Returns: stream to use on success, NIL on failure
+ */
+
+MAILSTREAM *
+context_open (CONTEXT_S *context, MAILSTREAM *old, char *name, long int opt, long int *retflags)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp)))){
+ /*
+ * If a stream is passed to context_open, we will either re-use
+ * it or close it.
+ */
+ if(old)
+ pine_mail_close(old);
+
+ return((MAILSTREAM *)NULL);
+ }
+
+ return(pine_mail_open(old, tmp, opt, retflags));
+}
+
+
+/* Context Manager status
+ * Accepts: context
+ * candidate stream for recycling
+ * mailbox name
+ * open options
+ * Returns: T if call succeeds, NIL on failure
+ */
+
+long
+context_status(CONTEXT_S *context, MAILSTREAM *stream, char *name, long int opt)
+{
+ return(context_status_full(context, stream, name, opt, NULL, NULL));
+}
+
+
+long
+context_status_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
+ long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+ long flags = opt;
+
+ return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
+ ? pine_mail_status_full(stream,tmp,flags,uidvalidity,uidnext) : 0L);
+}
+
+
+/* Context Manager status
+ *
+ * This is very similar to context_status. Instead of a stream pointer we
+ * receive a pointer to a pointer so that we can return a stream that we
+ * opened for further use by the caller.
+ *
+ * Accepts: context
+ * candidate stream for recycling
+ * mailbox name
+ * open options
+ * Returns: T if call succeeds, NIL on failure
+ */
+
+long
+context_status_streamp(CONTEXT_S *context, MAILSTREAM **streamp, char *name, long int opt)
+{
+ return(context_status_streamp_full(context,streamp,name,opt,NULL,NULL));
+}
+
+
+long
+context_status_streamp_full(CONTEXT_S *context, MAILSTREAM **streamp, char *name,
+ long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
+{
+ MAILSTREAM *stream;
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp))))
+ return(0L);
+
+ if(!streamp)
+ stream = NULL;
+ else{
+ if(!*streamp && IS_REMOTE(tmp)){
+ *streamp = pine_mail_open(NULL, tmp,
+ OP_SILENT|OP_HALFOPEN|SP_USEPOOL, NULL);
+ }
+
+ stream = *streamp;
+ }
+
+ return(pine_mail_status_full(stream, tmp, opt, uidvalidity, uidnext));
+}
+
+
+/* Context Manager rename
+ * Accepts: context
+ * mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_rename (CONTEXT_S *context, MAILSTREAM *stream, char *old, char *new)
+{
+ char tmp[MAILTMPLEN],tmp2[MAILTMPLEN];
+
+ return((context_allowed(context_apply(tmp, context, old, sizeof(tmp)))
+ && context_allowed(context_apply(tmp2, context, new, sizeof(tmp2))))
+ ? pine_mail_rename(stream,tmp,tmp2) : 0L);
+}
+
+
+MAILSTREAM *
+context_already_open_stream(CONTEXT_S *context, char *name, int flags)
+{
+ char tmp[MAILTMPLEN];
+
+ return(already_open_stream(context_apply(tmp, context, name, sizeof(tmp)),
+ flags));
+}
+
+
+/* Context Manager delete mailbox
+ * Accepts: context
+ * mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_delete (CONTEXT_S *context, MAILSTREAM *stream, char *name)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
+ ? pine_mail_delete(stream, tmp) : 0L);
+}
+
+
+/* Context Manager append message string
+ * Accepts: context
+ * mail stream
+ * destination mailbox
+ * stringstruct of message to append
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_append (CONTEXT_S *context, MAILSTREAM *stream, char *name, STRING *msg)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
+ ? pine_mail_append(stream, tmp, msg) : 0L);
+}
+
+
+/* Context Manager append message string with flags
+ * Accepts: context
+ * mail stream
+ * destination mailbox
+ * flags to assign message being appended
+ * date of message being appended
+ * stringstruct of message to append
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_append_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
+ char *flags, char *date, STRING *msg)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
+ ? pine_mail_append_full(stream, tmp, flags, date, msg) : 0L);
+}
+
+
+/* Context Manager append multiple message
+ * Accepts: context
+ * mail stream
+ * destination mailbox
+ * append data callback
+ * arbitrary ata for callback use
+ * Returns: T on success, NIL on failure
+ */
+
+long
+context_append_multiple(CONTEXT_S *context, MAILSTREAM *stream, char *name,
+ append_t af, APPENDPACKAGE *data, MAILSTREAM *not_this_stream)
+{
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
+ ? pine_mail_append_multiple(stream, tmp, af, data, not_this_stream) : 0L);
+}
+
+
+/* Mail copy message(s)
+ * Accepts: context
+ * mail stream
+ * sequence
+ * destination mailbox
+ */
+
+long
+context_copy (CONTEXT_S *context, MAILSTREAM *stream, char *sequence, char *name)
+{
+ char *s, tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ tmp[0] = '\0';
+
+ if(context_apply(tmp, context, name, sizeof(tmp))[0] == '{'){
+ if((s = strindex(tmp, '}')) != NULL)
+ s++;
+ else
+ return(0L);
+ }
+ else
+ s = tmp;
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ if(!*s)
+ strncpy(s = tmp, "INBOX", sizeof(tmp)); /* presume "inbox" ala c-client */
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ return(context_allowed(s) ? pine_mail_copy(stream, sequence, s) : 0L);
+}
+
+
+/*
+ * Context manager stream usefulness test
+ * Accepts: context
+ * mail name
+ * mailbox name
+ * mail stream to test against mailbox name
+ * Returns: stream if useful, else NIL
+ */
+MAILSTREAM *
+context_same_stream(CONTEXT_S *context, char *name, MAILSTREAM *stream)
+{
+ extern MAILSTREAM *same_stream(char *name, MAILSTREAM *stream);
+ char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
+
+ return(same_stream(context_apply(tmp, context, name, sizeof(tmp)), stream));
+}
+
+
+/*
+ * new_context - creates and fills in a new context structure, leaving
+ * blank the actual folder list (to be filled in later as
+ * needed). Also, parses the context string provided
+ * picking out any user defined label. Context lines are
+ * of the form:
+ *
+ * [ ["] <string> ["] <white-space>] <context>
+ *
+ */
+CONTEXT_S *
+new_context(char *cntxt_string, int *prime)
+{
+ CONTEXT_S *c;
+ char host[MAXPATH], rcontext[MAXPATH],
+ view[MAXPATH], dcontext[MAXPATH],
+ *nickname = NULL, *c_string = NULL, *p;
+
+ /*
+ * do any context string parsing (like splitting user-supplied
+ * label from actual context)...
+ */
+ get_pair(cntxt_string, &nickname, &c_string, 0, 0);
+
+ if(update_bboard_spec(c_string, tmp_20k_buf, SIZEOF_20KBUF)){
+ fs_give((void **) &c_string);
+ c_string = cpystr(tmp_20k_buf);
+ }
+
+ if(c_string && *c_string == '\"')
+ (void)removing_double_quotes(c_string);
+
+ view[0] = rcontext[0] = host[0] = dcontext[0] = '\0';
+ if((p = context_digest(c_string, dcontext, host, rcontext, view, MAXPATH)) != NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Bad context, %.200s : %.200s", p, c_string);
+ fs_give((void **) &c_string);
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ return(NULL);
+ }
+ else
+ fs_give((void **) &c_string);
+
+ c = (CONTEXT_S *) fs_get(sizeof(CONTEXT_S)); /* get new context */
+ memset((void *) c, 0, sizeof(CONTEXT_S)); /* and initialize it */
+ if(*host)
+ c->server = cpystr(host); /* server + params */
+
+ c->context = cpystr(dcontext);
+
+ if(strstr(c->context, "#news."))
+ c->use |= CNTXT_NEWS;
+
+ c->dir = new_fdir(NULL, view, (c->use & CNTXT_NEWS) ? '*' : '%');
+
+ /* fix up nickname */
+ if(!(c->nickname = nickname)){ /* make one up! */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%.100s",
+ (c->use & CNTXT_NEWS)
+ ? "News"
+ : (c->dir->ref)
+ ? (c->dir->ref) :"Mail",
+ (c->server) ? " on " : "",
+ (c->server) ? c->server : "");
+ c->nickname = cpystr(tmp_20k_buf);
+ }
+
+ if(prime && !*prime){
+ *prime = 1;
+ c->use |= CNTXT_SAVEDFLT;
+ }
+
+ /* fix up label */
+ if(NEWS_TEST(c)){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sews groups%s%.100s",
+ (*host) ? "N" : "Local n", (*host) ? " on " : "",
+ (*host) ? host : "");
+ }
+ else{
+ p = srchstr(rcontext, "[]");
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%solders%s%.100s in %.*s%s",
+ (*host) ? "F" : "Local f", (*host) ? " on " : "",
+ (*host) ? host : "",
+ p ? MIN(p - rcontext, 100) : 0,
+ rcontext, (p && (p - rcontext) > 0) ? "" : "home directory");
+ }
+
+ c->label = cpystr(tmp_20k_buf);
+
+ dprint((5, "Context: %s serv:%s ref: %s view: %s\n",
+ c->context ? c->context : "?",
+ (c->server) ? c->server : "\"\"",
+ (c->dir->ref) ? c->dir->ref : "\"\"",
+ (c->dir->view.user) ? c->dir->view.user : "\"\""));
+
+ return(c);
+}
+
+
+/*
+ * Release resources associated with global context list
+ */
+void
+free_contexts(CONTEXT_S **ctxt)
+{
+ if(ctxt && *ctxt){
+ free_contexts(&(*ctxt)->next);
+ free_context(ctxt);
+ }
+}
+
+
+/*
+ * Release resources associated with the given context structure
+ */
+void
+free_context(CONTEXT_S **cntxt)
+{
+ if(cntxt && *cntxt){
+ if(*cntxt == ps_global->context_current)
+ ps_global->context_current = NULL;
+
+ if(*cntxt == ps_global->context_last)
+ ps_global->context_last = NULL;
+
+ if(*cntxt == ps_global->last_save_context)
+ ps_global->last_save_context = NULL;
+
+ if((*cntxt)->context)
+ fs_give((void **) &(*cntxt)->context);
+
+ if((*cntxt)->server)
+ fs_give((void **) &(*cntxt)->server);
+
+ if((*cntxt)->nickname)
+ fs_give((void **)&(*cntxt)->nickname);
+
+ if((*cntxt)->label)
+ fs_give((void **) &(*cntxt)->label);
+
+ if((*cntxt)->comment)
+ fs_give((void **) &(*cntxt)->comment);
+
+ if((*cntxt)->selected.reference)
+ fs_give((void **) &(*cntxt)->selected.reference);
+
+ if((*cntxt)->selected.sub)
+ free_selected(&(*cntxt)->selected.sub);
+
+ free_strlist(&(*cntxt)->selected.folders);
+
+ free_fdir(&(*cntxt)->dir, 1);
+
+ fs_give((void **)cntxt);
+ }
+}
diff --git a/pith/context.h b/pith/context.h
new file mode 100644
index 00000000..a2917332
--- /dev/null
+++ b/pith/context.h
@@ -0,0 +1,52 @@
+/*
+ * $Id: context.h 764 2007-10-23 23:44:49Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_CONTEXT_INCLUDED
+#define PITH_CONTEXT_INCLUDED
+
+
+#include "../pith/foldertype.h"
+#include "../pith/savetype.h"
+
+
+/* exported prototypes */
+char *context_digest(char *, char *, char *, char *, char *, size_t);
+char *context_apply(char *, CONTEXT_S *, char *, size_t);
+int context_isambig(char *);
+int context_allowed(char *);
+long context_create(CONTEXT_S *, MAILSTREAM *, char *);
+MAILSTREAM *context_open(CONTEXT_S *, MAILSTREAM *, char *, long, long *);
+long context_status(CONTEXT_S *, MAILSTREAM *, char *, long);
+long context_status_full(CONTEXT_S *, MAILSTREAM *, char *,
+ long, imapuid_t *, imapuid_t *);
+long context_status_streamp(CONTEXT_S *, MAILSTREAM **,
+ char *, long);
+long context_status_streamp_full(CONTEXT_S *, MAILSTREAM **, char *, long,
+ imapuid_t *, imapuid_t *);
+long context_rename(CONTEXT_S *, MAILSTREAM *, char *, char *);
+MAILSTREAM *context_already_open_stream(CONTEXT_S *, char *, int);
+long context_delete(CONTEXT_S *, MAILSTREAM *, char *);
+long context_append(CONTEXT_S *, MAILSTREAM *, char *, STRING *);
+long context_append_full(CONTEXT_S *, MAILSTREAM *, char *, char *, char *, STRING *);
+long context_append_multiple(CONTEXT_S *, MAILSTREAM *, char *, append_t,
+ APPENDPACKAGE *, MAILSTREAM *);
+long context_copy(CONTEXT_S *, MAILSTREAM *, char *, char *);
+MAILSTREAM *context_same_stream(CONTEXT_S *, char *, MAILSTREAM *);
+CONTEXT_S *new_context(char *, int *);
+void free_contexts(CONTEXT_S **);
+void free_context(CONTEXT_S **);
+
+
+#endif /* PITH_CONTEXT_INCLUDED */
diff --git a/pith/copyaddr.c b/pith/copyaddr.c
new file mode 100644
index 00000000..1c8a5565
--- /dev/null
+++ b/pith/copyaddr.c
@@ -0,0 +1,70 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: copyaddr.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/copyaddr.h"
+
+
+/*
+ * Copy the first address in list a and return it in allocated memory.
+ */
+ADDRESS *
+copyaddr(struct mail_address *a)
+{
+ ADDRESS *new = NULL;
+
+ if(a){
+ new = mail_newaddr();
+ if(a->personal)
+ new->personal = cpystr(a->personal);
+
+ if(a->adl)
+ new->adl = cpystr(a->adl);
+
+ if(a->mailbox)
+ new->mailbox = cpystr(a->mailbox);
+
+ if(a->host)
+ new->host = cpystr(a->host);
+
+ new->next = NULL;
+ }
+
+ return(new);
+}
+
+
+/*
+ * Copy the whole list a.
+ */
+ADDRESS *
+copyaddrlist(struct mail_address *a)
+{
+ ADDRESS *new = NULL, *head = NULL, *current;
+
+ for(; a; a = a->next){
+ new = copyaddr(a);
+ if(!head)
+ head = current = new;
+ else{
+ current->next = new;
+ current = new;
+ }
+ }
+
+ return(head);
+}
diff --git a/pith/copyaddr.h b/pith/copyaddr.h
new file mode 100644
index 00000000..790cffe9
--- /dev/null
+++ b/pith/copyaddr.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: copyaddr.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_COPYADDR_INCLUDED
+#define PITH_COPYADDR_INCLUDED
+
+
+/* exported protoypes */
+ADDRESS *copyaddr(ADDRESS *);
+ADDRESS *copyaddrlist(ADDRESS *);
+
+
+#endif /* PITH_COPYADDR_INCLUDED */
diff --git a/pith/debug.h b/pith/debug.h
new file mode 100644
index 00000000..912791d8
--- /dev/null
+++ b/pith/debug.h
@@ -0,0 +1,58 @@
+/*
+ * $Id: debug.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_DEBUG_INCLUDED
+#define PITH_DEBUG_INCLUDED
+
+
+/*======================================================================
+ Macros for debug printfs
+ n is debugging level:
+ 1 logs only highest level events and errors
+ 2 logs events like file writes
+ 3
+ 4 logs each command
+ 5
+ 6
+ 7 logs details of command execution (7 is highest to run any production)
+ allows core dumps without cleaning up terminal modes
+ 8
+ 9 logs gross details of command execution
+
+ ====*/
+
+
+#ifdef DEBUG
+
+#define dprint(x) { output_debug_msg x ; }
+
+/* global debugging level */
+extern int debug;
+
+/* mandatory to implement stubs */
+void output_debug_msg(int, char *fmt, ...);
+void dump_configuration(int);
+void dump_contexts();
+
+
+
+#else /* !DEBUG */
+
+#define dprint(x)
+
+#endif /* !DEBUG */
+
+
+#endif /* PITH_DEBUG_INCLUDED */
diff --git a/pith/detach.c b/pith/detach.c
new file mode 100644
index 00000000..b3122c68
--- /dev/null
+++ b/pith/detach.c
@@ -0,0 +1,837 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: detach.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/detach.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/filter.h"
+#include "../pith/mailview.h"
+#include "../pith/status.h"
+#include "../pith/addrstring.h"
+#include "../pith/bldaddr.h"
+#include "../pith/mimedesc.h"
+#include "../pith/adjtime.h"
+#include "../pith/pipe.h"
+#include "../pith/busy.h"
+#include "../pith/signal.h"
+#include "../pico/osdep/filesys.h"
+
+
+/*
+ * We need to define simple functions here for the piping and
+ * temporary storage object below. We can use the filter.c functions
+ * because they're already in use for the "putchar" function passed to
+ * detach.
+ */
+static STORE_S *detach_so = NULL;
+
+
+/*
+ * The display filter is locally global because it's set in df_trigger_cmp
+ * which sniffs at lines of the unencoded segment...
+ */
+typedef struct _trigger {
+ int (*cmp)(char *, char *);
+ char *text;
+ char *cmd;
+ struct _trigger *next;
+} TRGR_S;
+
+static char *display_filter;
+static TRGR_S *df_trigger_list;
+
+FETCH_READC_S *g_fr_desc;
+
+#define INIT_FETCH_CHUNK ((unsigned long)(8 * 1024L))
+#define MIN_FETCH_CHUNK ((unsigned long)(4 * 1024L))
+#define MAX_FETCH_CHUNK ((unsigned long)(256 * 1024L))
+#define TARGET_INTR_TIME ((unsigned long)2000000L) /* two seconds */
+#define FETCH_READC g_fr_desc->readc
+
+
+/*
+ * Internal Prototypes
+ */
+/*
+ * This function is intentionally declared without an argument type so
+ * that warnings will go away in Windows. We're using gf_io_t for both
+ * input and output functions and the arguments aren't actually the
+ * same in the two cases. We should really have a read version and
+ * a write version of gf_io_t. That's why this is like this for now.
+ */
+int detach_writec();
+TRGR_S *build_trigger_list(void);
+void blast_trigger_list(TRGR_S **);
+int df_trigger_cmp(long, char *, LT_INS_S **, void *);
+int df_trigger_cmp_text(char *, char *);
+int df_trigger_cmp_lwsp(char *, char *);
+int df_trigger_cmp_start(char *, char *);
+int fetch_readc_cleanup(void);
+char *fetch_gets(readfn_t, void *, unsigned long, GETS_DATA *);
+int fetch_readc(unsigned char *);
+
+
+
+/*----------------------------------------------------------------------
+ detach the given raw body part; don't do any decoding
+
+ Args: a bunch
+
+ Returns: NULL on success, error message otherwise
+ ----*/
+char *
+detach_raw(MAILSTREAM *stream, /* c-client stream to use */
+ long int msg_no, /* message number to deal with */
+ char *part_no, /* part number of message */
+ gf_io_t pc, /* where to put it */
+ int flags)
+{
+ FETCH_READC_S *frd = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S));
+ char *err = NULL;
+ int column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
+
+ memset(g_fr_desc = frd, 0, sizeof(FETCH_READC_S));
+ frd->stream = stream;
+ frd->msgno = msg_no;
+ frd->section = part_no;
+ frd->size = 0; /* wouldn't be here otherwise */
+ frd->readc = fetch_readc;
+ frd->chunk = pine_mail_fetch_text(stream, msg_no, NULL, &frd->read, 0);
+ frd->endp = &frd->chunk[frd->read];
+ frd->chunkp = frd->chunk;
+
+ gf_filter_init();
+ if (!(flags & FM_NOWRAP))
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(column, column, NULL, 0,
+ (flags & FM_DISPLAY)
+ ? GFW_HANDLES : 0));
+ err = gf_pipe(FETCH_READC, pc);
+
+ return(err);
+}
+
+
+/*----------------------------------------------------------------------
+ detach the given body part using the given encoding
+
+ Args: a bunch
+
+ Returns: NULL on success, error message otherwise
+ ----*/
+char *
+detach(MAILSTREAM *stream, /* c-client stream to use */
+ long int msg_no, /* message number to deal with */
+ char *part_no, /* part number of message */
+ long int partial, /* if >0, limit read to this many bytes */
+ long int *len, /* returns bytes read in this arg */
+ gf_io_t pc, /* where to put it */
+ FILTLIST_S *aux_filters, /* null terminated array of filts */
+ long flags)
+{
+ unsigned long rv;
+ unsigned long size;
+ long fetch_flags;
+ int we_cancel = 0, is_text;
+ char *status, trigger[MAILTMPLEN];
+ char *charset = NULL;
+ BODY *body;
+ static char err_string[100];
+ FETCH_READC_S fetch_part;
+
+ err_string[0] = '\0';
+
+ if(!ps_global->print && !pc_is_picotext(pc))
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ gf_filter_init(); /* prepare for filtering! */
+
+ if(!(body = mail_body(stream, msg_no, (unsigned char *) part_no)))
+ return(_("Can't find body for requested message"));
+
+ is_text = body->type == TYPETEXT;
+
+ size = body->size.bytes;
+ if(partial > 0L && partial < size)
+ size = partial;
+
+ fetch_flags = (flags & ~DT_NODFILTER);
+ fetch_readc_init(&fetch_part, stream, msg_no, part_no, body->size.bytes, partial,
+ fetch_flags);
+ rv = size ? size : 1;
+
+ switch(body->encoding) { /* handle decoding */
+ case ENC7BIT:
+ case ENC8BIT:
+ case ENCBINARY:
+ break;
+
+ case ENCBASE64:
+ gf_link_filter(gf_b64_binary, NULL);
+ break;
+
+ case ENCQUOTEDPRINTABLE:
+ gf_link_filter(gf_qp_8bit, NULL);
+ break;
+
+ case ENCOTHER:
+ default:
+ dprint((1, "detach: unknown CTE: \"%s\" (%d)\n",
+ (body->encoding <= ENCMAX
+ && body_encodings[body->encoding])
+ ? body_encodings[body->encoding]
+ : "BEYOND-KNOWN-TYPES",
+ body->encoding));
+ break;
+ }
+
+ /* convert all text to UTF-8 */
+ if(is_text){
+ charset = parameter_val(body->parameter, "charset");
+
+ /*
+ * If the charset is unlabeled or unknown replace it
+ * with the user's configured unknown charset.
+ */
+ if(!charset || !strucmp(charset, UNKNOWN_CHARSET) || !strucmp(charset, "us-ascii")){
+ if(charset)
+ fs_give((void **) &charset);
+
+ if(ps_global->VAR_UNK_CHAR_SET)
+ charset = cpystr(ps_global->VAR_UNK_CHAR_SET);
+ }
+
+ /* convert to UTF-8 */
+ if(!(charset && !strucmp(charset, "utf-8")))
+ gf_link_filter(gf_utf8, gf_utf8_opt(charset));
+
+ if(charset)
+ fs_give((void **) &charset);
+ }
+
+ /*
+ * If we're detaching a text segment and there are user-defined
+ * filters and there are text triggers to look for, install filter
+ * to let us look at each line...
+ */
+ display_filter = NULL;
+ if(is_text
+ && ps_global->tools.display_filter
+ && ps_global->tools.display_filter_trigger
+ && ps_global->VAR_DISPLAY_FILTERS
+ && !(flags & DT_NODFILTER)){
+ /* check for "static" triggers (i.e., none or CHARSET) */
+ if(!(display_filter = (*ps_global->tools.display_filter_trigger)(body, trigger, sizeof(trigger)))
+ && (df_trigger_list = build_trigger_list())){
+ /* else look for matching text trigger */
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(df_trigger_cmp, NULL));
+ }
+ }
+ else
+ /* add aux filters if we're not going to MIME decode into a temporary
+ * storage object, otherwise we pass the aux_filters on to gf_filter
+ * below so it can pass what comes out of the external filter command
+ * thru the rest of the filters...
+ */
+ for( ; aux_filters && aux_filters->filter; aux_filters++)
+ gf_link_filter(aux_filters->filter, aux_filters->data);
+
+ /*
+ * Following canonical model, after decoding convert newlines from
+ * crlf to local convention. ALSO, convert newlines if we're fetching
+ * a multipart segment since an external handler's going to have to
+ * make sense of it...
+ */
+ if(is_text || body->type == TYPEMESSAGE || body->type == TYPEMULTIPART)
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ /*
+ * If we're detaching a text segment and a user-defined filter may
+ * need to be invoked later (see below), decode the segment into
+ * a temporary storage object...
+ */
+ if(is_text
+ && ps_global->tools.display_filter
+ && ps_global->tools.display_filter_trigger
+ && ps_global->VAR_DISPLAY_FILTERS
+ && !(flags & DT_NODFILTER)
+ && !(detach_so = so_get(CharStar, NULL, EDIT_ACCESS))){
+ strncpy(err_string,
+ _("Formatting error: no space to make copy, no display filters used"), sizeof(err_string));
+ err_string[sizeof(err_string)-1] = '\0';
+ }
+
+ if((status = gf_pipe(FETCH_READC, detach_so ? detach_writec : pc)) != NULL) {
+ snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
+ rv = 0L;
+ }
+
+ /*
+ * If we wrote to a temporary area, there MAY be a user-defined
+ * filter to invoke. Filter it it (or not if no trigger match)
+ * *AND* send the output thru any auxiliary filters, destroy the
+ * temporary object and be done with it...
+ */
+ if(detach_so){
+ if(!err_string[0] && display_filter && *display_filter){
+ FILTLIST_S *p, *aux = NULL;
+ size_t count;
+
+ if(aux_filters){
+ /* insert NL conversion filters around remaining aux_filters
+ * so they're not tripped up by local NL convention
+ */
+ for(p = aux_filters; p->filter; p++) /* count aux_filters */
+ ;
+
+ count = (p - aux_filters) + 3;
+ p = aux = (FILTLIST_S *) fs_get(count * sizeof(FILTLIST_S));
+ memset(p, 0, count * sizeof(FILTLIST_S));
+ p->filter = gf_local_nvtnl;
+ p++;
+ for(; aux_filters->filter; p++, aux_filters++)
+ *p = *aux_filters;
+
+ p->filter = gf_nvtnl_local;
+ }
+
+ if((status = (*ps_global->tools.display_filter)(display_filter, detach_so, pc, aux)) != NULL){
+ snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
+ rv = 0L;
+ }
+
+ if(aux)
+ fs_give((void **)&aux);
+ }
+ else{ /* just copy it, then */
+ gf_io_t gc;
+
+ gf_set_so_readc(&gc, detach_so);
+ so_seek(detach_so, 0L, 0);
+ gf_filter_init();
+ if(aux_filters){
+ /* if other filters are involved, correct for
+ * newlines on either side of the pipe...
+ */
+ gf_link_filter(gf_local_nvtnl, NULL);
+ for( ; aux_filters->filter ; aux_filters++)
+ gf_link_filter(aux_filters->filter, aux_filters->data);
+
+ gf_link_filter(gf_nvtnl_local, NULL);
+ }
+
+ if((status = gf_pipe(gc, pc)) != NULL){ /* Second pass, sheesh */
+ snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
+ rv = 0L;
+ }
+
+ gf_clear_so_readc(detach_so);
+ }
+
+ so_give(&detach_so); /* blast temp copy */
+ }
+
+ if(!ps_global->print && we_cancel)
+ cancel_busy_cue(0);
+
+ if (len)
+ *len = rv;
+
+ if(df_trigger_list)
+ blast_trigger_list(&df_trigger_list);
+
+ return((err_string[0] == '\0') ? NULL : err_string);
+}
+
+
+int
+detach_writec(int c)
+{
+ return(so_writec(c, detach_so));
+}
+
+
+/*
+ * build_trigger_list - return possible triggers in a list of triggers
+ * structs
+ */
+TRGR_S *
+build_trigger_list(void)
+{
+ TRGR_S *tp = NULL, **trailp;
+ char **l, *test, *str, *ep, *cmd = NULL;
+ int i;
+
+ trailp = &tp;
+ for(l = ps_global->VAR_DISPLAY_FILTERS ; l && *l; l++){
+ get_pair(*l, &test, &cmd, 1, 1);
+ if(test && valid_filter_command(&cmd)){
+ *trailp = (TRGR_S *) fs_get(sizeof(TRGR_S));
+ (*trailp)->cmp = df_trigger_cmp_text;
+ str = test;
+ if(*test == '_' && (i = strlen(test)) > 10
+ && *(ep = &test[i-1]) == '_' && *--ep == ')'){
+ if(struncmp(test, "_CHARSET(", 9) == 0){
+ fs_give((void **)&test);
+ fs_give((void **)&cmd);
+ fs_give((void **)trailp);
+ continue;
+ }
+
+ if(strncmp(test+1, "LEADING(", 8) == 0){
+ (*trailp)->cmp = df_trigger_cmp_lwsp;
+ *ep = '\0';
+ str = cpystr(test+9);
+ fs_give((void **)&test);
+ }
+ else if(strncmp(test+1, "BEGINNING(", 10) == 0){
+ (*trailp)->cmp = df_trigger_cmp_start;
+ *ep = '\0';
+ str = cpystr(test+11);
+ fs_give((void **)&test);
+ }
+ }
+
+ (*trailp)->text = str;
+ (*trailp)->cmd = cmd;
+ *(trailp = &(*trailp)->next) = NULL;
+ }
+ else{
+ fs_give((void **)&test);
+ fs_give((void **)&cmd);
+ }
+ }
+
+ return(tp);
+}
+
+
+/*
+ * blast_trigger_list - zot any list of triggers we've been using
+ */
+void
+blast_trigger_list(TRGR_S **tlist)
+{
+ if((*tlist)->next)
+ blast_trigger_list(&(*tlist)->next);
+
+ fs_give((void **)&(*tlist)->text);
+ fs_give((void **)&(*tlist)->cmd);
+ fs_give((void **)tlist);
+}
+
+
+/*
+ * df_trigger_cmp - compare the line passed us with the list of defined
+ * display filter triggers
+ */
+int
+df_trigger_cmp(long int n, char *s, LT_INS_S **e, void *l)
+{
+ register TRGR_S *tp;
+ int result;
+
+ if(!display_filter) /* already found? */
+ for(tp = df_trigger_list; tp; tp = tp->next)
+ if(tp->cmp){
+ if((result = (*tp->cmp)(s, tp->text)) < 0)
+ tp->cmp = NULL;
+ else if(result > 0)
+ return(((display_filter = tp->cmd) != NULL) ? 1 : 0);
+ }
+
+ return(0);
+}
+
+
+/*
+ * df_trigger_cmp_text - return 1 if s1 is in s2
+ */
+int
+df_trigger_cmp_text(char *s1, char *s2)
+{
+ return(strstr(s1, s2) != NULL);
+}
+
+
+/*
+ * df_trigger_cmp_lwsp - compare the line passed us with the list of defined
+ * display filter triggers. returns:
+ *
+ * 0 if we don't know yet
+ * 1 if we match
+ * -1 if we clearly don't match
+ */
+int
+df_trigger_cmp_lwsp(char *s1, char *s2)
+{
+ while(*s1 && isspace((unsigned char)*s1))
+ s1++;
+
+ return((*s1) ? (!strncmp(s1, s2, strlen(s2)) ? 1 : -1) : 0);
+}
+
+
+/*
+ * df_trigger_cmp_start - return 1 if first strlen(s2) chars start s1
+ */
+int
+df_trigger_cmp_start(char *s1, char *s2)
+{
+ return(!strncmp(s1, s2, strlen(s2)));
+}
+
+
+/*
+ * valid_filter_command - make sure argv[0] of command really exists.
+ * "cmd" is required to be an alloc'd string since
+ * it will get realloc'd if the command's path is
+ * expanded.
+ */
+int
+valid_filter_command(char **cmd)
+{
+ int i;
+ char cpath[MAXPATH+1], *p;
+
+ if(!(cmd && *cmd))
+ return(FALSE);
+
+ /*
+ * copy cmd to build expanded path if necessary.
+ */
+ for(i = 0; i < sizeof(cpath) && (cpath[i] = (*cmd)[i]); i++)
+ if(isspace((unsigned char)(*cmd)[i])){
+ cpath[i] = '\0'; /* tie off command's path*/
+ break;
+ }
+
+#if defined(DOS) || defined(OS2)
+ if(is_absolute_path(cpath)){
+ size_t l;
+
+ fixpath(cpath, sizeof(cpath));
+ l = strlen(cpath) + strlen(&(*cmd)[i]);
+ p = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(p, cpath, l); /* copy new path */
+ p[l] = '\0';
+ strncat(p, &(*cmd)[i], l+1-1-strlen(p)); /* and old args */
+ p[l] = '\0';
+ fs_give((void **) cmd); /* free it */
+ *cmd = p; /* and assign new buf */
+ }
+#else
+ if(cpath[0] == '~'){
+ if(fnexpand(cpath, sizeof(cpath))){
+ size_t l;
+
+ l = strlen(cpath) + strlen(&(*cmd)[i]);
+ p = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(p, cpath, l); /* copy new path */
+ p[l] = '\0';
+ strncat(p, &(*cmd)[i], l+1-1-strlen(p)); /* and old args */
+ p[l] = '\0';
+ fs_give((void **) cmd); /* free it */
+ *cmd = p; /* and assign new buf */
+ }
+ else
+ return(FALSE);
+ }
+#endif
+
+ return(is_absolute_path(cpath) && can_access(cpath, EXECUTE_ACCESS) == 0);
+}
+
+
+void
+fetch_readc_init(FETCH_READC_S *frd, MAILSTREAM *stream, long int msgno,
+ char *section, unsigned long size, long partial, long int flags)
+{
+ int nointr = 0;
+
+ nointr = flags & DT_NOINTR;
+ flags &= ~DT_NOINTR;
+
+ memset(g_fr_desc = frd, 0, sizeof(FETCH_READC_S));
+ frd->stream = stream;
+ frd->msgno = msgno;
+ frd->section = section;
+ frd->flags = flags;
+ frd->size = size;
+ frd->readc = fetch_readc;
+
+#ifdef SMIME
+ /*
+ * The call to imap_cache below will return true in the case where
+ * we've already stashed fake data in the content of the part.
+ * This happens when an S/MIME message is decrypted.
+ */
+#endif
+
+ if(modern_imap_stream(stream)
+ && !imap_cache(stream, msgno, section, NULL, NULL)
+ && (size > INIT_FETCH_CHUNK || (partial > 0L && partial < size))
+ && (F_OFF(F_QUELL_PARTIAL_FETCH, ps_global)
+ ||
+#ifdef _WINDOWS
+ F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)
+#else
+ 0
+#endif
+ )){
+
+ if(partial > 0L && partial < size){
+ /* partial fetch is being asked for */
+ frd->size = partial;
+ }
+
+ frd->allocsize = MIN(INIT_FETCH_CHUNK,frd->size);
+ frd->chunk = (char *) fs_get ((frd->allocsize + 1) * sizeof(char));
+ frd->chunksize = frd->allocsize/2; /* this gets doubled 1st time */
+ frd->endp = frd->chunk;
+ frd->free_me = 1;
+
+ if(!nointr)
+ if(intr_handling_on())
+ frd->we_turned_on = 1;
+
+ if(!(partial > 0L && partial < size)){
+ frd->cache = so_get(CharStar, NULL, EDIT_ACCESS);
+ so_truncate(frd->cache, size); /* pre-allocate */
+ }
+ }
+ else{ /* fetch the whole bloody thing here */
+ frd->chunk = mail_fetch_body(stream, msgno, section, &frd->read, flags);
+
+ /* This only happens if the server gave us a bogus size */
+ if(partial > 0L && partial < size){
+ /* partial fetch is being asked for */
+ frd->size = partial;
+ frd->endp = &frd->chunk[frd->size];
+ }
+ else if(size != frd->read){
+ dprint((1,
+ "fetch_readc_init: size mismatch: size=%lu read=%lu, continue...\n",
+ frd->size, frd->read));
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Message size does not match expected size, continuing..."));
+ frd->size = MIN(size, frd->read);
+ frd->endp = &frd->chunk[frd->read];
+ }
+ else
+ frd->endp = &frd->chunk[frd->read];
+ }
+
+ frd->chunkp = frd->chunk;
+}
+
+
+int
+fetch_readc_cleanup(void)
+{
+ if(g_fr_desc){
+ if(g_fr_desc->we_turned_on)
+ intr_handling_off();
+
+ if(g_fr_desc->chunk && g_fr_desc->free_me)
+ fs_give((void **) &g_fr_desc->chunk);
+
+ if(g_fr_desc->cache){
+ SIZEDTEXT text;
+
+ text.size = g_fr_desc->size;
+ text.data = (unsigned char *) so_text(g_fr_desc->cache);
+ imap_cache(g_fr_desc->stream, g_fr_desc->msgno,
+ g_fr_desc->section, NULL, &text);
+ g_fr_desc->cache->txt = (void *) NULL;
+ so_give(&g_fr_desc->cache);
+ }
+ }
+
+ return(0);
+}
+
+
+char *
+fetch_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md)
+{
+ unsigned long n;
+
+ n = MIN(g_fr_desc->chunksize, size);
+ g_fr_desc->read += n;
+ g_fr_desc->endp = &g_fr_desc->chunk[n];
+
+ (*f) (stream, n, g_fr_desc->chunkp = g_fr_desc->chunk);
+
+ if(g_fr_desc->cache)
+ so_nputs(g_fr_desc->cache, g_fr_desc->chunk, (long) n);
+
+ /* BUG: need to read requested "size" in case it's larger than chunk? */
+
+ return(NULL);
+}
+
+
+int
+fetch_readc(unsigned char *c)
+{
+ extern void gf_error(char *);
+
+ if(ps_global->intr_pending){
+ (void) fetch_readc_cleanup();
+ /* TRANSLATORS: data transfer was interrupted by something */
+ gf_error(g_fr_desc->error ? g_fr_desc->error :_("Transfer interrupted!"));
+ /* no return */
+ }
+ else if(g_fr_desc->chunkp == g_fr_desc->endp){
+
+ /* Anything to read, do it */
+ if(g_fr_desc->read < g_fr_desc->size){
+ void *old_gets;
+ int rv;
+ TIMEVAL_S before, after;
+ long diff, wdiff;
+ unsigned long save_read;
+
+ old_gets = mail_parameters(g_fr_desc->stream, GET_GETS,
+ (void *)NULL);
+ mail_parameters(g_fr_desc->stream, SET_GETS, (void *) fetch_gets);
+
+ /*
+ * Adjust chunksize with the goal that it will be about
+ * TARGET_INTR_TIME useconds +- 20%
+ * to finish the partial fetch. We want that time
+ * to be small so that interrupts will happen fast, but we want
+ * the chunksize large so that the whole fetch will happen
+ * fast. So it's a tradeoff between those two things.
+ *
+ * If the estimated fetchtime is getting too large, we
+ * half the chunksize. If it is small, we double
+ * the chunksize. If it is in between, we leave it. There is
+ * some risk of oscillating between two values, but who cares?
+ */
+ if(g_fr_desc->fetchtime <
+ TARGET_INTR_TIME - TARGET_INTR_TIME/5)
+ g_fr_desc->chunksize *= 2;
+ else if(g_fr_desc->fetchtime >
+ TARGET_INTR_TIME + TARGET_INTR_TIME/5)
+ g_fr_desc->chunksize /= 2;
+
+ g_fr_desc->chunksize = MIN(MAX_FETCH_CHUNK,
+ MAX(MIN_FETCH_CHUNK,
+ g_fr_desc->chunksize));
+
+#ifdef _WINDOWS
+ /*
+ * If this feature is set, limit the max size to less than
+ * 16K - 5, the magic number that avoids Microsoft's bug.
+ * Let's just go with 12K instead of 16K - 5.
+ */
+ if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
+ g_fr_desc->chunksize =
+ MIN(AVOID_MICROSOFT_SSL_CHUNKING_BUG, g_fr_desc->chunksize);
+#endif
+
+ /* don't ask for more than there should be left to ask for */
+ g_fr_desc->chunksize =
+ MIN(g_fr_desc->size - g_fr_desc->read, g_fr_desc->chunksize);
+
+ /*
+ * If chunksize grew, reallocate chunk.
+ */
+ if(g_fr_desc->chunksize > g_fr_desc->allocsize){
+ g_fr_desc->allocsize = g_fr_desc->chunksize;
+ fs_give((void **) &g_fr_desc->chunk);
+ g_fr_desc->chunk = (char *) fs_get ((g_fr_desc->allocsize + 1)
+ * sizeof(char));
+ g_fr_desc->endp = g_fr_desc->chunk;
+ g_fr_desc->chunkp = g_fr_desc->chunk;
+ }
+
+ save_read = g_fr_desc->read;
+ (void)get_time(&before);
+
+ rv = mail_partial_body(g_fr_desc->stream, g_fr_desc->msgno,
+ g_fr_desc->section, g_fr_desc->read,
+ g_fr_desc->chunksize, g_fr_desc->flags);
+
+ /*
+ * If the amount we actually read is less than the amount we
+ * asked for we assume that is because the server gave us a
+ * bogus size when we originally asked for it.
+ */
+ if(g_fr_desc->chunksize > (g_fr_desc->read - save_read)){
+ dprint((1,
+ "partial_body returned less than asked for: asked=%lu got=%lu, continue...\n",
+ g_fr_desc->chunksize, g_fr_desc->read - save_read));
+ if(g_fr_desc->read - save_read > 0)
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Message size does not match expected size, continuing..."));
+ else{
+ rv = 0;
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Server returns zero bytes, Quell-Partial-Fetch feature may help"));
+ }
+
+ g_fr_desc->size = g_fr_desc->read;
+ }
+
+ if(get_time(&after) == 0){
+ diff = time_diff(&after, &before);
+ wdiff = MIN(TARGET_INTR_TIME + TARGET_INTR_TIME/2,
+ MAX(TARGET_INTR_TIME - TARGET_INTR_TIME/2, diff));
+ /*
+ * Fetchtime is an exponentially weighted average of the number
+ * of usecs it takes to do a single fetch of whatever the
+ * current chunksize is. Since the fetch time probably isn't
+ * simply proportional to the chunksize, we don't try to
+ * calculate a chunksize by keeping track of the bytes per
+ * second. Instead, we just double or half the chunksize if
+ * we are too fast or too slow. That happens the next time
+ * through the loop a few lines up.
+ * Too settle it down a bit, Windsorize the mean.
+ */
+ g_fr_desc->fetchtime = (g_fr_desc->fetchtime == 0)
+ ? wdiff
+ : g_fr_desc->fetchtime/2 + wdiff/2;
+ dprint((8,
+ "fetch: diff=%ld wdiff=%ld fetchave=%ld prev chunksize=%ld\n",
+ diff, wdiff, g_fr_desc->fetchtime, g_fr_desc->chunksize));
+ }
+ else /* just set it so it won't affect anything */
+ g_fr_desc->fetchtime = TARGET_INTR_TIME;
+
+ /* UNinstall mailgets */
+ mail_parameters(g_fr_desc->stream, SET_GETS, old_gets);
+
+ if(!rv){
+ (void) fetch_readc_cleanup();
+ gf_error("Partial fetch failed!");
+ /* no return */
+ }
+ }
+ else /* clean up and return done. */
+ return(fetch_readc_cleanup());
+ }
+
+ *c = *g_fr_desc->chunkp++;
+
+ return(1);
+}
diff --git a/pith/detach.h b/pith/detach.h
new file mode 100644
index 00000000..b6cdaec0
--- /dev/null
+++ b/pith/detach.h
@@ -0,0 +1,69 @@
+/*
+ * $Id: detach.h 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_DETACH_INCLUDED
+#define PITH_DETACH_INCLUDED
+
+
+#include "../pith/filttype.h"
+#include "../pith/store.h"
+
+
+/*
+ * Data used to keep track of partial fetches...
+ */
+typedef struct _fetch_read {
+ unsigned free_me:1;
+ unsigned we_turned_on:1;
+ MAILSTREAM *stream; /* stream of open mailbox */
+ unsigned long msgno; /* message number within mailbox */
+ char *section, /* MIME section within message */
+ *chunk, /* block of partial fetched data */
+ *chunkp, /* pointer to next char in block */
+ *endp, /* cell past last char in block */
+ *error; /* Error message to report */
+ unsigned long read, /* bytes read so far */
+ size, /* total bytes to read */
+ chunksize, /* size of chunk block */
+ allocsize; /* allocated size of chunk block */
+ long flags, /* flags to use fetching block */
+ fetchtime; /* usecs avg per chunk fetch */
+ gf_io_t readc;
+ STORE_S *cache;
+} FETCH_READC_S;
+
+
+extern FETCH_READC_S *g_fr_desc;
+
+#define AVOID_MICROSOFT_SSL_CHUNKING_BUG ((unsigned long)(12 * 1024L))
+
+
+/*
+ * This lazily gets combined with FT_ flags from c-client so make
+ * it different from all those possible values.
+ */
+#define DT_NODFILTER (long) 0x10000
+#define DT_NOINTR (long) 0x20000
+
+
+/* exported protoypes */
+char *detach_raw(MAILSTREAM *, long, char *, gf_io_t, int);
+char *detach(MAILSTREAM *, long, char *, long, long *, gf_io_t, FILTLIST_S *, long);
+int valid_filter_command(char **);
+void fetch_readc_init(FETCH_READC_S *, MAILSTREAM *, long, char *,
+ unsigned long, long, long);
+
+#endif /* PITH_DETACH_INCLUDED */
diff --git a/pith/detoken.c b/pith/detoken.c
new file mode 100644
index 00000000..6f0584ab
--- /dev/null
+++ b/pith/detoken.c
@@ -0,0 +1,567 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: detoken.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/detoken.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/pattern.h"
+#include "../pith/reply.h"
+#include "../pith/mailindx.h"
+#include "../pith/options.h"
+
+
+/*
+ * Hook to read signature from local file
+ */
+char *(*pith_opt_get_signature_file)(char *, int, int, int);
+
+
+/*
+ * Internal prototypes
+ */
+char *detoken_guts(char *, int, ENVELOPE *, ACTION_S *, REDRAFT_POS_S **, int, int *);
+char *handle_if_token(char *, char *, int, ENVELOPE *, ACTION_S *, char **);
+char *get_token_arg(char *, char **);
+
+
+/*
+ * Detokenize signature or template files.
+ *
+ * If is_sig, we always use literal sigs before sigfiles if they are
+ * defined. So, check for role->litsig and use it. If it doesn't exist, use
+ * the global literal sig if defined. Else the role->sig file or the
+ * global signature file.
+ *
+ * If !is_sig, use role->template.
+ *
+ * So we start with a literal signature or a signature or template file.
+ * If that's a file, we read it first. The file could be remote.
+ * Then we detokenize the literal signature or file contents and return
+ * an allocated string which the caller frees.
+ *
+ * Args role -- See above about what happens depending on is_sig.
+ * relative to the pinerc dir.
+ * env -- The envelope to use for detokenizing. May be NULL.
+ * prenewlines -- How many blank lines should be included at start.
+ * postnewlines -- How many blank lines should be included after.
+ * is_sig -- This is a signature (not a template file).
+ * redraft_pos -- This is a return value. If it is non-NULL coming in,
+ * then the cursor position is returned here.
+ * impl -- This is a combination argument which is both an input
+ * argument and a return value. If it is non-NULL and = 0,
+ * that means that we want the cursor position returned here,
+ * even if that position is set implicitly to the end of
+ * the output string. If it is = 1 coming in, that means
+ * we only want the cursor position to be set if it is set
+ * explicitly. If it is 2, or if redraft_pos is NULL,
+ * we don't set it at all.
+ * If the cursor position gets set explicitly by a
+ * _CURSORPOS_ token in the file then this is set to 2
+ * on return. If the cursor position is set implicitly to
+ * the end of the included file, then this is set to 1
+ * on return.
+ *
+ * Returns -- An allocated string is returned.
+ */
+char *
+detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines,
+ int is_sig, REDRAFT_POS_S **redraft_pos, int *impl)
+{
+ char *ret = NULL,
+ *src = NULL,
+ *literal_sig = NULL,
+ *sigfile = NULL;
+
+ if(is_sig){
+ /*
+ * If role->litsig is set, we use it;
+ * Else, if VAR_LITERAL_SIG is set, we use that;
+ * Else, if role->sig is set, we use that;
+ * Else, if VAR_SIGNATURE_FILE is set, we use that.
+ * This can be a little surprising if you set the VAR_LITERAL_SIG
+ * and don't set a role->litsig but do set a role->sig. The
+ * VAR_LITERAL_SIG will be used, not role->sig. The reason for this
+ * is mostly that it is much easier to display the right stuff
+ * in the various config screens if we do it that way. Besides,
+ * people will typically use only literal sigs or only sig files,
+ * there is no reason to mix them, so we don't provide support to
+ * do so.
+ */
+ if(role && role->litsig)
+ literal_sig = role->litsig;
+ else if(ps_global->VAR_LITERAL_SIG)
+ literal_sig = ps_global->VAR_LITERAL_SIG;
+ else if(role && role->sig)
+ sigfile = role->sig;
+ else
+ sigfile = ps_global->VAR_SIGNATURE_FILE;
+ }
+ else if(role && role->template)
+ sigfile = role->template;
+
+ if(literal_sig)
+ src = get_signature_lit(literal_sig, prenewlines, postnewlines, is_sig,1);
+ else if(sigfile && pith_opt_get_signature_file)
+ src = (*pith_opt_get_signature_file)(sigfile, prenewlines, postnewlines, is_sig);
+
+ if(src){
+ if(*src)
+ ret = detoken_src(src, FOR_TEMPLATE, env, role, redraft_pos, impl);
+
+ fs_give((void **)&src);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Filter the source string from the template file and return an allocated
+ * copy of the result with text replacements for the tokens.
+ * Fill in offset in redraft_pos.
+ *
+ * This is really inefficient but who cares? It's just cpu time.
+ */
+char *
+detoken_src(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
+ REDRAFT_POS_S **redraft_pos, int *impl)
+{
+ int loopcnt = 25; /* just in case, avoid infinite loop */
+ char *ret, *str1, *str2;
+ int done = 0;
+
+ if(!src)
+ return(src);
+
+ /*
+ * We keep running it through until it stops changing so user can
+ * nest calls to token stuff.
+ */
+ str1 = src;
+ do {
+ /* short-circuit if no chance it will change */
+ if(strindex(str1, '_'))
+ str2 = detoken_guts(str1, for_what, env, role, NULL, 0, NULL);
+ else
+ str2 = str1;
+
+ if(str1 && str2 && (str1 == str2 || !strcmp(str1, str2))){
+ done++; /* It stopped changing */
+ if(str1 && str1 != src && str1 != str2)
+ fs_give((void **)&str1);
+ }
+ else{ /* Still changing */
+ if(str1 && str1 != src && str1 != str2)
+ fs_give((void **)&str1);
+
+ str1 = str2;
+ }
+
+ } while(str2 && !done && loopcnt-- > 0);
+
+ /*
+ * Have to run it through once more to get the redraft_pos and
+ * to remove any backslash escape for a token.
+ */
+ if((str2 && strindex(str2, '_')) ||
+ (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
+ ret = detoken_guts(str2, for_what, env, role, redraft_pos, 1, impl);
+ if(str2 != src)
+ fs_give((void **)&str2);
+ }
+ else if(str2){
+ if(str2 == src)
+ ret = cpystr(str2);
+ else
+ ret = str2;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * The guts of the detokenizing routines. Filter the src string looking for
+ * tokens and replace them with the appropriate text. In the case of the
+ * cursor_pos token we set redraft_pos instead.
+ *
+ * Args src -- The source string
+ * for_what --
+ * env -- Envelope to look in for token replacements.
+ * redraft_pos -- Return the redraft offset here, if non-zero.
+ * last_pass -- This is a flag to tell detoken_guts whether or not to do
+ * the replacement for _CURSORPOS_. Leave it as is until
+ * the last pass. We need this because we want to defer
+ * cursor placement until the very last call to detoken,
+ * otherwise we'd have to keep track of the cursor
+ * position as subsequent text replacements (nested)
+ * take place.
+ * This same flag is also used to decide when to eliminate
+ * backslash escapes from in front of tokens. The only
+ * use of backslash escapes is to escape an entire token.
+ * That is, \_DATE_ is a literal _DATE_, but any other
+ * backslash is a literal backslash. That way, nobody
+ * but wackos will have to worry about backslashes.
+ * impl -- This is a combination argument which is both an input
+ * argument and a return value. If it is non-NULL and 0
+ * coming in, that means that we should set redraft_pos,
+ * even if that position is set implicitly to the end of
+ * the output string. If it is 1 coming in, that means
+ * we only want the cursor position to be set if it is set
+ * explicitly. If it is 2 coming in (or if
+ * redraft_pos is NULL) then we don't set it at all.
+ * If the cursor position gets set explicitly by a
+ * _CURSORPOS_ token in the file then this is set to 2
+ * on return. If the cursor position is set implicitly to
+ * the end of the included file, then this is set to 1
+ * on return.
+ *
+ * Returns pointer to alloced result
+ */
+char *
+detoken_guts(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
+ REDRAFT_POS_S **redraft_pos, int last_pass, int *impl)
+{
+#define MAXSUB 500
+ char *p, *q = NULL, *dst = NULL;
+ char subbuf[MAXSUB+1], *repl;
+ INDEX_PARSE_T *pt;
+ long l, cnt = 0L;
+ int sizing_pass = 1, suppress_tokens = 0;
+
+ if(!src)
+ return(NULL);
+
+top:
+
+ /*
+ * The tokens we look for begin with _. The only escaping mechanism
+ * is a backslash in front of a token. This will give you the literal
+ * token. So \_DATE_ is a literal _DATE_.
+ * Tokens like _word_ are replaced with the appropriate text if
+ * word is recognized. If _word_ is followed immediately by a left paren
+ * it is an if-else thingie. _word_(match_this,if_text,else_text) means to
+ * replace that with either the if_text or else_text depending on whether
+ * what _word_ (without the paren) would produce matches match_this or not.
+ */
+ p = src;
+ while(*p){
+ switch(*p){
+ case '_': /* possible start of token */
+ if(!suppress_tokens &&
+ (pt = itoktype(p+1, for_what | DELIM_USCORE)) != NULL){
+ char *free_this = NULL;
+
+ p += (strlen(pt->name) + 2); /* skip over token */
+
+ repl = subbuf;
+ subbuf[0] = '\0';
+
+ if(pt->ctype == iCursorPos){
+ if(!last_pass){ /* put it back */
+ subbuf[0] = '_';
+ strncpy(subbuf+1, pt->name, sizeof(subbuf)-2);
+ subbuf[sizeof(subbuf)-1] = '\0';
+ strncat(subbuf, "_", sizeof(subbuf)-strlen(subbuf)-1);
+ subbuf[sizeof(subbuf)-1] = '\0';
+ }
+
+ if(!sizing_pass){
+ if(q-dst < cnt+1)
+ *q = '\0';
+
+ l = strlen(dst);
+ if(redraft_pos && impl && *impl != 2){
+ if(!*redraft_pos){
+ *redraft_pos =
+ (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
+ memset((void *)*redraft_pos, 0,
+ sizeof(**redraft_pos));
+ (*redraft_pos)->hdrname = cpystr(":");
+ }
+
+ (*redraft_pos)->offset = l;
+ *impl = 2; /* set explicitly */
+ }
+ }
+ }
+ else if(pt->what_for & FOR_REPLY_INTRO)
+ repl = get_reply_data(env, role, pt->ctype,
+ subbuf, sizeof(subbuf)-1);
+
+ if(*p == LPAREN){ /* if-else construct */
+ char *skip_ahead;
+
+ repl = free_this = handle_if_token(repl, p, for_what,
+ env, role,
+ &skip_ahead);
+ p = skip_ahead;
+ }
+
+ if(repl && repl[0]){
+ if(sizing_pass)
+ cnt += (long)strlen(repl);
+ else{
+ strncpy(q, repl, cnt-(q-dst));
+ dst[cnt] = '\0';
+ q += strlen(repl);
+ }
+ }
+
+ if(free_this)
+ fs_give((void **)&free_this);
+ }
+ else{ /* unrecognized token, treat it just like text */
+ suppress_tokens = 0;
+ if(sizing_pass)
+ cnt++;
+ else if(q-dst < cnt+1)
+ *q++ = *p;
+
+ p++;
+ }
+
+ break;
+
+ case BSLASH:
+ /*
+ * If a real token follows the backslash, then the backslash
+ * is here to escape the token. Otherwise, it's just a
+ * regular character.
+ */
+ if(*(p+1) == '_' &&
+ ((pt = itoktype(p+2, for_what | DELIM_USCORE)) != NULL)){
+ /*
+ * Backslash is escape for literal token.
+ * If we're on the last pass we want to eliminate the
+ * backslash, otherwise we keep it.
+ * In either case, suppress_tokens will cause the token
+ * lookup to be skipped above so that the token will
+ * be treated as literal text.
+ */
+ suppress_tokens++;
+ if(last_pass){
+ p++;
+ break;
+ }
+ /* else, fall through and keep backslash */
+ }
+ /* this is a literal backslash, fall through */
+
+ default:
+ if(sizing_pass)
+ cnt++;
+ else if(q-dst < cnt+1)
+ *q++ = *p; /* copy the character */
+
+ p++;
+ break;
+ }
+ }
+
+ if(!sizing_pass && q-dst < cnt+1)
+ *q = '\0';
+
+ if(sizing_pass){
+ sizing_pass = 0;
+ /*
+ * Now we're done figuring out how big the answer will be. We
+ * allocate space for it and go back through filling it in.
+ */
+ cnt = MAX(cnt, 0L);
+ q = dst = (char *)fs_get((cnt + 1) * sizeof(char));
+ goto top;
+ }
+
+ /*
+ * Set redraft_pos to character following the template, unless
+ * it has already been set.
+ */
+ if(dst && impl && *impl == 0 && redraft_pos && !*redraft_pos){
+ *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
+ memset((void *)*redraft_pos, 0, sizeof(**redraft_pos));
+ (*redraft_pos)->offset = strlen(dst);
+ (*redraft_pos)->hdrname = cpystr(":");
+ *impl = 1;
+ }
+
+ if(dst && cnt >= 0)
+ dst[cnt] = '\0';
+
+ return(dst);
+}
+
+
+/*
+ * Do the if-else part of the detokenization for one case of if-else.
+ * The input src should like (match_this, if_matched, else)...
+ *
+ * Args expands_to -- This is what the token to the left of the paren
+ * expanded to, and this is the thing we're going to
+ * compare with the match_this part.
+ * src -- The source string beginning with the left paren.
+ * for_what --
+ * env --
+ * skip_ahead -- Tells caller how long the (...) part was so caller can
+ * skip over that part of the source.
+ *
+ * Returns -- an allocated string which is the answer, or NULL if nothing.
+ */
+char *
+handle_if_token(char *expands_to, char *src, int for_what, ENVELOPE *env,
+ ACTION_S *role, char **skip_ahead)
+{
+ char *ret = NULL;
+ char *skip_to;
+ char *match_this, *if_matched, *else_part;
+
+ if(skip_ahead)
+ *skip_ahead = src;
+
+ if(!src || *src != LPAREN){
+ dprint((1,"botch calling handle_if_token, missing paren\n"));
+ return(ret);
+ }
+
+ if(!*++src){
+ q_status_message(SM_ORDER, 3, 3,
+ "Unexpected end of token string in Reply-LeadIn, Sig, or template");
+ return(ret);
+ }
+
+ match_this = get_token_arg(src, &skip_to);
+
+ /*
+ * If the match_this argument is a token, detokenize it first.
+ */
+ if(match_this && *match_this == '_'){
+ char *exp_match_this;
+
+ exp_match_this = detoken_src(match_this, for_what, env,
+ role, NULL, NULL);
+ fs_give((void **)&match_this);
+ match_this = exp_match_this;
+ }
+
+ if(!match_this)
+ match_this = cpystr("");
+
+ if(!expands_to)
+ expands_to = "";
+
+ src = skip_to;
+ while(src && *src && (isspace((unsigned char)*src) || *src == ','))
+ src++;
+
+ if_matched = get_token_arg(src, &skip_to);
+ src = skip_to;
+ while(src && *src && (isspace((unsigned char)*src) || *src == ','))
+ src++;
+
+ else_part = get_token_arg(src, &skip_to);
+ src = skip_to;
+ while(src && *src && *src != RPAREN)
+ src++;
+
+ if(src && *src == RPAREN)
+ src++;
+
+ if(skip_ahead)
+ *skip_ahead = src;
+
+ if(!strcmp(match_this, expands_to)){
+ ret = if_matched;
+ if(else_part)
+ fs_give((void **)&else_part);
+ }
+ else{
+ ret = else_part;
+ if(if_matched)
+ fs_give((void **)&if_matched);
+ }
+
+ fs_give((void **)&match_this);
+
+ return(ret);
+}
+
+
+char *
+get_token_arg(char *src, char **skip_to)
+{
+ int quotes = 0, done = 0;
+ char *ret = NULL, *p;
+
+ while(*src && isspace((unsigned char)*src)) /* skip space before string */
+ src++;
+
+ if(*src == RPAREN){
+ if(skip_to)
+ *skip_to = src;
+
+ return(ret);
+ }
+
+ p = ret = (char *)fs_get((strlen(src) + 1) * sizeof(char));
+ while(!done){
+ switch(*src){
+ case QUOTE:
+ if(++quotes == 2)
+ done++;
+
+ src++;
+ break;
+
+ case BSLASH: /* don't count \" as a quote, just copy */
+ if(*(src+1) == BSLASH || *(src+1) == QUOTE){
+ src++; /* skip backslash */
+ *p++ = *src++;
+ }
+ else
+ src++;
+
+ break;
+
+ case SPACE:
+ case TAB:
+ case RPAREN:
+ case COMMA:
+ if(quotes)
+ *p++ = *src++;
+ else
+ done++;
+
+ break;
+
+ case '\0':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+ if(skip_to)
+ *skip_to = src;
+
+ return(ret);
+}
diff --git a/pith/detoken.h b/pith/detoken.h
new file mode 100644
index 00000000..cf7f34ea
--- /dev/null
+++ b/pith/detoken.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: detoken.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_DETOKEN_INCLUDED
+#define PITH_DETOKEN_INCLUDED
+
+
+#include "../pith/pattern.h"
+#include "../pith/repltype.h"
+
+
+/* exported protoypes */
+char *detoken(ACTION_S *, ENVELOPE *, int, int, int, REDRAFT_POS_S **, int *);
+char *detoken_src(char *, int, ENVELOPE *, ACTION_S *, REDRAFT_POS_S **, int *);
+
+
+#endif /* PITH_DETOKEN_INCLUDED */
diff --git a/pith/editorial.c b/pith/editorial.c
new file mode 100644
index 00000000..a20d66ae
--- /dev/null
+++ b/pith/editorial.c
@@ -0,0 +1,198 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: editorial.c 768 2007-10-24 00:10:03Z hubert@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ editorial.c
+ Implements editorial text insertion/formatting
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/conf.h"
+#include "../pith/state.h"
+#include "../pith/margin.h"
+#include "../pith/filter.h"
+#include "../pith/handle.h"
+#include "../pith/mailview.h"
+#include "../pith/editorial.h"
+
+/*
+ * Internal prototypes
+ */
+int quote_editorial(long, char *, LT_INS_S **, void *);
+
+
+/*
+ * Struct to help with editorial comment insertion
+ */
+#define EDITORIAL_MAX 128
+typedef struct _editorial_s {
+ char prefix[EDITORIAL_MAX];
+ int prelen;
+ char postfix[EDITORIAL_MAX];
+ int postlen;
+ int do_color;
+} EDITORIAL_S;
+
+
+char *
+format_editorial(char *s, int width, int flags, HANDLE_S **handlesp, gf_io_t pc)
+{
+ gf_io_t gc;
+ int *margin;
+ EDITORIAL_S es;
+ URL_HILITE_S uh;
+
+ /* ASSUMPTION #2,341: All MIME-decoding is done by now */
+ gf_set_readc(&gc, s, strlen(s), CharStar, 0);
+
+ margin = format_view_margin();
+ if(flags & FM_NOINDENT)
+ margin[0] = margin[1] = 0;
+
+ /* safety net */
+ if(width - (margin[0] + margin[1]) < 5){
+ margin[0] = margin[1] = 0;
+ if(width < 5)
+ width = 80;
+ }
+
+ width -= (margin[0] + margin[1]);
+
+ if(width > 40){
+ width -= 12;
+
+ es.prelen = MAX(2, MIN(margin[0] + 6, sizeof(es.prefix) - 3));
+ snprintf(es.prefix, sizeof(es.prefix), "%s[ ", repeat_char(es.prelen - 2, ' '));
+ es.postlen = 2;
+ strncpy(es.postfix, " ]", sizeof(es.postfix));
+ es.postfix[sizeof(es.postfix)-1] = '\0';
+ }
+ else if(width > 20){
+ width -= 6;
+
+ es.prelen = MAX(2, MIN(margin[0] + 3, sizeof(es.prefix) - 3));
+ snprintf(es.prefix, sizeof(es.prefix), "%s[ ", repeat_char(es.prelen - 2, ' '));
+ es.postlen = 2;
+ strncpy(es.postfix, " ]", sizeof(es.postfix));
+ es.postfix[sizeof(es.postfix)-1] = '\0';
+ }
+ else{
+ width -= 2;
+ strncpy(es.prefix, "[", sizeof(es.prefix));
+ es.prefix[sizeof(es.prefix)-1] = '\0';
+ strncpy(es.postfix, "]", sizeof(es.postfix));
+ es.postfix[sizeof(es.postfix)-1] = '\0';
+ es.prelen = 1;
+ es.postlen = 1;
+ }
+
+ es.do_color = (!(flags & FM_NOCOLOR) && (flags & FM_DISPLAY) && pico_usingcolor());
+
+ gf_filter_init();
+
+ /* catch urls */
+ if((F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ && handlesp){
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite,
+ gf_url_hilite_opt(&uh,handlesp,0)));
+ }
+
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(width, width, NULL, 0,
+ (handlesp ? GFW_HANDLES : GFW_NONE)));
+ gf_link_filter(gf_line_test, gf_line_test_opt(quote_editorial, &es));
+
+ /* If not for display, change to local end of line */
+ if(!(flags & FM_DISPLAY))
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ return(gf_pipe(gc, pc));
+}
+
+
+int
+quote_editorial(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ COLOR_PAIR *col = NULL;
+
+ ins = gf_line_test_new_ins(ins, line,
+ ((EDITORIAL_S *)local)->prefix,
+ ((EDITORIAL_S *)local)->prelen);
+ if(((EDITORIAL_S *)local)->do_color
+ && ps_global->VAR_METAMSG_FORE_COLOR
+ && ps_global->VAR_METAMSG_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_METAMSG_FORE_COLOR,
+ ps_global->VAR_METAMSG_BACK_COLOR))){
+ if(!pico_is_good_colorpair(col))
+ free_color_pair(&col);
+
+ if(col){
+ char *p;
+ char normal_embed[(2 * RGBLEN) + 5];
+ char quote_color_embed[(2 * RGBLEN) + 5];
+
+ strncpy(quote_color_embed,
+ color_embed(col->fg, col->bg),
+ sizeof(quote_color_embed));
+ quote_color_embed[sizeof(quote_color_embed)-1] = '\0';
+
+ ins = gf_line_test_new_ins(ins, line,
+ quote_color_embed, (2 * RGBLEN) + 4);
+
+ /*
+ * If there was already a color change back to normal color
+ * in the line that was passed in, then instead of allowing
+ * that color change back to normal we want to change that
+ * to a color change back to our METAMSG color instead.
+ * Search line for that and modify it.
+ */
+
+ strncpy(normal_embed,
+ color_embed(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR),
+ sizeof(normal_embed));
+ normal_embed[sizeof(normal_embed)-1] = '\0';
+
+ for(p = line; (p = strstr(p, normal_embed)); p++){
+
+ /*
+ * Replace the normal color with our special quoting
+ * color. No need to change it if there are no
+ * characters after the color change because we're
+ * going to change the color to normal right below
+ * this anyway.
+ */
+ if(strlen(p) > strlen(quote_color_embed))
+ rplstr(p, strlen(p)+1, strlen(quote_color_embed), quote_color_embed);
+ }
+
+ ins = gf_line_test_new_ins(ins, line+strlen(line),
+ normal_embed, (2 * RGBLEN) + 4);
+ free_color_pair(&col);
+ }
+ }
+
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ ((EDITORIAL_S *)local)->postfix,
+ ((EDITORIAL_S *)local)->postlen);
+ return(0);
+}
diff --git a/pith/editorial.h b/pith/editorial.h
new file mode 100644
index 00000000..32be8fce
--- /dev/null
+++ b/pith/editorial.h
@@ -0,0 +1,28 @@
+/*-----------------------------------------------------------------------
+ $Id: editorial.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ -----------------------------------------------------------------------*/
+
+/* ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_EDITORIAL_INCLUDED
+#define PITH_EDITORIAL_INCLUDED
+
+
+#include "../pith/filter.h"
+
+
+/* exported protoypes */
+char *format_editorial(char *, int, int, HANDLE_S **, gf_io_t);
+
+
+#endif /* PITH_EDITORIAL_INCLUDED */
diff --git a/pith/escapes.c b/pith/escapes.c
new file mode 100644
index 00000000..f6f5c8fe
--- /dev/null
+++ b/pith/escapes.c
@@ -0,0 +1,69 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: escapes.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ escapes.c
+ Implements known escape code matching
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/escapes.h"
+
+
+
+/*------------------------------------------------------------------
+ This list of known escape sequences is taken from RFC's 1486 and 1554
+ and draft-apng-cc-encoding, and the X11R5 source with only a remote
+ understanding of what this all means...
+
+ NOTE: if the length of these should extend beyond 4 chars, fix
+ MAX_ESC_LEN in filter.c
+ ----*/
+#ifdef _WINDOWS
+static char *known_escapes[] = {
+ "(B", "(J", "$@", "$B", /* RFC 1468 */
+ "(H",
+ NULL};
+#else
+static char *known_escapes[] = {
+ "(B", "(J", "$@", "$B", /* RFC 1468 */
+ "(H",
+ "$A", "$(C", "$(D", ".A", ".F", /* added by RFC 1554 */
+ "$)C", "$)A", "$*E", "$*X", /* those in apng-draft */
+ "$+G", "$+H", "$+I", "$+J", "$+K",
+ "$+L", "$+M",
+ ")I", "-A", "-B", "-C", "-D", /* codes form X11R5 source */
+ "-F", "-G", "-H", "-L", "-M",
+ "-$(A", "$(B", "$)B", "$)D",
+ NULL};
+#endif
+
+
+int
+match_escapes(char *esc_seq)
+{
+ char **p;
+ int n;
+
+ for(p = known_escapes; *p && strncmp(esc_seq, *p, n = strlen(*p)); p++)
+ ;
+
+ return(*p ? n + 1 : 0);
+}
diff --git a/pith/escapes.h b/pith/escapes.h
new file mode 100644
index 00000000..1a4750f4
--- /dev/null
+++ b/pith/escapes.h
@@ -0,0 +1,24 @@
+/*
+ * $Id: escapes.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ESCAPES_INCLUDED
+#define PITH_ESCAPES_INCLUDED
+
+
+/* exported protoypes */
+int match_escapes(char *);
+
+
+#endif /* PITH_ESCAPES_INCLUDED */
diff --git a/pith/filter.c b/pith/filter.c
new file mode 100644
index 00000000..b8712b23
--- /dev/null
+++ b/pith/filter.c
@@ -0,0 +1,11305 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: filter.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ filter.c
+
+ This code provides a generalized, flexible way to allow
+ piping of data thru filters. Each filter is passed a structure
+ that it will use to hold its static data while it operates on
+ the stream of characters that are passed to it. After processing
+ it will either return or call the next filter in
+ the pipe with any character (or characters) it has ready to go. This
+ means some terminal type of filter has to be the last in the
+ chain (i.e., one that writes the passed char someplace, but doesn't
+ call another filter).
+
+ See below for more details.
+
+ The motivation is to handle MIME decoding, richtext conversion,
+ iso_code stripping and anything else that may come down the
+ pike (e.g., PEM) in an elegant fashion. mikes (920811)
+
+ TODO:
+ reasonable error handling
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/filter.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/color.h"
+#include "../pith/escapes.h"
+#include "../pith/pipe.h"
+#include "../pith/status.h"
+#include "../pith/string.h"
+#include "../pith/util.h"
+#include "../pith/url.h"
+#include "../pith/init.h"
+#include "../pith/help.h"
+#include "../pico/keydefs.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+int gf_so_writec(int);
+int gf_so_readc(unsigned char *);
+int gf_freadc(unsigned char *);
+int gf_freadc_locale(unsigned char *);
+int gf_freadc_getchar(unsigned char *, void *);
+int gf_fwritec(int);
+int gf_fwritec_locale(int);
+#ifdef _WINDOWS
+int gf_freadc_windows(unsigned char *);
+#endif /* _WINDOWS */
+int gf_preadc(unsigned char *);
+int gf_preadc_locale(unsigned char *);
+int gf_preadc_getchar(unsigned char *, void *);
+int gf_pwritec(int);
+int gf_pwritec_locale(int);
+int gf_sreadc(unsigned char *);
+int gf_sreadc_locale(unsigned char *);
+int gf_sreadc_getchar(unsigned char *, void *);
+int gf_swritec(int);
+int gf_swritec_locale(int);
+void gf_terminal(FILTER_S *, int);
+void gf_error(char *);
+char *gf_filter_puts(char *);
+void gf_filter_eod(void);
+
+void gf_8bit_put(FILTER_S *, int);
+
+
+
+/*
+ * System specific options
+ */
+#ifdef _WINDOWS
+#define CRLF_NEWLINES
+#endif
+
+
+/*
+ * Hooks for callers to adjust behavior
+ */
+char *(*pith_opt_pretty_var_name)(char *);
+char *(*pith_opt_pretty_feature_name)(char *, int);
+
+
+/*
+ * pointer to first function in a pipe, and pointer to last filter
+ */
+FILTER_S *gf_master = NULL;
+static gf_io_t last_filter;
+static char *gf_error_string;
+static long gf_byte_count;
+static jmp_buf gf_error_state;
+
+
+#define GF_NOOP 0x01 /* flags used by generalized */
+#define GF_EOD 0x02 /* filters */
+#define GF_DATA 0x04 /* See filter.c for more */
+#define GF_ERROR 0x08 /* details */
+#define GF_RESET 0x10
+
+
+/*
+ * A list of states used by the various filters. Reused in many filters.
+ */
+#define DFL 0
+#define EQUAL 1
+#define HEX 2
+#define WSPACE 3
+#define CCR 4
+#define CLF 5
+#define TOKEN 6
+#define TAG 7
+#define HANDLE 8
+#define HDATA 9
+#define ESC 10
+#define ESCDOL 11
+#define ESCPAR 12
+#define EUC 13
+#define BOL 14
+#define FL_QLEV 15
+#define FL_STF 16
+#define FL_SIG 17
+#define STOP_DECODING 18
+#define SPACECR 19
+
+
+
+/*
+ * Macros to reduce function call overhead associated with calling
+ * each filter for each byte filtered, and to minimize filter structure
+ * dereferences. NOTE: "queuein" has to do with putting chars into the
+ * filter structs data queue. So, writing at the queuein offset is
+ * what a filter does to pass processed data out of itself. Ditto for
+ * queueout. This explains the FI --> queueout init stuff below.
+ */
+#define GF_QUE_START(F) (&(F)->queue[0])
+#define GF_QUE_END(F) (&(F)->queue[GF_MAXBUF - 1])
+
+#define GF_IP_INIT(F) ip = (F) ? &(F)->queue[(F)->queuein] : NULL
+#define GF_IP_INIT_GLO(F) (*ipp) = (F) ? &(F)->queue[(F)->queuein] : NULL
+#define GF_EIB_INIT(F) eib = (F) ? GF_QUE_END(F) : NULL
+#define GF_EIB_INIT_GLO(F) (*eibp) = (F) ? GF_QUE_END(F) : NULL
+#define GF_OP_INIT(F) op = (F) ? &(F)->queue[(F)->queueout] : NULL
+#define GF_EOB_INIT(F) eob = (F) ? &(F)->queue[(F)->queuein] : NULL
+
+#define GF_IP_END(F) (F)->queuein = ip - GF_QUE_START(F)
+#define GF_IP_END_GLO(F) (F)->queuein = (unsigned char *)(*ipp) - (unsigned char *)GF_QUE_START(F)
+#define GF_OP_END(F) (F)->queueout = op - GF_QUE_START(F)
+
+#define GF_INIT(FI, FO) unsigned char *GF_OP_INIT(FI); \
+ unsigned char *GF_EOB_INIT(FI); \
+ unsigned char *GF_IP_INIT(FO); \
+ unsigned char *GF_EIB_INIT(FO);
+
+#define GF_CH_RESET(F) (op = eob = GF_QUE_START(F), \
+ (F)->queueout = (F)->queuein = 0)
+
+#define GF_END(FI, FO) (GF_OP_END(FI), GF_IP_END(FO))
+
+#define GF_FLUSH(F) ((GF_IP_END(F), (*(F)->f)((F), GF_DATA), \
+ GF_IP_INIT(F), GF_EIB_INIT(F)) ? 1 : 0)
+#define GF_FLUSH_GLO(F) ((GF_IP_END_GLO(F), (*(F)->f)((F), GF_DATA), \
+ GF_IP_INIT_GLO(F), GF_EIB_INIT_GLO(F)) ? 1 : 0)
+
+#define GF_PUTC(F, C) ((int)(*ip++ = (C), (ip >= eib) ? GF_FLUSH(F) : 1))
+#define GF_PUTC_GLO(F, C) ((int)(*(*ipp)++ = (C), ((*ipp) >= (*eibp)) ? GF_FLUSH_GLO(F) : 1))
+
+/*
+ * Introducing the *_GLO macros for use in splitting the big macros out
+ * into functions (wrap_flush, wrap_eol). The reason we need a
+ * separate macro is because of the vars ip, eib, op, and eob, which are
+ * set up locally in a call to GF_INIT. To preserve these variables
+ * in the new functions, we now pass pointers to these four vars. Each
+ * of these new functions expects the presence of pointer vars
+ * ipp, eibp, opp, and eobp.
+ */
+
+#define GF_GETC(F, C) ((op < eob) ? (((C) = *op++), 1) : GF_CH_RESET(F))
+
+#define GF_COLOR_PUTC(F, C) { \
+ char *p; \
+ char cb[RGBLEN+1]; \
+ GF_PUTC_GLO((F)->next, TAG_EMBED); \
+ GF_PUTC_GLO((F)->next, TAG_FGCOLOR); \
+ strncpy(cb, color_to_asciirgb((C)->fg), sizeof(cb)); \
+ cb[sizeof(cb)-1] = '\0'; \
+ p = cb; \
+ for(; *p; p++) \
+ GF_PUTC_GLO((F)->next, *p); \
+ GF_PUTC_GLO((F)->next, TAG_EMBED); \
+ GF_PUTC_GLO((F)->next, TAG_BGCOLOR); \
+ strncpy(cb, color_to_asciirgb((C)->bg), sizeof(cb)); \
+ cb[sizeof(cb)-1] = '\0'; \
+ p = cb; \
+ for(; *p; p++) \
+ GF_PUTC_GLO((F)->next, *p); \
+ }
+
+/*
+ * Generalized getc and putc routines. provided here so they don't
+ * need to be re-done elsewhere to
+ */
+
+/*
+ * pointers to objects to be used by the generic getc and putc
+ * functions
+ */
+static struct gf_io_struct {
+ FILE *file;
+ PIPE_S *pipe;
+ char *txtp;
+ unsigned long n;
+ int flags;
+ CBUF_S cb;
+} gf_in, gf_out;
+
+#define GF_SO_STACK struct gf_so_stack
+static GF_SO_STACK {
+ STORE_S *so;
+ GF_SO_STACK *next;
+} *gf_so_in, *gf_so_out;
+
+
+
+/*
+ * Returns 1 if pc will write into a PicoText object, 0 otherwise.
+ *
+ * The purpose of this routine is so that we can avoid setting SIGALARM
+ * when writing into a PicoText object, because that type of object uses
+ * unprotected malloc/free/realloc, which can't be interrupted.
+ */
+int
+pc_is_picotext(gf_io_t pc)
+{
+ return(pc == gf_so_writec && gf_so_out && gf_so_out->so &&
+ gf_so_out->so->src == ExternalText);
+}
+
+
+
+/*
+ * setup to use and return a pointer to the generic
+ * getc function
+ */
+void
+gf_set_readc(gf_io_t *gc, void *txt, long unsigned int len, SourceType src, int flags)
+{
+ gf_in.n = len;
+ gf_in.flags = flags;
+ gf_in.cb.cbuf[0] = '\0';
+ gf_in.cb.cbufp = gf_in.cb.cbuf;
+ gf_in.cb.cbufend = gf_in.cb.cbuf;
+
+ if(src == FileStar){
+ gf_in.file = (FILE *)txt;
+ fseek(gf_in.file, 0L, 0);
+#ifdef _WINDOWS
+ *gc = (flags & READ_FROM_LOCALE) ? gf_freadc_windows
+ : gf_freadc;
+#else /* UNIX */
+ *gc = (flags & READ_FROM_LOCALE) ? gf_freadc_locale
+ : gf_freadc;
+#endif /* UNIX */
+ }
+ else if(src == PipeStar){
+ gf_in.pipe = (PIPE_S *)txt;
+ *gc = gf_preadc;
+ *gc = (flags & READ_FROM_LOCALE) ? gf_preadc_locale
+ : gf_preadc;
+ }
+ else{
+ gf_in.txtp = (char *)txt;
+ *gc = (flags & READ_FROM_LOCALE) ? gf_sreadc_locale
+ : gf_sreadc;
+ }
+}
+
+
+/*
+ * setup to use and return a pointer to the generic
+ * putc function
+ */
+void
+gf_set_writec(gf_io_t *pc, void *txt, long unsigned int len, SourceType src, int flags)
+{
+ gf_out.n = len;
+ gf_out.flags = flags;
+ gf_out.cb.cbuf[0] = '\0';
+ gf_out.cb.cbufp = gf_out.cb.cbuf;
+ gf_out.cb.cbufend = gf_out.cb.cbuf;
+
+ if(src == FileStar){
+ gf_out.file = (FILE *)txt;
+#ifdef _WINDOWS
+ *pc = gf_fwritec;
+#else /* UNIX */
+ *pc = (flags & WRITE_TO_LOCALE) ? gf_fwritec_locale
+ : gf_fwritec;
+#endif /* UNIX */
+ }
+ else if(src == PipeStar){
+ gf_out.pipe = (PIPE_S *)txt;
+ *pc = (flags & WRITE_TO_LOCALE) ? gf_pwritec_locale
+ : gf_pwritec;
+ }
+ else{
+ gf_out.txtp = (char *)txt;
+ *pc = (flags & WRITE_TO_LOCALE) ? gf_swritec_locale
+ : gf_swritec;
+ }
+}
+
+
+/*
+ * setup to use and return a pointer to the generic
+ * getc function
+ */
+void
+gf_set_so_readc(gf_io_t *gc, STORE_S *so)
+{
+ GF_SO_STACK *sp = (GF_SO_STACK *) fs_get(sizeof(GF_SO_STACK));
+
+ sp->so = so;
+ sp->next = gf_so_in;
+ gf_so_in = sp;
+ *gc = gf_so_readc;
+}
+
+
+void
+gf_clear_so_readc(STORE_S *so)
+{
+ GF_SO_STACK *sp;
+
+ if((sp = gf_so_in) != NULL){
+ if(so == sp->so){
+ gf_so_in = gf_so_in->next;
+ fs_give((void **) &sp);
+ }
+ else
+ panic("Programmer botch: Can't unstack store readc");
+ }
+ else
+ panic("Programmer botch: NULL store clearing store readc");
+}
+
+
+/*
+ * setup to use and return a pointer to the generic
+ * putc function
+ */
+void
+gf_set_so_writec(gf_io_t *pc, STORE_S *so)
+{
+ GF_SO_STACK *sp = (GF_SO_STACK *) fs_get(sizeof(GF_SO_STACK));
+
+ sp->so = so;
+ sp->next = gf_so_out;
+ gf_so_out = sp;
+ *pc = gf_so_writec;
+}
+
+
+void
+gf_clear_so_writec(STORE_S *so)
+{
+ GF_SO_STACK *sp;
+
+ if((sp = gf_so_out) != NULL){
+ if(so == sp->so){
+ gf_so_out = gf_so_out->next;
+ fs_give((void **) &sp);
+ }
+ else
+ panic("Programmer botch: Can't unstack store writec");
+ }
+ else
+ panic("Programmer botch: NULL store clearing store writec");
+}
+
+
+/*
+ * put the character to the object previously defined
+ */
+int
+gf_so_writec(int c)
+{
+ return(so_writec(c, gf_so_out->so));
+}
+
+
+/*
+ * get a character from an object previously defined
+ */
+int
+gf_so_readc(unsigned char *c)
+{
+ return(so_readc(c, gf_so_in->so));
+}
+
+
+/* get a character from a file */
+/* assumes gf_out struct is filled in */
+int
+gf_freadc(unsigned char *c)
+{
+ int rv = 0;
+
+ do {
+ errno = 0;
+ clearerr(gf_in.file);
+ rv = fread(c, sizeof(unsigned char), (size_t)1, gf_in.file);
+ } while(!rv && ferror(gf_in.file) && errno == EINTR);
+
+ return(rv);
+}
+
+
+int
+gf_freadc_locale(unsigned char *c)
+{
+ return(generic_readc_locale(c, gf_freadc_getchar, (void *) gf_in.file, &gf_in.cb));
+}
+
+
+/*
+ * This is just to make it work with generic_readc_locale.
+ */
+int
+gf_freadc_getchar(unsigned char *c, void *extraarg)
+{
+ FILE *file;
+ int rv = 0;
+
+ file = (FILE *) extraarg;
+
+ do {
+ errno = 0;
+ clearerr(file);
+ rv = fread(c, sizeof(unsigned char), (size_t)1, file);
+ } while(!rv && ferror(file) && errno == EINTR);
+
+ return(rv);
+}
+
+
+/*
+ * Put a character to a file.
+ * Assumes gf_out struct is filled in.
+ * Returns 1 on success, <= 0 on failure.
+ */
+int
+gf_fwritec(int c)
+{
+ unsigned char ch = (unsigned char)c;
+ int rv = 0;
+
+ do
+ rv = fwrite(&ch, sizeof(unsigned char), (size_t)1, gf_out.file);
+ while(!rv && ferror(gf_out.file) && errno == EINTR);
+
+ return(rv);
+}
+
+
+/*
+ * The locale version converts from UTF-8 to user's locale charset
+ * before writing the characters.
+ */
+int
+gf_fwritec_locale(int c)
+{
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if((outchars = utf8_to_locale(c, &gf_out.cb, obuf, sizeof(obuf))) != 0){
+ for(i = 0; i < outchars; i++)
+ if(gf_fwritec(obuf[i]) != 1){
+ rv = 0;
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+#ifdef _WINDOWS
+/*
+ * Read unicode characters from windows filesystem and return
+ * them as a stream of UTF-8 characters. The stream is assumed
+ * opened so that it will know how to put together the unicode.
+ *
+ * (This is totally untested, copied loosely from so_file_readc_windows
+ * which may or may not be appropriate.)
+ */
+int
+gf_freadc_windows(unsigned char *c)
+{
+ int rv = 0;
+ UCS ucs;
+
+ /* already got some from previous call? */
+ if(gf_in.cb.cbufend > gf_in.cb.cbuf){
+ *c = *gf_in.cb.cbufp;
+ gf_in.cb.cbufp++;
+ rv++;
+ if(gf_in.cb.cbufp >= gf_in.cb.cbufend){
+ gf_in.cb.cbufend = gf_in.cb.cbuf;
+ gf_in.cb.cbufp = gf_in.cb.cbuf;
+ }
+
+ return(rv);
+ }
+
+ if(gf_in.file){
+ /* windows only so second arg is ignored */
+ ucs = read_a_wide_char(gf_in.file, NULL);
+ rv = (ucs == CCONV_EOF) ? 0 : 1;
+ }
+
+ if(rv){
+ /*
+ * Now we need to convert the UCS character to UTF-8
+ * and dole out the UTF-8 one char at a time.
+ */
+ gf_in.cb.cbufend = utf8_put(gf_in.cb.cbuf, (unsigned long) ucs);
+ gf_in.cb.cbufp = gf_in.cb.cbuf;
+ if(gf_in.cb.cbufend > gf_in.cb.cbuf){
+ *c = *gf_in.cb.cbufp;
+ gf_in.cb.cbufp++;
+ if(gf_in.cb.cbufp >= gf_in.cb.cbufend){
+ gf_in.cb.cbufend = gf_in.cb.cbuf;
+ gf_in.cb.cbufp = gf_in.cb.cbuf;
+ }
+ }
+ else
+ *c = '?';
+ }
+
+ return(rv);
+}
+#endif /* _WINDOWS */
+
+
+int
+gf_preadc(unsigned char *c)
+{
+ return(pipe_readc(c, gf_in.pipe));
+}
+
+
+int
+gf_preadc_locale(unsigned char *c)
+{
+ return(generic_readc_locale(c, gf_preadc_getchar, (void *) gf_in.pipe, &gf_in.cb));
+}
+
+
+/*
+ * This is just to make it work with generic_readc_locale.
+ */
+int
+gf_preadc_getchar(unsigned char *c, void *extraarg)
+{
+ PIPE_S *pipe;
+
+ pipe = (PIPE_S *) extraarg;
+
+ return(pipe_readc(c, pipe));
+}
+
+
+/*
+ * Put a character to a pipe.
+ * Assumes gf_out struct is filled in.
+ * Returns 1 on success, <= 0 on failure.
+ */
+int
+gf_pwritec(int c)
+{
+ return(pipe_writec(c, gf_out.pipe));
+}
+
+
+/*
+ * The locale version converts from UTF-8 to user's locale charset
+ * before writing the characters.
+ */
+int
+gf_pwritec_locale(int c)
+{
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if((outchars = utf8_to_locale(c, &gf_out.cb, obuf, sizeof(obuf))) != 0){
+ for(i = 0; i < outchars; i++)
+ if(gf_pwritec(obuf[i]) != 1){
+ rv = 0;
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+/* get a character from a string, return nonzero if things OK */
+/* assumes gf_out struct is filled in */
+int
+gf_sreadc(unsigned char *c)
+{
+ return((gf_in.n) ? *c = *(gf_in.txtp)++, gf_in.n-- : 0);
+}
+
+
+int
+gf_sreadc_locale(unsigned char *c)
+{
+ return(generic_readc_locale(c, gf_sreadc_getchar, NULL, &gf_in.cb));
+}
+
+
+int
+gf_sreadc_getchar(unsigned char *c, void *extraarg)
+{
+ /*
+ * extraarg is ignored and gf_sreadc just uses globals instead.
+ * That's ok as long as we don't call it more than once at a time.
+ */
+ return(gf_sreadc(c));
+}
+
+
+/*
+ * Put a character to a string.
+ * Assumes gf_out struct is filled in.
+ * Returns 1 on success, <= 0 on failure.
+ */
+int
+gf_swritec(int c)
+{
+ return((gf_out.n) ? *(gf_out.txtp)++ = c, gf_out.n-- : 0);
+}
+
+
+/*
+ * The locale version converts from UTF-8 to user's locale charset
+ * before writing the characters.
+ */
+int
+gf_swritec_locale(int c)
+{
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if((outchars = utf8_to_locale(c, &gf_out.cb, obuf, sizeof(obuf))) != 0){
+ for(i = 0; i < outchars; i++)
+ if(gf_swritec(obuf[i]) != 1){
+ rv = 0;
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * output the given string with the given function
+ */
+int
+gf_puts(register char *s, gf_io_t pc)
+{
+ while(*s != '\0')
+ if(!(*pc)((unsigned char)*s++))
+ return(0); /* ERROR putting char ! */
+
+ return(1);
+}
+
+
+/*
+ * output the given string with the given function
+ */
+int
+gf_nputs(register char *s, long int n, gf_io_t pc)
+{
+ while(n--)
+ if(!(*pc)((unsigned char)*s++))
+ return(0); /* ERROR putting char ! */
+
+ return(1);
+}
+
+
+/*
+ * Read a stream of multi-byte characters from the
+ * user's locale charset and return a stream of
+ * UTF-8 characters, one at a time. The input characters
+ * are obtained by using the get_a_char function.
+ *
+ * Args c -- the returned octet
+ * get_a_char -- function to get a single octet of the multibyte
+ * character. The first arg of that function is the
+ * returned value and the second arg is for the
+ * functions use. The second arg is replaced with
+ * extraarg when it is called.
+ * extraarg -- The second arg to get_a_char.
+ * cb -- Storage area for state between calls to this func.
+ */
+int
+generic_readc_locale(unsigned char *c,
+ int (*get_a_char)(unsigned char *, void *),
+ void *extraarg,
+ CBUF_S *cb)
+{
+ unsigned long octets_so_far = 0, remaining_octets;
+ unsigned char *inputp;
+ unsigned char ch;
+ UCS ucs;
+ unsigned char inputbuf[20];
+ int rv = 0;
+ int got_one = 0;
+
+ /* already got some from previous call? */
+ if(cb->cbufend > cb->cbuf){
+ *c = *cb->cbufp;
+ cb->cbufp++;
+ rv++;
+ if(cb->cbufp >= cb->cbufend){
+ cb->cbufend = cb->cbuf;
+ cb->cbufp = cb->cbuf;
+ }
+
+ return(rv);
+ }
+
+ memset(inputbuf, 0, sizeof(inputbuf));
+ if((*get_a_char)(&ch, extraarg) == 0)
+ return(0);
+
+ inputbuf[octets_so_far++] = ch;
+
+ while(!got_one){
+ remaining_octets = octets_so_far;
+ inputp = inputbuf;
+ ucs = mbtow(ps_global->input_cs, &inputp, &remaining_octets);
+ switch(ucs){
+ case CCONV_BADCHAR:
+ return(rv);
+
+ case CCONV_NEEDMORE:
+/*
+ * Do we need to do something with the characters we've
+ * collected that don't form a valid UCS character?
+ * Probably need to try discarding them one at a time
+ * from the front instead of just throwing them all out.
+ */
+ if(octets_so_far >= sizeof(inputbuf))
+ return(rv);
+
+ if((*get_a_char)(&ch, extraarg) == 0)
+ return(rv);
+
+ inputbuf[octets_so_far++] = ch;
+ break;
+
+ default:
+ /* got a good UCS-4 character */
+ got_one++;
+ break;
+ }
+ }
+
+ /*
+ * Now we need to convert the UCS character to UTF-8
+ * and dole out the UTF-8 one char at a time.
+ */
+ rv++;
+ cb->cbufend = utf8_put(cb->cbuf, (unsigned long) ucs);
+ cb->cbufp = cb->cbuf;
+ if(cb->cbufend > cb->cbuf){
+ *c = *cb->cbufp;
+ cb->cbufp++;
+ if(cb->cbufp >= cb->cbufend){
+ cb->cbufend = cb->cbuf;
+ cb->cbufp = cb->cbuf;
+ }
+ }
+ else
+ *c = '?';
+
+ return(rv);
+}
+
+
+/*
+ * Start of generalized filter routines
+ */
+
+/*
+ * initializing function to make sure list of filters is empty.
+ */
+void
+gf_filter_init(void)
+{
+ FILTER_S *flt, *fltn = gf_master;
+
+ while((flt = fltn) != NULL){ /* free list of old filters */
+ fltn = flt->next;
+ fs_give((void **)&flt);
+ }
+
+ gf_master = NULL;
+ gf_error_string = NULL; /* clear previous errors */
+ gf_byte_count = 0L; /* reset counter */
+}
+
+
+
+/*
+ * link the given filter into the filter chain
+ */
+void
+gf_link_filter(filter_t f, void *data)
+{
+ FILTER_S *new, *tail;
+
+#ifdef CRLF_NEWLINES
+ /*
+ * If the system's native EOL convention is CRLF, then there's no
+ * point in passing data thru a filter that's not doing anything
+ */
+ if(f == gf_nvtnl_local || f == gf_local_nvtnl)
+ return;
+#endif
+
+ new = (FILTER_S *)fs_get(sizeof(FILTER_S));
+ memset(new, 0, sizeof(FILTER_S));
+
+ new->f = f; /* set the function pointer */
+ new->opt = data; /* set any optional parameter data */
+ (*f)(new, GF_RESET); /* have it setup initial state */
+
+ if((tail = gf_master) != NULL){ /* or add it to end of existing */
+ while(tail->next) /* list */
+ tail = tail->next;
+
+ tail->next = new;
+ }
+ else /* attach new struct to list */
+ gf_master = new; /* start a new list */
+}
+
+
+/*
+ * terminal filter, doesn't call any other filters, typically just does
+ * something with the output
+ */
+void
+gf_terminal(FILTER_S *f, int flg)
+{
+ if(flg == GF_DATA){
+ GF_INIT(f, f);
+
+ while(op < eob)
+ if((*last_filter)(*op++) <= 0) /* generic terminal filter */
+ gf_error(errno ? error_description(errno) : "Error writing pipe");
+
+ GF_CH_RESET(f);
+ }
+ else if(flg == GF_RESET)
+ errno = 0; /* prepare for problems */
+}
+
+
+/*
+ * set some outside gf_io_t function to the terminal function
+ * for example: a function to write a char to a file or into a buffer
+ */
+void
+gf_set_terminal(gf_io_t f) /* function to set generic filter */
+
+{
+ last_filter = f;
+}
+
+
+/*
+ * common function for filter's to make it known that an error
+ * has occurred. Jumps back to gf_pipe with error message.
+ */
+void
+gf_error(char *s)
+{
+ /* let the user know the error passed in s */
+ gf_error_string = s;
+ longjmp(gf_error_state, 1);
+}
+
+
+/*
+ * The routine that shoves each byte through the chain of
+ * filters. It sets up error handling, and the terminal function.
+ * Then loops getting bytes with the given function, and passing
+ * it on to the first filter in the chain.
+ */
+char *
+gf_pipe(gf_io_t gc, gf_io_t pc)
+ /* how to get a character */
+{
+ unsigned char c;
+
+ dprint((4, "-- gf_pipe: "));
+
+ /*
+ * set up for any errors a filter may encounter
+ */
+ if(setjmp(gf_error_state)){
+ dprint((4, "ERROR: %s\n",
+ gf_error_string ? gf_error_string : "NULL"));
+ return(gf_error_string); /* */
+ }
+
+ /*
+ * set and link in the terminal filter
+ */
+ gf_set_terminal(pc);
+ gf_link_filter(gf_terminal, NULL);
+
+ /*
+ * while there are chars to process, send them thru the pipe.
+ * NOTE: it's necessary to enclose the loop below in a block
+ * as the GF_INIT macro calls some automatic var's into
+ * existence. It can't be placed at the start of gf_pipe
+ * because its useful for us to be called without filters loaded
+ * when we're just being used to copy bytes between storage
+ * objects.
+ */
+ {
+ GF_INIT(gf_master, gf_master);
+
+ while((*gc)(&c)){
+ gf_byte_count++;
+
+#ifdef _WINDOWS
+ if(!(gf_byte_count & 0x3ff))
+ /* Under windows we yield to allow event processing.
+ * Progress display is handled throught the alarm()
+ * mechinism.
+ */
+ mswin_yield ();
+#endif
+
+ GF_PUTC(gf_master, c & 0xff);
+ }
+
+ /*
+ * toss an end-of-data marker down the pipe to give filters
+ * that have any buffered data the opportunity to dump it
+ */
+ (void) GF_FLUSH(gf_master);
+ (*gf_master->f)(gf_master, GF_EOD);
+ }
+
+ dprint((4, "done.\n"));
+ return(NULL); /* everything went OK */
+}
+
+
+/*
+ * return the number of bytes piped so far
+ */
+long
+gf_bytes_piped(void)
+{
+ return(gf_byte_count);
+}
+
+
+/*
+ * filter the given input with the given command
+ *
+ * Args: cmd -- command string to execute
+ * prepend -- string to prepend to filtered input
+ * source_so -- storage object containing data to be filtered
+ * pc -- function to write filtered output with
+ * aux_filters -- additional filters to pass data thru after "cmd"
+ *
+ * Returns: NULL on sucess, reason for failure (not alloc'd!) on error
+ */
+char *
+gf_filter(char *cmd, char *prepend, STORE_S *source_so, gf_io_t pc,
+ FILTLIST_S *aux_filters, int disable_reset,
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ unsigned char c, obuf[MAX(MB_LEN_MAX,32)];
+ int flags, outchars, i;
+ char *errstr = NULL, buf[MAILTMPLEN];
+ PIPE_S *fpipe;
+ CBUF_S cb;
+#ifdef NON_BLOCKING_IO
+ int n;
+#endif
+
+ dprint((4, "so_filter: \"%s\"\n", cmd ? cmd : "?"));
+
+ gf_filter_init();
+
+ /*
+ * After coming back from user's pipe command we need to convert
+ * the output from the pipe back to UTF-8.
+ */
+ if(ps_global->keyboard_charmap && strucmp("UTF-8", ps_global->keyboard_charmap))
+ gf_link_filter(gf_utf8, gf_utf8_opt(ps_global->keyboard_charmap));
+
+ for( ; aux_filters && aux_filters->filter; aux_filters++)
+ gf_link_filter(aux_filters->filter, aux_filters->data);
+
+ gf_set_terminal(pc);
+ gf_link_filter(gf_terminal, NULL);
+
+ cb.cbuf[0] = '\0';
+ cb.cbufp = cb.cbuf;
+ cb.cbufend = cb.cbuf;
+
+ /*
+ * Spawn filter feeding it data, and reading what it writes.
+ */
+ so_seek(source_so, 0L, 0);
+ flags = PIPE_WRITE | PIPE_READ | PIPE_NOSHELL |
+ (!disable_reset ? PIPE_RESET : 0);
+
+ if((fpipe = open_system_pipe(cmd, NULL, NULL, flags, 0, pipecb_f, pipe_report_error)) != NULL){
+
+#ifdef NON_BLOCKING_IO
+
+ if(fcntl(fileno(fpipe->in.f), F_SETFL, NON_BLOCKING_IO) == -1)
+ errstr = "Can't set up non-blocking IO";
+
+ if(prepend && (fputs(prepend, fpipe->out.f) == EOF
+ || fputc('\n', fpipe->out.f) == EOF))
+ errstr = error_description(errno);
+
+ while(!errstr){
+ /* if the pipe can't hold a K we're sunk (too bad PIPE_MAX
+ * isn't ubiquitous ;).
+ */
+ for(n = 0; !errstr && fpipe->out.f && n < 1024; n++)
+ if(!so_readc(&c, source_so)){
+ fclose(fpipe->out.f);
+ fpipe->out.f = NULL;
+ }
+ else{
+ /*
+ * Got a UTF-8 character from source_so.
+ * We need to convert it to the user's locale charset
+ * and then send the result to the pipe.
+ */
+ if((outchars = utf8_to_locale((int) c, &cb, obuf, sizeof(obuf))) != 0)
+ for(i = 0; i < outchars && !errstr; i++)
+ if(fputc(obuf[i], fpipe->out.f) == EOF)
+ errstr = error_description(errno);
+ }
+
+ /*
+ * Note: We clear errno here and test below, before ferror,
+ * because *some* stdio implementations consider
+ * EAGAIN and EWOULDBLOCK equivalent to EOF...
+ */
+ errno = 0;
+ clearerr(fpipe->in.f); /* fix from <cananian@cananian.mit.edu> */
+
+ while(!errstr && fgets(buf, sizeof(buf), fpipe->in.f))
+ errstr = gf_filter_puts(buf);
+
+ /* then fgets failed! */
+ if(!errstr && !(errno == EAGAIN || errno == EWOULDBLOCK)){
+ if(feof(fpipe->in.f)) /* nothing else interesting! */
+ break;
+ else if(ferror(fpipe->in.f)) /* bummer. */
+ errstr = error_description(errno);
+ }
+ else if(errno == EAGAIN || errno == EWOULDBLOCK)
+ clearerr(fpipe->in.f);
+ }
+
+#else /* !NON_BLOCKING_IO */
+
+ if(prepend && (pipe_puts(prepend, fpipe) == EOF
+ || pipe_putc('\n', fpipe) == EOF))
+ errstr = error_description(errno);
+
+ /*
+ * Well, do the best we can, and hope the pipe we're writing
+ * doesn't fill up before we start reading...
+ */
+ while(!errstr && so_readc(&c, source_so))
+ if((outchars = utf8_to_locale((int) c, &cb, obuf, sizeof(obuf))) != 0)
+ for(i = 0; i < outchars && !errstr; i++)
+ if(pipe_putc(obuf[i], fpipe) == EOF)
+ errstr = error_description(errno);
+
+ if(pipe_close_write(fpipe))
+ errstr = _("Pipe command returned error.");
+
+ while(!errstr && pipe_gets(buf, sizeof(buf), fpipe))
+ errstr = gf_filter_puts(buf);
+
+#endif /* !NON_BLOCKING_IO */
+
+ if(close_system_pipe(&fpipe, NULL, pipecb_f) && !errstr)
+ errstr = _("Pipe command returned error.");
+
+ gf_filter_eod();
+ }
+ else
+ errstr = _("Error setting up pipe command.");
+
+ return(errstr);
+}
+
+
+/*
+ * gf_filter_puts - write the given string down the filter's pipe
+ */
+char *
+gf_filter_puts(register char *s)
+{
+ GF_INIT(gf_master, gf_master);
+
+ /*
+ * set up for any errors a filter may encounter
+ */
+ if(setjmp(gf_error_state)){
+ dprint((4, "ERROR: gf_filter_puts: %s\n",
+ gf_error_string ? gf_error_string : "NULL"));
+ return(gf_error_string);
+ }
+
+ while(*s)
+ GF_PUTC(gf_master, (*s++) & 0xff);
+
+ GF_END(gf_master, gf_master);
+ return(NULL);
+}
+
+
+/*
+ * gf_filter_eod - flush pending data filter's input queue and deliver
+ * the GF_EOD marker.
+ */
+void
+gf_filter_eod(void)
+{
+ GF_INIT(gf_master, gf_master);
+ (void) GF_FLUSH(gf_master);
+ (*gf_master->f)(gf_master, GF_EOD);
+}
+
+
+/*
+ * END OF PIPE SUPPORT ROUTINES, BEGINNING OF FILTERS
+ *
+ * Filters MUST use the specified interface (pointer to filter
+ * structure, the unsigned character buffer in that struct, and a
+ * cmd flag), and pass each resulting octet to the next filter in the
+ * chain. Only the terminal filter need not call another filter.
+ * As a result, filters share a pretty general structure.
+ * Typically three main conditionals separate initialization from
+ * data from end-of-data command processing.
+ *
+ * Lastly, being character-at-a-time, they're a little more complex
+ * to write than filters operating on buffers because some state
+ * must typically be kept between characters. However, for a
+ * little bit of complexity here, much convenience is gained later
+ * as they can be arbitrarily chained together at run time and
+ * consume few resources (especially memory or disk) as they work.
+ * (NOTE 951005: even less cpu now that data between filters is passed
+ * via a vector.)
+ *
+ * A few notes about implementing filters:
+ *
+ * - A generic filter template looks like:
+ *
+ * void
+ * gf_xxx_filter(f, flg)
+ * FILTER_S *f;
+ * int flg;
+ * {
+ * GF_INIT(f, f->next); // def's var's to speed queue drain
+ *
+ * if(flg == GF_DATA){
+ * register unsigned char c;
+ *
+ * while(GF_GETC(f, c)){ // macro taking data off input queue
+ * // operate on c and pass it on here
+ * GF_PUTC(f->next, c); // macro writing output queue
+ * }
+ *
+ * GF_END(f, f->next); // macro to sync pointers/offsets
+ * //WARNING: DO NOT RETURN BEFORE ALL INCOMING DATA'S PROCESSED
+ * }
+ * else if(flg == GF_EOD){
+ * // process any buffered data here and pass it on
+ * GF_FLUSH(f->next); // flush pending data to next filter
+ * (*f->next->f)(f->next, GF_EOD);
+ * }
+ * else if(flg == GF_RESET){
+ * // initialize any data in the struct here
+ * }
+ * }
+ *
+ * - Any free storage allocated during initialization (typically tied
+ * to the "line" pointer in FILTER_S) is the filter's responsibility
+ * to clean up when the GF_EOD command comes through.
+ *
+ * - Filter's must pass GF_EOD they receive on to the next
+ * filter in the chain so it has the opportunity to flush
+ * any buffered data.
+ *
+ * - All filters expect NVT end-of-lines. The idea is to prepend
+ * or append either the gf_local_nvtnl or gf_nvtnl_local
+ * os-dependant filters to the data on the appropriate end of the
+ * pipe for the task at hand.
+ *
+ * - NOTE: As of 951004, filters no longer take their input as a single
+ * char argument, but rather get data to operate on via a vector
+ * representing the input queue in the FILTER_S structure.
+ *
+ */
+
+
+
+/*
+ * BASE64 TO BINARY encoding and decoding routines below
+ */
+
+
+/*
+ * BINARY to BASE64 filter (encoding described in rfc1341)
+ */
+void
+gf_binary_b64(FILTER_S *f, int flg)
+{
+ static char *v =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register unsigned char t = f->t;
+ register long n = f->n;
+
+ while(GF_GETC(f, c)){
+
+ switch(n++){
+ case 0 : case 3 : case 6 : case 9 : case 12: case 15: case 18:
+ case 21: case 24: case 27: case 30: case 33: case 36: case 39:
+ case 42: case 45:
+ GF_PUTC(f->next, v[c >> 2]);
+ /* byte 1: high 6 bits (1) */
+ t = c << 4; /* remember high 2 bits for next */
+ break;
+
+ case 1 : case 4 : case 7 : case 10: case 13: case 16: case 19:
+ case 22: case 25: case 28: case 31: case 34: case 37: case 40:
+ case 43:
+ GF_PUTC(f->next, v[(t|(c>>4)) & 0x3f]);
+ t = c << 2;
+ break;
+
+ case 2 : case 5 : case 8 : case 11: case 14: case 17: case 20:
+ case 23: case 26: case 29: case 32: case 35: case 38: case 41:
+ case 44:
+ GF_PUTC(f->next, v[(t|(c >> 6)) & 0x3f]);
+ GF_PUTC(f->next, v[c & 0x3f]);
+ break;
+ }
+
+ if(n == 45){ /* start a new line? */
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ n = 0L;
+ }
+ }
+
+ f->n = n;
+ f->t = t;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){ /* no more data */
+ switch (f->n % 3) { /* handle trailing bytes */
+ case 0: /* no trailing bytes */
+ break;
+
+ case 1:
+ GF_PUTC(f->next, v[(f->t) & 0x3f]);
+ GF_PUTC(f->next, '='); /* byte 3 */
+ GF_PUTC(f->next, '='); /* byte 4 */
+ break;
+
+ case 2:
+ GF_PUTC(f->next, v[(f->t) & 0x3f]);
+ GF_PUTC(f->next, '='); /* byte 4 */
+ break;
+ }
+
+ /* end with CRLF */
+ if(f->n){
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ }
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset binary_b64\n"));
+ f->n = 0L;
+ }
+}
+
+
+
+/*
+ * BASE64 to BINARY filter (encoding described in rfc1341)
+ */
+void
+gf_b64_binary(FILTER_S *f, int flg)
+{
+ static char v[] = {65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,
+ 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,
+ 65,65,65,65,65,65,65,65,65,65,65,62,65,65,65,63,
+ 52,53,54,55,56,57,58,59,60,61,65,65,65,64,65,65,
+ 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
+ 15,16,17,18,19,20,21,22,23,24,25,65,65,65,65,65,
+ 65,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
+ 41,42,43,44,45,46,47,48,49,50,51,65,65,65,65,65};
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register unsigned char t = f->t;
+ register int n = (int) f->n;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+
+ if(state){
+ state = 0;
+ if (c != '=') {
+ gf_error("Illegal '=' in base64 text");
+ /* NO RETURN */
+ }
+ }
+
+ /* in range, and a valid value? */
+ if((c & ~0x7f) || (c = v[c]) > 63){
+ if(c == 64){
+ switch (n++) { /* check quantum position */
+ case 2:
+ state++; /* expect an equal as next char */
+ break;
+
+ case 3:
+ n = 0L; /* restart quantum */
+ break;
+
+ default: /* impossible quantum position */
+ gf_error("Internal base64 decoder error");
+ /* NO RETURN */
+ }
+ }
+ }
+ else{
+ switch (n++) { /* install based on quantum position */
+ case 0: /* byte 1: high 6 bits */
+ t = c << 2;
+ break;
+
+ case 1: /* byte 1: low 2 bits */
+ GF_PUTC(f->next, (t|(c >> 4)));
+ t = c << 4; /* byte 2: high 4 bits */
+ break;
+
+ case 2: /* byte 2: low 4 bits */
+ GF_PUTC(f->next, (t|(c >> 2)));
+ t = c << 6; /* byte 3: high 2 bits */
+ break;
+
+ case 3:
+ GF_PUTC(f->next, t | c);
+ n = 0L; /* reinitialize mechanism */
+ break;
+ }
+ }
+ }
+
+ f->f1 = state;
+ f->t = t;
+ f->n = n;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset b64_binary\n"));
+ f->n = 0L; /* quantum position */
+ f->f1 = 0; /* state holder: equal seen? */
+ }
+}
+
+
+
+
+/*
+ * QUOTED-PRINTABLE ENCODING AND DECODING filters below.
+ * encoding described in rfc1341
+ */
+
+#define GF_MAXLINE 80 /* good buffer size */
+
+/*
+ * default action for QUOTED-PRINTABLE to 8BIT decoder
+ */
+#define GF_QP_DEFAULT(f, c) { \
+ if((c) == ' '){ \
+ state = WSPACE; \
+ /* reset white space! */ \
+ (f)->linep = (f)->line; \
+ *((f)->linep)++ = ' '; \
+ } \
+ else if((c) == '='){ \
+ state = EQUAL; \
+ } \
+ else \
+ GF_PUTC((f)->next, (c)); \
+ }
+
+
+/*
+ * QUOTED-PRINTABLE to 8BIT filter
+ */
+void
+gf_qp_8bit(FILTER_S *f, int flg)
+{
+
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+
+ switch(state){
+ case DFL : /* default case */
+ default:
+ GF_QP_DEFAULT(f, c);
+ break;
+
+ case CCR : /* non-significant space */
+ state = DFL;
+ if(c == '\012')
+ continue; /* go on to next char */
+
+ GF_QP_DEFAULT(f, c);
+ break;
+
+ case EQUAL :
+ if(c == '\015'){ /* "=\015" is a soft EOL */
+ state = CCR;
+ break;
+ }
+
+ if(c == '='){ /* compatibility clause for old guys */
+ GF_PUTC(f->next, '=');
+ state = DFL;
+ break;
+ }
+
+ if(!isxdigit((unsigned char)c)){ /* must be hex! */
+ /*
+ * First character after '=' not a hex digit.
+ * This ain't right, but we're going to treat it as
+ * plain old text instead of an '=' followed by hex.
+ * In other words, they forgot to encode the '='.
+ * Before 4.60 we just bailed with an error here, but now
+ * we keep going as long as we are just displaying
+ * the result (and not saving it or something).
+ *
+ * Wait! The users don't like that. They want to be able
+ * to use it even if it might be wrong. So just plow
+ * ahead even if displaying.
+ *
+ * Better have this be a constant string so that if we
+ * get multiple instances of it in a single message we
+ * can avoid the too many error messages problem. It
+ * better be the same message as the one a few lines
+ * below, as well.
+ *
+ * Turn off decoding after encountering such an error and
+ * just dump the rest of the text as is.
+ */
+ state = STOP_DECODING;
+ GF_PUTC(f->next, '=');
+ GF_PUTC(f->next, c);
+ q_status_message(SM_ORDER,3,3,
+ _("Warning: Non-hexadecimal character in QP encoding!"));
+
+ dprint((2, "gf_qp_8bit: warning: non-hex char in QP encoding: char \"%c\" (%d) follows =\n", c, c));
+ break;
+ }
+
+ if (isdigit ((unsigned char)c))
+ f->t = c - '0';
+ else
+ f->t = c - (isupper((unsigned char)c) ? 'A' - 10 : 'a' - 10);
+
+ f->f2 = c; /* store character in case we have to
+ back out in !isxdigit below */
+
+ state = HEX;
+ break;
+
+ case HEX :
+ state = DFL;
+ if(!isxdigit((unsigned char)c)){ /* must be hex! */
+ state = STOP_DECODING;
+ GF_PUTC(f->next, '=');
+ GF_PUTC(f->next, f->f2);
+ GF_PUTC(f->next, c);
+ q_status_message(SM_ORDER,3,3,
+ _("Warning: Non-hexadecimal character in QP encoding!"));
+
+ dprint((2, "gf_qp_8bit: warning: non-hex char in QP encoding: char \"%c\" (%d) follows =%c\n", c, c, f->f2));
+ break;
+ }
+
+ if (isdigit((unsigned char)c))
+ c -= '0';
+ else
+ c -= (isupper((unsigned char)c) ? 'A' - 10 : 'a' - 10);
+
+ GF_PUTC(f->next, c + (f->t << 4));
+ break;
+
+ case WSPACE :
+ if(c == ' '){ /* toss it in with other spaces */
+ if(f->linep - f->line < GF_MAXLINE)
+ *(f->linep)++ = ' ';
+ break;
+ }
+
+ state = DFL;
+ if(c == '\015'){ /* not our white space! */
+ f->linep = f->line; /* reset buffer */
+ GF_PUTC(f->next, '\015');
+ break;
+ }
+
+ /* the spaces are ours, write 'em */
+ f->n = f->linep - f->line;
+ while((f->n)--)
+ GF_PUTC(f->next, ' ');
+
+ GF_QP_DEFAULT(f, c); /* take care of 'c' in default way */
+ break;
+
+ case STOP_DECODING :
+ GF_PUTC(f->next, c);
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ fs_give((void **)&(f->line));
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset qp_8bit\n"));
+ f->f1 = DFL;
+ f->linep = f->line = (char *)fs_get(GF_MAXLINE * sizeof(char));
+ }
+}
+
+
+
+/*
+ * USEFUL MACROS TO HELP WITH QP ENCODING
+ */
+
+#define QP_MAXL 75 /* 76th place only for continuation */
+
+/*
+ * Macro to test and wrap long quoted printable lines
+ */
+#define GF_8BIT_WRAP(f) { \
+ GF_PUTC((f)->next, '='); \
+ GF_PUTC((f)->next, '\015'); \
+ GF_PUTC((f)->next, '\012'); \
+ }
+
+/*
+ * write a quoted octet in QUOTED-PRINTABLE encoding, adding soft
+ * line break if needed.
+ */
+#define GF_8BIT_PUT_QUOTE(f, c) { \
+ if(((f)->n += 3) > QP_MAXL){ \
+ GF_8BIT_WRAP(f); \
+ (f)->n = 3; /* set line count */ \
+ } \
+ GF_PUTC((f)->next, '='); \
+ GF_PUTC((f)->next, HEX_CHAR1(c)); \
+ GF_PUTC((f)->next, HEX_CHAR2(c)); \
+ }
+
+/*
+ * just write an ordinary octet in QUOTED-PRINTABLE, wrapping line
+ * if needed.
+ */
+#define GF_8BIT_PUT(f, c) { \
+ if((++(f->n)) > QP_MAXL){ \
+ GF_8BIT_WRAP(f); \
+ f->n = 1L; \
+ } \
+ if(f->n == 1L && c == '.'){ \
+ GF_8BIT_PUT_QUOTE(f, c); \
+ f->n = 3; \
+ } \
+ else \
+ GF_PUTC(f->next, c); \
+ }
+
+
+/*
+ * default action for 8bit to quoted printable encoder
+ */
+#define GF_8BIT_DEFAULT(f, c) if((c) == ' '){ \
+ state = WSPACE; \
+ } \
+ else if(c == '\015'){ \
+ state = CCR; \
+ } \
+ else if(iscntrl(c & 0x7f) || (c == 0x7f) \
+ || (c & 0x80) || (c == '=')){ \
+ GF_8BIT_PUT_QUOTE(f, c); \
+ } \
+ else{ \
+ GF_8BIT_PUT(f, c); \
+ }
+
+
+/*
+ * 8BIT to QUOTED-PRINTABLE filter
+ */
+void
+gf_8bit_qp(FILTER_S *f, int flg)
+{
+ short dummy_dots = 0, dummy_dmap = 1;
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+
+ /* keep track of "^JFrom " */
+ Find_Froms(f->t, dummy_dots, f->f2, dummy_dmap, c);
+
+ switch(state){
+ case DFL : /* handle ordinary case */
+ GF_8BIT_DEFAULT(f, c);
+ break;
+
+ case CCR : /* true line break? */
+ state = DFL;
+ if(c == '\012'){
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ f->n = 0L;
+ }
+ else{ /* nope, quote the CR */
+ GF_8BIT_PUT_QUOTE(f, '\015');
+ GF_8BIT_DEFAULT(f, c); /* and don't forget about c! */
+ }
+ break;
+
+ case WSPACE:
+ state = DFL;
+ if(c == '\015' || f->t){ /* handle the space */
+ GF_8BIT_PUT_QUOTE(f, ' ');
+ f->t = 0; /* reset From flag */
+ }
+ else
+ GF_8BIT_PUT(f, ' ');
+
+ GF_8BIT_DEFAULT(f, c); /* handle 'c' in the default way */
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ switch(f->f1){
+ case CCR :
+ GF_8BIT_PUT_QUOTE(f, '\015'); /* write the last cr */
+ break;
+
+ case WSPACE :
+ GF_8BIT_PUT_QUOTE(f, ' '); /* write the last space */
+ break;
+ }
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset 8bit_qp\n"));
+ f->f1 = DFL; /* state from last character */
+ f->f2 = 1; /* state of "^NFrom " bitmap */
+ f->t = 0;
+ f->n = 0L; /* number of chars in current line */
+ }
+}
+
+/*
+ * This filter converts characters in one character set (the character
+ * set of a message, for example) to another (the user's character set).
+ */
+void
+gf_convert_8bit_charset(FILTER_S *f, int flg)
+{
+ static unsigned char *conv_table = NULL;
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+ GF_PUTC(f->next, conv_table ? conv_table[c] : c);
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset convert_8bit_charset\n"));
+ conv_table = (f->opt) ? (unsigned char *) (f->opt) : NULL;
+
+ }
+}
+
+
+typedef struct _utf8c_s {
+ void *conv_table;
+ int report_err;
+} UTF8C_S;
+
+
+/*
+ * This filter converts characters in UTF-8 to an 8-bit or 16-bit charset.
+ * Characters missing from the destination set, and invalid UTF-8 sequences,
+ * will be converted to "?".
+ */
+void
+gf_convert_utf8_charset(FILTER_S *f, int flg)
+{
+ static unsigned short *conv_table = NULL;
+ static int report_err = 0;
+ register int more = f->f2;
+ register long u = f->n;
+
+ /*
+ * "more" is the number of subsequent octets needed to complete a character,
+ * it is stored in f->f2.
+ * "u" is the accumulated Unicode character, it is stored in f->n
+ */
+
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+ if(!conv_table){ /* can't do much if no conversion table */
+ GF_PUTC(f->next, c);
+ }
+ /* UTF-8 continuation? */
+ else if((c > 0x7f) && (c < 0xc0)){
+ if(more){
+ u <<= 6; /* shift current value by 6 bits */
+ u |= c & 0x3f;
+ if (!--more){ /* last octet? */
+ if(u >= 0xffff || (u = conv_table[u]) == NOCHAR){
+ /*
+ * non-BMP character or a UTF-8 character
+ * which is not representable in the
+ * charset we're converting to.
+ */
+ c = '?';
+ if(report_err){
+ if(f->opt)
+ fs_give((void **) &f->opt);
+
+ /* TRANSLATORS: error while translating from one
+ character set to another, for example from UTF-8
+ to ISO-2022-JP or something like that. */
+ gf_error(_("translation error"));
+ /* NO RETURN */
+ }
+ }
+ else{
+ if(u > 0xff){
+ c = (unsigned char) (u >> 8);
+ GF_PUTC(f->next, c);
+ }
+
+ c = (unsigned char) u & 0xff;
+ }
+
+ GF_PUTC(f->next, c);
+ }
+ }
+ else{ /* continuation when not in progress */
+ GF_PUTC(f->next, '?');
+ }
+ }
+ else{
+ if(more){ /* incomplete UTF-8 character */
+ GF_PUTC(f->next, '?');
+ more = 0;
+ }
+ if(c < 0x80){ /* U+0000 - U+007f */
+ GF_PUTC(f->next, c);
+ }
+ else if(c < 0xe0){ /* U+0080 - U+07ff */
+ u = c & 0x1f; /* first 5 bits of 12 */
+ more = 1;
+ }
+ else if(c < 0xf0){ /* U+1000 - U+ffff */
+ u = c & 0x0f; /* first 4 bits of 16 */
+ more = 2;
+ }
+ /* in case we ever support non-BMP Unicode */
+ else if (c < 0xf8){ /* U+10000 - U+10ffff */
+ u = c & 0x07; /* first 3 bits of 20.5 */
+ more = 3;
+ }
+#if 0 /* ISO 10646 not in Unicode */
+ else if (c < 0xfc){ /* ISO 10646 20000 - 3ffffff */
+ u = c & 0x03; /* first 2 bits of 26 */
+ more = 4;
+ }
+ else if (c < 0xfe){ /* ISO 10646 4000000 - 7fffffff */
+ u = c & 0x03; /* first 2 bits of 26 */
+ more = 5;
+ }
+#endif
+ else{ /* not in Unicode */
+ GF_PUTC(f->next, '?');
+ }
+ }
+ }
+
+ f->f2 = more;
+ f->n = u;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ if(f->opt)
+ fs_give((void **) &f->opt);
+
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset convert_utf8_charset\n"));
+ conv_table = ((UTF8C_S *) f->opt)->conv_table;
+ report_err = ((UTF8C_S *) f->opt)->report_err;
+ f->f2 = 0;
+ f->n = 0L;
+ }
+}
+
+
+void *
+gf_convert_utf8_charset_opt(void *table, int report_err)
+{
+ UTF8C_S *utf8c;
+
+ utf8c = (UTF8C_S *) fs_get(sizeof(UTF8C_S));
+ utf8c->conv_table = table;
+ utf8c->report_err = report_err;
+ return((void *) utf8c);
+}
+
+
+/*
+ * ISO-2022-JP to EUC (on Unix) or Shift-JIS (on PC) filter
+ *
+ * The routine is call ..._to_euc but it is really to either euc (unix Pine)
+ * or to Shift-JIS (if PC-Pine).
+ */
+void
+gf_2022_jp_to_euc(FILTER_S *f, int flg)
+{
+ register unsigned char c;
+ register int state = f->f1;
+
+ /*
+ * f->t lit means we're in middle of decoding a sequence of characters.
+ * f->f2 keeps track of first character of pair for Shift-JIS.
+ * f->f1 is the state.
+ */
+
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ while(GF_GETC(f, c)){
+ switch(state){
+ case ESC: /* saw ESC */
+ if(!f->t && c == '$')
+ state = ESCDOL;
+ else if(f->t && c == '(')
+ state = ESCPAR;
+ else{
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, c);
+ state = DFL;
+ }
+
+ break;
+
+ case ESCDOL: /* saw ESC $ */
+ if(c == 'B' || c == '@'){
+ state = EUC;
+ f->t = 1; /* filtering into euc */
+ f->f2 = -1; /* first character of pair */
+ }
+ else{
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '$');
+ GF_PUTC(f->next, c);
+ state = DFL;
+ }
+
+ break;
+
+ case ESCPAR: /* saw ESC ( */
+ if(c == 'B' || c == 'J' || c == 'H'){
+ state = DFL;
+ f->t = 0; /* done filtering */
+ }
+ else{
+ GF_PUTC(f->next, '\033'); /* Don't set hibit for */
+ GF_PUTC(f->next, '('); /* escape sequences, which */
+ GF_PUTC(f->next, c); /* this appears to be. */
+ }
+
+ break;
+
+ case EUC: /* filtering into euc */
+ if(c == '\033')
+ state = ESC;
+ else{
+#ifdef _WINDOWS /* Shift-JIS */
+ c &= 0x7f; /* 8-bit can't win */
+ if (f->f2 >= 0){ /* second of a pair? */
+ int rowOffset = (f->f2 < 95) ? 112 : 176;
+ int cellOffset = (f->f2 % 2) ? ((c > 95) ? 32 : 31)
+ : 126;
+
+ GF_PUTC(f->next, ((f->f2 + 1) >> 1) + rowOffset);
+ GF_PUTC(f->next, c + cellOffset);
+ f->f2 = -1; /* restart */
+ }
+ else if(c > 0x20 && c < 0x7f)
+ f->f2 = c; /* first of pair */
+ else{
+ GF_PUTC(f->next, c); /* write CTL as itself */
+ f->f2 = -1;
+ }
+#else /* EUC */
+ GF_PUTC(f->next, (c > 0x20 && c < 0x7f) ? c | 0x80 : c);
+#endif
+ }
+
+ break;
+
+ case DFL:
+ default:
+ if(c == '\033')
+ state = ESC;
+ else
+ GF_PUTC(f->next, c);
+
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ switch(state){
+ case ESC:
+ GF_PUTC(f->next, '\033');
+ break;
+
+ case ESCDOL:
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '$');
+ break;
+
+ case ESCPAR:
+ GF_PUTC(f->next, '\033'); /* Don't set hibit for */
+ GF_PUTC(f->next, '('); /* escape sequences. */
+ break;
+ }
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset jp_to_euc\n"));
+ f->f1 = DFL; /* state */
+ f->t = 0; /* not translating to euc */
+ }
+}
+
+
+/*
+ * EUC (on Unix) or Shift-JIS (on PC) to ISO-2022-JP filter
+ */
+void
+gf_native8bitjapanese_to_2022_jp(FILTER_S *f, int flg)
+{
+#ifdef _WINDOWS
+ gf_sjis_to_2022_jp(f, flg);
+#else
+ gf_euc_to_2022_jp(f, flg);
+#endif
+}
+
+
+void
+gf_euc_to_2022_jp(FILTER_S *f, int flg)
+{
+ register unsigned char c;
+
+ /*
+ * f->t lit means we've sent the start esc seq but not the end seq.
+ * f->f2 keeps track of first character of pair for Shift-JIS.
+ */
+
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ while(GF_GETC(f, c)){
+ if(f->t){
+ if(c & 0x80){
+ GF_PUTC(f->next, c & 0x7f);
+ }
+ else{
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '(');
+ GF_PUTC(f->next, 'B');
+ GF_PUTC(f->next, c);
+ f->f2 = -1;
+ f->t = 0;
+ }
+ }
+ else{
+ if(c & 0x80){
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '$');
+ GF_PUTC(f->next, 'B');
+ GF_PUTC(f->next, c & 0x7f);
+ f->t = 1;
+ }
+ else{
+ GF_PUTC(f->next, c);
+ }
+ }
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ if(f->t){
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '(');
+ GF_PUTC(f->next, 'B');
+ f->t = 0;
+ f->f2 = -1;
+ }
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset euc_to_jp\n"));
+ f->t = 0;
+ f->f2 = -1;
+ }
+}
+
+void
+gf_sjis_to_2022_jp(FILTER_S *f, int flg)
+{
+ register unsigned char c;
+
+ /*
+ * f->t lit means we've sent the start esc seq but not the end seq.
+ * f->f2 keeps track of first character of pair for Shift-JIS.
+ */
+
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ while(GF_GETC(f, c)){
+ if(f->t){
+ if(f->f2 >= 0){ /* second of a pair? */
+ int adjust = c < 159;
+ int rowOffset = f->f2 < 160 ? 112 : 176;
+ int cellOffset = adjust ? (c > 127 ? 32 : 31) : 126;
+
+ GF_PUTC(f->next, ((f->f2 - rowOffset) << 1) - adjust);
+ GF_PUTC(f->next, c - cellOffset);
+ f->f2 = -1;
+ }
+ else if(c & 0x80){
+ f->f2 = c; /* remember first of pair */
+ }
+ else{
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '(');
+ GF_PUTC(f->next, 'B');
+ GF_PUTC(f->next, c);
+ f->f2 = -1;
+ f->t = 0;
+ }
+ }
+ else{
+ if(c & 0x80){
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '$');
+ GF_PUTC(f->next, 'B');
+ f->f2 = c;
+ f->t = 1;
+ }
+ else{
+ GF_PUTC(f->next, c);
+ }
+ }
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ if(f->t){
+ GF_PUTC(f->next, '\033');
+ GF_PUTC(f->next, '(');
+ GF_PUTC(f->next, 'B');
+ f->t = 0;
+ f->f2 = -1;
+ }
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset sjis_to_jp\n"));
+ f->t = 0;
+ f->f2 = -1;
+ }
+}
+
+
+
+/*
+ * Various charset to UTF-8 Translation filter
+ */
+
+/*
+ * utf8 conversion options
+ */
+typedef struct _utf8_s {
+ CHARSET *charset;
+ unsigned long ucsc;
+} UTF8_S;
+
+#define UTF8_BLOCK 1024
+#define UTF8_EOB(f) ((f)->line + (f)->f2 - 1)
+#define UTF8_ADD(f, c) \
+ { \
+ if(p >= eobuf){ \
+ f->f2 += UTF8_BLOCK; \
+ fs_resize((void **)&f->line, \
+ (size_t) f->f2 * sizeof(char)); \
+ eobuf = UTF8_EOB(f); \
+ p = eobuf - UTF8_BLOCK; \
+ } \
+ *p++ = c; \
+ }
+#define GF_UTF8_FLUSH(f) { \
+ register long n; \
+ SIZEDTEXT intext, outtext; \
+ intext.data = (unsigned char *) f->line; \
+ intext.size = p - f->line; \
+ memset(&outtext, 0, sizeof(SIZEDTEXT)); \
+ if(!((UTF8_S *) f->opt)->charset){ \
+ for(n = 0; n < intext.size; n++) \
+ GF_PUTC(f->next, (intext.data[n] & 0x80) ? '?' : intext.data[n]); \
+ } \
+ else if(utf8_text_cs(&intext, ((UTF8_S *) f->opt)->charset, &outtext, NULL, NULL)){ \
+ for(n = 0; n < outtext.size; n++) \
+ GF_PUTC(f->next, outtext.data[n]); \
+ if(outtext.data && intext.data != outtext.data) \
+ fs_give((void **) &outtext.data); \
+ } \
+ else{ \
+ for(n = 0; n < intext.size; n++) \
+ GF_PUTC(f->next, '?'); \
+ } \
+ }
+
+
+/*
+ * gf_utf8 - text in specified charset to to UTF-8 filter
+ * Process line-at-a-time rather than character
+ * because ISO-2022-JP. Call utf8_text_cs by hand
+ * rather than utf8_text to reduce the cost of
+ * utf8_charset() for each line.
+ */
+void
+gf_utf8(FILTER_S *f, int flg)
+{
+ register char *p = f->linep;
+ register char *eobuf = UTF8_EOB(f);
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register int state = f->f1;
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+
+ switch(state){
+ case CCR :
+ state = DFL;
+ if(c == '\012'){
+ GF_UTF8_FLUSH(f);
+ p = f->line;
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ }
+ else{
+ UTF8_ADD(f, '\015');
+ UTF8_ADD(f, c);
+ }
+
+ break;
+
+ default :
+ if(c == '\015'){
+ state = CCR;
+ }
+ else
+ UTF8_ADD(f, c);
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+
+ if(p != f->line)
+ GF_UTF8_FLUSH(f);
+
+ fs_give((void **) &f->line);
+ fs_give((void **) &f->opt);
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(GF_RESET){
+ dprint((9, "-- gf_reset utf8\n"));
+ f->f1 = DFL;
+ f->f2 = UTF8_BLOCK; /* input buffer length */
+ f->line = p = (char *) fs_get(f->f2 * sizeof(char));
+ }
+
+ f->linep = p;
+}
+
+
+void *
+gf_utf8_opt(char *charset)
+{
+ UTF8_S *utf8;
+
+ utf8 = (UTF8_S *) fs_get(sizeof(UTF8_S));
+
+ utf8->charset = (CHARSET *) utf8_charset(charset);
+
+ /*
+ * When we get 8-bit non-ascii characters but it is supposed to
+ * be ascii we want it to turn into question marks, not
+ * just behave as if it is UTF-8 which is what happens
+ * with ascii because there is no translation table.
+ * So we need to catch the ascii special case here.
+ */
+ if(utf8->charset && utf8->charset->type == CT_ASCII)
+ utf8->charset = NULL;
+
+ return((void *) utf8);
+}
+
+
+/*
+ * RICHTEXT-TO-PLAINTEXT filter
+ */
+
+/*
+ * option to be used by rich2plain (NOTE: if this filter is ever
+ * used more than once in a pipe, all instances will have the same
+ * option value)
+ */
+
+
+/*----------------------------------------------------------------------
+ richtext to plaintext filter
+
+ Args: f --
+ flg --
+
+ This basically removes all richtext formatting. A cute hack is used
+ to get bold and underlining to work.
+ Further work could be done to handle things like centering and right
+ and left flush, but then it could no longer be done in place. This
+ operates on text *with* CRLF's.
+
+ WARNING: does not wrap lines!
+ ----*/
+void
+gf_rich2plain(FILTER_S *f, int flg)
+{
+ static int rich_bold_on = 0, rich_uline_on = 0;
+
+/* BUG: qoute incoming \255 values */
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+ register int plain;
+
+ plain = f->opt ? (*(int *) f->opt) : 0;
+
+ while(GF_GETC(f, c)){
+
+ switch(state){
+ case TOKEN : /* collect a richtext token */
+ if(c == '>'){ /* what should we do with it? */
+ state = DFL; /* return to default next time */
+ *(f->linep) = '\0'; /* cap off token */
+ if(f->line[0] == 'l' && f->line[1] == 't'){
+ GF_PUTC(f->next, '<'); /* literal '<' */
+ }
+ else if(f->line[0] == 'n' && f->line[1] == 'l'){
+ GF_PUTC(f->next, '\015');/* newline! */
+ GF_PUTC(f->next, '\012');
+ }
+ else if(!strcmp("comment", f->line)){
+ (f->f2)++;
+ }
+ else if(!strcmp("/comment", f->line)){
+ f->f2 = 0;
+ }
+ else if(!strcmp("/paragraph", f->line)) {
+ GF_PUTC(f->next, '\r');
+ GF_PUTC(f->next, '\n');
+ GF_PUTC(f->next, '\r');
+ GF_PUTC(f->next, '\n');
+ }
+ else if(!plain /* gf_rich_plain */){
+ if(!strcmp(f->line, "bold")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_BOLDON);
+ rich_bold_on = 1;
+ } else if(!strcmp(f->line, "/bold")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_BOLDOFF);
+ rich_bold_on = 0;
+ } else if(!strcmp(f->line, "italic")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEON);
+ rich_uline_on = 1;
+ } else if(!strcmp(f->line, "/italic")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEOFF);
+ rich_uline_on = 0;
+ } else if(!strcmp(f->line, "underline")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEON);
+ rich_uline_on = 1;
+ } else if(!strcmp(f->line, "/underline")) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEOFF);
+ rich_uline_on = 0;
+ }
+ }
+ /* else we just ignore the token! */
+
+ f->linep = f->line; /* reset token buffer */
+ }
+ else{ /* add char to token */
+ if(f->linep - f->line > 40){
+ /* What? rfc1341 says 40 char tokens MAX! */
+ fs_give((void **)&(f->line));
+ gf_error("Richtext token over 40 characters");
+ /* NO RETURN */
+ }
+
+ *(f->linep)++ = isupper((unsigned char)c) ? c-'A'+'a' : c;
+ }
+ break;
+
+ case CCR :
+ state = DFL; /* back to default next time */
+ if(c == '\012'){ /* treat as single space? */
+ GF_PUTC(f->next, ' ');
+ break;
+ }
+ /* fall thru to process c */
+
+ case DFL :
+ default:
+ if(c == '<')
+ state = TOKEN;
+ else if(c == '\015')
+ state = CCR;
+ else if(!f->f2) /* not in comment! */
+ GF_PUTC(f->next, c);
+
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ if((f->f1 = (f->linep != f->line)) != 0){
+ /* incomplete token!! */
+ gf_error("Incomplete token in richtext");
+ /* NO RETURN */
+ }
+
+ if(rich_uline_on){
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEOFF);
+ rich_uline_on = 0;
+ }
+ if(rich_bold_on){
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_BOLDOFF);
+ rich_bold_on = 0;
+ }
+
+ fs_give((void **)&(f->line));
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset rich2plain\n"));
+ f->f1 = DFL; /* state */
+ f->f2 = 0; /* set means we're in a comment */
+ f->linep = f->line = (char *)fs_get(45 * sizeof(char));
+ }
+}
+
+
+/*
+ * function called from the outside to set
+ * richtext filter's options
+ */
+void *
+gf_rich2plain_opt(int *plain)
+{
+ return((void *) plain);
+}
+
+
+
+/*
+ * ENRICHED-TO-PLAIN text filter
+ */
+
+#define TEF_QUELL 0x01
+#define TEF_NOFILL 0x02
+
+
+
+/*----------------------------------------------------------------------
+ enriched text to plain text filter (ala rfc1523)
+
+ Args: f -- state and input data
+ flg --
+
+ This basically removes all enriched formatting. A cute hack is used
+ to get bold and underlining to work.
+
+ Further work could be done to handle things like centering and right
+ and left flush, but then it could no longer be done in place. This
+ operates on text *with* CRLF's.
+
+ WARNING: does not wrap lines!
+ ----*/
+void
+gf_enriched2plain(FILTER_S *f, int flg)
+{
+ static int enr_uline_on = 0, enr_bold_on = 0;
+
+/* BUG: qoute incoming \255 values */
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+ register int plain;
+
+ plain = f->opt ? (*(int *) f->opt) : 0;
+
+ while(GF_GETC(f, c)){
+
+ switch(state){
+ case TOKEN : /* collect a richtext token */
+ if(c == '>'){ /* what should we do with it? */
+ int off = *f->line == '/';
+ char *token = f->line + (off ? 1 : 0);
+ state = DFL;
+ *f->linep = '\0';
+ if(!strcmp("param", token)){
+ if(off)
+ f->f2 &= ~TEF_QUELL;
+ else
+ f->f2 |= TEF_QUELL;
+ }
+ else if(!strcmp("nofill", token)){
+ if(off)
+ f->f2 &= ~TEF_NOFILL;
+ else
+ f->f2 |= TEF_NOFILL;
+ }
+ else if(!plain /* gf_enriched_plain */){
+ /* Following is a cute hack or two to get
+ bold and underline on the screen.
+ See Putline0n() where these codes are
+ interpreted */
+ if(!strcmp("bold", token)) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, off ? TAG_BOLDOFF : TAG_BOLDON);
+ enr_bold_on = off ? 0 : 1;
+ } else if(!strcmp("italic", token)) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, off ? TAG_ULINEOFF : TAG_ULINEON);
+ enr_uline_on = off ? 0 : 1;
+ } else if(!strcmp("underline", token)) {
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, off ? TAG_ULINEOFF : TAG_ULINEON);
+ enr_uline_on = off ? 0 : 1;
+ }
+ }
+ /* else we just ignore the token! */
+
+ f->linep = f->line; /* reset token buffer */
+ }
+ else if(c == '<'){ /* literal '<'? */
+ if(f->linep == f->line){
+ GF_PUTC(f->next, '<');
+ state = DFL;
+ }
+ else{
+ fs_give((void **)&(f->line));
+ gf_error("Malformed Enriched text: unexpected '<'");
+ /* NO RETURN */
+ }
+ }
+ else{ /* add char to token */
+ if(f->linep - f->line > 60){ /* rfc1523 says 60 MAX! */
+ fs_give((void **)&(f->line));
+ gf_error("Malformed Enriched text: token too long");
+ /* NO RETURN */
+ }
+
+ *(f->linep)++ = isupper((unsigned char)c) ? c-'A'+'a' : c;
+ }
+ break;
+
+ case CCR :
+ if(c != '\012'){ /* treat as single space? */
+ state = DFL; /* lone cr? */
+ f->f2 &= ~TEF_QUELL;
+ GF_PUTC(f->next, '\015');
+ goto df;
+ }
+
+ state = CLF;
+ break;
+
+ case CLF :
+ if(c == '\015'){ /* treat as single space? */
+ state = CCR; /* repeat crlf's mean real newlines */
+ f->f2 |= TEF_QUELL;
+ GF_PUTC(f->next, '\r');
+ GF_PUTC(f->next, '\n');
+ break;
+ }
+ else{
+ state = DFL;
+ if(!((f->f2) & TEF_QUELL))
+ GF_PUTC(f->next, ' ');
+
+ f->f2 &= ~TEF_QUELL;
+ }
+
+ /* fall thru to take care of 'c' */
+
+ case DFL :
+ default :
+ df :
+ if(c == '<')
+ state = TOKEN;
+ else if(c == '\015' && (!((f->f2) & TEF_NOFILL)))
+ state = CCR;
+ else if(!((f->f2) & TEF_QUELL))
+ GF_PUTC(f->next, c);
+
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ if((f->f1 = (f->linep != f->line)) != 0){
+ /* incomplete token!! */
+ gf_error("Incomplete token in richtext");
+ /* NO RETURN */
+ }
+ if(enr_uline_on){
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_ULINEOFF);
+ enr_uline_on = 0;
+ }
+ if(enr_bold_on){
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, TAG_BOLDOFF);
+ enr_bold_on = 0;
+ }
+
+ /* Make sure we end with a newline so everything gets flushed */
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+
+ fs_give((void **)&(f->line));
+
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset enriched2plain\n"));
+ f->f1 = DFL; /* state */
+ f->f2 = 0; /* set means we're in a comment */
+ f->linep = f->line = (char *)fs_get(65 * sizeof(char));
+ }
+}
+
+
+/*
+ * function called from the outside to set
+ * richtext filter's options
+ */
+void *
+gf_enriched2plain_opt(int *plain)
+{
+ return((void *) plain);
+}
+
+
+
+/*
+ * HTML-TO-PLAIN text filter
+ */
+
+
+/* OK, here's the plan:
+
+ * a universal output function handles writing chars and worries
+ * about wrapping.
+
+ * a unversal element collector reads chars and collects params
+ * and dispatches the appropriate element handler.
+
+ * element handlers are stacked. The most recently dispatched gets
+ * first crack at the incoming character stream. It passes bytes it's
+ * done with or not interested in to the next
+
+ * installs that handler as the current one collecting data...
+
+ * stacked handlers take their params from the element collector and
+ * accept chars or do whatever they need to do. Sort of a vertical
+ * piping? recursion-like? hmmm.
+
+ * at least I think this is how it'll work. tres simple, non?
+
+ */
+
+
+/*
+ * Some important constants
+ */
+#define HTML_BUF_LEN 2048 /* max scratch buffer length */
+#define MAX_ENTITY 20 /* maximum length of an entity */
+#define MAX_ELEMENT 72 /* maximum length of an element */
+#define HTML_MOREDATA 0 /* expect more entity data */
+#define HTML_ENTITY 1 /* valid entity collected */
+#define HTML_BADVALUE 0x0100 /* good data, but bad entity value */
+#define HTML_BADDATA 0x0200 /* bad data found looking for entity */
+#define HTML_LITERAL 0x0400 /* Literal character value */
+#define HTML_NEWLINE 0x010A /* hard newline */
+#define HTML_DOBOLD 0x0400 /* Start Bold display */
+#define HTML_ID_GET 0 /* indent func: return current val */
+#define HTML_ID_SET 1 /* indent func: set to absolute val */
+#define HTML_ID_INC 2 /* indent func: increment by val */
+#define HTML_HX_CENTER 0x0001
+#define HTML_HX_ULINE 0x0002
+#define RSS_ITEM_LIMIT 20 /* RSS 2.0 ITEM depth limit */
+
+
+/*
+ * Handler data, state information including function that uses it
+ */
+typedef struct handler_s {
+ FILTER_S *html_data;
+ void *element;
+ long x, y, z;
+ void *dp;
+ unsigned char *s;
+ struct handler_s *below;
+} HANDLER_S;
+
+/*
+ * Element Property structure
+ */
+typedef struct _element_properties {
+ char *element;
+ int (*handler)(HANDLER_S *, int, int);
+ unsigned blocklevel:1;
+} ELPROP_S;
+
+/*
+ * Types used to manage HTML parsing
+ */
+static void html_handoff(HANDLER_S *, int);
+
+
+/*
+ * to help manage line wrapping.
+ */
+typedef struct _wrap_line {
+ char *buf; /* buf to collect wrapped text */
+ int used, /* number of chars in buf */
+ width, /* text's width as displayed */
+ len; /* length of allocated buf */
+} WRAPLINE_S;
+
+
+/*
+ * to help manage centered text
+ */
+typedef struct _center_s {
+ WRAPLINE_S line; /* buf to assembled centered text */
+ WRAPLINE_S word; /* word being to append to Line */
+ int anchor;
+ short space;
+} CENTER_S;
+
+
+/*
+ * Collector data and state information
+ */
+typedef struct collector_s {
+ char buf[HTML_BUF_LEN]; /* buffer to collect data */
+ int len; /* length of that buffer */
+ unsigned end_tag:1; /* collecting a closing tag */
+ unsigned hit_equal:1; /* collecting right half of attrib */
+ unsigned mkup_decl:1; /* markup declaration */
+ unsigned start_comment:1; /* markup declaration comment */
+ unsigned end_comment:1; /* legit comment format */
+ unsigned hyphen:1; /* markup hyphen read */
+ unsigned badform:1; /* malformed markup element */
+ unsigned overrun:1; /* Overran buf above */
+ unsigned proc_inst:1; /* XML processing instructions */
+ unsigned empty:1; /* empty element */
+ unsigned was_quoted:1; /* basically to catch null string */
+ char quoted; /* quoted element param value */
+ char *element; /* element's collected name */
+ PARAMETER *attribs; /* element's collected attributes */
+ PARAMETER *cur_attrib; /* attribute now being collected */
+} CLCTR_S;
+
+
+/*
+ * State information for all element handlers
+ */
+typedef struct html_data {
+ HANDLER_S *h_stack; /* handler list */
+ CLCTR_S *el_data; /* element collector data */
+ CENTER_S *centered; /* struct to manage centered text */
+ int (*token)(FILTER_S *, int);
+ char quoted; /* quoted, by either ' or ", text */
+ short indent_level; /* levels of indention */
+ int in_anchor; /* text now being written to anchor */
+ int blanks; /* Consecutive blank line count */
+ int wrapcol; /* column to wrap lines on */
+ int *prefix; /* buffer containing Anchor prefix */
+ int prefix_used;
+ long line_bufsize; /* current size of the line buffer */
+ COLOR_PAIR *color;
+ struct {
+ int state; /* embedded data state */
+ char *color; /* embedded color pointer */
+ } embedded;
+ CBUF_S cb; /* utf8->ucs4 conversion state */
+ unsigned wrapstate:1; /* whether or not to wrap output */
+ unsigned li_pending:1; /* <LI> next token expected */
+ unsigned de_pending:1; /* <DT> or <DD> next token expected */
+ unsigned bold_on:1; /* currently bolding text */
+ unsigned uline_on:1; /* currently underlining text */
+ unsigned center:1; /* center output text */
+ unsigned bitbucket:1; /* Ignore input */
+ unsigned head:1; /* In doc's HEAD */
+ unsigned body:1; /* In doc's BODY */
+ unsigned alt_entity:1; /* use alternative entity values */
+ unsigned wrote:1; /* anything witten yet? */
+} HTML_DATA_S;
+
+
+/*
+ * HTML filter options
+ */
+typedef struct _html_opts {
+ char *base; /* Base URL for this html file */
+ int columns, /* Display columns (excluding margins) */
+ indent; /* Left margin */
+ HANDLE_S **handlesp; /* Head of handles */
+ htmlrisk_t warnrisk_f; /* Nasty link warning call */
+ ELPROP_S *element_table; /* markup element table */
+ RSS_FEED_S **feedp; /* hook for RSS feed response */
+ unsigned strip:1; /* Hilite TAGs allowed */
+ unsigned handles_loc:1; /* Local handles requested? */
+ unsigned showserver:1; /* Display server after anchors */
+ unsigned outputted:1; /* any */
+ unsigned no_relative_links:1; /* Disable embeded relative links */
+ unsigned related_content:1; /* Embeded related content */
+ unsigned html:1; /* Output content in HTML */
+ unsigned html_imgs:1; /* Output IMG tags in HTML content */
+} HTML_OPT_S;
+
+
+
+/*
+ * Some macros to make life a little easier
+ */
+#define WRAP_COLS(X) ((X)->opt ? ((HTML_OPT_S *)(X)->opt)->columns : 80)
+#define HTML_INDENT(X) ((X)->opt ? ((HTML_OPT_S *)(X)->opt)->indent : 0)
+#define HTML_WROTE(X) (HD(X)->wrote)
+#define HTML_BASE(X) ((X)->opt ? ((HTML_OPT_S *)(X)->opt)->base : NULL)
+#define STRIP(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->strip)
+#define PASS_HTML(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->html)
+#define PASS_IMAGES(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->html_imgs)
+#define HANDLESP(X) (((HTML_OPT_S *)(X)->opt)->handlesp)
+#define DO_HANDLES(X) ((X)->opt && HANDLESP(X))
+#define HANDLES_LOC(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->handles_loc)
+#define SHOWSERVER(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->showserver)
+#define NO_RELATIVE(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->no_relative_links)
+#define RELATED_OK(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->related_content)
+#define ELEMENTS(X) (((HTML_OPT_S *)(X)->opt)->element_table)
+#define RSS_FEED(X) (*(((HTML_OPT_S *)(X)->opt)->feedp))
+#define MAKE_LITERAL(C) (HTML_LITERAL | ((C) & 0xff))
+#define IS_LITERAL(C) (HTML_LITERAL & (C))
+#define HD(X) ((HTML_DATA_S *)(X)->data)
+#define ED(X) (HD(X)->el_data)
+#define EL(X) ((ELPROP_S *) (X)->element)
+#define ASCII_ISSPACE(C) ((C) < 0x80 && isspace((unsigned char) (C)))
+#define HTML_ISSPACE(C) (IS_LITERAL(C) == 0 && ((C) == HTML_NEWLINE || ASCII_ISSPACE(C)))
+#define NEW_CLCTR(X) { \
+ ED(X) = (CLCTR_S *)fs_get(sizeof(CLCTR_S)); \
+ memset(ED(X), 0, sizeof(CLCTR_S)); \
+ HD(X)->token = html_element_collector; \
+ }
+
+#define FREE_CLCTR(X) { \
+ if(ED(X)->attribs){ \
+ PARAMETER *p; \
+ while((p = ED(X)->attribs) != NULL){ \
+ ED(X)->attribs = ED(X)->attribs->next; \
+ if(p->attribute) \
+ fs_give((void **)&p->attribute); \
+ if(p->value) \
+ fs_give((void **)&p->value); \
+ fs_give((void **)&p); \
+ } \
+ } \
+ if(ED(X)->element) \
+ fs_give((void **) &ED(X)->element); \
+ fs_give((void **) &ED(X)); \
+ HD(X)->token = NULL; \
+ }
+#define HANDLERS(X) (HD(X)->h_stack)
+#define BOLD_BIT(X) (HD(X)->bold_on)
+#define ULINE_BIT(X) (HD(X)->uline_on)
+#define CENTER_BIT(X) (HD(X)->center)
+#define HTML_FLUSH(X) { \
+ html_write(X, (X)->line, (X)->linep - (X)->line); \
+ (X)->linep = (X)->line; \
+ (X)->f2 = 0L; \
+ }
+#define HTML_BOLD(X, S) if(! STRIP(X)){ \
+ if((S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_BOLDON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_BOLDOFF); \
+ } \
+ }
+#define HTML_ULINE(X, S) \
+ if(! STRIP(X)){ \
+ if((S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_ULINEON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_ULINEOFF); \
+ } \
+ }
+#define HTML_ITALIC(X, S) \
+ if(! STRIP(X)){ \
+ if(S){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_ITALICON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_ITALICOFF); \
+ } \
+ }
+#define HTML_STRIKE(X, S) \
+ if(! STRIP(X)){ \
+ if(S){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_STRIKEON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_STRIKEOFF); \
+ } \
+ }
+#define HTML_BIG(X, S) \
+ if(! STRIP(X)){ \
+ if(S){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_BIGON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_BIGOFF); \
+ } \
+ }
+#define HTML_SMALL(X, S) \
+ if(! STRIP(X)){ \
+ if(S){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_SMALLON); \
+ } \
+ else if(!(S)){ \
+ html_output((X), TAG_EMBED); \
+ html_output((X), TAG_SMALLOFF); \
+ } \
+ }
+#define WRAPPED_LEN(X) ((HD(f)->centered) \
+ ? (HD(f)->centered->line.width \
+ + HD(f)->centered->word.width \
+ + ((HD(f)->centered->line.width \
+ && HD(f)->centered->word.width) \
+ ? 1 : 0)) \
+ : 0)
+#define HTML_DUMP_LIT(F, S, L) { \
+ int i, c; \
+ for(i = 0; i < (L); i++){ \
+ c = ASCII_ISSPACE((unsigned char)(S)[i]) \
+ ? (S)[i] \
+ : MAKE_LITERAL((S)[i]); \
+ HTML_TEXT(F, c); \
+ } \
+ }
+#define HTML_PROC(F, C) { \
+ if(HD(F)->token){ \
+ int i; \
+ if((i = (*(HD(F)->token))(F, C)) != 0){ \
+ if(i < 0){ \
+ HTML_DUMP_LIT(F, "<", 1); \
+ if(HD(F)->el_data->element){ \
+ HTML_DUMP_LIT(F, \
+ HD(F)->el_data->element, \
+ strlen(HD(F)->el_data->element));\
+ } \
+ if(HD(F)->el_data->len){ \
+ HTML_DUMP_LIT(F, \
+ HD(F)->el_data->buf, \
+ HD(F)->el_data->len); \
+ } \
+ HTML_TEXT(F, C); \
+ } \
+ FREE_CLCTR(F); \
+ } \
+ } \
+ else if((C) == '<'){ \
+ NEW_CLCTR(F); \
+ } \
+ else \
+ HTML_TEXT(F, C); \
+ }
+#define HTML_LINEP_PUTC(F, C) { \
+ if((F)->linep - (F)->line >= (HD(F)->line_bufsize - 1)){ \
+ size_t offset = (F)->linep - (F)->line; \
+ fs_resize((void **) &(F)->line, \
+ (HD(F)->line_bufsize * 2) * sizeof(char)); \
+ HD(F)->line_bufsize *= 2; \
+ (F)->linep = &(F)->line[offset]; \
+ } \
+ *(F)->linep++ = (C); \
+ }
+#define HTML_TEXT(F, C) switch((F)->f1){ \
+ case WSPACE : \
+ if(HTML_ISSPACE(C)) /* ignore repeated WS */ \
+ break; \
+ HTML_TEXT_OUT(F, ' '); \
+ (F)->f1 = DFL;/* stop sending chars here */ \
+ /* fall thru to process 'c' */ \
+ case DFL: \
+ if(HD(F)->bitbucket) \
+ (F)->f1 = DFL; /* no op */ \
+ else if(HTML_ISSPACE(C) && HD(F)->wrapstate) \
+ (F)->f1 = WSPACE;/* coalesce white space */ \
+ else HTML_TEXT_OUT(F, C); \
+ break; \
+ }
+#define HTML_TEXT_OUT(F, C) if(HANDLERS(F)) /* let handlers see C */ \
+ (*EL(HANDLERS(F))->handler)(HANDLERS(F),(C),GF_DATA); \
+ else \
+ html_output(F, C);
+#ifdef DEBUG
+#define HTML_DEBUG_EL(S, D) { \
+ dprint((5, "-- html %s: %s\n", \
+ S ? S : "?", \
+ (D)->element \
+ ? (D)->element : "NULL")); \
+ if(debug > 5){ \
+ PARAMETER *p; \
+ for(p = (D)->attribs; \
+ p && p->attribute; \
+ p = p->next) \
+ dprint((6, \
+ " PARM: %s%s%s\n", \
+ p->attribute \
+ ? p->attribute : "NULL",\
+ p->value ? "=" : "", \
+ p->value ? p->value : ""));\
+ } \
+ }
+#else
+#define HTML_DEBUG_EL(S, D)
+#endif
+
+#ifndef SYSTEM_PINE_INFO_PATH
+#define SYSTEM_PINE_INFO_PATH "/usr/local/lib/pine.info"
+#endif
+#define CHTML_VAR_EXPAND(S) (!strcmp(S, "PINE_INFO_PATH") \
+ ? SYSTEM_PINE_INFO_PATH : S)
+
+/*
+ * Protos for Tag handlers
+ */
+int html_head(HANDLER_S *, int, int);
+int html_base(HANDLER_S *, int, int);
+int html_title(HANDLER_S *, int, int);
+int html_body(HANDLER_S *, int, int);
+int html_a(HANDLER_S *, int, int);
+int html_br(HANDLER_S *, int, int);
+int html_hr(HANDLER_S *, int, int);
+int html_p(HANDLER_S *, int, int);
+int html_table(HANDLER_S *, int, int);
+int html_caption(HANDLER_S *, int, int);
+int html_tr(HANDLER_S *, int, int);
+int html_td(HANDLER_S *, int, int);
+int html_th(HANDLER_S *, int, int);
+int html_thead(HANDLER_S *, int, int);
+int html_tbody(HANDLER_S *, int, int);
+int html_tfoot(HANDLER_S *, int, int);
+int html_col(HANDLER_S *, int, int);
+int html_colgroup(HANDLER_S *, int, int);
+int html_b(HANDLER_S *, int, int);
+int html_u(HANDLER_S *, int, int);
+int html_i(HANDLER_S *, int, int);
+int html_em(HANDLER_S *, int, int);
+int html_strong(HANDLER_S *, int, int);
+int html_s(HANDLER_S *, int, int);
+int html_big(HANDLER_S *, int, int);
+int html_small(HANDLER_S *, int, int);
+int html_font(HANDLER_S *, int, int);
+int html_img(HANDLER_S *, int, int);
+int html_map(HANDLER_S *, int, int);
+int html_area(HANDLER_S *, int, int);
+int html_form(HANDLER_S *, int, int);
+int html_input(HANDLER_S *, int, int);
+int html_option(HANDLER_S *, int, int);
+int html_optgroup(HANDLER_S *, int, int);
+int html_button(HANDLER_S *, int, int);
+int html_select(HANDLER_S *, int, int);
+int html_textarea(HANDLER_S *, int, int);
+int html_label(HANDLER_S *, int, int);
+int html_fieldset(HANDLER_S *, int, int);
+int html_ul(HANDLER_S *, int, int);
+int html_ol(HANDLER_S *, int, int);
+int html_menu(HANDLER_S *, int, int);
+int html_dir(HANDLER_S *, int, int);
+int html_li(HANDLER_S *, int, int);
+int html_h1(HANDLER_S *, int, int);
+int html_h2(HANDLER_S *, int, int);
+int html_h3(HANDLER_S *, int, int);
+int html_h4(HANDLER_S *, int, int);
+int html_h5(HANDLER_S *, int, int);
+int html_h6(HANDLER_S *, int, int);
+int html_blockquote(HANDLER_S *, int, int);
+int html_address(HANDLER_S *, int, int);
+int html_pre(HANDLER_S *, int, int);
+int html_center(HANDLER_S *, int, int);
+int html_div(HANDLER_S *, int, int);
+int html_span(HANDLER_S *, int, int);
+int html_dl(HANDLER_S *, int, int);
+int html_dt(HANDLER_S *, int, int);
+int html_dd(HANDLER_S *, int, int);
+int html_script(HANDLER_S *, int, int);
+int html_applet(HANDLER_S *, int, int);
+int html_style(HANDLER_S *, int, int);
+int html_kbd(HANDLER_S *, int, int);
+int html_dfn(HANDLER_S *, int, int);
+int html_var(HANDLER_S *, int, int);
+int html_tt(HANDLER_S *, int, int);
+int html_samp(HANDLER_S *, int, int);
+int html_sub(HANDLER_S *, int, int);
+int html_sup(HANDLER_S *, int, int);
+int html_cite(HANDLER_S *, int, int);
+int html_code(HANDLER_S *, int, int);
+int html_ins(HANDLER_S *, int, int);
+int html_del(HANDLER_S *, int, int);
+int html_abbr(HANDLER_S *, int, int);
+
+/*
+ * Protos for RSS 2.0 Tag handlers
+ */
+int rss_rss(HANDLER_S *, int, int);
+int rss_channel(HANDLER_S *, int, int);
+int rss_title(HANDLER_S *, int, int);
+int rss_image(HANDLER_S *, int, int);
+int rss_link(HANDLER_S *, int, int);
+int rss_description(HANDLER_S *, int, int);
+int rss_ttl(HANDLER_S *, int, int);
+int rss_item(HANDLER_S *, int, int);
+
+/*
+ * Proto's for support routines
+ */
+void html_pop(FILTER_S *, ELPROP_S *);
+int html_push(FILTER_S *, ELPROP_S *);
+int html_element_collector(FILTER_S *, int);
+int html_element_flush(CLCTR_S *);
+void html_element_comment(FILTER_S *, char *);
+void html_element_output(FILTER_S *, int);
+int html_entity_collector(FILTER_S *, int, UCS *, char **);
+void html_a_prefix(FILTER_S *);
+void html_a_finish(HANDLER_S *);
+void html_a_output_prefix(FILTER_S *, int);
+void html_a_output_info(HANDLER_S *);
+void html_a_relative(char *, char *, HANDLE_S *);
+int html_href_relative(char *);
+int html_indent(FILTER_S *, int, int);
+void html_blank(FILTER_S *, int);
+void html_newline(FILTER_S *);
+void html_output(FILTER_S *, int);
+void html_output_string(FILTER_S *, char *);
+void html_output_raw_tag(FILTER_S *, char *);
+void html_output_normal(FILTER_S *, int, int);
+void html_output_flush(FILTER_S *);
+void html_output_centered(FILTER_S *, int, int);
+void html_centered_handle(int *, char *, int);
+void html_centered_putc(WRAPLINE_S *, int);
+void html_centered_flush(FILTER_S *);
+void html_centered_flush_line(FILTER_S *);
+void html_write_anchor(FILTER_S *, int);
+void html_write_newline(FILTER_S *);
+void html_write_indent(FILTER_S *, int);
+void html_write(FILTER_S *, char *, int);
+void html_putc(FILTER_S *, int);
+int html_event_attribute(char *);
+char *rss_skip_whitespace(char *s);
+ELPROP_S *element_properties(FILTER_S *, char *);
+
+
+/*
+ * Named entity table -- most from HTML 2.0 (rfc1866) plus some from
+ * W3C doc "Additional named entities for HTML"
+ */
+static struct html_entities {
+ char *name; /* entity name */
+ UCS value; /* UCS entity value */
+ char *plain; /* US-ASCII representation */
+} entity_tab[] = {
+ {"quot", 0x0022}, /* 34 - quotation mark */
+ {"amp", 0x0026}, /* 38 - ampersand */
+ {"apos", 0x0027}, /* 39 - apostrophe */
+ {"lt", 0x003C}, /* 60 - less-than sign */
+ {"gt", 0x003E}, /* 62 - greater-than sign */
+ {"nbsp", 0x00A0, " "}, /* 160 - no-break space */
+ {"iexcl", 0x00A1}, /* 161 - inverted exclamation mark */
+ {"cent", 0x00A2}, /* 162 - cent sign */
+ {"pound", 0x00A3}, /* 163 - pound sign */
+ {"curren", 0x00A4, "CUR"}, /* 164 - currency sign */
+ {"yen", 0x00A5}, /* 165 - yen sign */
+ {"brvbar", 0x00A6, "|"}, /* 166 - broken bar */
+ {"sect", 0x00A7}, /* 167 - section sign */
+ {"uml", 0x00A8, "\""}, /* 168 - diaeresis */
+ {"copy", 0x00A9, "(C)"}, /* 169 - copyright sign */
+ {"ordf", 0x00AA, "a"}, /* 170 - feminine ordinal indicator */
+ {"laquo", 0x00AB, "<<"}, /* 171 - left-pointing double angle quotation mark */
+ {"not", 0x00AC, "NOT"}, /* 172 - not sign */
+ {"shy", 0x00AD, "-"}, /* 173 - soft hyphen */
+ {"reg", 0x00AE, "(R)"}, /* 174 - registered sign */
+ {"macr", 0x00AF}, /* 175 - macron */
+ {"deg", 0x00B0, "DEG"}, /* 176 - degree sign */
+ {"plusmn", 0x00B1, "+/-"}, /* 177 - plus-minus sign */
+ {"sup2", 0x00B2}, /* 178 - superscript two */
+ {"sup3", 0x00B3}, /* 179 - superscript three */
+ {"acute", 0x00B4, "'"}, /* 180 - acute accent */
+ {"micro", 0x00B5}, /* 181 - micro sign */
+ {"para", 0x00B6}, /* 182 - pilcrow sign */
+ {"middot", 0x00B7}, /* 183 - middle dot */
+ {"cedil", 0x00B8}, /* 184 - cedilla */
+ {"sup1", 0x00B9}, /* 185 - superscript one */
+ {"ordm", 0x00BA, "o"}, /* 186 - masculine ordinal indicator */
+ {"raquo", 0x00BB, ">>"}, /* 187 - right-pointing double angle quotation mark */
+ {"frac14", 0x00BC, " 1/4"}, /* 188 - vulgar fraction one quarter */
+ {"frac12", 0x00BD, " 1/2"}, /* 189 - vulgar fraction one half */
+ {"frac34", 0x00BE, " 3/4"}, /* 190 - vulgar fraction three quarters */
+ {"iquest", 0x00BF}, /* 191 - inverted question mark */
+ {"Agrave", 0x00C0, "A"}, /* 192 - latin capital letter a with grave */
+ {"Aacute", 0x00C1, "A"}, /* 193 - latin capital letter a with acute */
+ {"Acirc", 0x00C2, "A"}, /* 194 - latin capital letter a with circumflex */
+ {"Atilde", 0x00C3, "A"}, /* 195 - latin capital letter a with tilde */
+ {"Auml", 0x00C4, "AE"}, /* 196 - latin capital letter a with diaeresis */
+ {"Aring", 0x00C5, "A"}, /* 197 - latin capital letter a with ring above */
+ {"AElig", 0x00C6, "AE"}, /* 198 - latin capital letter ae */
+ {"Ccedil", 0x00C7, "C"}, /* 199 - latin capital letter c with cedilla */
+ {"Egrave", 0x00C8, "E"}, /* 200 - latin capital letter e with grave */
+ {"Eacute", 0x00C9, "E"}, /* 201 - latin capital letter e with acute */
+ {"Ecirc", 0x00CA, "E"}, /* 202 - latin capital letter e with circumflex */
+ {"Euml", 0x00CB, "E"}, /* 203 - latin capital letter e with diaeresis */
+ {"Igrave", 0x00CC, "I"}, /* 204 - latin capital letter i with grave */
+ {"Iacute", 0x00CD, "I"}, /* 205 - latin capital letter i with acute */
+ {"Icirc", 0x00CE, "I"}, /* 206 - latin capital letter i with circumflex */
+ {"Iuml", 0x00CF, "I"}, /* 207 - latin capital letter i with diaeresis */
+ {"ETH", 0x00D0, "DH"}, /* 208 - latin capital letter eth */
+ {"Ntilde", 0x00D1, "N"}, /* 209 - latin capital letter n with tilde */
+ {"Ograve", 0x00D2, "O"}, /* 210 - latin capital letter o with grave */
+ {"Oacute", 0x00D3, "O"}, /* 211 - latin capital letter o with acute */
+ {"Ocirc", 0x00D4, "O"}, /* 212 - latin capital letter o with circumflex */
+ {"Otilde", 0x00D5, "O"}, /* 213 - latin capital letter o with tilde */
+ {"Ouml", 0x00D6, "O"}, /* 214 - latin capital letter o with diaeresis */
+ {"times", 0x00D7, "x"}, /* 215 - multiplication sign */
+ {"Oslash", 0x00D8, "O"}, /* 216 - latin capital letter o with stroke */
+ {"Ugrave", 0x00D9, "U"}, /* 217 - latin capital letter u with grave */
+ {"Uacute", 0x00DA, "U"}, /* 218 - latin capital letter u with acute */
+ {"Ucirc", 0x00DB, "U"}, /* 219 - latin capital letter u with circumflex */
+ {"Uuml", 0x00DC, "UE"}, /* 220 - latin capital letter u with diaeresis */
+ {"Yacute", 0x00DD, "Y"}, /* 221 - latin capital letter y with acute */
+ {"THORN", 0x00DE, "P"}, /* 222 - latin capital letter thorn */
+ {"szlig", 0x00DF, "ss"}, /* 223 - latin small letter sharp s (German <a href="/wiki/Eszett" title="Eszett">Eszett</a>) */
+ {"agrave", 0x00E0, "a"}, /* 224 - latin small letter a with grave */
+ {"aacute", 0x00E1, "a"}, /* 225 - latin small letter a with acute */
+ {"acirc", 0x00E2, "a"}, /* 226 - latin small letter a with circumflex */
+ {"atilde", 0x00E3, "a"}, /* 227 - latin small letter a with tilde */
+ {"auml", 0x00E4, "ae"}, /* 228 - latin small letter a with diaeresis */
+ {"aring", 0x00E5, "a"}, /* 229 - latin small letter a with ring above */
+ {"aelig", 0x00E6, "ae"}, /* 230 - latin lowercase ligature ae */
+ {"ccedil", 0x00E7, "c"}, /* 231 - latin small letter c with cedilla */
+ {"egrave", 0x00E8, "e"}, /* 232 - latin small letter e with grave */
+ {"eacute", 0x00E9, "e"}, /* 233 - latin small letter e with acute */
+ {"ecirc", 0x00EA, "e"}, /* 234 - latin small letter e with circumflex */
+ {"euml", 0x00EB, "e"}, /* 235 - latin small letter e with diaeresis */
+ {"igrave", 0x00EC, "i"}, /* 236 - latin small letter i with grave */
+ {"iacute", 0x00ED, "i"}, /* 237 - latin small letter i with acute */
+ {"icirc", 0x00EE, "i"}, /* 238 - latin small letter i with circumflex */
+ {"iuml", 0x00EF, "i"}, /* 239 - latin small letter i with diaeresis */
+ {"eth", 0x00F0, "dh"}, /* 240 - latin small letter eth */
+ {"ntilde", 0x00F1, "n"}, /* 241 - latin small letter n with tilde */
+ {"ograve", 0x00F2, "o"}, /* 242 - latin small letter o with grave */
+ {"oacute", 0x00F3, "o"}, /* 243 - latin small letter o with acute */
+ {"ocirc", 0x00F4, "o"}, /* 244 - latin small letter o with circumflex */
+ {"otilde", 0x00F5, "o"}, /* 245 - latin small letter o with tilde */
+ {"ouml", 0x00F6, "oe"}, /* 246 - latin small letter o with diaeresis */
+ {"divide", 0x00F7, "/"}, /* 247 - division sign */
+ {"oslash", 0x00F8, "o"}, /* 248 - latin small letter o with stroke */
+ {"ugrave", 0x00F9, "u"}, /* 249 - latin small letter u with grave */
+ {"uacute", 0x00FA, "u"}, /* 250 - latin small letter u with acute */
+ {"ucirc", 0x00FB, "u"}, /* 251 - latin small letter u with circumflex */
+ {"uuml", 0x00FC, "ue"}, /* 252 - latin small letter u with diaeresis */
+ {"yacute", 0x00FD, "y"}, /* 253 - latin small letter y with acute */
+ {"thorn", 0x00FE, "p"}, /* 254 - latin small letter thorn */
+ {"yuml", 0x00FF, "y"}, /* 255 - latin small letter y with diaeresis */
+ {"OElig", 0x0152, "OE"}, /* 338 - latin capital ligature oe */
+ {"oelig", 0x0153, "oe"}, /* 339 - latin small ligature oe */
+ {"Scaron", 0x0160, "S"}, /* 352 - latin capital letter s with caron */
+ {"scaron", 0x0161, "s"}, /* 353 - latin small letter s with caron */
+ {"Yuml", 0x0178, "Y"}, /* 376 - latin capital letter y with diaeresis */
+ {"fnof", 0x0192, "f"}, /* 402 - latin small letter f with hook */
+ {"circ", 0x02C6}, /* 710 - modifier letter circumflex accent */
+ {"tilde", 0x02DC, "~"}, /* 732 - small tilde */
+ {"Alpha", 0x0391}, /* 913 - greek capital letter alpha */
+ {"Beta", 0x0392}, /* 914 - greek capital letter beta */
+ {"Gamma", 0x0393}, /* 915 - greek capital letter gamma */
+ {"Delta", 0x0394}, /* 916 - greek capital letter delta */
+ {"Epsilon", 0x0395}, /* 917 - greek capital letter epsilon */
+ {"Zeta", 0x0396}, /* 918 - greek capital letter zeta */
+ {"Eta", 0x0397}, /* 919 - greek capital letter eta */
+ {"Theta", 0x0398}, /* 920 - greek capital letter theta */
+ {"Iota", 0x0399}, /* 921 - greek capital letter iota */
+ {"Kappa", 0x039A}, /* 922 - greek capital letter kappa */
+ {"Lambda", 0x039B}, /* 923 - greek capital letter lamda */
+ {"Mu", 0x039C}, /* 924 - greek capital letter mu */
+ {"Nu", 0x039D}, /* 925 - greek capital letter nu */
+ {"Xi", 0x039E}, /* 926 - greek capital letter xi */
+ {"Omicron", 0x039F}, /* 927 - greek capital letter omicron */
+ {"Pi", 0x03A0}, /* 928 - greek capital letter pi */
+ {"Rho", 0x03A1}, /* 929 - greek capital letter rho */
+ {"Sigma", 0x03A3}, /* 931 - greek capital letter sigma */
+ {"Tau", 0x03A4}, /* 932 - greek capital letter tau */
+ {"Upsilon", 0x03A5}, /* 933 - greek capital letter upsilon */
+ {"Phi", 0x03A6}, /* 934 - greek capital letter phi */
+ {"Chi", 0x03A7}, /* 935 - greek capital letter chi */
+ {"Psi", 0x03A8}, /* 936 - greek capital letter psi */
+ {"Omega", 0x03A9}, /* 937 - greek capital letter omega */
+ {"alpha", 0x03B1}, /* 945 - greek small letter alpha */
+ {"beta", 0x03B2}, /* 946 - greek small letter beta */
+ {"gamma", 0x03B3}, /* 947 - greek small letter gamma */
+ {"delta", 0x03B4}, /* 948 - greek small letter delta */
+ {"epsilon", 0x03B5}, /* 949 - greek small letter epsilon */
+ {"zeta", 0x03B6}, /* 950 - greek small letter zeta */
+ {"eta", 0x03B7}, /* 951 - greek small letter eta */
+ {"theta", 0x03B8}, /* 952 - greek small letter theta */
+ {"iota", 0x03B9}, /* 953 - greek small letter iota */
+ {"kappa", 0x03BA}, /* 954 - greek small letter kappa */
+ {"lambda", 0x03BB}, /* 955 - greek small letter lamda */
+ {"mu", 0x03BC}, /* 956 - greek small letter mu */
+ {"nu", 0x03BD}, /* 957 - greek small letter nu */
+ {"xi", 0x03BE}, /* 958 - greek small letter xi */
+ {"omicron", 0x03BF}, /* 959 - greek small letter omicron */
+ {"pi", 0x03C0}, /* 960 - greek small letter pi */
+ {"rho", 0x03C1}, /* 961 - greek small letter rho */
+ {"sigmaf", 0x03C2}, /* 962 - greek small letter final sigma */
+ {"sigma", 0x03C3}, /* 963 - greek small letter sigma */
+ {"tau", 0x03C4}, /* 964 - greek small letter tau */
+ {"upsilon", 0x03C5}, /* 965 - greek small letter upsilon */
+ {"phi", 0x03C6}, /* 966 - greek small letter phi */
+ {"chi", 0x03C7}, /* 967 - greek small letter chi */
+ {"psi", 0x03C8}, /* 968 - greek small letter psi */
+ {"omega", 0x03C9}, /* 969 - greek small letter omega */
+ {"thetasym", 0x03D1}, /* 977 - greek theta symbol */
+ {"upsih", 0x03D2}, /* 978 - greek upsilon with hook symbol */
+ {"piv", 0x03D6}, /* 982 - greek pi symbol */
+ {"ensp", 0x2002}, /* 8194 - en space */
+ {"emsp", 0x2003}, /* 8195 - em space */
+ {"thinsp", 0x2009}, /* 8201 - thin space */
+ {"zwnj", 0x200C}, /* 8204 - zero width non-joiner */
+ {"zwj", 0x200D}, /* 8205 - zero width joiner */
+ {"lrm", 0x200E}, /* 8206 - left-to-right mark */
+ {"rlm", 0x200F}, /* 8207 - right-to-left mark */
+ {"ndash", 0x2013}, /* 8211 - en dash */
+ {"mdash", 0x2014}, /* 8212 - em dash */
+ {"#8213", 0x2015, "--"}, /* 2015 - horizontal bar */
+ {"#8214", 0x2016, "||"}, /* 2016 - double vertical line */
+ {"#8215", 0x2017, "__"}, /* 2017 - double low line */
+ {"lsquo", 0x2018}, /* 8216 - left single quotation mark */
+ {"rsquo", 0x2019}, /* 8217 - right single quotation mark */
+ {"sbquo", 0x201A}, /* 8218 - single low-9 quotation mark */
+ {"ldquo", 0x201C}, /* 8220 - left double quotation mark */
+ {"rdquo", 0x201D}, /* 8221 - right double quotation mark */
+ {"bdquo", 0x201E, ",,"}, /* 8222 - double low-9 quotation mark */
+ {"#8223", 0x201F, "``"}, /* 201F - double high reversed-9 quotation mark */
+ {"dagger", 0x2020}, /* 8224 - dagger */
+ {"Dagger", 0x2021}, /* 8225 - double dagger */
+ {"bull", 0x2022, "*"}, /* 8226 - bullet */
+ {"hellip", 0x2026}, /* 8230 - horizontal ellipsis */
+ {"permil", 0x2030}, /* 8240 - per mille sign */
+ {"prime", 0x2032, "\'"}, /* 8242 - prime */
+ {"Prime", 0x2033, "\'\'"}, /* 8243 - double prime */
+ {"#8244", 0x2034, "\'\'\'"}, /* 2034 - triple prime */
+ {"lsaquo", 0x2039}, /* 8249 - single left-pointing angle quotation mark */
+ {"rsaquo", 0x203A}, /* 8250 - single right-pointing angle quotation mark */
+ {"#8252", 0x203C, "!!"}, /* 203C - double exclamation mark */
+ {"oline", 0x203E, "-"}, /* 8254 - overline */
+ {"frasl", 0x2044}, /* 8260 - fraction slash */
+ {"#8263", 0x2047, "??"}, /* 2047 - double question mark */
+ {"#8264", 0x2048, "?!"}, /* 2048 - question exclamation mark */
+ {"#8265", 0x2049, "!?"}, /* 2049 - exclamation question mark */
+ {"#8279", 0x2057, "\'\'\'\'"}, /* 2057 - quad prime */
+ {"euro", 0x20AC, "EUR"}, /* 8364 - euro sign */
+ {"image", 0x2111}, /* 8465 - black-letter capital i */
+ {"weierp", 0x2118}, /* 8472 - script capital p (<a href="/wiki/Weierstrass" title="Weierstrass">Weierstrass</a> p) */
+ {"real", 0x211C}, /* 8476 - black-letter capital r */
+ {"trade", 0x2122, "[tm]"}, /* 8482 - trademark sign */
+ {"alefsym", 0x2135}, /* 8501 - alef symbol */
+ {"larr", 0x2190}, /* 8592 - leftwards arrow */
+ {"uarr", 0x2191}, /* 8593 - upwards arrow */
+ {"rarr", 0x2192}, /* 8594 - rightwards arrow */
+ {"darr", 0x2193}, /* 8595 - downwards arrow */
+ {"harr", 0x2194}, /* 8596 - left right arrow */
+ {"crarr", 0x21B5}, /* 8629 - downwards arrow with corner leftwards */
+ {"lArr", 0x21D0}, /* 8656 - leftwards double arrow */
+ {"uArr", 0x21D1}, /* 8657 - upwards double arrow */
+ {"rArr", 0x21D2}, /* 8658 - rightwards double arrow */
+ {"dArr", 0x21D3}, /* 8659 - downwards double arrow */
+ {"hArr", 0x21D4}, /* 8660 - left right double arrow */
+ {"forall", 0x2200}, /* 8704 - for all */
+ {"part", 0x2202}, /* 8706 - partial differential */
+ {"exist", 0x2203}, /* 8707 - there exists */
+ {"empty", 0x2205}, /* 8709 - empty set */
+ {"nabla", 0x2207}, /* 8711 - nabla */
+ {"isin", 0x2208}, /* 8712 - element of */
+ {"notin", 0x2209}, /* 8713 - not an element of */
+ {"ni", 0x220B}, /* 8715 - contains as member */
+ {"prod", 0x220F}, /* 8719 - n-ary product */
+ {"sum", 0x2211}, /* 8721 - n-ary summation */
+ {"minus", 0x2212}, /* 8722 - minus sign */
+ {"lowast", 0x2217}, /* 8727 - asterisk operator */
+ {"radic", 0x221A}, /* 8730 - square root */
+ {"prop", 0x221D}, /* 8733 - proportional to */
+ {"infin", 0x221E}, /* 8734 - infinity */
+ {"ang", 0x2220}, /* 8736 - angle */
+ {"and", 0x2227}, /* 8743 - logical and */
+ {"or", 0x2228}, /* 8744 - logical or */
+ {"cap", 0x2229}, /* 8745 - intersection */
+ {"cup", 0x222A}, /* 8746 - union */
+ {"int", 0x222B}, /* 8747 - integral */
+ {"there4", 0x2234}, /* 8756 - therefore */
+ {"sim", 0x223C}, /* 8764 - tilde operator */
+ {"cong", 0x2245}, /* 8773 - congruent to */
+ {"asymp", 0x2248}, /* 8776 - almost equal to */
+ {"ne", 0x2260}, /* 8800 - not equal to */
+ {"equiv", 0x2261}, /* 8801 - identical to (equivalent to) */
+ {"le", 0x2264}, /* 8804 - less-than or equal to */
+ {"ge", 0x2265}, /* 8805 - greater-than or equal to */
+ {"sub", 0x2282}, /* 8834 - subset of */
+ {"sup", 0x2283}, /* 8835 - superset of */
+ {"nsub", 0x2284}, /* 8836 - not a subset of */
+ {"sube", 0x2286}, /* 8838 - subset of or equal to */
+ {"supe", 0x2287}, /* 8839 - superset of or equal to */
+ {"oplus", 0x2295}, /* 8853 - circled plus */
+ {"otimes", 0x2297}, /* 8855 - circled times */
+ {"perp", 0x22A5}, /* 8869 - up tack */
+ {"sdot", 0x22C5}, /* 8901 - dot operator */
+ {"lceil", 0x2308}, /* 8968 - left ceiling */
+ {"rceil", 0x2309}, /* 8969 - right ceiling */
+ {"lfloor", 0x230A}, /* 8970 - left floor */
+ {"rfloor", 0x230B}, /* 8971 - right floor */
+ {"lang", 0x2329}, /* 9001 - left-pointing angle bracket */
+ {"rang", 0x232A}, /* 9002 - right-pointing angle bracket */
+ {"loz", 0x25CA}, /* 9674 - lozenge */
+ {"spades", 0x2660}, /* 9824 - black spade suit */
+ {"clubs", 0x2663}, /* 9827 - black club suit */
+ {"hearts", 0x2665}, /* 9829 - black heart suit */
+ {"diams", 0x2666} /* 9830 - black diamond suit */
+};
+
+
+/*
+ * Table of supported elements and corresponding handlers
+ */
+static ELPROP_S html_element_table[] = {
+ {"HTML"}, /* HTML ignore if seen? */
+ {"HEAD", html_head}, /* slurp until <BODY> ? */
+ {"TITLE", html_title}, /* Document Title */
+ {"BASE", html_base}, /* HREF base */
+ {"BODY", html_body}, /* HTML BODY */
+ {"A", html_a}, /* Anchor */
+ {"ABBR", html_abbr}, /* Abbreviation */
+ {"IMG", html_img}, /* Image */
+ {"MAP", html_map}, /* Image Map */
+ {"AREA", html_area}, /* Image Map Area */
+ {"HR", html_hr, 1}, /* Horizontal Rule */
+ {"BR", html_br}, /* Line Break */
+ {"P", html_p, 1}, /* Paragraph */
+ {"OL", html_ol, 1}, /* Ordered List */
+ {"UL", html_ul, 1}, /* Unordered List */
+ {"MENU", html_menu}, /* Menu List */
+ {"DIR", html_dir}, /* Directory List */
+ {"LI", html_li}, /* ... List Item */
+ {"DL", html_dl, 1}, /* Definition List */
+ {"DT", html_dt}, /* ... Def. Term */
+ {"DD", html_dd}, /* ... Def. Definition */
+ {"I", html_i}, /* Italic Text */
+ {"EM", html_em}, /* Typographic Emphasis */
+ {"STRONG", html_strong}, /* STRONG Typo Emphasis */
+ {"VAR", html_i}, /* Variable Name */
+ {"B", html_b}, /* Bold Text */
+ {"U", html_u}, /* Underline Text */
+ {"S", html_s}, /* Strike-Through Text */
+ {"STRIKE", html_s}, /* Strike-Through Text */
+ {"BIG", html_big}, /* Big Font Text */
+ {"SMALL", html_small}, /* Small Font Text */
+ {"FONT", html_font}, /* Font display directives */
+ {"BLOCKQUOTE", html_blockquote, 1}, /* Blockquote */
+ {"ADDRESS", html_address, 1}, /* Address */
+ {"CENTER", html_center}, /* Centered Text v3.2 */
+ {"DIV", html_div, 1}, /* Document Division 3.2 */
+ {"SPAN", html_span}, /* Text Span */
+ {"H1", html_h1, 1}, /* Headings... */
+ {"H2", html_h2, 1},
+ {"H3", html_h3,1},
+ {"H4", html_h4, 1},
+ {"H5", html_h5, 1},
+ {"H6", html_h6, 1},
+ {"PRE", html_pre, 1}, /* Preformatted Text */
+ {"KBD", html_kbd}, /* Keyboard Input (NO OP) */
+ {"DFN", html_dfn}, /* Definition (NO OP) */
+ {"VAR", html_var}, /* Variable (NO OP) */
+ {"TT", html_tt}, /* Typetype (NO OP) */
+ {"SAMP", html_samp}, /* Sample Text (NO OP) */
+ {"CITE", html_cite}, /* Citation (NO OP) */
+ {"CODE", html_code}, /* Code Text (NO OP) */
+ {"INS", html_ins}, /* Text Inseted (NO OP) */
+ {"DEL", html_del}, /* Text Deleted (NO OP) */
+ {"SUP", html_sup}, /* Text Superscript (NO OP) */
+ {"SUB", html_sub}, /* Text Superscript (NO OP) */
+ {"STYLE", html_style}, /* CSS Definitions */
+
+/*----- Handlers below UNIMPLEMENTED (and won't until later) -----*/
+
+ {"FORM", html_form, 1}, /* form within a document */
+ {"INPUT", html_input}, /* One input field, options */
+ {"BUTTON", html_button}, /* Push Button */
+ {"OPTION", html_option}, /* One option within Select */
+ {"OPTION", html_optgroup}, /* Option Group Definition */
+ {"SELECT", html_select}, /* Selection from a set */
+ {"TEXTAREA", html_textarea}, /* A multi-line input field */
+ {"LABEL", html_label}, /* Control Label */
+ {"FIELDSET", html_fieldset, 1}, /* Fieldset Control Group */
+
+/*----- Handlers below NEVER TO BE IMPLEMENTED -----*/
+ {"SCRIPT", html_script}, /* Embedded scripting statements */
+ {"APPLET", NULL}, /* Embedded applet statements */
+ {"OBJECT", NULL}, /* Embedded object statements */
+ {"LINK", NULL}, /* References to external data */
+ {"PARAM", NULL}, /* Applet/Object parameters */
+
+/*----- Handlers below provide limited support for RFC 1942 Tables -----*/
+
+ {"TABLE", html_table, 1}, /* Table */
+ {"CAPTION", html_caption}, /* Table Caption */
+ {"TR", html_tr}, /* Table Table Row */
+ {"TD", html_td}, /* Table Table Data */
+ {"TH", html_th}, /* Table Table Head */
+ {"THEAD", html_thead}, /* Table Table Head */
+ {"TBODY", html_tbody}, /* Table Table Body */
+ {"TFOOT", html_tfoot}, /* Table Table Foot */
+ {"COL", html_col}, /* Table Column Attibutes */
+ {"COLGROUP", html_colgroup}, /* Table Column Group Attibutes */
+
+ {NULL, NULL}
+};
+
+
+/*
+ * Table of supported RSS 2.0 elements
+ */
+static ELPROP_S rss_element_table[] = {
+ {"RSS", rss_rss}, /* RSS 2.0 version */
+ {"CHANNEL", rss_channel}, /* RSS 2.0 Channel */
+ {"TITLE", rss_title}, /* RSS 2.0 Title */
+ {"IMAGE", rss_image}, /* RSS 2.0 Channel Image */
+ {"LINK", rss_link}, /* RSS 2.0 Channel/Item Link */
+ {"DESCRIPTION", rss_description}, /* RSS 2.0 Channel/Item Description */
+ {"ITEM", rss_item}, /* RSS 2.0 Channel ITEM */
+ {"TTL", rss_ttl}, /* RSS 2.0 Item TTL */
+ {NULL, NULL}
+};
+
+
+/*
+ * Initialize the given handler, and add it to the stack if it
+ * requests it.
+ *
+ * Returns: 1 if handler chose to get pushed on stack
+ * 0 if handler declined
+ */
+int
+html_push(FILTER_S *fd, ELPROP_S *ep)
+{
+ HANDLER_S *new;
+
+ new = (HANDLER_S *)fs_get(sizeof(HANDLER_S));
+ memset(new, 0, sizeof(HANDLER_S));
+ new->html_data = fd;
+ new->element = ep;
+ if((*ep->handler)(new, 0, GF_RESET)){ /* stack the handler? */
+ new->below = HANDLERS(fd);
+ HANDLERS(fd) = new; /* push */
+ return(1);
+ }
+
+ fs_give((void **) &new);
+ return(0);
+}
+
+
+/*
+ * Remove the most recently installed the given handler
+ * after letting it accept its demise.
+ */
+void
+html_pop(FILTER_S *fd, ELPROP_S *ep)
+{
+ HANDLER_S *tp;
+
+ for(tp = HANDLERS(fd); tp && ep != EL(tp); tp = tp->below){
+ HANDLER_S *tp2;
+ ELPROP_S *ep2;
+
+ dprint((3, "-- html error: bad nesting: given /%s expected /%s", ep->element, EL(tp)->element));
+ /* if no evidence of opening tag, ignore given closing tag */
+ for(tp2 = HANDLERS(fd); tp2 && ep != EL(tp2); tp2 = tp2->below)
+ ;
+
+ if(!tp2){
+ dprint((3, "-- html error: no opening tag for given tag /%s", ep->element));
+ return;
+ }
+
+ (void) (*EL(tp)->handler)(tp, 0, GF_EOD);
+ HANDLERS(fd) = tp->below;
+ }
+
+ if(tp){
+ (void) (*EL(tp)->handler)(tp, 0, GF_EOD); /* may adjust handler list */
+ if(tp != HANDLERS(fd)){
+ HANDLER_S *p;
+
+ for(p = HANDLERS(fd); p->below != tp; p = p->below)
+ ;
+
+ if(p)
+ p->below = tp->below; /* remove from middle of stack */
+ /* BUG: else programming botch and we should die */
+ }
+ else
+ HANDLERS(fd) = tp->below; /* pop */
+
+ fs_give((void **)&tp);
+ }
+ else{
+ /* BUG: should MAKE SURE NOT TO EMIT IT */
+ dprint((3, "-- html error: end tag without a start: %s", ep->element));
+ }
+}
+
+
+/*
+ * Deal with data passed a hander in its GF_DATA state
+ */
+static void
+html_handoff(HANDLER_S *hd, int ch)
+{
+ if(hd->below)
+ (void) (*EL(hd->below)->handler)(hd->below, ch, GF_DATA);
+ else
+ html_output(hd->html_data, ch);
+}
+
+
+/*
+ * HTML <BR> element handler
+ */
+int
+html_br(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "br");
+ }
+ else{
+ html_output(hd->html_data, HTML_NEWLINE);
+ }
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <HR> (Horizontal Rule) element handler
+ */
+int
+html_hr(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "hr");
+ }
+ else{
+ int i, old_wrap, width, align;
+ PARAMETER *p;
+
+ width = WRAP_COLS(hd->html_data);
+ align = 0;
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(p->value){
+ if(!strucmp(p->attribute, "ALIGN")){
+ if(!strucmp(p->value, "LEFT"))
+ align = 1;
+ else if(!strucmp(p->value, "RIGHT"))
+ align = 2;
+ }
+ else if(!strucmp(p->attribute, "WIDTH")){
+ char *cp;
+
+ width = 0;
+ for(cp = p->value; *cp; cp++)
+ if(*cp == '%'){
+ width = (WRAP_COLS(hd->html_data)*MIN(100,width))/100;
+ break;
+ }
+ else if(isdigit((unsigned char) *cp))
+ width = (width * 10) + (*cp - '0');
+
+ width = MIN(width, WRAP_COLS(hd->html_data));
+ }
+ }
+
+ html_blank(hd->html_data, 1); /* at least one blank line */
+
+ old_wrap = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ if((i = MAX(0, WRAP_COLS(hd->html_data) - width))
+ && ((align == 0) ? i /= 2 : (align == 2)))
+ for(; i > 0; i--)
+ html_output(hd->html_data, ' ');
+
+ for(i = 0; i < width; i++)
+ html_output(hd->html_data, '_');
+
+ html_blank(hd->html_data, 1);
+ HD(hd->html_data)->wrapstate = old_wrap;
+ }
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <P> (paragraph) element handler
+ */
+int
+html_p(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "p");
+ }
+ else{
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 1);
+
+ /* adjust indent level if needed */
+ if(HD(hd->html_data)->li_pending){
+ html_indent(hd->html_data, 4, HTML_ID_INC);
+ HD(hd->html_data)->li_pending = 0;
+ }
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</p>");
+ }
+ else{
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 1);
+ }
+ }
+
+ return(1); /* GET linked */
+}
+
+
+/*
+ * HTML Table <TABLE> (paragraph) table row
+ */
+int
+html_table(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "table");
+ }
+ else
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 0);
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</table>");
+ }
+ else
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 0);
+ }
+ return(PASS_HTML(hd->html_data)); /* maybe get linked */
+}
+
+
+/*
+ * HTML <CAPTION> (Table Caption) element handler
+ */
+int
+html_caption(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "caption");
+ }
+ else{
+ /* turn ON the centered bit */
+ CENTER_BIT(hd->html_data) = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</caption>");
+ }
+ else{
+ /* turn OFF the centered bit */
+ CENTER_BIT(hd->html_data) = 0;
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * HTML Table <TR> (paragraph) table row
+ */
+int
+html_tr(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "tr");
+ }
+ else
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 0);
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</tr>");
+ }
+ else
+ /* Make sure there's at least 1 blank line */
+ html_blank(hd->html_data, 0);
+ }
+ return(PASS_HTML(hd->html_data)); /* maybe get linked */
+}
+
+
+/*
+ * HTML Table <TD> (paragraph) table data
+ */
+int
+html_td(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "td");
+ }
+ else{
+ PARAMETER *p;
+
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(!strucmp(p->attribute, "nowrap")
+ && (hd->html_data->f2 || hd->html_data->n)){
+ HTML_DUMP_LIT(hd->html_data, " | ", 3);
+ break;
+ }
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</td>");
+ }
+ }
+
+ return(PASS_HTML(hd->html_data)); /* maybe get linked */
+}
+
+
+/*
+ * HTML Table <TH> (paragraph) table head
+ */
+int
+html_th(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "th");
+ }
+ else{
+ PARAMETER *p;
+
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(!strucmp(p->attribute, "nowrap")
+ && (hd->html_data->f2 || hd->html_data->n)){
+ HTML_DUMP_LIT(hd->html_data, " | ", 3);
+ break;
+ }
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</th>");
+ }
+ }
+
+ return(PASS_HTML(hd->html_data)); /* don't get linked */
+}
+
+
+/*
+ * HTML Table <THEAD> table head
+ */
+int
+html_thead(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "thead");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</thead>");
+ }
+
+ return(1); /* GET linked */
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML Table <TBODY> table body
+ */
+int
+html_tbody(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "tbody");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</tbody>");
+ }
+
+ return(1); /* GET linked */
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML Table <TFOOT> table body
+ */
+int
+html_tfoot(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "tfoot");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</tfoot>");
+ }
+
+ return(1); /* GET linked */
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <COL> (Table Column Attributes) element handler
+ */
+int
+html_col(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "col");
+ }
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML Table <COLGROUP> table body
+ */
+int
+html_colgroup(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "colgroup");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</colgroup>");
+ }
+
+ return(1); /* GET linked */
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <I> (italic text) element handler
+ */
+int
+html_i(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_ITALIC(hd->html_data, 1);
+ hd->x = 0;
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ hd->x = 1;
+ }
+ else if(cmd == GF_EOD){
+ if(!hd->x)
+ HTML_ITALIC(hd->html_data, 0);
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <EM> element handler
+ */
+int
+html_em(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(!PASS_HTML(hd->html_data)){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_ITALIC(hd->html_data, 1);
+ hd->x = 0;
+ }
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "em");
+ }
+ else{
+ hd->x = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</em>");
+ }
+ else{
+ if(!hd->x)
+ HTML_ITALIC(hd->html_data, 0);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <STRONG> element handler
+ */
+int
+html_strong(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(!PASS_HTML(hd->html_data)){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_ITALIC(hd->html_data, 1);
+ hd->x = 0;
+ }
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "strong");
+ }
+ else{
+ hd->x = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</strong>");
+ }
+ else{
+ if(!hd->x)
+ HTML_ITALIC(hd->html_data, 0);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <u> (Underline text) element handler
+ */
+int
+html_u(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "u");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</u>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0); /* do NOT get linked */
+}
+
+
+/*
+ * HTML <b> (Bold text) element handler
+ */
+int
+html_b(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(!PASS_HTML(hd->html_data)){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_BOLD(hd->html_data, 1);
+ hd->x = 0;
+ }
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "b");
+ }
+ else{
+ hd->x = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</b>");
+ }
+ else{
+ if(!hd->x)
+ HTML_BOLD(hd->html_data, 0);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <s> (strike-through text) element handler
+ */
+int
+html_s(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(!PASS_HTML(hd->html_data)){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_STRIKE(hd->html_data, 1);
+ hd->x = 0;
+ }
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "s");
+ }
+ else{
+ hd->x = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</s>");
+ }
+ else{
+ if(!hd->x)
+ HTML_STRIKE(hd->html_data, 0);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <big> (BIG text) element handler
+ */
+int
+html_big(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_BIG(hd->html_data, 1);
+ hd->x = 0;
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ hd->x = 1;
+ }
+ else if(cmd == GF_EOD){
+ if(!hd->x)
+ HTML_BIG(hd->html_data, 0);
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <small> (SMALL text) element handler
+ */
+int
+html_small(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ /* include LITERAL in spaceness test! */
+ if(hd->x && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_SMALL(hd->html_data, 1);
+ hd->x = 0;
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ hd->x = 1;
+ }
+ else if(cmd == GF_EOD){
+ if(!hd->x)
+ HTML_SMALL(hd->html_data, 0);
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <FONT> element handler
+ */
+int
+html_font(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "font");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</font>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <IMG> element handler
+ */
+int
+html_img(HANDLER_S *hd, int ch, int cmd)
+{
+ PARAMETER *p;
+ char *alt = NULL, *src = NULL, *s;
+
+ if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "img");
+ }
+ else{
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(p->value && p->value[0]){
+ if(!strucmp(p->attribute, "alt"))
+ alt = p->value;
+ if(!strucmp(p->attribute, "src"))
+ src = p->value;
+ }
+
+ /*
+ * Multipart/Related Content ID pointer
+ * ONLY attached messages are recognized
+ * if we ever decide web bugs aren't a problem
+ * anymore then we might expand the scope
+ */
+ if(src
+ && DO_HANDLES(hd->html_data)
+ && RELATED_OK(hd->html_data)
+ && struncmp(src, "cid:", 4) == 0){
+ char buf[32];
+ int i, n;
+ HANDLE_S *h = new_handle(HANDLESP(hd->html_data));
+
+ h->type = IMG;
+ h->h.img.src = cpystr(src + 4);
+ h->h.img.alt = cpystr((alt) ? alt : "Attached Image");
+
+ HTML_TEXT(hd->html_data, TAG_EMBED);
+ HTML_TEXT(hd->html_data, TAG_HANDLE);
+
+ sprintf(buf, "%d", h->key);
+ n = strlen(buf);
+ HTML_TEXT(hd->html_data, n);
+ for(i = 0; i < n; i++){
+ unsigned int uic = buf[i];
+ HTML_TEXT(hd->html_data, uic);
+ }
+
+ return(0);
+ }
+ else if(alt && strlen(alt) < 256){ /* arbitrary "reasonable" limit */
+ HTML_DUMP_LIT(hd->html_data, alt, strlen(alt));
+ HTML_TEXT(hd->html_data, ' ');
+ return(0);
+ }
+ else if(src
+ && (s = strrindex(src, '/'))
+ && *++s != '\0'){
+ HTML_TEXT(hd->html_data, '[');
+ HTML_DUMP_LIT(hd->html_data, s, strlen(s));
+ HTML_TEXT(hd->html_data, ']');
+ HTML_TEXT(hd->html_data, ' ');
+ return(0);
+ }
+
+ /* text filler of last resort */
+ HTML_DUMP_LIT(hd->html_data, "[IMAGE] ", 7);
+ }
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <MAP> (Image Map) element handler
+ */
+int
+html_map(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data) && PASS_IMAGES(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "map");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</map>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <AREA> (Image Map Area) element handler
+ */
+int
+html_area(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data) && PASS_IMAGES(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "area");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</area>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <FORM> (Form) element handler
+ */
+int
+html_form(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ PARAMETER **pp;
+
+ /* SECURITY: make sure to redirect to new browser instance */
+ for(pp = &(HD(hd->html_data)->el_data->attribs);
+ *pp && (*pp)->attribute;
+ pp = &(*pp)->next)
+ if(!strucmp((*pp)->attribute, "target")){
+ if((*pp)->value)
+ fs_give((void **) &(*pp)->value);
+
+ (*pp)->value = cpystr("_blank");
+ }
+
+ if(!*pp){
+ *pp = (PARAMETER *)fs_get(sizeof(PARAMETER));
+ memset(*pp, 0, sizeof(PARAMETER));
+ (*pp)->attribute = cpystr("target");
+ (*pp)->value = cpystr("_blank");
+ }
+
+ html_output_raw_tag(hd->html_data, "form");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</form>");
+ }
+ }
+ else{
+ if(cmd == GF_RESET){
+ html_blank(hd->html_data, 0);
+ HTML_DUMP_LIT(hd->html_data, "[FORM]", 6);
+ html_blank(hd->html_data, 0);
+ }
+ }
+
+ return(PASS_HTML(hd->html_data)); /* maybe get linked */
+}
+
+
+/*
+ * HTML <INPUT> (Form) element handler
+ */
+int
+html_input(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "input");
+ }
+ }
+
+ return(0); /* don't get linked */
+}
+
+
+/*
+ * HTML <BUTTON> (Form) element handler
+ */
+int
+html_button(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "button");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</button>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <OPTION> (Form) element handler
+ */
+int
+html_option(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "option");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</option>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <OPTGROUP> (Form) element handler
+ */
+int
+html_optgroup(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "optgroup");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</optgroup>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <SELECT> (Form) element handler
+ */
+int
+html_select(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "select");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</select>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <TEXTAREA> (Form) element handler
+ */
+int
+html_textarea(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "textarea");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</textarea>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <LABEL> (Form) element handler
+ */
+int
+html_label(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "label");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</label>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <FIELDSET> (Form) element handler
+ */
+int
+html_fieldset(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "fieldset");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</fieldset>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <HEAD> element handler
+ */
+int
+html_head(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ HD(hd->html_data)->head = 1;
+ }
+ else if(cmd == GF_EOD){
+ HD(hd->html_data)->head = 0;
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <BASE> element handler
+ */
+int
+html_base(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_RESET){
+ if(HD(hd->html_data)->head && !HTML_BASE(hd->html_data)){
+ PARAMETER *p;
+
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute && strucmp(p->attribute, "HREF");
+ p = p->next)
+ ;
+
+ if(p && p->value && !((HTML_OPT_S *)(hd->html_data)->opt)->base)
+ ((HTML_OPT_S *)(hd->html_data)->opt)->base = cpystr(p->value);
+ }
+ }
+
+ return(0); /* DON'T get linked */
+}
+
+
+/*
+ * HTML <TITLE> element handler
+ */
+int
+html_title(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(hd->x + 1 >= hd->y){
+ hd->y += 80;
+ fs_resize((void **)&hd->s, (size_t)hd->y * sizeof(unsigned char));
+ }
+
+ hd->s[hd->x++] = (unsigned char) ch;
+ }
+ else if(cmd == GF_RESET){
+ hd->x = 0L;
+ hd->y = 80L;
+ hd->s = (unsigned char *)fs_get((size_t)hd->y * sizeof(unsigned char));
+ }
+ else if(cmd == GF_EOD){
+ /* Down the road we probably want to give these bytes to
+ * someone...
+ */
+ hd->s[hd->x] = '\0';
+ fs_give((void **)&hd->s);
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <BODY> element handler
+ */
+int
+html_body(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ PARAMETER *p, *tp;
+ char **style = NULL, *text = NULL, *bgcolor = NULL, *pcs;
+
+ /* modify any attributes in a useful way? */
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(p->value){
+ if(!strucmp(p->attribute, "style"))
+ style = &p->value;
+ else if(!strucmp(p->attribute, "text"))
+ text = p->value;
+ /*
+ * bgcolor NOT passed since user setting takes precedence
+ *
+ else if(!strucmp(p->attribute, "bgcolor"))
+ bgcolor = p->value;
+ */
+ }
+
+ /* colors pretty much it */
+ if(text || bgcolor){
+ if(!style){
+ tp = (PARAMETER *)fs_get(sizeof(PARAMETER));
+ memset(tp, 0, sizeof(PARAMETER));
+ tp->next = HD(hd->html_data)->el_data->attribs;
+ HD(hd->html_data)->el_data->attribs = tp;
+ tp->attribute = cpystr("style");
+
+ tmp_20k_buf[0] = '\0';
+ style = &tp->value;
+ pcs = "%s%s%s%s%s";
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s", *style);
+ fs_give((void **) style);
+ pcs = "; %s%s%s%s%s";
+ }
+
+ snprintf(tmp_20k_buf + strlen(tmp_20k_buf),
+ SIZEOF_20KBUF - strlen(tmp_20k_buf),
+ pcs,
+ (text) ? "color: " : "", (text) ? text : "",
+ (text && bgcolor) ? ";" : "",
+ (bgcolor) ? "background-color: " : "", (bgcolor) ? bgcolor : "");
+ *style = cpystr(tmp_20k_buf);
+ }
+
+ html_output_raw_tag(hd->html_data, "div");
+ }
+
+ HD(hd->html_data)->body = 1;
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</div>");
+ }
+
+ HD(hd->html_data)->body = 0;
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <A> (Anchor) element handler
+ */
+int
+html_a(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+
+ if(hd->dp) /* remember text within anchor tags */
+ so_writec(ch, (STORE_S *) hd->dp);
+ }
+ else if(cmd == GF_RESET){
+ int i, n, x;
+ char buf[256];
+ HANDLE_S *h;
+ PARAMETER *p, *href = NULL, *name = NULL;
+
+ /*
+ * Pending Anchor!?!?
+ * space insertion/line breaking that's yet to get done...
+ */
+ if(HD(hd->html_data)->prefix){
+ dprint((2, "-- html error: nested or unterminated anchor\n"));
+ html_a_finish(hd);
+ }
+
+ /*
+ * Look for valid Anchor data vis the filter installer's parms
+ * (e.g., Only allow references to our internal URLs if asked)
+ */
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(!strucmp(p->attribute, "HREF")
+ && p->value
+ && (HANDLES_LOC(hd->html_data)
+ || struncmp(p->value, "x-alpine-", 9)
+ || struncmp(p->value, "x-pine-help", 11)
+ || p->value[0] == '#'))
+ href = p;
+ else if(!strucmp(p->attribute, "NAME"))
+ name = p;
+
+ if(DO_HANDLES(hd->html_data) && (href || name)){
+ h = new_handle(HANDLESP(hd->html_data));
+
+ /*
+ * Enhancement: we might want to get fancier and parse the
+ * href a bit further such that we can launch images using
+ * our image viewer, or browse local files or directories
+ * with our internal tools. Of course, having the jump-off
+ * point into text/html always be the defined "web-browser",
+ * just might be the least confusing UI-wise...
+ */
+ h->type = URL;
+
+ if(name && name->value)
+ h->h.url.name = cpystr(name->value);
+
+ /*
+ * Prepare to build embedded prefix...
+ */
+ HD(hd->html_data)->prefix = (int *) fs_get(64 * sizeof(int));
+ x = 0;
+
+ /*
+ * Is this something that looks like a URL? If not and
+ * we were giving some "base" string, proceed ala RFC1808...
+ */
+ if(href){
+ if(HTML_BASE(hd->html_data) && !rfc1738_scan(href->value, &n)){
+ html_a_relative(HTML_BASE(hd->html_data), href->value, h);
+ }
+ else if(!(NO_RELATIVE(hd->html_data) && html_href_relative(href->value)))
+ h->h.url.path = cpystr(href->value);
+
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *q;
+
+ if(ps_global->VAR_SLCTBL_FORE_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR,
+ ps_global->VAR_NORM_FORE_COLOR))
+ fg = ps_global->VAR_SLCTBL_FORE_COLOR;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR))
+ bg = ps_global->VAR_SLCTBL_BACK_COLOR;
+
+ if(fg || bg){
+ COLOR_PAIR *tmp;
+
+ /*
+ * The blacks are just known good colors for testing
+ * whether the other color is good.
+ */
+ tmp = new_color_pair(fg ? fg : colorx(COL_BLACK),
+ bg ? bg : colorx(COL_BLACK));
+ if(pico_is_good_colorpair(tmp)){
+ q = color_embed(fg, bg);
+
+ for(i = 0; q[i]; i++)
+ HD(hd->html_data)->prefix[x++] = q[i];
+ }
+
+ if(tmp)
+ free_color_pair(&tmp);
+ }
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global))
+ HD(hd->html_data)->prefix[x++] = HTML_DOBOLD;
+ }
+ else
+ HD(hd->html_data)->prefix[x++] = HTML_DOBOLD;
+ }
+
+ HD(hd->html_data)->prefix[x++] = TAG_EMBED;
+ HD(hd->html_data)->prefix[x++] = TAG_HANDLE;
+
+ snprintf(buf, sizeof(buf), "%ld", hd->x = h->key);
+ HD(hd->html_data)->prefix[x++] = n = strlen(buf);
+ for(i = 0; i < n; i++)
+ HD(hd->html_data)->prefix[x++] = buf[i];
+
+ HD(hd->html_data)->prefix_used = x;
+
+ hd->dp = (void *) so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ }
+ else if(cmd == GF_EOD){
+ html_a_finish(hd);
+ }
+
+ return(1); /* get linked */
+}
+
+
+void
+html_a_prefix(FILTER_S *f)
+{
+ int *prefix, n;
+
+ /* Do this so we don't visit from html_output... */
+ prefix = HD(f)->prefix;
+ HD(f)->prefix = NULL;
+
+ for(n = 0; n < HD(f)->prefix_used; n++)
+ html_a_output_prefix(f, prefix[n]);
+
+ fs_give((void **) &prefix);
+}
+
+
+/*
+ * html_a_finish - house keeping associated with end of link tag
+ */
+void
+html_a_finish(HANDLER_S *hd)
+{
+ if(DO_HANDLES(hd->html_data)){
+ if(HD(hd->html_data)->prefix){
+ if(!PASS_HTML(hd->html_data)){
+ char *empty_link = "[LINK]";
+ int i;
+
+ html_a_prefix(hd->html_data);
+ for(i = 0; empty_link[i]; i++)
+ html_output(hd->html_data, empty_link[i]);
+ }
+ }
+
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *p;
+ int i;
+
+ if(ps_global->VAR_SLCTBL_FORE_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR,
+ ps_global->VAR_NORM_FORE_COLOR))
+ fg = ps_global->VAR_NORM_FORE_COLOR;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR))
+ bg = ps_global->VAR_NORM_BACK_COLOR;
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global))
+ HTML_BOLD(hd->html_data, 0); /* turn OFF bold */
+
+ if(fg || bg){
+ COLOR_PAIR *tmp;
+
+ /*
+ * The blacks are just known good colors for testing
+ * whether the other color is good.
+ */
+ tmp = new_color_pair(fg ? fg : colorx(COL_BLACK),
+ bg ? bg : colorx(COL_BLACK));
+ if(pico_is_good_colorpair(tmp)){
+ p = color_embed(fg, bg);
+
+ for(i = 0; p[i]; i++)
+ html_output(hd->html_data, p[i]);
+ }
+
+ if(tmp)
+ free_color_pair(&tmp);
+ }
+ }
+ else
+ HTML_BOLD(hd->html_data, 0); /* turn OFF bold */
+
+ html_output(hd->html_data, TAG_EMBED);
+ html_output(hd->html_data, TAG_HANDLEOFF);
+
+ html_a_output_info(hd);
+ }
+}
+
+
+/*
+ * html_output_a_prefix - dump Anchor prefix data
+ */
+void
+html_a_output_prefix(FILTER_S *f, int c)
+{
+ switch(c){
+ case HTML_DOBOLD :
+ HTML_BOLD(f, 1);
+ break;
+
+ default :
+ html_output(f, c);
+ break;
+ }
+}
+
+
+
+/*
+ * html_a_output_info - dump possibly deceptive link info into text.
+ * phark the phishers.
+ */
+void
+html_a_output_info(HANDLER_S *hd)
+{
+ int l, risky = 0, hl = 0, tl;
+ char *url = NULL, *hn = NULL, *txt;
+ HANDLE_S *h;
+
+ /* find host anchor references */
+ if((h = get_handle(*HANDLESP(hd->html_data), (int) hd->x)) != NULL
+ && h->h.url.path != NULL
+ && (hn = rfc1738_scan(rfc1738_str(url = cpystr(h->h.url.path)), &l)) != NULL
+ && (hn = srchstr(hn,"://")) != NULL){
+
+ for(hn += 3, hl = 0; hn[hl] && hn[hl] != '/' && hn[hl] != '?'; hl++)
+ ;
+ }
+
+ if(hn && hl){
+ /*
+ * look over anchor's text to see if there's a
+ * mismatch between href target and url-ish
+ * looking text. throw a red flag if so.
+ * similarly, toss one if the target's referenced
+ * by a
+ */
+ if(hd->dp){
+ so_writec('\0', (STORE_S *) hd->dp);
+
+ if((txt = (char *) so_text((STORE_S *) hd->dp)) != NULL
+ && (txt = rfc1738_scan(txt, &tl)) != NULL
+ && (txt = srchstr(txt,"://")) != NULL){
+
+ for(txt += 3, tl = 0; txt[tl] && txt[tl] != '/' && txt[tl] != '?'; tl++)
+ ;
+
+ if(tl != hl)
+ risky++;
+ else
+ /* look for non matching text */
+ for(l = 0; l < tl && l < hl; l++)
+ if(tolower((unsigned char) txt[l]) != tolower((unsigned char) hn[l])){
+ risky++;
+ break;
+ }
+ }
+
+ so_give((STORE_S **) &hd->dp);
+ }
+
+ /* look for literal IP, anything possibly encoded or auth specifier */
+ if(!risky){
+ int digits = 1;
+
+ for(l = 0; l < hl; l++){
+ if(hn[l] == '@' || hn[l] == '%'){
+ risky++;
+ break;
+ }
+ else if(!(hn[l] == '.' || isdigit((unsigned char) hn[l])))
+ digits = 0;
+ }
+
+ if(digits)
+ risky++;
+ }
+
+ /* Insert text of link's domain */
+ if(SHOWSERVER(hd->html_data)){
+ char *q;
+ COLOR_PAIR *col = NULL, *colnorm = NULL;
+
+ html_output(hd->html_data, ' ');
+ html_output(hd->html_data, '[');
+
+ if(pico_usingcolor()
+ && ps_global->VAR_METAMSG_FORE_COLOR
+ && ps_global->VAR_METAMSG_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_METAMSG_FORE_COLOR,
+ ps_global->VAR_METAMSG_BACK_COLOR))){
+ if(!pico_is_good_colorpair(col))
+ free_color_pair(&col);
+
+ if(col){
+ q = color_embed(col->fg, col->bg);
+
+ for(l = 0; q[l]; l++)
+ html_output(hd->html_data, q[l]);
+ }
+ }
+
+ for(l = 0; l < hl; l++)
+ html_output(hd->html_data, hn[l]);
+
+ if(col){
+ if(ps_global->VAR_NORM_FORE_COLOR
+ && ps_global->VAR_NORM_BACK_COLOR
+ && (colnorm = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR))){
+ if(!pico_is_good_colorpair(colnorm))
+ free_color_pair(&colnorm);
+
+ if(colnorm){
+ q = color_embed(colnorm->fg, colnorm->bg);
+ free_color_pair(&colnorm);
+
+ for(l = 0; q[l]; l++)
+ html_output(hd->html_data, q[l]);
+ }
+ }
+
+ free_color_pair(&col);
+ }
+
+ html_output(hd->html_data, ']');
+ }
+ }
+
+ /*
+ * if things look OK so far, make sure nothing within
+ * the url looks too fishy...
+ */
+ while(!risky && hn
+ && (hn = rfc1738_scan(hn, &l)) != NULL
+ && (hn = srchstr(hn,"://")) != NULL){
+ int digits = 1;
+
+ for(hn += 3, hl = 0; hn[hl] && hn[hl] != '/' && hn[hl] != '?'; hl++){
+ /*
+ * auth spec, encoded characters, or possibly non-standard port
+ * should raise a red flag
+ */
+ if(hn[hl] == '@' || hn[hl] == '%' || hn[hl] == ':'){
+ risky++;
+ break;
+ }
+ else if(!(hn[hl] == '.' || isdigit((unsigned char) hn[hl])))
+ digits = 0;
+ }
+
+ /* dotted-dec/raw-int address should cause suspicion as well */
+ if(digits)
+ risky++;
+ }
+
+ if(risky && ((HTML_OPT_S *) hd->html_data->opt)->warnrisk_f)
+ (*((HTML_OPT_S *) hd->html_data->opt)->warnrisk_f)();
+
+ fs_give((void **) &url);
+}
+
+
+
+/*
+ * relative_url - put full url path in h based on base and relative url
+ */
+void
+html_a_relative(char *base_url, char *rel_url, HANDLE_S *h)
+{
+ size_t len;
+ char tmp[MAILTMPLEN], *p, *q;
+ char *scheme = NULL, *net = NULL, *path = NULL,
+ *parms = NULL, *query = NULL, *frag = NULL,
+ *base_scheme = NULL, *base_net_loc = NULL,
+ *base_path = NULL, *base_parms = NULL,
+ *base_query = NULL, *base_frag = NULL,
+ *rel_scheme = NULL, *rel_net_loc = NULL,
+ *rel_path = NULL, *rel_parms = NULL,
+ *rel_query = NULL, *rel_frag = NULL;
+
+ /* Rough parse of base URL */
+ rfc1808_tokens(base_url, &base_scheme, &base_net_loc, &base_path,
+ &base_parms, &base_query, &base_frag);
+
+ /* Rough parse of this URL */
+ rfc1808_tokens(rel_url, &rel_scheme, &rel_net_loc, &rel_path,
+ &rel_parms, &rel_query, &rel_frag);
+
+ scheme = rel_scheme; /* defaults */
+ net = rel_net_loc;
+ path = rel_path;
+ parms = rel_parms;
+ query = rel_query;
+ frag = rel_frag;
+ if(!scheme && base_scheme){
+ scheme = base_scheme;
+ if(!net){
+ net = base_net_loc;
+ if(path){
+ if(*path != '/'){
+ if(base_path){
+ for(p = q = base_path; /* Drop base path's tail */
+ (p = strchr(p, '/'));
+ q = ++p)
+ ;
+
+ len = q - base_path;
+ }
+ else
+ len = 0;
+
+ if(len + strlen(rel_path) < sizeof(tmp)-1){
+ if(len)
+ snprintf(path = tmp, sizeof(tmp), "%.*s", len, base_path);
+
+ strncpy(tmp + len, rel_path, sizeof(tmp)-len);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ /* Follow RFC 1808 "Step 6" */
+ for(p = tmp; (p = strchr(p, '.')); )
+ switch(*(p+1)){
+ /*
+ * a) All occurrences of "./", where "." is a
+ * complete path segment, are removed.
+ */
+ case '/' :
+ if(p > tmp)
+ for(q = p; (*q = *(q+2)) != '\0'; q++)
+ ;
+ else
+ p++;
+
+ break;
+
+ /*
+ * b) If the path ends with "." as a
+ * complete path segment, that "." is
+ * removed.
+ */
+ case '\0' :
+ if(p == tmp || *(p-1) == '/')
+ *p = '\0';
+ else
+ p++;
+
+ break;
+
+ /*
+ * c) All occurrences of "<segment>/../",
+ * where <segment> is a complete path
+ * segment not equal to "..", are removed.
+ * Removal of these path segments is
+ * performed iteratively, removing the
+ * leftmost matching pattern on each
+ * iteration, until no matching pattern
+ * remains.
+ *
+ * d) If the path ends with "<segment>/..",
+ * where <segment> is a complete path
+ * segment not equal to "..", that
+ * "<segment>/.." is removed.
+ */
+ case '.' :
+ if(p > tmp + 1){
+ for(q = p - 2; q > tmp && *q != '/'; q--)
+ ;
+
+ if(*q == '/')
+ q++;
+
+ if(q + 1 == p /* no "//.." */
+ || (*q == '.' /* and "../.." */
+ && *(q+1) == '.'
+ && *(q+2) == '/')){
+ p += 2;
+ break;
+ }
+
+ switch(*(p+2)){
+ case '/' :
+ len = (p - q) + 3;
+ p = q;
+ for(; (*q = *(q+len)) != '\0'; q++)
+ ;
+
+ break;
+
+ case '\0':
+ *(p = q) = '\0';
+ break;
+
+ default:
+ p += 2;
+ break;
+ }
+ }
+ else
+ p += 2;
+
+ break;
+
+ default :
+ p++;
+ break;
+ }
+ }
+ else
+ path = ""; /* lame. */
+ }
+ }
+ else{
+ path = base_path;
+ if(!parms){
+ parms = base_parms;
+ if(!query)
+ query = base_query;
+ }
+ }
+ }
+ }
+
+ len = (scheme ? strlen(scheme) : 0) + (net ? strlen(net) : 0)
+ + (path ? strlen(path) : 0) + (parms ? strlen(parms) : 0)
+ + (query ? strlen(query) : 0) + (frag ? strlen(frag ) : 0) + 8;
+
+ h->h.url.path = (char *) fs_get(len * sizeof(char));
+ snprintf(h->h.url.path, len, "%s%s%s%s%s%s%s%s%s%s%s%s",
+ scheme ? scheme : "", scheme ? ":" : "",
+ net ? "//" : "", net ? net : "",
+ (path && *path == '/') ? "" : ((path && net) ? "/" : ""),
+ path ? path : "",
+ parms ? ";" : "", parms ? parms : "",
+ query ? "?" : "", query ? query : "",
+ frag ? "#" : "", frag ? frag : "");
+
+ if(base_scheme)
+ fs_give((void **) &base_scheme);
+
+ if(base_net_loc)
+ fs_give((void **) &base_net_loc);
+
+ if(base_path)
+ fs_give((void **) &base_path);
+
+ if(base_parms)
+ fs_give((void **) &base_parms);
+
+ if(base_query)
+ fs_give((void **) &base_query);
+
+ if(base_frag)
+ fs_give((void **) &base_frag);
+
+ if(rel_scheme)
+ fs_give((void **) &rel_scheme);
+
+ if(rel_net_loc)
+ fs_give((void **) &rel_net_loc);
+
+ if(rel_parms)
+ fs_give((void **) &rel_parms);
+
+ if(rel_query)
+ fs_give((void **) &rel_query);
+
+ if(rel_frag)
+ fs_give((void **) &rel_frag);
+
+ if(rel_path)
+ fs_give((void **) &rel_path);
+}
+
+
+/*
+ * html_href_relative - href
+ */
+int
+html_href_relative(char *url)
+{
+ int i;
+
+ if(url)
+ for(i = 0; i < 32 && url[i]; i++)
+ if(!(isalpha((unsigned char) url[i]) || url[i] == '_' || url[i] == '-')){
+ if(url[i] == ':')
+ return(FALSE);
+ else
+ break;
+ }
+
+ return(TRUE);
+}
+
+
+/*
+ * HTML <UL> (Unordered List) element handler
+ */
+int
+html_ul(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "ul");
+ }
+ else{
+ HD(hd->html_data)->li_pending = 1;
+ html_blank(hd->html_data, 0);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</ul>");
+ }
+ else{
+ html_blank(hd->html_data, 0);
+
+ if(!HD(hd->html_data)->li_pending)
+ html_indent(hd->html_data, -4, HTML_ID_INC);
+ else
+ HD(hd->html_data)->li_pending = 0;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <OL> (Ordered List) element handler
+ */
+int
+html_ol(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "ol");
+ }
+ else{
+ /*
+ * Signal that we're expecting to see <LI> as our next elemnt
+ * and set the the initial ordered count.
+ */
+ HD(hd->html_data)->li_pending = 1;
+ hd->x = 1L;
+ html_blank(hd->html_data, 0);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</ol>");
+ }
+ else{
+ html_blank(hd->html_data, 0);
+
+ if(!HD(hd->html_data)->li_pending)
+ html_indent(hd->html_data, -4, HTML_ID_INC);
+ else
+ HD(hd->html_data)->li_pending = 0;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <MENU> (Menu List) element handler
+ */
+int
+html_menu(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "menu");
+ }
+ else{
+ HD(hd->html_data)->li_pending = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</menu>");
+ }
+ else{
+ html_blank(hd->html_data, 0);
+
+ if(!HD(hd->html_data)->li_pending)
+ html_indent(hd->html_data, -4, HTML_ID_INC);
+ else
+ HD(hd->html_data)->li_pending = 0;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <DIR> (Directory List) element handler
+ */
+int
+html_dir(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "dir");
+ }
+ else{
+ HD(hd->html_data)->li_pending = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</dir>");
+ }
+ else{
+ html_blank(hd->html_data, 0);
+
+ if(!HD(hd->html_data)->li_pending)
+ html_indent(hd->html_data, -4, HTML_ID_INC);
+ else
+ HD(hd->html_data)->li_pending = 0;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <LI> (List Item) element handler
+ */
+int
+html_li(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ HANDLER_S *p, *found = NULL;
+
+ /*
+ * There better be a an unordered list, ordered list,
+ * Menu or Directory handler installed
+ * or else we crap out...
+ */
+ for(p = HANDLERS(hd->html_data); p; p = p->below)
+ if(EL(p)->handler == html_ul
+ || EL(p)->handler == html_ol
+ || EL(p)->handler == html_menu
+ || EL(p)->handler == html_dir){
+ found = p;
+ break;
+ }
+
+ if(found){
+ if(PASS_HTML(hd->html_data)){
+ }
+ else{
+ char buf[8], *p;
+ int wrapstate;
+
+ /* Start a new line */
+ html_blank(hd->html_data, 0);
+
+ /* adjust indent level if needed */
+ if(HD(hd->html_data)->li_pending){
+ html_indent(hd->html_data, 4, HTML_ID_INC);
+ HD(hd->html_data)->li_pending = 0;
+ }
+
+ if(EL(found)->handler == html_ul){
+ int l = html_indent(hd->html_data, 0, HTML_ID_GET);
+
+ strncpy(buf, " ", sizeof(buf));
+ buf[1] = (l < 5) ? '*' : (l < 9) ? '+' : (l < 17) ? 'o' : '#';
+ }
+ else if(EL(found)->handler == html_ol)
+ snprintf(buf, sizeof(buf), "%2ld.", found->x++);
+ else if(EL(found)->handler == html_menu){
+ strncpy(buf, " ->", sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ html_indent(hd->html_data, -4, HTML_ID_INC);
+
+ /* So we don't munge whitespace */
+ wrapstate = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+
+ html_write_indent(hd->html_data, HD(hd->html_data)->indent_level);
+ for(p = buf; *p; p++)
+ html_output(hd->html_data, (int) *p);
+
+ HD(hd->html_data)->wrapstate = wrapstate;
+ html_indent(hd->html_data, 4, HTML_ID_INC);
+ }
+ /* else BUG: should really bitch about this */
+ }
+
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "li");
+ return(1); /* get linked */
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</li>");
+ }
+ }
+
+ return(PASS_HTML(hd->html_data)); /* DON'T get linked */
+}
+
+
+/*
+ * HTML <DL> (Definition List) element handler
+ */
+int
+html_dl(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "dl");
+ }
+ else{
+ /*
+ * Set indention level for definition terms and definitions...
+ */
+ hd->x = html_indent(hd->html_data, 0, HTML_ID_GET);
+ hd->y = hd->x + 2;
+ hd->z = hd->y + 4;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</dl>");
+ }
+ else{
+ html_indent(hd->html_data, (int) hd->x, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <DT> (Definition Term) element handler
+ */
+int
+html_dt(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "dt");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</dt>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ if(cmd == GF_RESET){
+ HANDLER_S *p;
+
+ /*
+ * There better be a Definition Handler installed
+ * or else we crap out...
+ */
+ for(p = HANDLERS(hd->html_data); p && EL(p)->handler != html_dl; p = p->below)
+ ;
+
+ if(p){ /* adjust indent level if needed */
+ html_indent(hd->html_data, (int) p->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ }
+ /* BUG: else should really bitch about this */
+ }
+
+ return(0); /* DON'T get linked */
+}
+
+
+/*
+ * HTML <DD> (Definition Definition) element handler
+ */
+int
+html_dd(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "dd");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</dd>");
+ }
+
+ return(1); /* get linked */
+ }
+
+ if(cmd == GF_RESET){
+ HANDLER_S *p;
+
+ /*
+ * There better be a Definition Handler installed
+ * or else we crap out...
+ */
+ for(p = HANDLERS(hd->html_data); p && EL(p)->handler != html_dl; p = p->below)
+ ;
+
+ if(p){ /* adjust indent level if needed */
+ html_indent(hd->html_data, (int) p->z, HTML_ID_SET);
+ html_blank(hd->html_data, 0);
+ }
+ /* BUG: should really bitch about this */
+ }
+
+ return(0); /* DON'T get linked */
+}
+
+
+/*
+ * HTML <H1> (Headings 1) element handler.
+ *
+ * Bold, very-large font, CENTERED. One or two blank lines
+ * above and below. For our silly character cell's that
+ * means centered and ALL CAPS...
+ */
+int
+html_h1(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h1");
+ }
+ else{
+ /* turn ON the centered bit */
+ CENTER_BIT(hd->html_data) = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h1>");
+ }
+ else{
+ /* turn OFF the centered bit, add blank line */
+ CENTER_BIT(hd->html_data) = 0;
+ html_blank(hd->html_data, 1);
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <H2> (Headings 2) element handler
+ */
+int
+html_h2(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(PASS_HTML(hd->html_data)){
+ html_handoff(hd, ch);
+ }
+ else{
+ if((hd->x & HTML_HX_ULINE) && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_ULINE(hd->html_data, 1);
+ hd->x ^= HTML_HX_ULINE; /* only once! */
+ }
+
+ html_handoff(hd, (ch < 128 && islower((unsigned char) ch))
+ ? toupper((unsigned char) ch) : ch);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h2");
+ }
+ else{
+ /*
+ * Bold, large font, flush-left. One or two blank lines
+ * above and below.
+ */
+ if(CENTER_BIT(hd->html_data)) /* stop centering for now */
+ hd->x = HTML_HX_CENTER;
+ else
+ hd->x = 0;
+
+ hd->x |= HTML_HX_ULINE;
+
+ CENTER_BIT(hd->html_data) = 0;
+ hd->y = html_indent(hd->html_data, 0, HTML_ID_SET);
+ hd->z = HD(hd->html_data)->wrapcol;
+ HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8;
+ html_blank(hd->html_data, 1);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h2>");
+ }
+ else{
+ /*
+ * restore previous centering, and indent level
+ */
+ if(!(hd->x & HTML_HX_ULINE))
+ HTML_ULINE(hd->html_data, 0);
+
+ html_indent(hd->html_data, hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ CENTER_BIT(hd->html_data) = (hd->x & HTML_HX_CENTER) != 0;
+ HD(hd->html_data)->wrapcol = hd->z;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <H3> (Headings 3) element handler
+ */
+int
+html_h3(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ if(!PASS_HTML(hd->html_data)){
+ if((hd->x & HTML_HX_ULINE) && !ASCII_ISSPACE((unsigned char) (ch & 0xff))){
+ HTML_ULINE(hd->html_data, 1);
+ hd->x ^= HTML_HX_ULINE; /* only once! */
+ }
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h3");
+ }
+ else{
+ /*
+ * Italic, large font, slightly indented from the left
+ * margin. One or two blank lines above and below.
+ */
+ if(CENTER_BIT(hd->html_data)) /* stop centering for now */
+ hd->x = HTML_HX_CENTER;
+ else
+ hd->x = 0;
+
+ hd->x |= HTML_HX_ULINE;
+ CENTER_BIT(hd->html_data) = 0;
+ hd->y = html_indent(hd->html_data, 2, HTML_ID_SET);
+ hd->z = HD(hd->html_data)->wrapcol;
+ HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8;
+ html_blank(hd->html_data, 1);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h3>");
+ }
+ else{
+ /*
+ * restore previous centering, and indent level
+ */
+ if(!(hd->x & HTML_HX_ULINE))
+ HTML_ULINE(hd->html_data, 0);
+
+ html_indent(hd->html_data, hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ CENTER_BIT(hd->html_data) = (hd->x & HTML_HX_CENTER) != 0;
+ HD(hd->html_data)->wrapcol = hd->z;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <H4> (Headings 4) element handler
+ */
+int
+html_h4(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h4");
+ }
+ else{
+ /*
+ * Bold, normal font, indented more than H3. One blank line
+ * above and below.
+ */
+ hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */
+ CENTER_BIT(hd->html_data) = 0;
+ hd->y = html_indent(hd->html_data, 4, HTML_ID_SET);
+ hd->z = HD(hd->html_data)->wrapcol;
+ HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8;
+ html_blank(hd->html_data, 1);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h4>");
+ }
+ else{
+ /*
+ * restore previous centering, and indent level
+ */
+ html_indent(hd->html_data, (int) hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ CENTER_BIT(hd->html_data) = hd->x;
+ HD(hd->html_data)->wrapcol = hd->z;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <H5> (Headings 5) element handler
+ */
+int
+html_h5(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h5");
+ }
+ else{
+ /*
+ * Italic, normal font, indented as H4. One blank line
+ * above.
+ */
+ hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */
+ CENTER_BIT(hd->html_data) = 0;
+ hd->y = html_indent(hd->html_data, 6, HTML_ID_SET);
+ hd->z = HD(hd->html_data)->wrapcol;
+ HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8;
+ html_blank(hd->html_data, 1);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h5>");
+ }
+ else{
+ /*
+ * restore previous centering, and indent level
+ */
+ html_indent(hd->html_data, (int) hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ CENTER_BIT(hd->html_data) = hd->x;
+ HD(hd->html_data)->wrapcol = hd->z;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <H6> (Headings 6) element handler
+ */
+int
+html_h6(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "h6");
+ }
+ else{
+ /*
+ * Bold, indented same as normal text, more than H5. One
+ * blank line above.
+ */
+ hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */
+ CENTER_BIT(hd->html_data) = 0;
+ hd->y = html_indent(hd->html_data, 8, HTML_ID_SET);
+ hd->z = HD(hd->html_data)->wrapcol;
+ HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8;
+ html_blank(hd->html_data, 1);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</h6>");
+ }
+ else{
+ /*
+ * restore previous centering, and indent level
+ */
+ html_indent(hd->html_data, (int) hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 1);
+ CENTER_BIT(hd->html_data) = hd->x;
+ HD(hd->html_data)->wrapcol = hd->z;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <BlockQuote> element handler
+ */
+int
+html_blockquote(HANDLER_S *hd, int ch, int cmd)
+{
+ int j;
+#define HTML_BQ_INDENT 6
+
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "blockquote");
+ }
+ else{
+ /*
+ * A typical rendering might be a slight extra left and
+ * right indent, and/or italic font. The Blockquote element
+ * causes a paragraph break, and typically provides space
+ * above and below the quote.
+ */
+ html_indent(hd->html_data, HTML_BQ_INDENT, HTML_ID_INC);
+ j = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ html_blank(hd->html_data, 1);
+ HD(hd->html_data)->wrapstate = j;
+ HD(hd->html_data)->wrapcol -= HTML_BQ_INDENT;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</blockquote>");
+ }
+ else{
+ html_blank(hd->html_data, 1);
+
+ j = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ html_indent(hd->html_data, -(HTML_BQ_INDENT), HTML_ID_INC);
+ HD(hd->html_data)->wrapstate = j;
+ HD(hd->html_data)->wrapcol += HTML_BQ_INDENT;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <Address> element handler
+ */
+int
+html_address(HANDLER_S *hd, int ch, int cmd)
+{
+ int j;
+#define HTML_ADD_INDENT 2
+
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "address");
+ }
+ else{
+ /*
+ * A typical rendering might be a slight extra left and
+ * right indent, and/or italic font. The Blockquote element
+ * causes a paragraph break, and typically provides space
+ * above and below the quote.
+ */
+ html_indent(hd->html_data, HTML_ADD_INDENT, HTML_ID_INC);
+ j = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ html_blank(hd->html_data, 1);
+ HD(hd->html_data)->wrapstate = j;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</address>");
+ }
+ else{
+ html_blank(hd->html_data, 1);
+
+ j = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ html_indent(hd->html_data, -(HTML_ADD_INDENT), HTML_ID_INC);
+ HD(hd->html_data)->wrapstate = j;
+ }
+ }
+
+ return(1); /* get linked */
+}
+
+
+/*
+ * HTML <PRE> (Preformatted Text) element handler
+ */
+int
+html_pre(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ /*
+ * remove CRLF after '>' in element.
+ * We see CRLF because wrapstate is off.
+ */
+ switch(hd->y){
+ case 2 :
+ if(ch == '\012'){
+ hd->y = 3;
+ return(1);
+ }
+ else
+ html_handoff(hd, '\015');
+
+ break;
+
+ case 1 :
+ if(ch == '\015'){
+ hd->y = 2;
+ return(1);
+ }
+
+ case 3 :
+ /* passing tags? replace CRLF with <BR> to make
+ * sure hard newline survives in the end...
+ */
+ if(PASS_HTML(hd->html_data))
+ hd->y = 4; /* keep looking for CRLF */
+ else
+ hd->y = 0; /* stop looking */
+
+ break;
+
+ case 4 :
+ if(ch == '\015'){
+ hd->y = 5;
+ return(1);
+ }
+
+ break;
+
+ case 5 :
+ hd->y = 4;
+ if(ch == '\012'){
+ html_output_string(hd->html_data, "<br />");
+ return(1);
+ }
+ else
+ html_handoff(hd, '\015'); /* not CRLF, pass raw CR */
+
+ break;
+
+ default : /* zero case */
+ break;
+ }
+
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ hd->y = 1;
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "pre");
+ }
+ else{
+ if(hd->html_data)
+ hd->html_data->f1 = DFL; \
+
+ html_blank(hd->html_data, 1);
+ hd->x = HD(hd->html_data)->wrapstate;
+ HD(hd->html_data)->wrapstate = 0;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</pre>");
+ }
+ else{
+ HD(hd->html_data)->wrapstate = (hd->x != 0);
+ html_blank(hd->html_data, 0);
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * HTML <CENTER> (Centerd Text) element handler
+ */
+int
+html_center(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "center");
+ }
+ else{
+ /* turn ON the centered bit */
+ CENTER_BIT(hd->html_data) = 1;
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</center>");
+ }
+ else{
+ /* turn OFF the centered bit */
+ CENTER_BIT(hd->html_data) = 0;
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * HTML <DIV> (Document Divisions) element handler
+ */
+int
+html_div(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ if(PASS_HTML(hd->html_data)){
+ html_output_raw_tag(hd->html_data, "div");
+ }
+ else{
+ PARAMETER *p;
+
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(!strucmp(p->attribute, "ALIGN")){
+ if(p->value){
+ /* remember previous values */
+ hd->x = CENTER_BIT(hd->html_data);
+ hd->y = html_indent(hd->html_data, 0, HTML_ID_GET);
+
+ html_blank(hd->html_data, 0);
+ CENTER_BIT(hd->html_data) = !strucmp(p->value, "CENTER");
+ html_indent(hd->html_data, 0, HTML_ID_SET);
+ /* NOTE: "RIGHT" not supported yet */
+ }
+ }
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(PASS_HTML(hd->html_data)){
+ html_output_string(hd->html_data, "</div>");
+ }
+ else{
+ /* restore centered bit and indentiousness */
+ CENTER_BIT(hd->html_data) = hd->y;
+ html_indent(hd->html_data, hd->y, HTML_ID_SET);
+ html_blank(hd->html_data, 0);
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * HTML <SPAN> (Text Span) element handler
+ */
+int
+html_span(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "span");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</span>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <KBD> (Text Kbd) element handler
+ */
+int
+html_kbd(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "kbd");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</kbd>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <DFN> (Text Definition) element handler
+ */
+int
+html_dfn(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "dfn");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</dfn>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <TT> (Text Tt) element handler
+ */
+int
+html_tt(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "tt");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</tt>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <VAR> (Text Var) element handler
+ */
+int
+html_var(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "var");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</var>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <SAMP> (Text Samp) element handler
+ */
+int
+html_samp(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "samp");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</samp>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <SUP> (Text Superscript) element handler
+ */
+int
+html_sup(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "sup");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</sup>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <SUB> (Text Subscript) element handler
+ */
+int
+html_sub(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "sub");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</sub>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <CITE> (Text Citation) element handler
+ */
+int
+html_cite(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "cite");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</cite>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <CODE> (Text Code) element handler
+ */
+int
+html_code(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "code");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</code>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <INS> (Text Inserted) element handler
+ */
+int
+html_ins(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "ins");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</ins>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <DEL> (Text Deleted) element handler
+ */
+int
+html_del(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "del");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</del>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <ABBR> (Text Abbreviation) element handler
+ */
+int
+html_abbr(HANDLER_S *hd, int ch, int cmd)
+{
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ html_output_raw_tag(hd->html_data, "abbr");
+ }
+ else if(cmd == GF_EOD){
+ html_output_string(hd->html_data, "</abbr>");
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * HTML <SCRIPT> element handler
+ */
+int
+html_script(HANDLER_S *hd, int ch, int cmd)
+{
+ /* Link in and drop everything within on the floor */
+ return(1);
+}
+
+
+/*
+ * HTML <APPLET> element handler
+ */
+int
+html_applet(HANDLER_S *hd, int ch, int cmd)
+{
+ /* Link in and drop everything within on the floor */
+ return(1);
+}
+
+
+/*
+ * HTML <STYLE> CSS element handler
+ */
+int
+html_style(HANDLER_S *hd, int ch, int cmd)
+{
+ static STORE_S *css_stuff ;
+
+ if(PASS_HTML(hd->html_data)){
+ if(cmd == GF_DATA){
+ /* collect style settings */
+ so_writec(ch, css_stuff);
+ }
+ else if(cmd == GF_RESET){
+ if(css_stuff)
+ so_give(&css_stuff);
+
+ css_stuff = so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ else if(cmd == GF_EOD){
+ /*
+ * TODO: strip anything mischievous and pass on
+ */
+
+ so_give(&css_stuff);
+ }
+ }
+
+ return(1);
+}
+
+/*
+ * RSS 2.0 <RSS> version
+ */
+int
+rss_rss(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_RESET){
+ PARAMETER *p;
+
+ for(p = HD(hd->html_data)->el_data->attribs;
+ p && p->attribute;
+ p = p->next)
+ if(!strucmp(p->attribute, "VERSION")){
+ if(p->value && !strucmp(p->value,"2.0"))
+ return(0); /* do not link in */
+ }
+
+ gf_error("Incompatible RSS version");
+ /* NO RETURN */
+ }
+
+ return(0); /* not linked or error means we never get here */
+}
+
+/*
+ * RSS 2.0 <CHANNEL>
+ */
+int
+rss_channel(HANDLER_S *hd, int ch, int cmd)
+{
+ if(cmd == GF_DATA){
+ html_handoff(hd, ch);
+ }
+ else if(cmd == GF_RESET){
+ RSS_FEED_S *feed;
+
+ feed = RSS_FEED(hd->html_data) = fs_get(sizeof(RSS_FEED_S));
+ memset(feed, 0, sizeof(RSS_FEED_S));
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <TITLE>
+ */
+int
+rss_title(HANDLER_S *hd, int ch, int cmd)
+{
+ static STORE_S *title_so;
+
+ if(cmd == GF_DATA){
+ /* collect data */
+ if(title_so){
+ so_writec(ch, title_so);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(RSS_FEED(hd->html_data)){
+ /* prepare for data */
+ if(title_so)
+ so_give(&title_so);
+
+ title_so = so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(title_so){
+ RSS_FEED_S *feed = RSS_FEED(hd->html_data);
+ RSS_ITEM_S *rip;
+
+ if(feed){
+ if(rip = feed->items){
+ for(; rip->next; rip = rip->next)
+ ;
+
+ if(rip->title)
+ fs_give((void **) &rip->title);
+
+ rip->title = cpystr(rss_skip_whitespace(so_text(title_so)));
+ }
+ else{
+ if(feed->title)
+ fs_give((void **) &feed->title);
+
+ feed->title = cpystr(rss_skip_whitespace(so_text(title_so)));
+ }
+ }
+
+ so_give(&title_so);
+ }
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <IMAGE>
+ */
+int
+rss_image(HANDLER_S *hd, int ch, int cmd)
+{
+ static STORE_S *img_so;
+
+ if(cmd == GF_DATA){
+ /* collect data */
+ if(img_so){
+ so_writec(ch, img_so);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(RSS_FEED(hd->html_data)){
+ /* prepare to collect data */
+ if(img_so)
+ so_give(&img_so);
+
+ img_so = so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(img_so){
+ RSS_FEED_S *feed = RSS_FEED(hd->html_data);
+
+ if(feed){
+ if(feed->image)
+ fs_give((void **) &feed->image);
+
+ feed->image = cpystr(rss_skip_whitespace(so_text(img_so)));
+ }
+
+ so_give(&img_so);
+ }
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <LINK>
+ */
+int
+rss_link(HANDLER_S *hd, int ch, int cmd)
+{
+ static STORE_S *link_so;
+
+ if(cmd == GF_DATA){
+ /* collect data */
+ if(link_so){
+ so_writec(ch, link_so);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(RSS_FEED(hd->html_data)){
+ /* prepare to collect data */
+ if(link_so)
+ so_give(&link_so);
+
+ link_so = so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(link_so){
+ RSS_FEED_S *feed = RSS_FEED(hd->html_data);
+ RSS_ITEM_S *rip;
+
+ if(feed){
+ if(rip = feed->items){
+ for(; rip->next; rip = rip->next)
+ ;
+
+ if(rip->link)
+ fs_give((void **) &rip->link);
+
+ rip->link = cpystr(rss_skip_whitespace(so_text(link_so)));
+ }
+ else{
+ if(feed->link)
+ fs_give((void **) &feed->link);
+
+ feed->link = cpystr(rss_skip_whitespace(so_text(link_so)));
+ }
+ }
+
+ so_give(&link_so);
+ }
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <DESCRIPTION>
+ */
+int
+rss_description(HANDLER_S *hd, int ch, int cmd)
+{
+ static STORE_S *desc_so;
+
+ if(cmd == GF_DATA){
+ /* collect data */
+ if(desc_so){
+ so_writec(ch, desc_so);
+ }
+ }
+ else if(cmd == GF_RESET){
+ if(RSS_FEED(hd->html_data)){
+ /* prepare to collect data */
+ if(desc_so)
+ so_give(&desc_so);
+
+ desc_so = so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+ }
+ else if(cmd == GF_EOD){
+ if(desc_so){
+ RSS_FEED_S *feed = RSS_FEED(hd->html_data);
+ RSS_ITEM_S *rip;
+
+ if(feed){
+ if(rip = feed->items){
+ for(; rip->next; rip = rip->next)
+ ;
+
+ if(rip->description)
+ fs_give((void **) &rip->description);
+
+ rip->description = cpystr(rss_skip_whitespace(so_text(desc_so)));
+ }
+ else{
+ if(feed->description)
+ fs_give((void **) &feed->description);
+
+ feed->description = cpystr(rss_skip_whitespace(so_text(desc_so)));
+ }
+ }
+
+ so_give(&desc_so);
+ }
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <TTL> (in minutes)
+ */
+int
+rss_ttl(HANDLER_S *hd, int ch, int cmd)
+{
+ RSS_FEED_S *feed = RSS_FEED(hd->html_data);
+
+ if(cmd == GF_DATA){
+ if(isdigit((unsigned char) ch))
+ feed->ttl = ((feed->ttl * 10) + (ch - '0'));
+ }
+ else if(cmd == GF_RESET){
+ /* prepare to collect data */
+ feed->ttl = 0;
+ }
+ else if(cmd == GF_EOD){
+ }
+
+ return(1); /* link in */
+}
+
+/*
+ * RSS 2.0 <ITEM>
+ */
+int
+rss_item(HANDLER_S *hd, int ch, int cmd)
+{
+ /* BUG: verify no ITEM nesting? */
+ if(cmd == GF_RESET){
+ RSS_FEED_S *feed;
+
+ if((feed = RSS_FEED(hd->html_data)) != NULL){
+ RSS_ITEM_S **rip;
+ int n = 0;
+
+ for(rip = &feed->items; *rip; rip = &(*rip)->next)
+ if(++n > RSS_ITEM_LIMIT)
+ return(0);
+
+ *rip = fs_get(sizeof(RSS_ITEM_S));
+ memset(*rip, 0, sizeof(RSS_ITEM_S));
+ }
+ }
+
+ return(0); /* don't link in */
+}
+
+
+char *
+rss_skip_whitespace(char *s)
+{
+ for(; *s && isspace((unsigned char) *s); s++)
+ ;
+
+ return(s);
+}
+
+
+/*
+ * return the function associated with the given element name
+ */
+ELPROP_S *
+element_properties(FILTER_S *fd, char *el_name)
+{
+ register ELPROP_S *el_table = ELEMENTS(fd);
+
+ for(; el_table->element; el_table++)
+ if(!strucmp(el_name, el_table->element))
+ return(el_table);
+
+ return(NULL);
+}
+
+
+/*
+ * collect element's name and any attribute/value pairs then
+ * dispatch to the appropriate handler.
+ *
+ * Returns 1 : got what we wanted
+ * 0 : we need more data
+ * -1 : bogus input
+ */
+int
+html_element_collector(FILTER_S *fd, int ch)
+{
+ if(ch == '>'){
+ if(ED(fd)->overrun){
+ /*
+ * If problem processing, don't bother doing anything
+ * internally, just return such that none of what we've
+ * digested is displayed.
+ */
+ HTML_DEBUG_EL("too long", ED(fd));
+ return(1); /* Let it go, Jim */
+ }
+ else if(ED(fd)->mkup_decl){
+ if(ED(fd)->badform){
+ dprint((2, "-- html error: bad form: %.*s\n",
+ ED(fd)->len, ED(fd)->buf ? ED(fd)->buf : "?"));
+ /*
+ * Invalid comment -- make some guesses as
+ * to whether we should stop with this greater-than...
+ */
+ if(ED(fd)->buf[0] != '-'
+ || ED(fd)->len < 4
+ || (ED(fd)->buf[1] == '-'
+ && ED(fd)->buf[ED(fd)->len - 1] == '-'
+ && ED(fd)->buf[ED(fd)->len - 2] == '-'))
+ return(1);
+ }
+ else{
+ dprint((5, "-- html: OK: %.*s\n",
+ ED(fd)->len, ED(fd)->buf ? ED(fd)->buf : "?"));
+ if(ED(fd)->start_comment == ED(fd)->end_comment){
+ if(ED(fd)->len > 10){
+ ED(fd)->buf[ED(fd)->len - 2] = '\0';
+ html_element_comment(fd, ED(fd)->buf + 2);
+ }
+
+ return(1);
+ }
+ /* else keep collecting comment below */
+ }
+ }
+ else if(ED(fd)->proc_inst){
+ return(1); /* return without display... */
+ }
+ else if(!strucmp(ED(fd)->element,"STYLE") && ED(fd)->badform){
+ dprint((2, "-- html error: empty tag with STYLE parameter!"));
+ return(1);
+ }
+ else if(!ED(fd)->quoted || ED(fd)->badform){
+ ELPROP_S *ep;
+
+ /*
+ * We either have the whole thing or all that we could
+ * salvage from it. Try our best...
+ */
+
+ if(HD(fd)->bitbucket)
+ return(1); /* element inside chtml clause! */
+
+ if(!ED(fd)->badform && html_element_flush(ED(fd)))
+ return(1); /* return without display... */
+
+ /*
+ * If we ran into an empty tag or we don't know how to deal
+ * with it, just go on, ignoring it...
+ */
+ if(ED(fd)->element && (ep = element_properties(fd, ED(fd)->element))){
+ if(ep->handler){
+ /* dispatch the element's handler */
+ HTML_DEBUG_EL(ED(fd)->end_tag ? "POP" : "PUSH", ED(fd));
+ if(ED(fd)->end_tag){
+ html_pop(fd, ep); /* remove it's handler */
+ }
+ else{
+ /* if a block element, pop any open <p>'s */
+ if(ep->blocklevel){
+ HANDLER_S *tp;
+
+ for(tp = HANDLERS(fd); tp && EL(tp)->handler == html_p; tp = tp->below){
+ HTML_DEBUG_EL("Unclosed <P>", ED(fd));
+ html_pop(fd, EL(tp));
+ break;
+ }
+ }
+
+ /* enforce table nesting */
+ if(!strucmp(ep->element, "tr")){
+ if(!HANDLERS(fd) || (strucmp(EL(HANDLERS(fd))->element, "table") && strucmp(EL(HANDLERS(fd))->element, "tbody") && strucmp(EL(HANDLERS(fd))->element, "thead"))){
+ dprint((2, "-- html error: bad nesting for <TR>, GOT %s\n", (HANDLERS(fd)) ? EL(HANDLERS(fd))->element : "NO-HANDLERS"));
+ if(HANDLERS(fd) && !strucmp(EL(HANDLERS(fd))->element,"tr")){
+ dprint((2, "-- html error: bad nesting popping previous <TR>"));
+ html_pop(fd, EL(HANDLERS(fd)));
+ }
+ else{
+ dprint((2, "-- html error: bad nesting pusing <TABLE>"));
+ html_push(fd, element_properties(fd, "table"));
+ }
+ }
+ }
+ else if(!strucmp(ep->element, "td") || !strucmp(ep->element, "th")){
+ if(!HANDLERS(fd)){
+ dprint((2, "-- html error: bad nesting: NO HANDLERS before <TD>"));
+ html_push(fd, element_properties(fd, "table"));
+ html_push(fd, element_properties(fd, "tr"));
+ }
+ else if(strucmp(EL(HANDLERS(fd))->element, "tr")){
+ dprint((2, "-- html error: bad nesting for <TD>, GOT %s\n", EL(HANDLERS(fd))->element));
+ html_push(fd, element_properties(fd, "tr"));
+ }
+ else if(!strucmp(EL(HANDLERS(fd))->element, "td")){
+ dprint((2, "-- html error: bad nesting popping <TD>"));
+ html_pop(fd, EL(HANDLERS(fd)));
+ }
+ }
+
+ /* add it's handler */
+ if(html_push(fd, ep)){
+ if(ED(fd)->empty){
+ /* remove empty element */
+ html_pop(fd, ep);
+ }
+ }
+ }
+ }
+ else {
+ HTML_DEBUG_EL("IGNORED", ED(fd));
+ }
+ }
+ else{ /* else, empty or unrecognized */
+ HTML_DEBUG_EL("?", ED(fd));
+ }
+
+ return(1); /* all done! see, that didn't hurt */
+ }
+ }
+ else if(ch == '/' && ED(fd)->element && ED(fd)->len){
+ ED(fd)->empty = 1;
+ }
+ else
+ ED(fd)->empty = 0;
+
+ if(ED(fd)->mkup_decl){
+ if((ch &= 0xff) == '-'){
+ if(ED(fd)->hyphen){
+ ED(fd)->hyphen = 0;
+ if(ED(fd)->start_comment)
+ ED(fd)->end_comment = 1;
+ else
+ ED(fd)->start_comment = 1;
+ }
+ else
+ ED(fd)->hyphen = 1;
+ }
+ else{
+ if(ED(fd)->end_comment)
+ ED(fd)->start_comment = ED(fd)->end_comment = 0;
+
+ /*
+ * no "--" after ! or non-whitespace between comments - bad
+ */
+ if(ED(fd)->len < 2 || (!ED(fd)->start_comment
+ && !ASCII_ISSPACE((unsigned char) ch)))
+ ED(fd)->badform = 1; /* non-comment! */
+
+ ED(fd)->hyphen = 0;
+ }
+
+ /*
+ * Remember the comment for possible later processing, if
+ * it get's too long, remember first and last few chars
+ * so we know when to terminate (and throw some garbage
+ * in between when we toss out what's between.
+ */
+ if(ED(fd)->len == HTML_BUF_LEN){
+ ED(fd)->buf[2] = ED(fd)->buf[3] = 'X';
+ ED(fd)->buf[4] = ED(fd)->buf[ED(fd)->len - 2];
+ ED(fd)->buf[5] = ED(fd)->buf[ED(fd)->len - 1];
+ ED(fd)->len = 6;
+ }
+
+ ED(fd)->buf[(ED(fd)->len)++] = ch;
+ return(0); /* comments go in the bit bucket */
+ }
+ else if(ED(fd)->overrun || ED(fd)->badform){
+ return(0); /* swallow char's until next '>' */
+ }
+ else if(!ED(fd)->element && !ED(fd)->len){
+ if(ch == '/'){ /* validate leading chars */
+ ED(fd)->end_tag = 1;
+ return(0);
+ }
+ else if(ch == '!'){
+ ED(fd)->mkup_decl = 1;
+ return(0);
+ }
+ else if(ch == '?'){
+ ED(fd)->proc_inst = 1;
+ return(0);
+ }
+ else if(!isalpha((unsigned char) ch))
+ return(-1); /* can't be a tag! */
+ }
+ else if(ch == '\"' || ch == '\''){
+ if(!ED(fd)->hit_equal){
+ ED(fd)->badform = 1; /* quote in element name?!? */
+ return(0);
+ }
+
+ if(ED(fd)->quoted){
+ if(ED(fd)->quoted == (char) ch){
+ /* end of a quoted value */
+ ED(fd)->quoted = 0;
+ if(ED(fd)->len && html_element_flush(ED(fd)))
+ ED(fd)->badform = 1;
+
+ return(0); /* continue collecting chars */
+ }
+ /* ELSE fall thru writing other quoting char */
+ }
+ else{
+ ED(fd)->quoted = (char) ch;
+ ED(fd)->was_quoted = 1;
+ return(0); /* need more data */
+ }
+ }
+
+ ch &= 0xff; /* strip any "literal" high bits */
+ if(ED(fd)->quoted
+ || isalnum(ch)
+ || strchr("#-.!", ch)){
+ if(ED(fd)->len < ((ED(fd)->element || !ED(fd)->hit_equal)
+ ? HTML_BUF_LEN:MAX_ELEMENT)){
+ ED(fd)->buf[(ED(fd)->len)++] = ch;
+ }
+ else
+ ED(fd)->overrun = 1; /* flag it broken */
+ }
+ else if(ASCII_ISSPACE((unsigned char) ch) || ch == '='){
+ if((ED(fd)->len || ED(fd)->was_quoted) && html_element_flush(ED(fd))){
+ ED(fd)->badform = 1;
+ return(0); /* else, we ain't done yet */
+ }
+
+ if(!ED(fd)->hit_equal)
+ ED(fd)->hit_equal = (ch == '=');
+ }
+ else
+ ED(fd)->badform = 1; /* unrecognized data?? */
+
+ return(0); /* keep collecting */
+}
+
+
+/*
+ * Element collector found complete string, integrate it and reset
+ * internal collection buffer.
+ *
+ * Returns zero if element collection buffer flushed, error flag otherwise
+ */
+int
+html_element_flush(CLCTR_S *el_data)
+{
+ int rv = 0;
+
+ if(el_data->hit_equal){ /* adding a value */
+ el_data->hit_equal = 0;
+ if(el_data->cur_attrib){
+ if(!el_data->cur_attrib->value){
+ el_data->cur_attrib->value = cpystr(el_data->len
+ ? el_data->buf : "");
+ }
+ else{
+ dprint((2, "** element: unexpected value: %.10s...\n",
+ (el_data->len && el_data->buf) ? el_data->buf : "\"\""));
+ rv = 1;
+ }
+ }
+ else{
+ dprint((2, "** element: missing attribute name: %.10s...\n",
+ (el_data->len && el_data->buf) ? el_data->buf : "\"\""));
+ rv = 2;
+ }
+ }
+ else if(el_data->len){
+ if(!el_data->element){
+ el_data->element = cpystr(el_data->buf);
+ }
+ else{
+ PARAMETER *p = (PARAMETER *)fs_get(sizeof(PARAMETER));
+ memset(p, 0, sizeof(PARAMETER));
+ if(el_data->attribs){
+ el_data->cur_attrib->next = p;
+ el_data->cur_attrib = p;
+ }
+ else
+ el_data->attribs = el_data->cur_attrib = p;
+
+ p->attribute = cpystr(el_data->buf);
+ }
+
+ }
+
+ el_data->was_quoted = 0; /* reset collector buf and state */
+ el_data->len = 0;
+ memset(el_data->buf, 0, HTML_BUF_LEN);
+ return(rv); /* report whatever happened above */
+}
+
+
+/*
+ * html_element_comment - "Special" comment handling here
+ */
+void
+html_element_comment(FILTER_S *f, char *s)
+{
+ char *p;
+
+ while(*s && ASCII_ISSPACE((unsigned char) *s))
+ s++;
+
+ /*
+ * WARNING: "!--chtml" denotes "Conditional HTML", a UW-ism.
+ */
+ if(!struncmp(s, "chtml ", 6)){
+ s += 6;
+ if(!struncmp(s, "if ", 3)){
+ HD(f)->bitbucket = 1; /* default is failure! */
+ switch(*(s += 3)){
+ case 'P' :
+ case 'p' :
+ if(!struncmp(s + 1, "inemode=", 8)){
+ if(!strucmp(s = removing_quotes(s + 9), "function_key")
+ && F_ON(F_USE_FK, ps_global))
+ HD(f)->bitbucket = 0;
+ else if(!strucmp(s, "running"))
+ HD(f)->bitbucket = 0;
+ else if(!strucmp(s, "phone_home") && ps_global->phone_home)
+ HD(f)->bitbucket = 0;
+#ifdef _WINDOWS
+ else if(!strucmp(s, "os_windows"))
+ HD(f)->bitbucket = 0;
+#endif
+ }
+
+ break;
+
+ case '[' : /* test */
+ if((p = strindex(++s, ']')) != NULL){
+ *p = '\0'; /* tie off test string */
+ removing_leading_white_space(s);
+ removing_trailing_white_space(s);
+ if(*s == '-' && *(s+1) == 'r'){ /* readable file? */
+ for(s += 2; *s && ASCII_ISSPACE((unsigned char) *s); s++)
+ ;
+
+
+ HD(f)->bitbucket = (can_access(CHTML_VAR_EXPAND(removing_quotes(s)),
+ READ_ACCESS) != 0);
+ }
+ }
+
+ break;
+
+ default :
+ break;
+ }
+ }
+ else if(!strucmp(s, "else")){
+ HD(f)->bitbucket = !HD(f)->bitbucket;
+ }
+ else if(!strucmp(s, "endif")){
+ /* Clean up after chtml here */
+ HD(f)->bitbucket = 0;
+ }
+ }
+ else if(!HD(f)->bitbucket){
+ if(!struncmp(s, "#include ", 9)){
+ char buf[MAILTMPLEN], *bufp;
+ int len, end_of_line;
+ FILE *fp;
+
+ /* Include the named file */
+ if(!struncmp(s += 9, "file=", 5)
+ && (fp = our_fopen(CHTML_VAR_EXPAND(removing_quotes(s+5)), "r"))){
+ html_element_output(f, HTML_NEWLINE);
+
+ while(fgets(buf, sizeof(buf), fp)){
+ if((len = strlen(buf)) && buf[len-1] == '\n'){
+ end_of_line = 1;
+ buf[--len] = '\0';
+ }
+ else
+ end_of_line = 0;
+
+ for(bufp = buf; len; bufp++, len--)
+ html_element_output(f, (int) *bufp);
+
+ if(end_of_line)
+ html_element_output(f, HTML_NEWLINE);
+ }
+
+ fclose(fp);
+ html_element_output(f, HTML_NEWLINE);
+ HD(f)->blanks = 0;
+ if(f->f1 == WSPACE)
+ f->f1 = DFL;
+ }
+ }
+ else if(!struncmp(s, "#echo ", 6)){
+ if(!struncmp(s += 6, "var=", 4)){
+ char *p, buf[MAILTMPLEN];
+ ADDRESS *adr;
+ extern char datestamp[];
+
+ if(!strcmp(s = removing_quotes(s + 4), "ALPINE_VERSION")){
+ p = ALPINE_VERSION;
+ }
+ else if(!strcmp(s, "ALPINE_REVISION")){
+ p = get_alpine_revision_string(buf, sizeof(buf));
+ }
+ else if(!strcmp(s, "C_CLIENT_VERSION")){
+ p = CCLIENTVERSION;
+ }
+ else if(!strcmp(s, "ALPINE_COMPILE_DATE")){
+ p = datestamp;
+ }
+ else if(!strcmp(s, "ALPINE_TODAYS_DATE")){
+ rfc822_date(p = buf);
+ }
+ else if(!strcmp(s, "_LOCAL_FULLNAME_")){
+ p = (ps_global->VAR_LOCAL_FULLNAME
+ && ps_global->VAR_LOCAL_FULLNAME[0])
+ ? ps_global->VAR_LOCAL_FULLNAME
+ : "Local Support";
+ }
+ else if(!strcmp(s, "_LOCAL_ADDRESS_")){
+ p = (ps_global->VAR_LOCAL_ADDRESS
+ && ps_global->VAR_LOCAL_ADDRESS[0])
+ ? ps_global->VAR_LOCAL_ADDRESS
+ : "postmaster";
+ adr = rfc822_parse_mailbox(&p, ps_global->maildomain);
+ snprintf(p = buf, sizeof(buf), "%s@%s", adr->mailbox, adr->host);
+ mail_free_address(&adr);
+ }
+ else if(!strcmp(s, "_BUGS_FULLNAME_")){
+ p = (ps_global->VAR_BUGS_FULLNAME
+ && ps_global->VAR_BUGS_FULLNAME[0])
+ ? ps_global->VAR_BUGS_FULLNAME
+ : "Place to report Alpine Bugs";
+ }
+ else if(!strcmp(s, "_BUGS_ADDRESS_")){
+ p = (ps_global->VAR_BUGS_ADDRESS
+ && ps_global->VAR_BUGS_ADDRESS[0])
+ ? ps_global->VAR_BUGS_ADDRESS : "postmaster";
+ adr = rfc822_parse_mailbox(&p, ps_global->maildomain);
+ snprintf(p = buf, sizeof(buf), "%s@%s", adr->mailbox, adr->host);
+ mail_free_address(&adr);
+ }
+ else if(!strcmp(s, "CURRENT_DIR")){
+ getcwd(p = buf, sizeof(buf));
+ }
+ else if(!strcmp(s, "HOME_DIR")){
+ p = ps_global->home_dir;
+ }
+ else if(!strcmp(s, "PINE_CONF_PATH")){
+#if defined(_WINDOWS) || !defined(SYSTEM_PINERC)
+ p = "/usr/local/lib/pine.conf";
+#else
+ p = SYSTEM_PINERC;
+#endif
+ }
+ else if(!strcmp(s, "PINE_CONF_FIXED_PATH")){
+#ifdef SYSTEM_PINERC_FIXED
+ p = SYSTEM_PINERC_FIXED;
+#else
+ p = "/usr/local/lib/pine.conf.fixed";
+#endif
+ }
+ else if(!strcmp(s, "PINE_INFO_PATH")){
+ p = SYSTEM_PINE_INFO_PATH;
+ }
+ else if(!strcmp(s, "MAIL_SPOOL_PATH")){
+ p = sysinbox();
+ }
+ else if(!strcmp(s, "MAIL_SPOOL_LOCK_PATH")){
+ /* Don't put the leading /tmp/. */
+ int i, j;
+
+ p = sysinbox();
+ if(p){
+ for(j = 0, i = 0; p[i] && j < MAILTMPLEN - 1; i++){
+ if(p[i] == '/')
+ buf[j++] = '\\';
+ else
+ buf[j++] = p[i];
+ }
+ buf[j++] = '\0';
+ p = buf;
+ }
+ }
+ else if(!struncmp(s, "VAR_", 4)){
+ p = s+4;
+ if(pith_opt_pretty_var_name)
+ p = (*pith_opt_pretty_var_name)(p);
+ }
+ else if(!struncmp(s, "FEAT_", 5)){
+ p = s+5;
+ if(pith_opt_pretty_feature_name)
+ p = (*pith_opt_pretty_feature_name)(p, -1);
+ }
+ else
+ p = NULL;
+
+ if(p){
+ if(f->f1 == WSPACE){
+ html_element_output(f, ' ');
+ f->f1 = DFL; /* clear it */
+ }
+
+ while(*p)
+ html_element_output(f, (int) *p++);
+ }
+ }
+ }
+ }
+}
+
+
+void
+html_element_output(FILTER_S *f, int ch)
+{
+ if(HANDLERS(f))
+ (*EL(HANDLERS(f))->handler)(HANDLERS(f), ch, GF_DATA);
+ else
+ html_output(f, ch);
+}
+
+
+/*
+ * collect html entity and return its UCS value when done.
+ *
+ * Returns HTML_MOREDATA : we need more data
+ * HTML_ENTITY : entity collected
+ * HTML_BADVALUE : good data, but no named match or out of range
+ * HTML_BADDATA : invalid input
+ *
+ * NOTES:
+ * - entity format is "'&' tag ';'" and represents a literal char
+ * - named entities are CASE SENSITIVE.
+ * - numeric char references (where the tag is prefixed with a '#')
+ * are a char with that numbers value
+ * - numeric vals are 0-255 except for the ranges: 0-8, 11-31, 127-159.
+ */
+int
+html_entity_collector(FILTER_S *f, int ch, UCS *ucs, char **alt)
+{
+ static int len = 0;
+ static char buf[MAX_ENTITY+2];
+ int rv, i;
+
+ if(len == MAX_ENTITY){
+ rv = HTML_BADDATA;
+ }
+ else if((len == 0)
+ ? (isalpha((unsigned char) ch) || ch == '#')
+ : ((isdigit((unsigned char) ch)
+ || (isalpha((unsigned char) ch) && buf[0] != '#')))){
+ buf[len++] = ch;
+ return(HTML_MOREDATA);
+ }
+ else if(ch == ';' || ASCII_ISSPACE((unsigned char) ch)){
+ buf[len] = '\0'; /* got something! */
+ if(buf[0] == '#'){
+ *ucs = (UCS) strtoul(&buf[1], NULL, 10);
+ if(alt){
+ *alt = NULL;
+ for(i = 0; i < sizeof(entity_tab)/sizeof(struct html_entities); i++)
+ if(entity_tab[i].value == *ucs){
+ *alt = entity_tab[i].plain;
+ break;
+ }
+ }
+
+ len = 0;
+ return(HTML_ENTITY);
+ }
+ else{
+ rv = HTML_BADVALUE; /* in case of no match */
+ for(i = 0; i < sizeof(entity_tab)/sizeof(struct html_entities); i++)
+ if(strcmp(entity_tab[i].name, buf) == 0){
+ *ucs = entity_tab[i].value;
+ if(alt)
+ *alt = entity_tab[i].plain;
+
+ len = 0;
+ return(HTML_ENTITY);
+ }
+ }
+ }
+ else
+ rv = HTML_BADDATA; /* bogus input! */
+
+ if(alt){
+ buf[len] = '\0';
+ *alt = buf;
+ }
+
+ len = 0;
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ HTML text to plain text filter
+
+ This basically tries to do the best it can with HTML 2.0 (RFC1866)
+ with bits of RFC 1942 (plus some HTML 3.2 thrown in as well) text
+ formatting.
+
+ ----*/
+void
+gf_html2plain(FILTER_S *f, int flg)
+{
+/* BUG: qoute incoming \255 values (see "yuml" above!) */
+ if(flg == GF_DATA){
+ register int c;
+ GF_INIT(f, f->next);
+
+ if(!HTML_WROTE(f)){
+ int ii;
+
+ for(ii = HTML_INDENT(f); ii > 0; ii--)
+ html_putc(f, ' ');
+
+ HTML_WROTE(f) = 1;
+ }
+
+ while(GF_GETC(f, c)){
+ /*
+ * First we have to collect any literal entities...
+ * that is, IF we're not already collecting one
+ * AND we're not in element's text or, if we are, we're
+ * not in quoted text. Whew.
+ */
+ if(f->t){
+ char *alt = NULL;
+ UCS ucs;
+
+ switch(html_entity_collector(f, c, &ucs, &alt)){
+ case HTML_MOREDATA: /* more data required? */
+ continue; /* go get another char */
+
+ case HTML_BADVALUE :
+ case HTML_BADDATA :
+ /* if supplied, process bogus data */
+ HTML_PROC(f, '&');
+ for(; *alt; alt++){
+ unsigned int uic = *alt;
+ HTML_PROC(f, uic);
+ }
+
+ if(c == '&' && !HD(f)->quoted){
+ f->t = '&';
+ continue;
+ }
+ else
+ f->t = 0; /* don't come back next time */
+
+ break;
+
+ default : /* thing to process */
+ f->t = 0; /* don't come back */
+
+ /*
+ * do something with UCS codepoint. If it's
+ * not displayable then use the alt version
+ * otherwise
+ * cvt UCS to UTF-8 and toss into next filter.
+ */
+ if(ucs > 127 && wcellwidth(ucs) < 0){
+ if(alt){
+ for(; *alt; alt++){
+ c = MAKE_LITERAL(*alt);
+ HTML_PROC(f, c);
+ }
+
+ continue;
+ }
+ else
+ c = MAKE_LITERAL('?');
+ }
+ else{
+ unsigned char utf8buf[8], *p1, *p2;
+
+ p2 = utf8_put(p1 = (unsigned char *) utf8buf, (unsigned long) ucs);
+ for(; p1 < p2; p1++){
+ c = MAKE_LITERAL(*p1);
+ HTML_PROC(f, c);
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ }
+ else if(!PASS_HTML(f) && c == '&' && !HD(f)->quoted){
+ f->t = '&';
+ continue;
+ }
+
+ /*
+ * then we process whatever we got...
+ */
+
+ HTML_PROC(f, c);
+ }
+
+ GF_OP_END(f); /* clean up our input pointers */
+ }
+ else if(flg == GF_EOD){
+ while(HANDLERS(f)){
+ dprint((2, "-- html error: no closing tag for %s",EL(HANDLERS(f))->element));
+ html_pop(f, EL(HANDLERS(f)));
+ }
+
+ html_output(f, HTML_NEWLINE);
+ if(ULINE_BIT(f))
+ HTML_ULINE(f, ULINE_BIT(f) = 0);
+
+ if(BOLD_BIT(f))
+ HTML_BOLD(f, BOLD_BIT(f) = 0);
+
+ HTML_FLUSH(f);
+ fs_give((void **)&f->line);
+ if(HD(f)->color)
+ free_color_pair(&HD(f)->color);
+
+ fs_give(&f->data);
+ if(f->opt){
+ if(((HTML_OPT_S *)f->opt)->base)
+ fs_give((void **) &((HTML_OPT_S *)f->opt)->base);
+
+ fs_give(&f->opt);
+ }
+
+ (*f->next->f)(f->next, GF_DATA);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset html2plain\n"));
+ f->data = (HTML_DATA_S *) fs_get(sizeof(HTML_DATA_S));
+ memset(f->data, 0, sizeof(HTML_DATA_S));
+ /* start with flowing text */
+ HD(f)->wrapstate = !PASS_HTML(f);
+ HD(f)->wrapcol = WRAP_COLS(f);
+ f->f1 = DFL; /* state */
+ f->f2 = 0; /* chars in wrap buffer */
+ f->n = 0L; /* chars on line so far */
+ f->linep = f->line = (char *)fs_get(HTML_BUF_LEN * sizeof(char));
+ HD(f)->line_bufsize = HTML_BUF_LEN; /* initial bufsize of line */
+ HD(f)->alt_entity = (!ps_global->display_charmap
+ || strucmp(ps_global->display_charmap, "iso-8859-1"));
+ HD(f)->cb.cbufp = HD(f)->cb.cbufend = HD(f)->cb.cbuf;
+ }
+}
+
+
+
+/*
+ * html_indent - do the requested indent level function with appropriate
+ * flushing and such.
+ *
+ * Returns: indent level prior to set/increment
+ */
+int
+html_indent(FILTER_S *f, int val, int func)
+{
+ int old = HD(f)->indent_level;
+
+ /* flush pending data at old indent level */
+ switch(func){
+ case HTML_ID_INC :
+ html_output_flush(f);
+ if((HD(f)->indent_level += val) < 0)
+ HD(f)->indent_level = 0;
+
+ break;
+
+ case HTML_ID_SET :
+ html_output_flush(f);
+ HD(f)->indent_level = val;
+ break;
+
+ default :
+ break;
+ }
+
+ return(old);
+}
+
+
+
+/*
+ * html_blanks - Insert n blank lines into output
+ */
+void
+html_blank(FILTER_S *f, int n)
+{
+ /* Cap off any flowing text, and then write blank lines */
+ if(f->f2 || f->n || CENTER_BIT(f) || HD(f)->centered || WRAPPED_LEN(f))
+ html_output(f, HTML_NEWLINE);
+
+ if(HD(f)->wrapstate)
+ while(HD(f)->blanks < n) /* blanks inc'd by HTML_NEWLINE */
+ html_output(f, HTML_NEWLINE);
+}
+
+
+
+/*
+ * html_newline -- insert a newline mindful of embedded tags
+ */
+void
+html_newline(FILTER_S *f)
+{
+ html_write_newline(f); /* commit an actual newline */
+
+ if(f->n){ /* and keep track of blank lines */
+ HD(f)->blanks = 0;
+ f->n = 0L;
+ }
+ else
+ HD(f)->blanks++;
+}
+
+
+/*
+ * output the given char, handling any requested wrapping.
+ * It's understood that all whitespace handed us is written. In other
+ * words, junk whitespace is weeded out before it's given to us here.
+ *
+ */
+void
+html_output(FILTER_S *f, int ch)
+{
+ UCS uc;
+ int width;
+ void (*o_f)(FILTER_S *, int, int) = CENTER_BIT(f) ? html_output_centered : html_output_normal;
+
+ /*
+ * if ch is a control token, just pass it on, else, collect
+ * utf8-encoded characters to determine width,then feed into
+ * output routines
+ */
+ if(ch == TAG_EMBED || HD(f)->embedded.state || (ch > 0xff && IS_LITERAL(ch) == 0)){
+ (*o_f)(f, ch, 1);
+ }
+ else if(utf8_to_ucs4_oneatatime(ch & 0xff, &(HD(f)->cb), &uc, &width)){
+ unsigned char *cp;
+
+ for(cp = HD(f)->cb.cbuf; cp <= HD(f)->cb.cbufend; cp++){
+ (*o_f)(f, *cp, width);
+ width = 0; /* only count it once */
+ }
+
+ HD(f)->cb.cbufp = HD(f)->cb.cbufend = HD(f)->cb.cbuf;
+ }
+ else
+ HD(f)->cb.cbufend = HD(f)->cb.cbufp;
+ /* else do nothing until we have a full character */
+}
+
+
+void
+html_output_string(FILTER_S *f, char *s)
+{
+ for(; *s; s++)
+ html_output(f, *s);
+}
+
+
+void
+html_output_raw_tag(FILTER_S *f, char *tag)
+{
+ PARAMETER *p;
+ char *vp;
+ int i;
+
+ html_output(f, '<');
+ html_output_string(f, tag);
+ for(p = HD(f)->el_data->attribs;
+ p && p->attribute;
+ p = p->next){
+ /* SECURITY: no javascript */
+ /* PRIVACY: no img src without permission */
+ /* BUGS: no class collisions since <head> ignored */
+ if(html_event_attribute(p->attribute)
+ || !strucmp(p->attribute, "class")
+ || (!PASS_IMAGES(f) && !strucmp(tag, "img") && !strucmp(p->attribute, "src")))
+ continue;
+
+ /* PRIVACY: sniff out background images */
+ if(p->value && !PASS_IMAGES(f)){
+ if(!strucmp(p->attribute, "style")){
+ if((vp = srchstr(p->value, "background-image")) != NULL){
+ /* neuter in place */
+ vp[11] = vp[12] = vp[13] = vp[14] = vp[15] = 'X';
+ }
+ else{
+ for(vp = p->value; (vp = srchstr(vp, "background")) != NULL; vp++)
+ if(vp[10] == ' ' || vp[10] == ':')
+ for(i = 11; vp[i] && vp[i] != ';'; i++)
+ if((vp[i] == 'u' && vp[i+1] == 'r' && vp[i+2] == 'l' && vp[i+3] == '(')
+ || vp[i] == ':' || vp[i] == '/' || vp[i] == '.')
+ vp[0] = 'X';
+ }
+ }
+ else if(!strucmp(p->attribute, "background")){
+ char *ip;
+
+ for(ip = p->value; *ip && !(*ip == ':' || *ip == '/' || *ip == '.'); ip++)
+ ;
+
+ if(ip)
+ continue;
+ }
+ }
+
+ html_output(f, ' ');
+ html_output_string(f, p->attribute);
+ if(p->value){
+ html_output(f, '=');
+ html_output(f, '\"');
+ html_output_string(f, p->value);
+ html_output(f, '\"');
+ }
+ }
+
+ /* append warning to form submission */
+ if(!strucmp(tag, "form")){
+ html_output_string(f, " onsubmit=\"return window.confirm('This form is submitting information to an outside server.\\nAre you sure?');\"");
+ }
+
+ if(ED(f)->end_tag){
+ html_output(f, ' ');
+ html_output(f, '/');
+ }
+
+ html_output(f, '>');
+}
+
+
+int
+html_event_attribute(char *attr)
+{
+ int i;
+ static char *events[] = {
+ "onabort", "onblur", "onchange", "onclick", "ondblclick", "ondragdrop",
+ "onerror", "onfocus", "onkeydown", "onkeypress", "onkeyup", "onload",
+ "onmousedown", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmove",
+ "onreset", "onresize", "onselec", "onsubmit", "onunload"
+ };
+
+ if((attr[0] == 'o' || attr[0] == 'O') && (attr[1] == 'n' || attr[1] == 'N'))
+ for(i = 0; i < sizeof(events)/sizeof(events[0]); i++)
+ if(!strucmp(attr, events[i]))
+ return(TRUE);
+
+ return(FALSE);
+}
+
+
+void
+html_output_normal(FILTER_S *f, int ch, int width)
+{
+ if(HD(f)->centered){
+ html_centered_flush(f);
+ fs_give((void **) &HD(f)->centered->line.buf);
+ fs_give((void **) &HD(f)->centered->word.buf);
+ fs_give((void **) &HD(f)->centered);
+ }
+
+ if(HD(f)->wrapstate){
+ if(ch == HTML_NEWLINE){ /* hard newline */
+ html_output_flush(f);
+ html_newline(f);
+ }
+ else
+ HD(f)->blanks = 0; /* reset blank line counter */
+
+ if(ch == TAG_EMBED){ /* takes up no space */
+ HD(f)->embedded.state = -5;
+ HTML_LINEP_PUTC(f, TAG_EMBED);
+ }
+ else if(HD(f)->embedded.state){ /* ditto */
+ if(HD(f)->embedded.state == -5){
+ /* looking for specially handled tags following TAG_EMBED */
+ if(ch == TAG_HANDLE)
+ HD(f)->embedded.state = -1; /* next ch is length */
+ else if(ch == TAG_FGCOLOR || ch == TAG_BGCOLOR){
+ if(!HD(f)->color)
+ HD(f)->color = new_color_pair(NULL, NULL);
+
+ if(ch == TAG_FGCOLOR)
+ HD(f)->embedded.color = HD(f)->color->fg;
+ else
+ HD(f)->embedded.color = HD(f)->color->bg;
+
+ HD(f)->embedded.state = RGBLEN;
+ }
+ else
+ HD(f)->embedded.state = 0; /* non-special */
+ }
+ else if(HD(f)->embedded.state > 0){
+ /* collecting up an RGBLEN color or length, ignore tags */
+ (HD(f)->embedded.state)--;
+ if(HD(f)->embedded.color)
+ *HD(f)->embedded.color++ = ch;
+
+ if(HD(f)->embedded.state == 0 && HD(f)->embedded.color){
+ *HD(f)->embedded.color = '\0';
+ HD(f)->embedded.color = NULL;
+ }
+ }
+ else if(HD(f)->embedded.state < 0){
+ HD(f)->embedded.state = ch; /* number of embedded chars */
+ }
+ else{
+ (HD(f)->embedded.state)--;
+ if(HD(f)->embedded.color)
+ *HD(f)->embedded.color++ = ch;
+
+ if(HD(f)->embedded.state == 0 && HD(f)->embedded.color){
+ *HD(f)->embedded.color = '\0';
+ HD(f)->embedded.color = NULL;
+ }
+ }
+
+ HTML_LINEP_PUTC(f, ch);
+ }
+ else if(HTML_ISSPACE(ch)){
+ html_output_flush(f);
+ }
+ else{
+ if(HD(f)->prefix)
+ html_a_prefix(f);
+
+ if((f->f2 += width) + 1 >= WRAP_COLS(f)){
+ HTML_LINEP_PUTC(f, ch & 0xff);
+ HTML_FLUSH(f);
+ html_newline(f);
+ if(HD(f)->in_anchor)
+ html_write_anchor(f, HD(f)->in_anchor);
+ }
+ else
+ HTML_LINEP_PUTC(f, ch & 0xff);
+ }
+ }
+ else{
+ if(HD(f)->prefix)
+ html_a_prefix(f);
+
+ html_output_flush(f);
+
+ switch(HD(f)->embedded.state){
+ case 0 :
+ switch(ch){
+ default :
+ /*
+ * It's difficult to both preserve whitespace and wrap at the
+ * same time so we'll do a dumb wrap at the edge of the screen.
+ * Since this shouldn't come up much in real life we'll hope
+ * it is good enough.
+ */
+ if(!PASS_HTML(f) && (f->n + width) > WRAP_COLS(f))
+ html_newline(f);
+
+ f->n += width; /* inc displayed char count */
+ HD(f)->blanks = 0; /* reset blank line counter */
+ html_putc(f, ch & 0xff);
+ break;
+
+ case TAG_EMBED : /* takes up no space */
+ html_putc(f, TAG_EMBED);
+ HD(f)->embedded.state = -2;
+ break;
+
+ case HTML_NEWLINE : /* newline handling */
+ if(!f->n)
+ break;
+
+ case '\n' :
+ html_newline(f);
+
+ case '\r' :
+ break;
+ }
+
+ break;
+
+ case -2 :
+ HD(f)->embedded.state = 0;
+ switch(ch){
+ case TAG_HANDLE :
+ HD(f)->embedded.state = -1; /* next ch is length */
+ break;
+
+ case TAG_BOLDON :
+ BOLD_BIT(f) = 1;
+ break;
+
+ case TAG_BOLDOFF :
+ BOLD_BIT(f) = 0;
+ break;
+
+ case TAG_ULINEON :
+ ULINE_BIT(f) = 1;
+ break;
+
+ case TAG_ULINEOFF :
+ ULINE_BIT(f) = 0;
+ break;
+
+ case TAG_FGCOLOR :
+ if(!HD(f)->color)
+ HD(f)->color = new_color_pair(NULL, NULL);
+
+ HD(f)->embedded.color = HD(f)->color->fg;
+ HD(f)->embedded.state = 11;
+ break;
+
+ case TAG_BGCOLOR :
+ if(!HD(f)->color)
+ HD(f)->color = new_color_pair(NULL, NULL);
+
+ HD(f)->embedded.color = HD(f)->color->bg;
+ HD(f)->embedded.state = 11;
+ break;
+
+ case TAG_HANDLEOFF :
+ ch = TAG_INVOFF;
+ HD(f)->in_anchor = 0;
+ break;
+
+ default :
+ break;
+ }
+
+ html_putc(f, ch);
+ break;
+
+ case -1 :
+ HD(f)->embedded.state = ch; /* number of embedded chars */
+ html_putc(f, ch);
+ break;
+
+ default :
+ HD(f)->embedded.state--;
+ if(HD(f)->embedded.color)
+ *HD(f)->embedded.color++ = ch;
+
+ if(HD(f)->embedded.state == 0 && HD(f)->embedded.color){
+ *HD(f)->embedded.color = '\0';
+ HD(f)->embedded.color = NULL;
+ }
+
+ html_putc(f, ch);
+ break;
+ }
+ }
+}
+
+
+/*
+ * flush any buffered chars waiting for wrapping.
+ */
+void
+html_output_flush(FILTER_S *f)
+{
+ if(f->f2){
+ if(f->n && ((int) f->n) + 1 + f->f2 > HD(f)->wrapcol)
+ html_newline(f); /* wrap? */
+
+ if(f->n){ /* text already on the line? */
+ html_putc(f, ' ');
+ f->n++; /* increment count */
+ }
+ else{
+ /* write at start of new line */
+ html_write_indent(f, HD(f)->indent_level);
+
+ if(HD(f)->in_anchor)
+ html_write_anchor(f, HD(f)->in_anchor);
+ }
+
+ f->n += f->f2;
+ HTML_FLUSH(f);
+ }
+}
+
+
+
+/*
+ * html_output_centered - managed writing centered text
+ */
+void
+html_output_centered(FILTER_S *f, int ch, int width)
+{
+ if(!HD(f)->centered){ /* new text? */
+ html_output_flush(f);
+ if(f->n) /* start on blank line */
+ html_newline(f);
+
+ HD(f)->centered = (CENTER_S *) fs_get(sizeof(CENTER_S));
+ memset(HD(f)->centered, 0, sizeof(CENTER_S));
+ /* and grab a buf to start collecting centered text */
+ HD(f)->centered->line.len = WRAP_COLS(f);
+ HD(f)->centered->line.buf = (char *) fs_get(HD(f)->centered->line.len
+ * sizeof(char));
+ HD(f)->centered->line.used = HD(f)->centered->line.width = 0;
+ HD(f)->centered->word.len = 32;
+ HD(f)->centered->word.buf = (char *) fs_get(HD(f)->centered->word.len
+ * sizeof(char));
+ HD(f)->centered->word.used = HD(f)->centered->word.width = 0;
+ }
+
+ if(ch == HTML_NEWLINE){ /* hard newline */
+ html_centered_flush(f);
+ }
+ else if(ch == TAG_EMBED){ /* takes up no space */
+ HD(f)->embedded.state = -5;
+ html_centered_putc(&HD(f)->centered->word, TAG_EMBED);
+ }
+ else if(HD(f)->embedded.state){
+ if(HD(f)->embedded.state == -5){
+ /* looking for specially handled tags following TAG_EMBED */
+ if(ch == TAG_HANDLE)
+ HD(f)->embedded.state = -1; /* next ch is length */
+ else if(ch == TAG_FGCOLOR || ch == TAG_BGCOLOR){
+ if(!HD(f)->color)
+ HD(f)->color = new_color_pair(NULL, NULL);
+
+ if(ch == TAG_FGCOLOR)
+ HD(f)->embedded.color = HD(f)->color->fg;
+ else
+ HD(f)->embedded.color = HD(f)->color->bg;
+
+ HD(f)->embedded.state = RGBLEN;
+ }
+ else
+ HD(f)->embedded.state = 0; /* non-special */
+ }
+ else if(HD(f)->embedded.state > 0){
+ /* collecting up an RGBLEN color or length, ignore tags */
+ (HD(f)->embedded.state)--;
+ if(HD(f)->embedded.color)
+ *HD(f)->embedded.color++ = ch;
+
+ if(HD(f)->embedded.state == 0 && HD(f)->embedded.color){
+ *HD(f)->embedded.color = '\0';
+ HD(f)->embedded.color = NULL;
+ }
+ }
+ else if(HD(f)->embedded.state < 0){
+ HD(f)->embedded.state = ch; /* number of embedded chars */
+ }
+ else{
+ (HD(f)->embedded.state)--;
+ if(HD(f)->embedded.color)
+ *HD(f)->embedded.color++ = ch;
+
+ if(HD(f)->embedded.state == 0 && HD(f)->embedded.color){
+ *HD(f)->embedded.color = '\0';
+ HD(f)->embedded.color = NULL;
+ }
+ }
+
+ html_centered_putc(&HD(f)->centered->word, ch);
+ }
+ else if(ASCII_ISSPACE((unsigned char) ch)){
+ if(!HD(f)->centered->space++){ /* end of a word? flush! */
+ int i;
+
+ if(WRAPPED_LEN(f) > HD(f)->wrapcol){
+ html_centered_flush_line(f);
+ /* fall thru to put current "word" on blank "line" */
+ }
+ else if(HD(f)->centered->line.width){
+ /* put space char between line and appended word */
+ html_centered_putc(&HD(f)->centered->line, ' ');
+ HD(f)->centered->line.width++;
+ }
+
+ for(i = 0; i < HD(f)->centered->word.used; i++)
+ html_centered_putc(&HD(f)->centered->line,
+ HD(f)->centered->word.buf[i]);
+
+ HD(f)->centered->line.width += HD(f)->centered->word.width;
+ HD(f)->centered->word.used = 0;
+ HD(f)->centered->word.width = 0;
+ }
+ }
+ else{
+ if(HD(f)->prefix)
+ html_a_prefix(f);
+
+ /* ch is start of next word */
+ HD(f)->centered->space = 0;
+ if(HD(f)->centered->word.width >= WRAP_COLS(f))
+ html_centered_flush(f);
+
+ html_centered_putc(&HD(f)->centered->word, ch);
+ HD(f)->centered->word.width++;
+ }
+}
+
+
+/*
+ * html_centered_putc -- add given char to given WRAPLINE_S
+ */
+void
+html_centered_putc(WRAPLINE_S *wp, int ch)
+{
+ if(wp->used + 1 >= wp->len){
+ wp->len += 64;
+ fs_resize((void **) &wp->buf, wp->len * sizeof(char));
+ }
+
+ wp->buf[wp->used++] = ch;
+}
+
+
+
+/*
+ * html_centered_flush - finish writing any pending centered output
+ */
+void
+html_centered_flush(FILTER_S *f)
+{
+ int i;
+
+ /*
+ * If word present (what about line?) we need to deal with
+ * appending it...
+ */
+ if(HD(f)->centered->word.width && WRAPPED_LEN(f) > HD(f)->wrapcol)
+ html_centered_flush_line(f);
+
+ if(WRAPPED_LEN(f)){
+ /* figure out how much to indent */
+ if((i = (WRAP_COLS(f) - WRAPPED_LEN(f))/2) > 0)
+ html_write_indent(f, i);
+
+ if(HD(f)->centered->anchor)
+ html_write_anchor(f, HD(f)->centered->anchor);
+
+ html_centered_handle(&HD(f)->centered->anchor,
+ HD(f)->centered->line.buf,
+ HD(f)->centered->line.used);
+ html_write(f, HD(f)->centered->line.buf, HD(f)->centered->line.used);
+
+ if(HD(f)->centered->word.used){
+ if(HD(f)->centered->line.width)
+ html_putc(f, ' ');
+
+ html_centered_handle(&HD(f)->centered->anchor,
+ HD(f)->centered->word.buf,
+ HD(f)->centered->word.used);
+ html_write(f, HD(f)->centered->word.buf,
+ HD(f)->centered->word.used);
+ }
+
+ HD(f)->centered->line.used = HD(f)->centered->word.used = 0;
+ HD(f)->centered->line.width = HD(f)->centered->word.width = 0;
+ }
+ else{
+ if(HD(f)->centered->word.used){
+ html_write(f, HD(f)->centered->word.buf,
+ HD(f)->centered->word.used);
+ HD(f)->centered->line.used = HD(f)->centered->word.used = 0;
+ HD(f)->centered->line.width = HD(f)->centered->word.width = 0;
+ }
+ HD(f)->blanks++; /* advance the blank line counter */
+ }
+
+ html_newline(f); /* finish the line */
+}
+
+
+/*
+ * html_centered_handle - scan the line for embedded handles
+ */
+void
+html_centered_handle(int *h, char *line, int len)
+{
+ int n;
+
+ while(len-- > 0)
+ if(*line++ == TAG_EMBED && len-- > 0)
+ switch(*line++){
+ case TAG_HANDLE :
+ if((n = *line++) >= --len){
+ *h = 0;
+ len -= n;
+ while(n--)
+ *h = (*h * 10) + (*line++ - '0');
+ }
+ break;
+
+ case TAG_HANDLEOFF :
+ case TAG_INVOFF :
+ *h = 0; /* assumption 23,342: inverse off ends tags */
+ break;
+
+ default :
+ break;
+ }
+}
+
+
+
+/*
+ * html_centered_flush_line - flush the centered "line" only
+ */
+void
+html_centered_flush_line(FILTER_S *f)
+{
+ if(HD(f)->centered->line.used){
+ int i, j;
+
+ /* hide "word" from flush */
+ i = HD(f)->centered->word.used;
+ j = HD(f)->centered->word.width;
+ HD(f)->centered->word.used = 0;
+ HD(f)->centered->word.width = 0;
+ html_centered_flush(f);
+
+ HD(f)->centered->word.used = i;
+ HD(f)->centered->word.width = j;
+ }
+}
+
+
+/*
+ * html_write_indent - write indention mindful of display attributes
+ */
+void
+html_write_indent(FILTER_S *f, int indent)
+{
+ if(! STRIP(f)){
+ if(BOLD_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_BOLDOFF);
+ }
+
+ if(ULINE_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_ULINEOFF);
+ }
+ }
+
+ f->n = indent;
+ while(indent-- > 0)
+ html_putc(f, ' '); /* indent as needed */
+
+ /*
+ * Resume any previous embedded state
+ */
+ if(! STRIP(f)){
+ if(BOLD_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_BOLDON);
+ }
+
+ if(ULINE_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_ULINEON);
+ }
+ }
+}
+
+
+/*
+ *
+ */
+void
+html_write_anchor(FILTER_S *f, int anchor)
+{
+ char buf[256];
+ int i;
+
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_HANDLE);
+ snprintf(buf, sizeof(buf), "%d", anchor);
+ html_putc(f, (int) strlen(buf));
+
+ for(i = 0; buf[i]; i++)
+ html_putc(f, buf[i]);
+}
+
+
+/*
+ * html_write_newline - write a newline mindful of display attributes
+ */
+void
+html_write_newline(FILTER_S *f)
+{
+ int i;
+
+ if(! STRIP(f)){ /* First tie, off any embedded state */
+ if(HD(f)->in_anchor){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_INVOFF);
+ }
+
+ if(BOLD_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_BOLDOFF);
+ }
+
+ if(ULINE_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_ULINEOFF);
+ }
+
+ if(HD(f)->color && (HD(f)->color->fg[0] || HD(f)->color->bg[0])){
+ char *p;
+ int i;
+
+ p = color_embed(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR);
+ for(i = 0; i < 2 * (RGBLEN + 2); i++)
+ html_putc(f, p[i]);
+ }
+ }
+
+ html_write(f, "\015\012", 2);
+ for(i = HTML_INDENT(f); i > 0; i--)
+ html_putc(f, ' ');
+
+ if(! STRIP(f)){ /* First tie, off any embedded state */
+ if(BOLD_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_BOLDON);
+ }
+
+ if(ULINE_BIT(f)){
+ html_putc(f, TAG_EMBED);
+ html_putc(f, TAG_ULINEON);
+ }
+
+ if(HD(f)->color && (HD(f)->color->fg[0] || HD(f)->color->bg[0])){
+ char *p, *tfg, *tbg;
+ int i;
+ COLOR_PAIR *tmp;
+
+ tfg = HD(f)->color->fg;
+ tbg = HD(f)->color->bg;
+ tmp = new_color_pair(tfg[0] ? tfg
+ : color_to_asciirgb(ps_global->VAR_NORM_FORE_COLOR),
+ tbg[0] ? tbg
+ : color_to_asciirgb(ps_global->VAR_NORM_BACK_COLOR));
+ if(pico_is_good_colorpair(tmp)){
+ p = color_embed(tfg[0] ? tfg
+ : ps_global->VAR_NORM_FORE_COLOR,
+ tbg[0] ? tbg
+ : ps_global->VAR_NORM_BACK_COLOR);
+ for(i = 0; i < 2 * (RGBLEN + 2); i++)
+ html_putc(f, p[i]);
+ }
+
+ if(tmp)
+ free_color_pair(&tmp);
+ }
+ }
+}
+
+
+/*
+ * html_write - write given n-length string to next filter
+ */
+void
+html_write(FILTER_S *f, char *s, int n)
+{
+ GF_INIT(f, f->next);
+
+ while(n-- > 0){
+ /* keep track of attribute state? Not if last char! */
+ if(!STRIP(f) && *s == TAG_EMBED && n-- > 0){
+ GF_PUTC(f->next, TAG_EMBED);
+ switch(*++s){
+ case TAG_BOLDON :
+ BOLD_BIT(f) = 1;
+ break;
+ case TAG_BOLDOFF :
+ BOLD_BIT(f) = 0;
+ break;
+ case TAG_ULINEON :
+ ULINE_BIT(f) = 1;
+ break;
+ case TAG_ULINEOFF :
+ ULINE_BIT(f) = 0;
+ break;
+ case TAG_HANDLEOFF :
+ HD(f)->in_anchor = 0;
+ GF_PUTC(f->next, TAG_INVOFF);
+ s++;
+ continue;
+ case TAG_HANDLE :
+ if(n-- > 0){
+ int i = *++s;
+
+ GF_PUTC(f->next, TAG_HANDLE);
+ if(i <= n){
+ int anum = 0;
+ HANDLE_S *h;
+
+ n -= i;
+ GF_PUTC(f->next, i);
+ while(1){
+ anum = (anum * 10) + (*++s - '0');
+ if(--i)
+ GF_PUTC(f->next, *s);
+ else
+ break;
+ }
+
+ if(DO_HANDLES(f)
+ && (h = get_handle(*HANDLESP(f), anum)) != NULL
+ && (h->type == URL || h->type == Attach)){
+ HD(f)->in_anchor = anum;
+ }
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ GF_PUTC(f->next, (*s++) & 0xff);
+ }
+
+ GF_IP_END(f->next); /* clean up next's input pointers */
+}
+
+
+/*
+ * html_putc -- actual work of writing to next filter.
+ * NOTE: Small opt not using full GF_END since our input
+ * pointers don't need adjusting.
+ */
+void
+html_putc(FILTER_S *f, int ch)
+{
+ GF_INIT(f, f->next);
+ GF_PUTC(f->next, ch & 0xff);
+ GF_IP_END(f->next); /* clean up next's input pointers */
+}
+
+
+
+/*
+ * Only current option is to turn on embedded data stripping for text
+ * bound to a printer or composer.
+ */
+void *
+gf_html2plain_opt(char *base,
+ int columns,
+ int *margin,
+ HANDLE_S **handlesp,
+ htmlrisk_t risk_f,
+ int flags)
+{
+ HTML_OPT_S *op;
+ int margin_l, margin_r;
+
+ op = (HTML_OPT_S *) fs_get(sizeof(HTML_OPT_S));
+
+ op->base = cpystr(base);
+ margin_l = (margin) ? margin[0] : 0;
+ margin_r = (margin) ? margin[1] : 0;
+ op->indent = margin_l;
+ op->columns = columns - (margin_l + margin_r);
+ op->strip = ((flags & GFHP_STRIPPED) == GFHP_STRIPPED);
+ op->handlesp = handlesp;
+ op->handles_loc = ((flags & GFHP_LOCAL_HANDLES) == GFHP_LOCAL_HANDLES);
+ op->showserver = ((flags & GFHP_SHOW_SERVER) == GFHP_SHOW_SERVER);
+ op->warnrisk_f = risk_f;
+ op->no_relative_links = ((flags & GFHP_NO_RELATIVE) == GFHP_NO_RELATIVE);
+ op->related_content = ((flags & GFHP_RELATED_CONTENT) == GFHP_RELATED_CONTENT);
+ op->html = ((flags & GFHP_HTML) == GFHP_HTML);
+ op->html_imgs = ((flags & GFHP_HTML_IMAGES) == GFHP_HTML_IMAGES);
+ op->element_table = html_element_table;
+ return((void *) op);
+}
+
+
+void *
+gf_html2plain_rss_opt(RSS_FEED_S **feedp, int flags)
+{
+ HTML_OPT_S *op;
+ int margin_l, margin_r;
+
+ op = (HTML_OPT_S *) fs_get(sizeof(HTML_OPT_S));
+ memset(op, 0, sizeof(HTML_OPT_S));
+
+ op->base = cpystr("");
+ op->element_table = rss_element_table;
+ *(op->feedp = feedp) = NULL;
+ return((void *) op);
+}
+
+void
+gf_html2plain_rss_free(RSS_FEED_S **feedp)
+{
+ if(feedp && *feedp){
+ if((*feedp)->title)
+ fs_give((void **) &(*feedp)->title);
+
+ if((*feedp)->link)
+ fs_give((void **) &(*feedp)->link);
+
+ if((*feedp)->description)
+ fs_give((void **) &(*feedp)->description);
+
+ if((*feedp)->source)
+ fs_give((void **) &(*feedp)->source);
+
+ if((*feedp)->image)
+ fs_give((void **) &(*feedp)->image);
+
+ gf_html2plain_rss_free_items(&((*feedp)->items));
+ fs_give((void **) feedp);
+ }
+}
+
+void
+gf_html2plain_rss_free_items(RSS_ITEM_S **itemp)
+{
+ if(itemp && *itemp){
+ if((*itemp)->title)
+ fs_give((void **) &(*itemp)->title);
+
+ if((*itemp)->link)
+ fs_give((void **) &(*itemp)->link);
+
+ if((*itemp)->description)
+ fs_give((void **) &(*itemp)->description);
+
+ if((*itemp)->source)
+ fs_give((void **) &(*itemp)->source);
+
+ gf_html2plain_rss_free_items(&(*itemp)->next);
+ fs_give((void **) itemp);
+ }
+}
+
+
+/* END OF HTML-TO-PLAIN text filter */
+
+/*
+ * ESCAPE CODE FILTER - remove unknown and possibly dangerous escape codes
+ * from the text stream.
+ */
+
+#define MAX_ESC_LEN 5
+
+/*
+ * the simple filter, removes unknown escape codes from the stream
+ */
+void
+gf_escape_filter(FILTER_S *f, int flg)
+{
+ register char *p;
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+
+ if(state){
+ if(c == '\033' || f->n == MAX_ESC_LEN){
+ f->line[f->n] = '\0';
+ f->n = 0L;
+ if(!match_escapes(f->line)){
+ GF_PUTC(f->next, '^');
+ GF_PUTC(f->next, '[');
+ }
+ else
+ GF_PUTC(f->next, '\033');
+
+ p = f->line;
+ while(*p)
+ GF_PUTC(f->next, *p++);
+
+ if(c == '\033')
+ continue;
+ else
+ state = 0; /* fall thru */
+ }
+ else{
+ f->line[f->n++] = c; /* collect */
+ continue;
+ }
+ }
+
+ if(c == '\033')
+ state = 1;
+ else
+ GF_PUTC(f->next, c);
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ if(f->f1){
+ if(!match_escapes(f->line)){
+ GF_PUTC(f->next, '^');
+ GF_PUTC(f->next, '[');
+ }
+ else
+ GF_PUTC(f->next, '\033');
+ }
+
+ for(p = f->line; f->n; f->n--, p++)
+ GF_PUTC(f->next, *p);
+
+ fs_give((void **)&(f->line)); /* free temp line buffer */
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset escape\n"));
+ f->f1 = 0;
+ f->n = 0L;
+ f->linep = f->line = (char *)fs_get((MAX_ESC_LEN + 1) * sizeof(char));
+ }
+}
+
+
+
+/*
+ * CONTROL CHARACTER FILTER - transmogrify control characters into their
+ * corresponding string representations (you know, ^blah and such)...
+ */
+
+/*
+ * the simple filter transforms unknown control characters in the stream
+ * into harmless strings.
+ */
+void
+gf_control_filter(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int filt_only_c0;
+
+ filt_only_c0 = f->opt ? (*(int *) f->opt) : 0;
+
+ while(GF_GETC(f, c)){
+
+ if(((c < 0x20 || c == 0x7f)
+ || (c >= 0x80 && c < 0xA0 && !filt_only_c0))
+ && !(ASCII_ISSPACE((unsigned char) c)
+ || c == '\016' || c == '\017' || c == '\033')){
+ GF_PUTC(f->next, c >= 0x80 ? '~' : '^');
+ GF_PUTC(f->next, (c == 0x7f) ? '?' : (c & 0x1f) + '@');
+ }
+ else
+ GF_PUTC(f->next, c);
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+}
+
+
+/*
+ * function called from the outside to set
+ * control filter's option, which says to filter C0 control characters
+ * but not C1 control chars. We don't call it at all if we don't want
+ * to filter C0 chars either.
+ */
+void *
+gf_control_filter_opt(int *filt_only_c0)
+{
+ return((void *) filt_only_c0);
+}
+
+
+/*
+ * TAG FILTER - quote all TAG_EMBED characters by doubling them.
+ * This prevents the possibility of embedding other tags.
+ * We assume that this filter should only be used for something
+ * that is eventually writing to a display, which has the special
+ * knowledge of quoted TAG_EMBEDs.
+ */
+void
+gf_tag_filter(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+
+ if((c & 0xff) == (TAG_EMBED & 0xff)){
+ GF_PUTC(f->next, TAG_EMBED);
+ GF_PUTC(f->next, c);
+ }
+ else
+ GF_PUTC(f->next, c);
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+}
+
+
+/*
+ * LINEWRAP FILTER - insert CRLF's at end of nearest whitespace before
+ * specified line width
+ */
+
+
+typedef struct wrap_col_s {
+ unsigned bold:1;
+ unsigned uline:1;
+ unsigned inverse:1;
+ unsigned tags:1;
+ unsigned do_indent:1;
+ unsigned on_comma:1;
+ unsigned flowed:1;
+ unsigned delsp:1;
+ unsigned quoted:1;
+ unsigned allwsp:1;
+ unsigned hard_nl:1;
+ unsigned leave_flowed:1;
+ unsigned use_color:1;
+ unsigned hdr_color:1;
+ unsigned for_compose:1;
+ unsigned handle_soft_hyphen:1;
+ unsigned saw_soft_hyphen:1;
+ unsigned trailing_space:1;
+ unsigned char utf8buf[7];
+ unsigned char *utf8bufp;
+ COLOR_PAIR *color;
+ STORE_S *spaces;
+ short embedded,
+ space_len;
+ char *lineendp;
+ int anchor,
+ prefbrk,
+ prefbrkn,
+ quote_depth,
+ quote_count,
+ sig,
+ state,
+ wrap_col,
+ wrap_max,
+ margin_l,
+ margin_r,
+ indent;
+ char special[256];
+} WRAP_S;
+
+#define WRAP_MARG_L(F) (((WRAP_S *)(F)->opt)->margin_l)
+#define WRAP_MARG_R(F) (((WRAP_S *)(F)->opt)->margin_r)
+#define WRAP_COL(F) (((WRAP_S *)(F)->opt)->wrap_col - WRAP_MARG_R(F) - ((((WRAP_S *)(F)->opt)->leave_flowed) ? 1 : 0))
+#define WRAP_MAX_COL(F) (((WRAP_S *)(F)->opt)->wrap_max - WRAP_MARG_R(F) - ((((WRAP_S *)(F)->opt)->leave_flowed) ? 1 : 0))
+#define WRAP_INDENT(F) (((WRAP_S *)(F)->opt)->indent)
+#define WRAP_DO_IND(F) (((WRAP_S *)(F)->opt)->do_indent)
+#define WRAP_COMMA(F) (((WRAP_S *)(F)->opt)->on_comma)
+#define WRAP_FLOW(F) (((WRAP_S *)(F)->opt)->flowed)
+#define WRAP_DELSP(F) (((WRAP_S *)(F)->opt)->delsp)
+#define WRAP_FL_QD(F) (((WRAP_S *)(F)->opt)->quote_depth)
+#define WRAP_FL_QC(F) (((WRAP_S *)(F)->opt)->quote_count)
+#define WRAP_FL_SIG(F) (((WRAP_S *)(F)->opt)->sig)
+#define WRAP_HARD(F) (((WRAP_S *)(F)->opt)->hard_nl)
+#define WRAP_LV_FLD(F) (((WRAP_S *)(F)->opt)->leave_flowed)
+#define WRAP_USE_CLR(F) (((WRAP_S *)(F)->opt)->use_color)
+#define WRAP_HDR_CLR(F) (((WRAP_S *)(F)->opt)->hdr_color)
+#define WRAP_FOR_CMPS(F) (((WRAP_S *)(F)->opt)->for_compose)
+#define WRAP_HANDLE_SOFT_HYPHEN(F) (((WRAP_S *)(F)->opt)->handle_soft_hyphen)
+#define WRAP_SAW_SOFT_HYPHEN(F) (((WRAP_S *)(F)->opt)->saw_soft_hyphen)
+#define WRAP_UTF8BUF(F, C) (((WRAP_S *)(F)->opt)->utf8buf[C])
+#define WRAP_UTF8BUFP(F) (((WRAP_S *)(F)->opt)->utf8bufp)
+#define WRAP_STATE(F) (((WRAP_S *)(F)->opt)->state)
+#define WRAP_QUOTED(F) (((WRAP_S *)(F)->opt)->quoted)
+#define WRAP_TAGS(F) (((WRAP_S *)(F)->opt)->tags)
+#define WRAP_BOLD(F) (((WRAP_S *)(F)->opt)->bold)
+#define WRAP_ULINE(F) (((WRAP_S *)(F)->opt)->uline)
+#define WRAP_INVERSE(F) (((WRAP_S *)(F)->opt)->inverse)
+#define WRAP_LASTC(F) (((WRAP_S *)(F)->opt)->lineendp)
+#define WRAP_EMBED(F) (((WRAP_S *)(F)->opt)->embedded)
+#define WRAP_ANCHOR(F) (((WRAP_S *)(F)->opt)->anchor)
+#define WRAP_PB_OFF(F) (((WRAP_S *)(F)->opt)->prefbrk)
+#define WRAP_PB_LEN(F) (((WRAP_S *)(F)->opt)->prefbrkn)
+#define WRAP_ALLWSP(F) (((WRAP_S *)(F)->opt)->allwsp)
+#define WRAP_SPC_LEN(F) (((WRAP_S *)(F)->opt)->space_len)
+#define WRAP_TRL_SPC(F) (((WRAP_S *)(F)->opt)->trailing_space)
+#define WRAP_SPEC(F, C) ((WRAP_S *) (F)->opt)->special[C]
+#define WRAP_COLOR(F) (((WRAP_S *)(F)->opt)->color)
+#define WRAP_COLOR_SET(F) ((WRAP_COLOR(F)) && (WRAP_COLOR(F)->fg[0]))
+#define WRAP_SPACES(F) (((WRAP_S *)(F)->opt)->spaces)
+#define WRAP_PUTC(F,C,W) { \
+ if((F)->linep == WRAP_LASTC(F)){ \
+ size_t offset = (F)->linep - (F)->line; \
+ fs_resize((void **) &(F)->line, \
+ (2 * offset) * sizeof(char)); \
+ (F)->linep = &(F)->line[offset]; \
+ WRAP_LASTC(F) = &(F)->line[2*offset-1]; \
+ } \
+ *(F)->linep++ = (C); \
+ (F)->f2 += (W); \
+ }
+
+#define WRAP_EMBED_PUTC(F,C) { \
+ if((F)->f2){ \
+ WRAP_PUTC((F), C, 0); \
+ } \
+ else \
+ so_writec(C, WRAP_SPACES(F)); \
+}
+
+#define WRAP_COLOR_UNSET(F) { \
+ if(WRAP_COLOR_SET(F)){ \
+ WRAP_COLOR(F)->fg[0] = '\0'; \
+ } \
+ }
+
+/*
+ * wrap_flush_embed flags
+ */
+#define WFE_NONE 0 /* Nothing special */
+#define WFE_CNT_HANDLE 1 /* account for/don't write handles */
+
+
+int wrap_flush(FILTER_S *, unsigned char **, unsigned char **, unsigned char **, unsigned char **);
+int wrap_flush_embed(FILTER_S *, unsigned char **, unsigned char **,
+ unsigned char **, unsigned char **);
+int wrap_flush_s(FILTER_S *,char *, int, int, unsigned char **, unsigned char **,
+ unsigned char **, unsigned char **, int);
+int wrap_eol(FILTER_S *, int, unsigned char **, unsigned char **,
+ unsigned char **, unsigned char **);
+int wrap_bol(FILTER_S *, int, int, unsigned char **,
+ unsigned char **, unsigned char **, unsigned char **);
+int wrap_quote_insert(FILTER_S *, unsigned char **, unsigned char **,
+ unsigned char **, unsigned char **);
+
+/*
+ * the no longer simple filter, breaks lines at end of white space nearest
+ * to global "gf_wrap_width" in length
+ * It also supports margins, indents (inverse indenting, really) and
+ * flowed text (ala RFC 3676)
+ *
+ */
+void
+gf_wrap(FILTER_S *f, int flg)
+{
+ register long i;
+ GF_INIT(f, f->next);
+
+ /*
+ * f->f1 state
+ * f->line buffer where next "word" being considered is stored
+ * f->f2 width in screen cells of f->line stuff
+ * f->n width in screen cells of the part of this line committed to next
+ * filter so far
+ */
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+ int width, full_character;
+
+ while(GF_GETC(f, c)){
+
+ switch(state){
+ case CCR : /* CRLF or CR in text ? */
+ state = BOL; /* either way, handle start */
+
+ if(WRAP_FLOW(f)){
+ /* wrapped line? */
+ if(f->f2 == 0 && WRAP_SPC_LEN(f) && WRAP_TRL_SPC(f)){
+ /*
+ * whack trailing space char, but be aware
+ * of embeds in space buffer. grok them just
+ * in case they contain a 0x20 value
+ */
+ if(WRAP_DELSP(f)){
+ char *sb, *sbp, *scp = NULL;
+ int x;
+
+ for(sb = sbp = (char *)so_text(WRAP_SPACES(f)); *sbp; sbp++){
+ switch(*sbp){
+ case ' ' :
+ scp = sbp;
+ break;
+
+ case TAG_EMBED :
+ sbp++;
+ switch (*sbp++){
+ case TAG_HANDLE :
+ x = (int) *sbp++;
+ if(strlen(sbp) >= x)
+ sbp += (x - 1);
+
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ if(strlen(sbp) >= RGBLEN)
+ sbp += (RGBLEN - 1);
+
+ break;
+
+ default :
+ break;
+ }
+
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ /* replace space buf without trailing space char */
+ if(scp){
+ STORE_S *ns = so_get(CharStar, NULL, EDIT_ACCESS);
+
+ *scp++ = '\0';
+ WRAP_SPC_LEN(f)--;
+ WRAP_TRL_SPC(f) = 0;
+
+ so_puts(ns, sb);
+ so_puts(ns, scp);
+
+ so_give(&WRAP_SPACES(f));
+ WRAP_SPACES(f) = ns;
+ }
+ }
+ }
+ else{ /* fixed line */
+ WRAP_HARD(f) = 1;
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+
+ /*
+ * When we get to a real end of line, we don't need to
+ * remember what the special color was anymore because
+ * we aren't going to be changing back to it. We unset it
+ * so that we don't keep resetting the color to normal.
+ */
+ WRAP_COLOR_UNSET(f);
+ }
+
+ if(c == '\012'){ /* get c following LF */
+ break;
+ }
+ /* else c is first char of new line, fall thru */
+ }
+ else{
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ WRAP_COLOR_UNSET(f); /* see note above */
+ if(c == '\012'){
+ break;
+ }
+ /* else fall thru to deal with beginning of line */
+ }
+
+ case BOL :
+ if(WRAP_FLOW(f)){
+ if(c == '>'){
+ WRAP_FL_QC(f) = 1; /* init it */
+ state = FL_QLEV; /* go collect it */
+ }
+ else {
+ /* if EMBEDed, process it and return here */
+ if(c == (unsigned char) TAG_EMBED){
+ WRAP_EMBED_PUTC(f, TAG_EMBED);
+ WRAP_STATE(f) = state;
+ state = TAG;
+ continue;
+ }
+
+ /* quote level change implies new paragraph */
+ if(WRAP_FL_QD(f)){
+ WRAP_FL_QD(f) = 0;
+ if(WRAP_HARD(f) == 0){
+ WRAP_HARD(f) = 1;
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ WRAP_COLOR_UNSET(f); /* see note above */
+ }
+ }
+
+ if(WRAP_HARD(f)){
+ wrap_bol(f, 0, 1, &ip, &eib, &op,
+ &eob); /* write quoting prefix */
+ WRAP_HARD(f) = 0;
+ }
+
+ switch (c) {
+ case '\015' : /* a blank line? */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ state = CCR; /* go collect it */
+ break;
+
+ case ' ' : /* space stuffed */
+ state = FL_STF; /* just eat it */
+ break;
+
+ case '-' : /* possible sig-dash */
+ WRAP_FL_SIG(f) = 1; /* init state */
+ state = FL_SIG; /* go collect it */
+ break;
+
+ default :
+ state = DFL; /* go back to normal */
+ goto case_dfl; /* handle c like DFL case */
+ }
+ }
+ }
+ else{
+ state = DFL;
+ if(WRAP_COMMA(f) && c == TAB){
+ wrap_bol(f, 1, 0, &ip, &eib, &op,
+ &eob); /* convert to normal indent */
+ break;
+ }
+
+ wrap_bol(f,0,0, &ip, &eib, &op, &eob);
+ goto case_dfl; /* handle c like DFL case */
+ }
+
+ break;
+
+ case FL_QLEV :
+ if(c == '>'){ /* another level */
+ WRAP_FL_QC(f)++;
+ }
+ else {
+ /* if EMBEDed, process it and return here */
+ if(c == (unsigned char) TAG_EMBED){
+ WRAP_EMBED_PUTC(f, TAG_EMBED);
+ WRAP_STATE(f) = state;
+ state = TAG;
+ continue;
+ }
+
+ /* quote level change signals new paragraph */
+ if(WRAP_FL_QC(f) != WRAP_FL_QD(f)){
+ WRAP_FL_QD(f) = WRAP_FL_QC(f);
+ if(WRAP_HARD(f) == 0){ /* add hard newline */
+ WRAP_HARD(f) = 1; /* hard newline */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ WRAP_COLOR_UNSET(f); /* see note above */
+ }
+ }
+
+ if(WRAP_HARD(f)){
+ wrap_bol(f,0,1, &ip, &eib, &op, &eob);
+ WRAP_HARD(f) = 0;
+ }
+
+ switch (c) {
+ case '\015' : /* a blank line? */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ state = CCR; /* go collect it */
+ break;
+
+ case ' ' : /* space-stuffed! */
+ state = FL_STF; /* just eat it */
+ break;
+
+ case '-' : /* sig dash? */
+ WRAP_FL_SIG(f) = 1;
+ state = FL_SIG;
+ break;
+
+ default : /* something else */
+ state = DFL;
+ goto case_dfl; /* handle c like DFL */
+ }
+ }
+
+ break;
+
+ case FL_STF : /* space stuffed */
+ switch (c) {
+ case '\015' : /* a blank line? */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ state = CCR; /* go collect it */
+ break;
+
+ case (unsigned char) TAG_EMBED : /* process TAG data */
+ WRAP_EMBED_PUTC(f, TAG_EMBED);
+ WRAP_STATE(f) = state; /* and return */
+ state = TAG;
+ continue;
+
+ case '-' : /* sig dash? */
+ WRAP_FL_SIG(f) = 1;
+ WRAP_ALLWSP(f) = 0;
+ state = FL_SIG;
+ break;
+
+ default : /* something else */
+ state = DFL;
+ goto case_dfl; /* handle c like DFL */
+ }
+
+ break;
+
+ case FL_SIG : /* sig-dash collector */
+ switch (WRAP_FL_SIG(f)){ /* possible sig-dash? */
+ case 1 :
+ if(c != '-'){ /* not a sigdash */
+ if((f->n + WRAP_SPC_LEN(f) + 1) > WRAP_COL(f)){
+ wrap_flush_embed(f, &ip, &eib, &op,
+ &eob); /* note any embedded*/
+ wrap_eol(f, 1, &ip, &eib,
+ &op, &eob); /* plunk down newline */
+ wrap_bol(f, 1, 1, &ip, &eib,
+ &op, &eob); /* write any prefix */
+ }
+
+ WRAP_PUTC(f,'-', 1); /* write what we got */
+
+ WRAP_FL_SIG(f) = 0;
+ state = DFL;
+ goto case_dfl;
+ }
+
+ /* don't put anything yet until we know to wrap or not */
+ WRAP_FL_SIG(f) = 2;
+ break;
+
+ case 2 :
+ if(c != ' '){ /* not a sigdash */
+ WRAP_PUTC(f, '-', 1);
+ if((f->n + WRAP_SPC_LEN(f) + 2) > WRAP_COL(f)){
+ wrap_flush_embed(f, &ip, &eib, &op,
+ &eob); /* note any embedded*/
+ wrap_eol(f, 1, &ip, &eib,
+ &op, &eob); /* plunk down newline */
+ wrap_bol(f, 1, 1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+ }
+
+ WRAP_PUTC(f,'-', 1); /* write what we got */
+
+ WRAP_FL_SIG(f) = 0;
+ state = DFL;
+ goto case_dfl;
+ }
+
+ /* don't put anything yet until we know to wrap or not */
+ WRAP_FL_SIG(f) = 3;
+ break;
+
+ case 3 :
+ if(c == '\015'){ /* success! */
+ /* known sigdash, newline if soft nl */
+ if(WRAP_SPC_LEN(f)){
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ wrap_bol(f, 0, 1, &ip, &eib, &op, &eob);
+ }
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,' ',1);
+
+ state = CCR;
+ break;
+ }
+ else{
+ WRAP_FL_SIG(f) = 4; /* possible success */
+ }
+
+ case 4 :
+ switch(c){
+ case (unsigned char) TAG_EMBED :
+ /*
+ * At this point we're almost 100% sure that we've got
+ * a sigdash. Putc it (adding newline if previous
+ * was a soft nl) so we get it the right color
+ * before we store this new embedded stuff
+ */
+ if(WRAP_SPC_LEN(f)){
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ wrap_bol(f, 0, 1, &ip, &eib, &op, &eob);
+ }
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,' ',1);
+
+ WRAP_FL_SIG(f) = 5;
+ break;
+
+ case '\015' : /* success! */
+ /*
+ * We shouldn't get here, but in case we do, we have
+ * not yet put the sigdash
+ */
+ if(WRAP_SPC_LEN(f)){
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 0, &ip, &eib, &op, &eob);
+ wrap_bol(f, 0, 1, &ip, &eib, &op, &eob);
+ }
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,'-',1);
+ WRAP_PUTC(f,' ',1);
+
+ state = CCR;
+ break;
+
+ default : /* that's no sigdash! */
+ /* write what we got but didn't put yet */
+ WRAP_PUTC(f,'-', 1);
+ WRAP_PUTC(f,'-', 1);
+ WRAP_PUTC(f,' ', 1);
+
+ WRAP_FL_SIG(f) = 0;
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ WRAP_SPC_LEN(f) = 1;
+ state = DFL; /* set normal state */
+ goto case_dfl; /* and go do "c" */
+ }
+
+ break;
+
+ case 5 :
+ WRAP_STATE(f) = FL_SIG; /* come back here */
+ WRAP_FL_SIG(f) = 6; /* and seek EOL */
+ WRAP_EMBED_PUTC(f, TAG_EMBED);
+ state = TAG; /* process embed */
+ goto case_tag;
+
+ case 6 :
+ /*
+ * at this point we've already putc the sigdash in case 4
+ */
+ switch(c){
+ case (unsigned char) TAG_EMBED :
+ WRAP_FL_SIG(f) = 5;
+ break;
+
+ case '\015' : /* success! */
+ state = CCR;
+ break;
+
+ default : /* that's no sigdash! */
+ /*
+ * probably never reached (fake sigdash with embedded
+ * stuff) but if this did get reached, then we
+ * might have accidentally disobeyed a soft nl
+ */
+ WRAP_FL_SIG(f) = 0;
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ WRAP_SPC_LEN(f) = 1;
+ state = DFL; /* set normal state */
+ goto case_dfl; /* and go do "c" */
+ }
+
+ break;
+
+
+ default :
+ dprint((2, "-- gf_wrap: BROKEN FLOW STATE: %d\n",
+ WRAP_FL_SIG(f)));
+ WRAP_FL_SIG(f) = 0;
+ state = DFL; /* set normal state */
+ goto case_dfl; /* and go process "c" */
+ }
+
+ break;
+
+ case_dfl :
+ case DFL :
+ /*
+ * This was just if(WRAP_SPEC(f, c)) before the change to add
+ * the == 0 test. This isn't quite right, either. We should really
+ * be looking for special characters in the UCS characters, not
+ * in the incoming stream of UTF-8. It is not right to
+ * call this on bytes that are in the middle of a UTF-8 character,
+ * hence the == 0 test which restricts it to the first byte
+ * of a character. This isn't right, either, but it's closer.
+ * Also change the definition of WRAP_SPEC so that isspace only
+ * matches ascii characters, which will never be in the middle
+ * of a UTF-8 multi-byte character.
+ */
+ if((WRAP_UTF8BUFP(f) - &WRAP_UTF8BUF(f, 0)) == 0 && WRAP_SPEC(f, c)){
+ WRAP_SAW_SOFT_HYPHEN(f) = 0;
+ switch(c){
+ default :
+ if(WRAP_QUOTED(f))
+ break;
+
+ if(f->f2){ /* any non-lwsp to flush? */
+ if(WRAP_COMMA(f)){
+ /* remember our second best break point */
+ WRAP_PB_OFF(f) = f->linep - f->line;
+ WRAP_PB_LEN(f) = f->f2;
+ break;
+ }
+ else
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ }
+
+ switch(c){ /* remember separator */
+ case ' ' :
+ WRAP_SPC_LEN(f)++;
+ WRAP_TRL_SPC(f) = 1;
+ so_writec(' ',WRAP_SPACES(f));
+ break;
+
+ case TAB :
+ {
+ int i = (int) f->n + WRAP_SPC_LEN(f);
+
+ do
+ WRAP_SPC_LEN(f)++;
+ while(++i & 0x07);
+
+ so_writec(TAB,WRAP_SPACES(f));
+ WRAP_TRL_SPC(f) = 0;
+ }
+
+ break;
+
+ default : /* some control char? */
+ WRAP_SPC_LEN(f) += 2;
+ WRAP_TRL_SPC(f) = 0;
+ break;
+ }
+
+ continue;
+
+ case '\"' :
+ WRAP_QUOTED(f) = !WRAP_QUOTED(f);
+ break;
+
+ case '\015' : /* already has newline? */
+ state = CCR;
+ continue;
+
+ case '\012' : /* bare LF in text? */
+ wrap_flush(f, &ip, &eib, &op, &eob); /* they must've */
+ wrap_eol(f, 0, &ip, &eib, &op, &eob); /* meant */
+ wrap_bol(f,1,1, &ip, &eib, &op, &eob); /* newline... */
+ continue;
+
+ case (unsigned char) TAG_EMBED :
+ WRAP_EMBED_PUTC(f, TAG_EMBED);
+ WRAP_STATE(f) = state;
+ state = TAG;
+ continue;
+
+ case ',' :
+ if(!WRAP_QUOTED(f)){
+ /* handle this special case in general code below */
+ if(f->n + WRAP_SPC_LEN(f) + f->f2 + 1 > WRAP_MAX_COL(f)
+ && WRAP_ALLWSP(f) && WRAP_PB_OFF(f))
+ break;
+
+ if(f->n + WRAP_SPC_LEN(f) + f->f2 + 1 > WRAP_COL(f)){
+ if(WRAP_ALLWSP(f)) /* if anything visible */
+ wrap_flush(f, &ip, &eib, &op,
+ &eob); /* ... blat buf'd chars */
+
+ wrap_eol(f, 1, &ip, &eib, &op,
+ &eob); /* plunk down newline */
+ wrap_bol(f, 1, 1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+ }
+
+ WRAP_PUTC(f, ',', 1); /* put out comma */
+ wrap_flush(f, &ip, &eib, &op,
+ &eob); /* write buf'd chars */
+ continue;
+ }
+
+ break;
+ }
+ }
+ else if(WRAP_HANDLE_SOFT_HYPHEN(f)
+ && (WRAP_UTF8BUFP(f) - &WRAP_UTF8BUF(f, 0)) == 1
+ && WRAP_UTF8BUF(f, 0) == 0xC2 && c == 0xAD){
+ /*
+ * This is a soft hyphen. If there is enough space for
+ * a real hyphen to fit on the line here then we can
+ * flush everything up to before the soft hyphen,
+ * and simply remember that we saw a soft hyphen.
+ * If it turns out that we can't fit the next piece in
+ * then wrap_eol will append a real hyphen to the line.
+ * If we can fit another piece in it will be because we've
+ * reached the next break point. At that point we'll flush
+ * everything but won't include the unneeded hyphen. We erase
+ * the fact that we saw this soft hyphen because it have
+ * become irrelevant.
+ *
+ * If the hyphen is the character that puts us over the edge
+ * we go through the else case.
+ */
+
+ /* erase this soft hyphen character from buffer */
+ WRAP_UTF8BUFP(f) = &WRAP_UTF8BUF(f, 0);
+
+ if((f->n + WRAP_SPC_LEN(f) + f->f2 + 1) <= WRAP_COL(f)){
+ if(f->f2) /* any non-lwsp to flush? */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+
+ /* remember that we saw the soft hyphen */
+ WRAP_SAW_SOFT_HYPHEN(f) = 1;
+ }
+ else{
+ /*
+ * Everything up to the hyphen fits, otherwise it
+ * would have already been flushed the last time
+ * through the loop. But the hyphen won't fit. So
+ * we need to go back to the last line break and
+ * break there instead. Then start a new line with
+ * the buffered up characters and the soft hyphen.
+ */
+ wrap_flush_embed(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 1, &ip, &eib, &op,
+ &eob); /* plunk down newline */
+ wrap_bol(f,1,1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+
+ /*
+ * Now we're in the same situation as we would have
+ * been above except we're on a new line. Try to
+ * flush out the characters seen up to the hyphen.
+ */
+ if((f->n + WRAP_SPC_LEN(f) + f->f2 + 1) <= WRAP_COL(f)){
+ if(f->f2) /* any non-lwsp to flush? */
+ wrap_flush(f, &ip, &eib, &op, &eob);
+
+ /* remember that we saw the soft hyphen */
+ WRAP_SAW_SOFT_HYPHEN(f) = 1;
+ }
+ else
+ WRAP_SAW_SOFT_HYPHEN(f) = 0;
+ }
+
+ continue;
+ }
+
+ full_character = 0;
+
+ {
+ unsigned char *inputp;
+ unsigned long remaining_octets;
+ UCS ucs;
+
+ if(WRAP_UTF8BUFP(f) < &WRAP_UTF8BUF(f, 0) + 6){ /* always true */
+
+ *WRAP_UTF8BUFP(f)++ = c;
+ remaining_octets = WRAP_UTF8BUFP(f) - &WRAP_UTF8BUF(f, 0);
+ if(remaining_octets == 1 && isascii(WRAP_UTF8BUF(f, 0))){
+ full_character++;
+ if(c == TAB){
+ int i = (int) f->n;
+
+ while(i & 0x07)
+ i++;
+
+ width = i - f->n;
+ }
+ else if(c < 0x80 && iscntrl((unsigned char) c))
+ width = 2;
+ else
+ width = 1;
+ }
+ else{
+ inputp = &WRAP_UTF8BUF(f, 0);
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ switch(ucs){
+ case U8G_ENDSTRG: /* incomplete character, wait */
+ case U8G_ENDSTRI: /* incomplete character, wait */
+ width = 0;
+ break;
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * None of these cases is supposed to happen. If it
+ * does happen then the input stream isn't UTF-8
+ * so something is wrong. Writechar will treat
+ * each octet in the input buffer as a separate
+ * error character and print a '?' for each,
+ * so the width will be the number of octets.
+ */
+ width = WRAP_UTF8BUFP(f) - &WRAP_UTF8BUF(f, 0);
+ full_character++;
+ }
+ else{
+ /* got a character */
+ width = wcellwidth(ucs);
+ full_character++;
+
+ if(width < 0){
+ /*
+ * This happens when we have a UTF-8 character that
+ * we aren't able to print in our locale. For example,
+ * if the locale is setup with the terminal
+ * expecting ISO-8859-1 characters then there are
+ * lots of UTF-8 characters that can't be printed.
+ * Print a '?' instead.
+ */
+ width = 1;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ else{
+ /*
+ * This cannot happen because an error would have
+ * happened at least by character #6. So if we get
+ * here there is a bug in utf8_get().
+ */
+ if(WRAP_UTF8BUFP(f) == &WRAP_UTF8BUF(f, 0) + 6){
+ *WRAP_UTF8BUFP(f)++ = c;
+ }
+
+ /*
+ * We could possibly do some more sophisticated
+ * resynchronization here, but we aren't doing
+ * anything in Writechar so it wouldn't match up
+ * with that anyway. Just figure each character will
+ * end up being printed as a ? character.
+ */
+ width = WRAP_UTF8BUFP(f) - &WRAP_UTF8BUF(f, 0);
+ full_character++;
+ }
+ }
+
+ if(WRAP_ALLWSP(f)){
+ /*
+ * Nothing is visible yet but the first word may be too long
+ * all by itself. We need to break early.
+ */
+ if(f->n + WRAP_SPC_LEN(f) + f->f2 + width > WRAP_MAX_COL(f)){
+ /*
+ * A little reaching behind the curtain here.
+ * if there's at least a preferable break point, use
+ * it and stuff what's left back into the wrap buffer.
+ * The "nwsp" latch is used to skip leading whitespace
+ * The second half of the test prevents us from wrapping
+ * at the preferred break point in the case that it
+ * is so early in the line that it doesn't help.
+ * That is, the width of the indent is even more than
+ * the width of the first part before the preferred
+ * break point. An example would be breaking after
+ * "To:" when the indent is 4 which is > 3.
+ */
+ if(WRAP_PB_OFF(f) && WRAP_PB_LEN(f) >= WRAP_INDENT(f)){
+ char *p1 = f->line + WRAP_PB_OFF(f);
+ char *p2 = f->linep;
+ char c2;
+ int nwsp = 0, left_after_wrap;
+
+ left_after_wrap = f->f2 - WRAP_PB_LEN(f);
+
+ f->f2 = WRAP_PB_LEN(f);
+ f->linep = p1;
+
+ wrap_flush(f, &ip, &eib, &op, &eob); /* flush shortened buf */
+
+ /* put back rest of characters */
+ while(p1 < p2){
+ c2 = *p1++;
+ if(!(c2 == ' ' || c2 == '\t') || nwsp){
+ WRAP_PUTC(f, c2, 0);
+ nwsp = 1;
+ }
+ else
+ left_after_wrap--; /* wrong if a tab! */
+ }
+
+ f->f2 = MAX(left_after_wrap, 0);
+
+ wrap_eol(f, 1, &ip, &eib, &op,
+ &eob); /* plunk down newline */
+ wrap_bol(f,1,1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+
+ /*
+ * What's this for?
+ * If we do the less preferable break point at
+ * the space we don't want to lose the fact that
+ * we might be able to break at this comma for
+ * the next one.
+ */
+ if(full_character && c == ','){
+ WRAP_PUTC(f, c, 1);
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ WRAP_UTF8BUFP(f) = &WRAP_UTF8BUF(f, 0);
+ }
+ }
+ else{
+ wrap_flush(f, &ip, &eib, &op, &eob);
+
+ wrap_eol(f, 1, &ip, &eib, &op,
+ &eob); /* plunk down newline */
+ wrap_bol(f,1,1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+ }
+ }
+ }
+ else if((f->n + WRAP_SPC_LEN(f) + f->f2 + width) > WRAP_COL(f)){
+ wrap_flush_embed(f, &ip, &eib, &op, &eob);
+ wrap_eol(f, 1, &ip, &eib, &op,
+ &eob); /* plunk down newline */
+ wrap_bol(f,1,1, &ip, &eib, &op,
+ &eob); /* write any prefix */
+ }
+
+ /*
+ * Commit entire multibyte UTF-8 character at once
+ * instead of writing partial characters into the
+ * buffer.
+ */
+ if(full_character){
+ unsigned char *q;
+
+ for(q = &WRAP_UTF8BUF(f, 0); q < WRAP_UTF8BUFP(f); q++){
+ WRAP_PUTC(f, *q, width);
+ width = 0;
+ }
+
+ WRAP_UTF8BUFP(f) = &WRAP_UTF8BUF(f, 0);
+ }
+
+ break;
+
+ case_tag :
+ case TAG :
+ WRAP_EMBED_PUTC(f, c);
+ switch(c){
+ case TAG_HANDLE :
+ WRAP_EMBED(f) = -1;
+ state = HANDLE;
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ WRAP_EMBED(f) = RGBLEN;
+ state = HDATA;
+ break;
+
+ default :
+ state = WRAP_STATE(f);
+ break;
+ }
+
+ break;
+
+ case HANDLE :
+ WRAP_EMBED_PUTC(f, c);
+ WRAP_EMBED(f) = c;
+ state = HDATA;
+ break;
+
+ case HDATA :
+ if(f->f2){
+ WRAP_PUTC(f, c, 0);
+ }
+ else
+ so_writec(c, WRAP_SPACES(f));
+
+ if(!(WRAP_EMBED(f) -= 1)){
+ state = WRAP_STATE(f);
+ }
+
+ break;
+ }
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ wrap_flush(f, &ip, &eib, &op, &eob);
+ if(WRAP_COLOR(f))
+ free_color_pair(&WRAP_COLOR(f));
+
+ fs_give((void **) &f->line); /* free temp line buffer */
+ so_give(&WRAP_SPACES(f));
+ fs_give((void **) &f->opt); /* free wrap widths struct */
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset wrap\n"));
+ f->f1 = BOL;
+ f->n = 0L; /* displayed length of line so far */
+ f->f2 = 0; /* displayed length of buffered chars */
+ WRAP_HARD(f) = 1; /* starting at beginning of line */
+ if(! (WRAP_S *) f->opt)
+ f->opt = gf_wrap_filter_opt(75, 80, NULL, 0, 0);
+
+ while(WRAP_INDENT(f) >= WRAP_MAX_COL(f))
+ WRAP_INDENT(f) /= 2;
+
+ f->line = (char *) fs_get(WRAP_MAX_COL(f) * sizeof(char));
+ f->linep = f->line;
+ WRAP_LASTC(f) = &f->line[WRAP_MAX_COL(f) - 1];
+
+ for(i = 0; i < 256; i++)
+ ((WRAP_S *) f->opt)->special[i] = ((i == '\"' && WRAP_COMMA(f))
+ || i == '\015'
+ || i == '\012'
+ || (i == (unsigned char) TAG_EMBED
+ && WRAP_TAGS(f))
+ || (i == ',' && WRAP_COMMA(f)
+ && !WRAP_QUOTED(f))
+ || ASCII_ISSPACE(i));
+ WRAP_SPACES(f) = so_get(CharStar, NULL, EDIT_ACCESS);
+ WRAP_UTF8BUFP(f) = &WRAP_UTF8BUF(f, 0);
+ }
+}
+
+int
+wrap_flush(FILTER_S *f, unsigned char **ipp, unsigned char **eibp,
+ unsigned char **opp, unsigned char **eobp)
+{
+ register char *s;
+ register int n;
+
+ s = (char *)so_text(WRAP_SPACES(f));
+ n = so_tell(WRAP_SPACES(f));
+ so_seek(WRAP_SPACES(f), 0L, 0);
+ wrap_flush_s(f, s, n, WRAP_SPC_LEN(f), ipp, eibp, opp, eobp, WFE_NONE);
+ so_truncate(WRAP_SPACES(f), 0L);
+ WRAP_SPC_LEN(f) = 0;
+ WRAP_TRL_SPC(f) = 0;
+ s = f->line;
+ n = f->linep - f->line;
+ wrap_flush_s(f, s, n, f->f2, ipp, eibp, opp, eobp, WFE_NONE);
+ f->f2 = 0;
+ f->linep = f->line;
+ WRAP_PB_OFF(f) = 0;
+ WRAP_PB_LEN(f) = 0;
+
+ return 0;
+}
+
+int
+wrap_flush_embed(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, unsigned char **opp, unsigned char **eobp)
+{
+ register char *s;
+ register int n;
+ s = (char *)so_text(WRAP_SPACES(f));
+ n = so_tell(WRAP_SPACES(f));
+ so_seek(WRAP_SPACES(f), 0L, 0);
+ wrap_flush_s(f, s, n, 0, ipp, eibp, opp, eobp, WFE_CNT_HANDLE);
+ so_truncate(WRAP_SPACES(f), 0L);
+ WRAP_SPC_LEN(f) = 0;
+ WRAP_TRL_SPC(f) = 0;
+
+ return 0;
+}
+
+int
+wrap_flush_s(FILTER_S *f, char *s, int n, int w, unsigned char **ipp,
+ unsigned char **eibp, unsigned char **opp, unsigned char **eobp, int flags)
+{
+ f->n += w;
+
+ for(; n > 0; n--,s++){
+ if(*s == TAG_EMBED){
+ if(n-- > 0){
+ switch(*++s){
+ case TAG_BOLDON :
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_BOLDON);
+ WRAP_BOLD(f) = 1;
+ break;
+ case TAG_BOLDOFF :
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_BOLDOFF);
+ WRAP_BOLD(f) = 0;
+ break;
+ case TAG_ULINEON :
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_ULINEON);
+ WRAP_ULINE(f) = 1;
+ break;
+ case TAG_ULINEOFF :
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_ULINEOFF);
+ WRAP_ULINE(f) = 0;
+ break;
+ case TAG_INVOFF :
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_INVOFF);
+ WRAP_ANCHOR(f) = 0;
+ break;
+ case TAG_HANDLE :
+ if((flags & WFE_CNT_HANDLE) == 0)
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+
+ if(n-- > 0){
+ int i = *++s;
+
+ if((flags & WFE_CNT_HANDLE) == 0)
+ GF_PUTC_GLO(f->next, TAG_HANDLE);
+
+ if(i <= n){
+ n -= i;
+
+ if((flags & WFE_CNT_HANDLE) == 0)
+ GF_PUTC_GLO(f->next, i);
+
+ WRAP_ANCHOR(f) = 0;
+ while(i-- > 0){
+ WRAP_ANCHOR(f) = (WRAP_ANCHOR(f) * 10) + (*++s-'0');
+
+ if((flags & WFE_CNT_HANDLE) == 0)
+ GF_PUTC_GLO(f->next,*s);
+ }
+
+ }
+ }
+ break;
+ case TAG_FGCOLOR :
+ if(pico_usingcolor() && n >= RGBLEN){
+ int i;
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_FGCOLOR);
+ if(!WRAP_COLOR(f))
+ WRAP_COLOR(f)=new_color_pair(NULL,NULL);
+ strncpy(WRAP_COLOR(f)->fg, s+1, RGBLEN);
+ WRAP_COLOR(f)->fg[RGBLEN]='\0';
+ i = RGBLEN;
+ n -= i;
+ while(i-- > 0)
+ GF_PUTC_GLO(f->next,
+ (*++s) & 0xff);
+ }
+ break;
+ case TAG_BGCOLOR :
+ if(pico_usingcolor() && n >= RGBLEN){
+ int i;
+ GF_PUTC_GLO(f->next,TAG_EMBED);
+ GF_PUTC_GLO(f->next,TAG_BGCOLOR);
+ if(!WRAP_COLOR(f))
+ WRAP_COLOR(f)=new_color_pair(NULL,NULL);
+ strncpy(WRAP_COLOR(f)->bg, s+1, RGBLEN);
+ WRAP_COLOR(f)->bg[RGBLEN]='\0';
+ i = RGBLEN;
+ n -= i;
+ while(i-- > 0)
+ GF_PUTC_GLO(f->next,
+ (*++s) & 0xff);
+ }
+ break;
+ default :
+ break;
+ }
+ }
+ }
+ else if(w){
+
+ if(f->n <= WRAP_MAX_COL(f)){
+ GF_PUTC_GLO(f->next, (*s) & 0xff);
+ }
+ else{
+ dprint((2, "-- gf_wrap: OVERRUN: %c\n", (*s) & 0xff));
+ }
+
+ WRAP_ALLWSP(f) = 0;
+ }
+ }
+
+ return 0;
+}
+
+int
+wrap_eol(FILTER_S *f, int c, unsigned char **ipp, unsigned char **eibp,
+ unsigned char **opp, unsigned char **eobp)
+{
+ if(WRAP_SAW_SOFT_HYPHEN(f)){
+ WRAP_SAW_SOFT_HYPHEN(f) = 0;
+ GF_PUTC_GLO(f->next, '-'); /* real hyphen */
+ }
+
+ if(c && WRAP_LV_FLD(f))
+ GF_PUTC_GLO(f->next, ' ');
+
+ if(WRAP_BOLD(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_BOLDOFF);
+ }
+
+ if(WRAP_ULINE(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_ULINEOFF);
+ }
+
+ if(WRAP_INVERSE(f) || WRAP_ANCHOR(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_INVOFF);
+ }
+
+ if(WRAP_COLOR_SET(f)){
+ char *p;
+ char cb[RGBLEN+1];
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_FGCOLOR);
+ strncpy(cb, color_to_asciirgb(ps_global->VAR_NORM_FORE_COLOR), sizeof(cb));
+ cb[sizeof(cb)-1] = '\0';
+ p = cb;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_BGCOLOR);
+ strncpy(cb, color_to_asciirgb(ps_global->VAR_NORM_BACK_COLOR), sizeof(cb));
+ cb[sizeof(cb)-1] = '\0';
+ p = cb;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ }
+
+ GF_PUTC_GLO(f->next, '\015');
+ GF_PUTC_GLO(f->next, '\012');
+ f->n = 0L;
+ so_truncate(WRAP_SPACES(f), 0L);
+ WRAP_SPC_LEN(f) = 0;
+ WRAP_TRL_SPC(f) = 0;
+
+ return 0;
+}
+
+int
+wrap_bol(FILTER_S *f, int ivar, int q, unsigned char **ipp, unsigned char **eibp,
+ unsigned char **opp, unsigned char **eobp)
+{
+ int n = WRAP_MARG_L(f) + (ivar ? WRAP_INDENT(f) : 0);
+
+ if(WRAP_HDR_CLR(f)){
+ char *p;
+ char cbuf[RGBLEN+1];
+ int k;
+
+ if((k = WRAP_MARG_L(f)) > 0)
+ while(k-- > 0){
+ n--;
+ f->n++;
+ GF_PUTC_GLO(f->next, ' ');
+ }
+
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_FGCOLOR);
+ strncpy(cbuf,
+ color_to_asciirgb(ps_global->VAR_HEADER_GENERAL_FORE_COLOR),
+ sizeof(cbuf));
+ cbuf[sizeof(cbuf)-1] = '\0';
+ p = cbuf;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_BGCOLOR);
+ strncpy(cbuf,
+ color_to_asciirgb(ps_global->VAR_HEADER_GENERAL_BACK_COLOR),
+ sizeof(cbuf));
+ cbuf[sizeof(cbuf)-1] = '\0';
+ p = cbuf;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ }
+
+ while(n-- > 0){
+ f->n++;
+ GF_PUTC_GLO(f->next, ' ');
+ }
+
+ WRAP_ALLWSP(f) = 1;
+
+ if(q)
+ wrap_quote_insert(f, ipp, eibp, opp, eobp);
+
+ if(WRAP_BOLD(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_BOLDON);
+ }
+ if(WRAP_ULINE(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_ULINEON);
+ }
+ if(WRAP_INVERSE(f)){
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_INVON);
+ }
+ if(WRAP_COLOR_SET(f)){
+ char *p;
+ if(WRAP_COLOR(f)->fg[0]){
+ char cb[RGBLEN+1];
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_FGCOLOR);
+ strncpy(cb, color_to_asciirgb(WRAP_COLOR(f)->fg), sizeof(cb));
+ cb[sizeof(cb)-1] = '\0';
+ p = cb;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ }
+ if(WRAP_COLOR(f)->bg[0]){
+ char cb[RGBLEN+1];
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_BGCOLOR);
+ strncpy(cb, color_to_asciirgb(WRAP_COLOR(f)->bg), sizeof(cb));
+ cb[sizeof(cb)-1] = '\0';
+ p = cb;
+ for(; *p; p++)
+ GF_PUTC_GLO(f->next, *p);
+ }
+ }
+ if(WRAP_ANCHOR(f)){
+ char buf[64]; int i;
+ GF_PUTC_GLO(f->next, TAG_EMBED);
+ GF_PUTC_GLO(f->next, TAG_HANDLE);
+ snprintf(buf, sizeof(buf), "%d", WRAP_ANCHOR(f));
+ GF_PUTC_GLO(f->next, (int) strlen(buf));
+ for(i = 0; buf[i]; i++)
+ GF_PUTC_GLO(f->next, buf[i]);
+ }
+
+ return 0;
+}
+
+int
+wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp,
+ unsigned char **opp, unsigned char **eobp)
+{
+ int j, i;
+ COLOR_PAIR *col = NULL;
+ char *prefix = NULL, *last_prefix = NULL;
+
+ if(ps_global->VAR_QUOTE_REPLACE_STRING){
+ get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0);
+ if(!prefix && last_prefix){
+ prefix = last_prefix;
+ last_prefix = NULL;
+ }
+ }
+
+ for(j = 0; j < WRAP_FL_QD(f); j++){
+ if(WRAP_USE_CLR(f)){
+ if((j % 3) == 0
+ && ps_global->VAR_QUOTE1_FORE_COLOR
+ && ps_global->VAR_QUOTE1_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_QUOTE1_FORE_COLOR,
+ ps_global->VAR_QUOTE1_BACK_COLOR))
+ && pico_is_good_colorpair(col)){
+ GF_COLOR_PUTC(f, col);
+ }
+ else if((j % 3) == 1
+ && ps_global->VAR_QUOTE2_FORE_COLOR
+ && ps_global->VAR_QUOTE2_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_QUOTE2_FORE_COLOR,
+ ps_global->VAR_QUOTE2_BACK_COLOR))
+ && pico_is_good_colorpair(col)){
+ GF_COLOR_PUTC(f, col);
+ }
+ else if((j % 3) == 2
+ && ps_global->VAR_QUOTE3_FORE_COLOR
+ && ps_global->VAR_QUOTE3_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_QUOTE3_FORE_COLOR,
+ ps_global->VAR_QUOTE3_BACK_COLOR))
+ && pico_is_good_colorpair(col)){
+ GF_COLOR_PUTC(f, col);
+ }
+ if(col){
+ free_color_pair(&col);
+ col = NULL;
+ }
+ }
+
+ if(!WRAP_LV_FLD(f)){
+ if(!WRAP_FOR_CMPS(f) && ps_global->VAR_QUOTE_REPLACE_STRING && prefix){
+ for(i = 0; prefix[i]; i++)
+ GF_PUTC_GLO(f->next, prefix[i]);
+ f->n += utf8_width(prefix);
+ }
+ else if(ps_global->VAR_REPLY_STRING
+ && (!strcmp(ps_global->VAR_REPLY_STRING, ">")
+ || !strcmp(ps_global->VAR_REPLY_STRING, "\">\""))){
+ GF_PUTC_GLO(f->next, '>');
+ f->n += 1;
+ }
+ else{
+ GF_PUTC_GLO(f->next, '>');
+ GF_PUTC_GLO(f->next, ' ');
+ f->n += 2;
+ }
+ }
+ else{
+ GF_PUTC_GLO(f->next, '>');
+ f->n += 1;
+ }
+ }
+ if(j && WRAP_LV_FLD(f)){
+ GF_PUTC_GLO(f->next, ' ');
+ f->n++;
+ }
+ else if(j && last_prefix){
+ for(i = 0; last_prefix[i]; i++)
+ GF_PUTC_GLO(f->next, last_prefix[i]);
+ f->n += utf8_width(last_prefix);
+ }
+
+ if(prefix)
+ fs_give((void **)&prefix);
+ if(last_prefix)
+ fs_give((void **)&last_prefix);
+
+ return 0;
+}
+
+
+/*
+ * function called from the outside to set
+ * wrap filter's width option
+ */
+void *
+gf_wrap_filter_opt(int width, int width_max, int *margin, int indent, int flags)
+{
+ WRAP_S *wrap;
+
+ /* NOTE: variables MUST be sanity checked before they get here */
+ wrap = (WRAP_S *) fs_get(sizeof(WRAP_S));
+ memset(wrap, 0, sizeof(WRAP_S));
+ wrap->wrap_col = width;
+ wrap->wrap_max = width_max;
+ wrap->indent = indent;
+ wrap->margin_l = (margin) ? margin[0] : 0;
+ wrap->margin_r = (margin) ? margin[1] : 0;
+ wrap->tags = (GFW_HANDLES & flags) == GFW_HANDLES;
+ wrap->on_comma = (GFW_ONCOMMA & flags) == GFW_ONCOMMA;
+ wrap->flowed = (GFW_FLOWED & flags) == GFW_FLOWED;
+ wrap->leave_flowed = (GFW_FLOW_RESULT & flags) == GFW_FLOW_RESULT;
+ wrap->delsp = (GFW_DELSP & flags) == GFW_DELSP;
+ wrap->use_color = (GFW_USECOLOR & flags) == GFW_USECOLOR;
+ wrap->hdr_color = (GFW_HDRCOLOR & flags) == GFW_HDRCOLOR;
+ wrap->for_compose = (GFW_FORCOMPOSE & flags) == GFW_FORCOMPOSE;
+ wrap->handle_soft_hyphen = (GFW_SOFTHYPHEN & flags) == GFW_SOFTHYPHEN;
+
+ return((void *) wrap);
+}
+
+
+void *
+gf_url_hilite_opt(URL_HILITE_S *uh, HANDLE_S **handlesp, int flags)
+{
+ if(uh){
+ memset(uh, 0, sizeof(URL_HILITE_S));
+ uh->handlesp = handlesp;
+ uh->hdr_color = (URH_HDRCOLOR & flags) == URH_HDRCOLOR;
+ }
+
+ return((void *) uh);
+}
+
+
+#define PF_QD(F) (((PREFLOW_S *)(F)->opt)->quote_depth)
+#define PF_QC(F) (((PREFLOW_S *)(F)->opt)->quote_count)
+#define PF_SIG(F) (((PREFLOW_S *)(F)->opt)->sig)
+
+typedef struct preflow_s {
+ int quote_depth,
+ quote_count,
+ sig;
+} PREFLOW_S;
+
+/*
+ * This would normally be handled in gf_wrap. If there is a possibility
+ * that a url we want to recognize is cut in half by a soft newline we
+ * want to fix that up by putting the halves back together. We do that
+ * by deleting the soft newline and putting it all in one line. It will
+ * still get wrapped later in gf_wrap. It isn't pretty with all the
+ * goto's, but whatta ya gonna do?
+ */
+void
+gf_preflow(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+ register int pending = f->f2;
+
+ while(GF_GETC(f, c)){
+ switch(state){
+ case DFL:
+default_case:
+ switch(c){
+ case ' ':
+ state = WSPACE;
+ break;
+
+ case '\015':
+ state = CCR;
+ break;
+
+ default:
+ GF_PUTC(f->next, c);
+ break;
+ }
+
+ break;
+
+ case CCR:
+ switch(c){
+ case '\012':
+ pending = 1;
+ state = BOL;
+ break;
+
+ default:
+ GF_PUTC(f->next, '\012');
+ state = DFL;
+ goto default_case;
+ break;
+ }
+
+ break;
+
+ case WSPACE:
+ switch(c){
+ case '\015':
+ state = SPACECR;
+ break;
+
+ default:
+ GF_PUTC(f->next, ' ');
+ state = DFL;
+ goto default_case;
+ break;
+ }
+
+ break;
+
+ case SPACECR:
+ switch(c){
+ case '\012':
+ pending = 2;
+ state = BOL;
+ break;
+
+ default:
+ GF_PUTC(f->next, ' ');
+ GF_PUTC(f->next, '\012');
+ state = DFL;
+ goto default_case;
+ break;
+ }
+
+ break;
+
+ case BOL:
+ PF_QC(f) = 0;
+ if(c == '>'){ /* count quote level */
+ PF_QC(f)++;
+ state = FL_QLEV;
+ }
+ else{
+done_counting_quotes:
+ if(c == ' '){ /* eat stuffed space */
+ state = FL_STF;
+ break;
+ }
+
+done_with_stuffed_space:
+ if(c == '-'){ /* look for signature */
+ PF_SIG(f) = 1;
+ state = FL_SIG;
+ break;
+ }
+
+done_with_sig:
+ if(pending == 2){
+ if(PF_QD(f) == PF_QC(f) && PF_SIG(f) < 4){
+ /* delete pending */
+
+ PF_QD(f) = PF_QC(f);
+
+ /* suppress quotes, too */
+ PF_QC(f) = 0;
+ }
+ else{
+ /*
+ * This should have been a hard new line
+ * instead so leave out the trailing space.
+ */
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+
+ PF_QD(f) = PF_QC(f);
+ }
+ }
+ else if(pending == 1){
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ PF_QD(f) = PF_QC(f);
+ }
+ else{
+ PF_QD(f) = PF_QC(f);
+ }
+
+ pending = 0;
+ state = DFL;
+ while(PF_QC(f)-- > 0)
+ GF_PUTC(f->next, '>');
+
+ switch(PF_SIG(f)){
+ case 0:
+ default:
+ break;
+
+ case 1:
+ GF_PUTC(f->next, '-');
+ break;
+
+ case 2:
+ GF_PUTC(f->next, '-');
+ GF_PUTC(f->next, '-');
+ break;
+
+ case 3:
+ case 4:
+ GF_PUTC(f->next, '-');
+ GF_PUTC(f->next, '-');
+ GF_PUTC(f->next, ' ');
+ break;
+ }
+
+ PF_SIG(f) = 0;
+ goto default_case; /* to handle c */
+ }
+
+ break;
+
+ case FL_QLEV: /* count quote level */
+ if(c == '>')
+ PF_QC(f)++;
+ else
+ goto done_counting_quotes;
+
+ break;
+
+ case FL_STF: /* eat stuffed space */
+ goto done_with_stuffed_space;
+ break;
+
+ case FL_SIG: /* deal with sig indicator */
+ switch(PF_SIG(f)){
+ case 1: /* saw '-' */
+ if(c == '-')
+ PF_SIG(f) = 2;
+ else
+ goto done_with_sig;
+
+ break;
+
+ case 2: /* saw '--' */
+ if(c == ' ')
+ PF_SIG(f) = 3;
+ else
+ goto done_with_sig;
+
+ break;
+
+ case 3: /* saw '-- ' */
+ if(c == '\015')
+ PF_SIG(f) = 4; /* it really is a sig line */
+
+ goto done_with_sig;
+ break;
+ }
+
+ break;
+ }
+ }
+
+ f->f1 = state;
+ f->f2 = pending;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ fs_give((void **) &f->opt);
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ PREFLOW_S *pf;
+
+ pf = (PREFLOW_S *) fs_get(sizeof(*pf));
+ memset(pf, 0, sizeof(*pf));
+ f->opt = (void *) pf;
+
+ f->f1 = BOL; /* state */
+ f->f2 = 0; /* pending */
+ PF_QD(f) = 0; /* quote depth */
+ PF_QC(f) = 0; /* quote count */
+ PF_SIG(f) = 0; /* sig level */
+ }
+}
+
+
+
+
+/*
+ * LINE PREFIX FILTER - insert given text at beginning of each
+ * line
+ */
+
+
+#define GF_PREFIX_WRITE(s) { \
+ register char *p; \
+ if((p = (s)) != NULL) \
+ while(*p) \
+ GF_PUTC(f->next, *p++); \
+ }
+
+
+/*
+ * the simple filter, prepends each line with the requested prefix.
+ * if prefix is null, does nothing, and as with all filters, assumes
+ * NVT end of lines.
+ */
+void
+gf_prefix(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+ register int first = f->f2;
+
+ while(GF_GETC(f, c)){
+
+ if(first){ /* write initial prefix!! */
+ first = 0; /* but just once */
+ GF_PREFIX_WRITE((char *) f->opt);
+ }
+
+ /*
+ * State == 0 is the starting state and the usual state.
+ * State == 1 means we saw a CR and haven't acted on it yet.
+ * We are looking for a LF to get the CRLF end of line.
+ * However, we also treat bare CR and bare LF as if they
+ * were CRLF sequences. What else could it mean in text?
+ * This filter is only used for text so that is probably
+ * a reasonable interpretation of the bad input.
+ */
+ if(c == '\015'){ /* CR */
+ if(state){ /* Treat pending CR as endofline, */
+ GF_PUTC(f->next, '\015'); /* and remain in saw-a-CR state. */
+ GF_PUTC(f->next, '\012');
+ GF_PREFIX_WRITE((char *) f->opt);
+ }
+ else{
+ state = 1;
+ }
+ }
+ else if(c == '\012'){ /* LF */
+ GF_PUTC(f->next, '\015'); /* Got either a CRLF or a bare LF, */
+ GF_PUTC(f->next, '\012'); /* treat both as if a CRLF. */
+ GF_PREFIX_WRITE((char *) f->opt);
+ state = 0;
+ }
+ else{ /* any other character */
+ if(state){
+ GF_PUTC(f->next, '\015'); /* Treat pending CR as endofline. */
+ GF_PUTC(f->next, '\012');
+ GF_PREFIX_WRITE((char *) f->opt);
+ state = 0;
+ }
+
+ GF_PUTC(f->next, c);
+ }
+ }
+
+ f->f1 = state; /* save state for next chunk of data */
+ f->f2 = first;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset prefix\n"));
+ f->f1 = 0;
+ f->f2 = 1; /* nothing written yet */
+ }
+}
+
+
+/*
+ * function called from the outside to set
+ * prefix filter's prefix string
+ */
+void *
+gf_prefix_opt(char *prefix)
+{
+ return((void *) prefix);
+}
+
+
+/*
+ * LINE TEST FILTER - accumulate lines and offer each to the provided
+ * test function.
+ */
+
+typedef struct _linetest_s {
+ linetest_t f;
+ void *local;
+} LINETEST_S;
+
+
+/* accumulator growth increment */
+#define LINE_TEST_BLOCK 1024
+
+#define GF_LINE_TEST_EOB(f) \
+ ((f)->line + ((f)->f2 - 1))
+
+#define GF_LINE_TEST_ADD(f, c) \
+ { \
+ if(p >= eobuf){ \
+ f->f2 += LINE_TEST_BLOCK; \
+ fs_resize((void **)&f->line, \
+ (size_t) f->f2 * sizeof(char)); \
+ eobuf = GF_LINE_TEST_EOB(f); \
+ p = eobuf - LINE_TEST_BLOCK; \
+ } \
+ *p++ = c; \
+ }
+
+#define GF_LINE_TEST_TEST(F, D) \
+ { \
+ unsigned char c; \
+ register char *cp; \
+ register int l; \
+ LT_INS_S *ins = NULL, *insp; \
+ *p = '\0'; \
+ (D) = (*((LINETEST_S *) (F)->opt)->f)((F)->n++, \
+ (F)->line, &ins, \
+ ((LINETEST_S *) (F)->opt)->local); \
+ if((D) < 2){ \
+ if((D) < 0){ \
+ if((F)->line) \
+ fs_give((void **) &(F)->line); \
+ if((F)->opt) \
+ fs_give((void **) &(F)->opt); \
+ gf_error(_("translation error")); \
+ /* NO RETURN */ \
+ } \
+ for(insp = ins, cp = (F)->line; cp < p; ){ \
+ if(insp && cp == insp->where){ \
+ if(insp->len > 0){ \
+ for(l = 0; l < insp->len; l++){ \
+ c = (unsigned char) insp->text[l]; \
+ GF_PUTC((F)->next, c); \
+ } \
+ insp = insp->next; \
+ continue; \
+ } else if(insp->len < 0){ \
+ cp -= insp->len; \
+ insp = insp->next; \
+ continue; \
+ } \
+ } \
+ GF_PUTC((F)->next, *cp); \
+ cp++; \
+ } \
+ while(insp){ \
+ for(l = 0; l < insp->len; l++){ \
+ c = (unsigned char) insp->text[l]; \
+ GF_PUTC((F)->next, c); \
+ } \
+ insp = insp->next; \
+ } \
+ gf_line_test_free_ins(&ins); \
+ } \
+ }
+
+
+
+/*
+ * this simple filter accumulates characters until a newline, offers it
+ * to the provided test function, and then passes it on. It assumes
+ * NVT EOLs.
+ */
+void
+gf_line_test(FILTER_S *f, int flg)
+{
+ register char *p = f->linep;
+ register char *eobuf = GF_LINE_TEST_EOB(f);
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+
+ if(state){
+ state = 0;
+ if(c == '\012'){
+ int done;
+
+ GF_LINE_TEST_TEST(f, done);
+
+ p = (f)->line;
+
+ if(done == 2) /* skip this line! */
+ continue;
+
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ /*
+ * if the line tester returns TRUE, it's
+ * telling us its seen enough and doesn't
+ * want to see any more. Remove ourself
+ * from the pipeline...
+ */
+ if(done){
+ if(gf_master == f){
+ gf_master = f->next;
+ }
+ else{
+ FILTER_S *fprev;
+
+ for(fprev = gf_master;
+ fprev && fprev->next != f;
+ fprev = fprev->next)
+ ;
+
+ if(fprev) /* wha??? */
+ fprev->next = f->next;
+ else
+ continue;
+ }
+
+ while(GF_GETC(f, c)) /* pass input */
+ GF_PUTC(f->next, c);
+
+ (void) GF_FLUSH(f->next); /* and drain queue */
+ fs_give((void **)&f->line);
+ fs_give((void **)&f); /* wax our data */
+ return;
+ }
+ else
+ continue;
+ }
+ else /* add CR to buffer */
+ GF_LINE_TEST_ADD(f, '\015');
+ } /* fall thru to handle 'c' */
+
+ if(c == '\015') /* newline? */
+ state = 1;
+ else
+ GF_LINE_TEST_ADD(f, c);
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ int i;
+
+ GF_LINE_TEST_TEST(f, i); /* examine remaining data */
+ fs_give((void **) &f->line); /* free line buffer */
+ fs_give((void **) &f->opt); /* free test struct */
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset line_test\n"));
+ f->f1 = 0; /* state */
+ f->n = 0L; /* line number */
+ f->f2 = LINE_TEST_BLOCK; /* size of alloc'd line */
+ f->line = p = (char *) fs_get(f->f2 * sizeof(char));
+ }
+
+ f->linep = p;
+}
+
+
+/*
+ * function called from the outside to operate on accumulated line.
+ */
+void *
+gf_line_test_opt(linetest_t test_f, void *local)
+{
+ LINETEST_S *ltp;
+
+ ltp = (LINETEST_S *) fs_get(sizeof(LINETEST_S));
+ memset(ltp, 0, sizeof(LINETEST_S));
+ ltp->f = test_f;
+ ltp->local = local;
+ return((void *) ltp);
+}
+
+
+
+LT_INS_S **
+gf_line_test_new_ins(LT_INS_S **ins, char *p, char *s, int n)
+{
+ *ins = (LT_INS_S *) fs_get(sizeof(LT_INS_S));
+ if(((*ins)->len = n) > 0)
+ strncpy((*ins)->text = (char *) fs_get(n * sizeof(char)), s, n);
+ else
+ (*ins)->text = NULL;
+
+ (*ins)->where = p;
+ (*ins)->next = NULL;
+ return(&(*ins)->next);
+}
+
+
+void
+gf_line_test_free_ins(LT_INS_S **ins)
+{
+ if(ins && *ins){
+ if((*ins)->next)
+ gf_line_test_free_ins(&(*ins)->next);
+
+ if((*ins)->text)
+ fs_give((void **) &(*ins)->text);
+
+ fs_give((void **) ins);
+ }
+}
+
+
+/*
+ * PREPEND EDITORIAL FILTER - conditionally prepend output text
+ * with editorial comment
+ */
+
+typedef struct _preped_s {
+ prepedtest_t f;
+ char *text;
+} PREPED_S;
+
+
+/*
+ * gf_prepend_editorial - accumulate filtered text and prepend its
+ * output with given text
+ *
+ *
+ */
+void
+gf_prepend_editorial(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+ so_writec(c, (STORE_S *) f->data);
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ unsigned char c;
+
+ if(!((PREPED_S *)(f)->opt)->f || (*((PREPED_S *)(f)->opt)->f)()){
+ char *p = ((PREPED_S *)(f)->opt)->text;
+
+ for( ; p && *p; p++)
+ GF_PUTC(f->next, *p);
+ }
+
+ so_seek((STORE_S *) f->data, 0L, 0);
+ while(so_readc(&c, (STORE_S *) f->data)){
+ GF_PUTC(f->next, c);
+ }
+
+ so_give((STORE_S **) &f->data);
+ fs_give((void **) &f->opt);
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset line_test\n"));
+ f->data = (void *) so_get(CharStar, NULL, EDIT_ACCESS);
+ }
+}
+
+
+/*
+ * function called from the outside to setup prepending editorial
+ * to output text
+ */
+void *
+gf_prepend_editorial_opt(prepedtest_t test_f, char *text)
+{
+ PREPED_S *pep;
+
+ pep = (PREPED_S *) fs_get(sizeof(PREPED_S));
+ memset(pep, 0, sizeof(PREPED_S));
+ pep->f = test_f;
+ pep->text = text;
+ return((void *) pep);
+}
+
+
+/*
+ * Network virtual terminal to local newline convention filter
+ */
+void
+gf_nvtnl_local(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+ register int state = f->f1;
+
+ while(GF_GETC(f, c)){
+ if(state){
+ state = 0;
+ if(c == '\012'){
+ GF_PUTC(f->next, '\012');
+ continue;
+ }
+ else
+ GF_PUTC(f->next, '\015');
+ /* fall thru to deal with 'c' */
+ }
+
+ if(c == '\015')
+ state = 1;
+ else
+ GF_PUTC(f->next, c);
+ }
+
+ f->f1 = state;
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(flg == GF_RESET){
+ dprint((9, "-- gf_reset nvtnl_local\n"));
+ f->f1 = 0;
+ }
+}
+
+
+/*
+ * local to network newline convention filter
+ */
+void
+gf_local_nvtnl(FILTER_S *f, int flg)
+{
+ GF_INIT(f, f->next);
+
+ if(flg == GF_DATA){
+ register unsigned char c;
+
+ while(GF_GETC(f, c)){
+ if(c == '\012'){
+ GF_PUTC(f->next, '\015');
+ GF_PUTC(f->next, '\012');
+ }
+ else
+ GF_PUTC(f->next, c);
+ }
+
+ GF_END(f, f->next);
+ }
+ else if(flg == GF_EOD){
+ (void) GF_FLUSH(f->next);
+ (*f->next->f)(f->next, GF_EOD);
+ }
+ else if(GF_RESET){
+ dprint((9, "-- gf_reset local_nvtnl\n"));
+ /* no op */
+ }
+
+}
diff --git a/pith/filter.h b/pith/filter.h
new file mode 100644
index 00000000..07cfbd7a
--- /dev/null
+++ b/pith/filter.h
@@ -0,0 +1,222 @@
+/*
+ * $Id: filter.h 1169 2008-08-27 06:42:06Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_FILTER_INCLUDED
+#define PITH_FILTER_INCLUDED
+
+
+#include "../pith/filttype.h"
+#include "../pith/handle.h"
+#include "../pith/store.h"
+#include "../pith/osdep/pipe.h"
+
+
+/* gf_html2plain flags */
+#define GFHP_STRIPPED 0x01
+#define GFHP_HANDLES 0x02
+#define GFHP_LOCAL_HANDLES 0x04
+#define GFHP_NO_RELATIVE 0x08
+#define GFHP_RELATED_CONTENT 0x10
+#define GFHP_SHOW_SERVER 0x20
+#define GFHP_HTML 0x40
+#define GFHP_HTML_IMAGES 0x80
+
+
+/* gf_wrap flags */
+#define GFW_NONE 0x000 /* no flags */
+#define GFW_HANDLES 0x001 /* anticpate handle data */
+#define GFW_ONCOMMA 0x002 /* prefer comma wrap to spaces */
+#define GFW_FLOWED 0x004
+#define GFW_FLOW_RESULT 0x008
+#define GFW_DELSP 0x010
+#define GFW_USECOLOR 0x020
+#define GFW_HDRCOLOR 0x040
+#define GFW_FORCOMPOSE 0x080
+#define GFW_SOFTHYPHEN 0x100 /* do something special with soft-hyphens */
+
+
+/* gf_url_hilite flags */
+#define URH_NONE 0x00 /* no flags */
+#define URH_HDRCOLOR 0x01 /* in header, use header base color */
+
+
+#define TAG_EMBED '\377' /* Announces embedded data in text string */
+#define TAG_INVON 'A' /* Supported character attributes */
+#define TAG_INVOFF 'a'
+#define TAG_BOLDON 'B'
+#define TAG_BOLDOFF 'b'
+#define TAG_ULINEON 'C'
+#define TAG_ULINEOFF 'c'
+#define TAG_FGCOLOR 'D' /* Change to this foreground color */
+#define TAG_BGCOLOR 'd' /* Change to this background color */
+#define TAG_ITALICON 'E'
+#define TAG_ITALICOFF 'e'
+#define TAG_STRIKEON 'F'
+#define TAG_STRIKEOFF 'f'
+#define TAG_BIGON 'G'
+#define TAG_BIGOFF 'g'
+#define TAG_SMALLON 'H'
+#define TAG_SMALLOFF 'h'
+#define TAG_HANDLE 'Z' /* indicate's a handle to an action */
+#define TAG_HANDLEOFF 'z' /* indicate's end of handle text */
+
+
+typedef struct url_hilite_s {
+ HANDLE_S **handlesp;
+ int hdr_color;
+} URL_HILITE_S;
+
+typedef struct _rss_item_s {
+ char *title;
+ char *link;
+ char *description;
+ char *source;
+ struct _rss_item_s *next;
+} RSS_ITEM_S;
+
+typedef struct _rss_feed_s {
+ char *title;
+ char *image;
+ char *link;
+ char *description;
+ char *source;
+ int ttl;
+ struct _rss_item_s *items;
+} RSS_FEED_S;
+
+
+/*
+ * This searchs for lines beginning with From<space> so that we can QP-encode
+ * them. It also searches for lines consisting of only a dot. Some mailers
+ * will mangle these lines. The reason it is ifdef'd is because most people
+ * seem to prefer the >From style escape provided by a lot of mail software
+ * to the QP-encoding.
+ * Froms, dots, bmap, and dmap may be any integer type. C is the next char.
+ * bmap and dmap should be initialized to 1.
+ * froms is incremented by 1 whenever a line beginning From_ is found.
+ * dots is incremented by 1 whenever a line with only a dot is found.
+ */
+#define Find_Froms(froms,dots,bmap,dmap,c) { int x,y; \
+ switch (c) { \
+ case '\n': case '\r': \
+ x = 0x1; \
+ y = 0x7; \
+ bmap = 0; \
+ break; \
+ case 'F': \
+ x = 0x3; \
+ y = 0; \
+ break; \
+ case 'r': \
+ x = 0x7; \
+ y = 0; \
+ break; \
+ case 'o': \
+ x = 0xf; \
+ y = 0; \
+ break; \
+ case 'm': \
+ x = 0x1f; \
+ y = 0; \
+ break; \
+ case ' ': \
+ x = 0x3f; \
+ y = 0; \
+ break; \
+ case '.': \
+ x = 0; \
+ y = 0x3; \
+ break; \
+ default: \
+ x = 0; \
+ y = 0; \
+ break; \
+ } \
+ bmap = ((x >> 1) == bmap) ? x : 0; \
+ froms += (bmap == 0x3f ? 1 : 0); \
+ if(y == 0x7 && dmap != 0x3){ \
+ y = 0x1; \
+ dmap = 0; \
+ } \
+ dmap = ((y >> 1) == dmap) ? y : 0; \
+ dots += (dmap == 0x7 ? 1 : 0); \
+ }
+
+
+/* exported protoypes */
+int generic_readc_locale(unsigned char *c,
+ int (*get_a_char)(unsigned char *, void *),
+ void *extraarg,
+ CBUF_S *cb);
+int pc_is_picotext(gf_io_t);
+void gf_set_readc(gf_io_t *, void *, unsigned long, SourceType, int);
+void gf_set_writec(gf_io_t *, void *, unsigned long, SourceType, int);
+void gf_set_so_readc(gf_io_t *, STORE_S *);
+void gf_clear_so_readc(STORE_S *);
+void gf_set_so_writec(gf_io_t *, STORE_S *);
+void gf_clear_so_writec(STORE_S *);
+int gf_puts(char *, gf_io_t);
+int gf_nputs(char *, long, gf_io_t);
+void gf_filter_init(void);
+void gf_link_filter(filter_t, void *);
+void gf_set_terminal(gf_io_t);
+char *gf_pipe(gf_io_t, gf_io_t);
+long gf_bytes_piped(void);
+char *gf_filter(char *, char *, STORE_S *, gf_io_t, FILTLIST_S *, int,
+ void (*)(PIPE_S *, int, void *));
+void gf_binary_b64(FILTER_S *, int);
+void gf_b64_binary(FILTER_S *, int);
+void gf_qp_8bit(FILTER_S *, int);
+void gf_8bit_qp(FILTER_S *, int);
+void gf_convert_8bit_charset(FILTER_S *, int);
+void gf_convert_utf8_charset(FILTER_S *, int);
+void *gf_convert_utf8_charset_opt(void *, int);
+void gf_2022_jp_to_euc(FILTER_S *, int);
+void gf_native8bitjapanese_to_2022_jp(FILTER_S *, int);
+void gf_euc_to_2022_jp(FILTER_S *, int);
+void gf_sjis_to_2022_jp(FILTER_S *, int);
+void gf_utf8(FILTER_S *, int);
+void *gf_utf8_opt(char *);
+void gf_rich2plain(FILTER_S *, int);
+void *gf_rich2plain_opt(int *);
+void gf_enriched2plain(FILTER_S *, int);
+void *gf_enriched2plain_opt(int *);
+void gf_html2plain(FILTER_S *, int);
+void *gf_html2plain_opt(char *, int, int *, HANDLE_S **, htmlrisk_t, int);
+void *gf_html2plain_rss_opt(RSS_FEED_S **, int);
+void gf_html2plain_rss_free(RSS_FEED_S **);
+void gf_html2plain_rss_free_items(RSS_ITEM_S **);
+void gf_escape_filter(FILTER_S *, int);
+void gf_control_filter(FILTER_S *, int);
+void *gf_control_filter_opt(int *);
+void gf_tag_filter(FILTER_S *, int);
+void gf_wrap(FILTER_S *, int);
+void *gf_wrap_filter_opt(int, int, int *, int, int);
+void gf_preflow(FILTER_S *, int);
+void gf_prefix(FILTER_S *, int);
+void *gf_prefix_opt(char *);
+void gf_line_test(FILTER_S *, int);
+void *gf_line_test_opt(linetest_t, void *);
+LT_INS_S **gf_line_test_new_ins(LT_INS_S **, char *, char *, int);
+void gf_line_test_free_ins(LT_INS_S **);
+void gf_prepend_editorial(FILTER_S *, int);
+void *gf_prepend_editorial_opt(prepedtest_t, char *);
+void gf_nvtnl_local(FILTER_S *, int);
+void gf_local_nvtnl(FILTER_S *, int);
+void *gf_url_hilite_opt(URL_HILITE_S *, HANDLE_S **, int);
+
+
+#endif /* PITH_FILTER_INCLUDED */
diff --git a/pith/filttype.h b/pith/filttype.h
new file mode 100644
index 00000000..21a1bec5
--- /dev/null
+++ b/pith/filttype.h
@@ -0,0 +1,75 @@
+/*
+ * $Id: filttype.h 767 2007-10-24 00:03:59Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_FILTTYPE_INCLUDED
+#define PITH_FILTTYPE_INCLUDED
+
+
+/*
+ * Size of generic filter's input/output queue
+ */
+#define GF_MAXBUF 256
+
+
+/*
+ * typedefs of generalized filters used by gf_pipe
+ */
+typedef struct filter_s { /* type to hold data for filter function */
+ void (*f)(struct filter_s *, int);
+ struct filter_s *next; /* next filter to call */
+ long n; /* number of chars seen */
+ short f1; /* flags */
+ int f2; /* second place for flags */
+ unsigned char t; /* temporary char */
+ char *line; /* place for temporary storage */
+ char *linep; /* pointer into storage space */
+ void *opt; /* optional per instance data */
+ void *data; /* misc internal data pointer */
+ unsigned char queue[1 + GF_MAXBUF];
+ short queuein, queueout;
+} FILTER_S;
+
+
+typedef struct filter_insert_s {
+ char *where;
+ char *text;
+ int len;
+ struct filter_insert_s *next;
+} LT_INS_S;
+
+
+typedef int (*gf_io_t)(); /* type of get and put char function */
+typedef void (*filter_t)(FILTER_S *, int);
+typedef int (*linetest_t)(long, char *, LT_INS_S **, void *);
+typedef void (*htmlrisk_t)(void);
+typedef int (*prepedtest_t)(void);
+
+typedef struct filtlist_s {
+ filter_t filter;
+ void *data;
+} FILTLIST_S;
+
+
+typedef struct cbuf_s {
+ unsigned char cbuf[6]; /* used for converting to or from */
+ unsigned char *cbufp; /* locale-specific charset */
+ unsigned char *cbufend;
+} CBUF_S;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_FILTTYPE_INCLUDED */
diff --git a/pith/flag.c b/pith/flag.c
new file mode 100644
index 00000000..b1bbf9c4
--- /dev/null
+++ b/pith/flag.c
@@ -0,0 +1,758 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: flag.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ flag.c
+ Implements Pine message flag management routines
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/flag.h"
+#include "../pith/pineelt.h"
+#include "../pith/icache.h"
+#include "../pith/mailindx.h"
+#include "../pith/mailcmd.h"
+#include "../pith/msgno.h"
+#include "../pith/thread.h"
+#include "../pith/sort.h"
+#include "../pith/news.h"
+#include "../pith/sequence.h"
+
+
+/*
+ * Internal prototypes
+ */
+void flag_search(MAILSTREAM *, int, MsgNo, MSGNO_S *, long (*)(MAILSTREAM *));
+long flag_search_sequence(MAILSTREAM *, MSGNO_S *, long, int);
+
+
+
+/*----------------------------------------------------------------------
+ Return sequence number based on given index that are search-worthy
+
+ Args: stream --
+ msgmap --
+ msgno --
+ flags -- flags for msgline_hidden
+
+ Result: 0 : index not search-worthy
+ -1 : index out of bounds
+ 1 - stream->nmsgs : sequence number to flag search
+
+ ----*/
+long
+flag_search_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
+{
+ long rawno = msgno;
+
+ return((msgno > stream->nmsgs
+ || msgno <= 0
+ || (msgmap && !(rawno = mn_m2raw(msgmap, msgno))))
+ ? -1L /* out of range! */
+ : ((get_lflag(stream, NULL, rawno, MN_EXLD)
+ || (msgmap && msgline_hidden(stream, msgmap, msgno, flags)))
+ ? 0L /* NOT interesting! */
+ : rawno));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Perform mail_search based on flag bits
+
+ Args: stream --
+ flags --
+ start --
+ msgmap --
+
+ Result: if no set_* specified, call mail_search to light the searched
+ bit for all the messages matching the given flags. If set_start
+ specified, it is an index (possibly into set_msgmap) telling
+ us where to search for invalid flag state hence when we
+ return everything with the searched bit is interesting and
+ everything with the valid bit lit is believably valid.
+
+ ----*/
+void
+flag_search(MAILSTREAM *stream, int flags, MsgNo set_start, MSGNO_S *set_msgmap,
+ long int (*ping)(MAILSTREAM *))
+{
+ long n, i, new;
+ char *seq;
+ SEARCHPGM *pgm;
+ SEARCHSET *full_set = NULL, **set;
+ MESSAGECACHE *mc;
+ extern MAILSTREAM *mm_search_stream;
+
+ if(!stream)
+ return;
+
+ new = sp_new_mail_count(stream);
+
+ /* Anything we don't already have flags for? */
+ if(set_start){
+ /*
+ * Use elt's sequence bit to coalesce runs in ascending
+ * sequence order...
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ for(i = set_start;
+ (n = flag_search_sequence(stream, set_msgmap, i, MH_ANYTHD)) >= 0L;
+ (flags & F_SRCHBACK) ? i-- : i++)
+ if(n > 0L && n <= stream->nmsgs
+ && (mc = mail_elt(stream, n)) && !mc->valid)
+ mc->sequence = 1;
+
+ /* Unroll searchset in ascending sequence order */
+ set = &full_set;
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) && mc->sequence){
+ if(*set){
+ if(((*set)->last ? (*set)->last : (*set)->first)+1L == i)
+ (*set)->last = i;
+ else
+ set = &(*set)->next;
+ }
+
+ if(!*set){
+ *set = mail_newsearchset();
+ (*set)->first = i;
+ }
+ }
+
+ /*
+ * No search-worthy messsages?, prod the server for
+ * any flag updates and clear the searched bits...
+ */
+ if(full_set){
+ if(full_set->first == 1
+ && full_set->last == stream->nmsgs
+ && full_set->next == NULL)
+ mail_free_searchset(&full_set);
+ }
+ else{
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = 0;
+
+ if(ping)
+ (*ping)(stream); /* prod server for any flag updates */
+
+ if(!(flags & F_NOFILT) && new != sp_new_mail_count(stream)){
+ process_filter_patterns(stream, sp_msgmap(stream),
+ sp_new_mail_count(stream));
+
+ refresh_sort(stream, sp_msgmap(stream), SRT_NON);
+ flag_search(stream, flags, set_start, set_msgmap, ping);
+ }
+
+ return;
+ }
+ }
+
+ if((!is_imap_stream(stream) || modern_imap_stream(stream))
+ && !(IS_NEWS(stream))){
+ pgm = mail_newsearchpgm();
+
+ if(flags & F_SEEN)
+ pgm->seen = 1;
+
+ if(flags & F_UNSEEN)
+ pgm->unseen = 1;
+
+ if(flags & F_DEL)
+ pgm->deleted = 1;
+
+ if(flags & F_UNDEL)
+ pgm->undeleted = 1;
+
+ if(flags & F_ANS)
+ pgm->answered = 1;
+
+ if(flags & F_UNANS)
+ pgm->unanswered = 1;
+
+ if(flags & F_FLAG)
+ pgm->flagged = 1;
+
+ if(flags & F_UNFLAG)
+ pgm->unflagged = 1;
+
+ if(flags & (F_FWD | F_UNFWD)){
+ STRINGLIST **slpp;
+
+ for(slpp = (flags & F_FWD) ? &pgm->keyword : &pgm->unkeyword;
+ *slpp;
+ slpp = &(*slpp)->next)
+ ;
+
+ *slpp = mail_newstringlist();
+ (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
+ (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
+ }
+
+ if(flags & F_RECENT)
+ pgm->recent = 1;
+
+ if(flags & F_OR_SEEN){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->seen = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_UNSEEN){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->unseen = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_DEL){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->deleted = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_UNDEL){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->undeleted = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_FLAG){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->flagged = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_UNFLAG){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->unflagged = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_ANS){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->answered = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_UNANS){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->unanswered = 1;
+ pgm = pgm2;
+ }
+
+ if(flags & (F_OR_FWD | F_OR_UNFWD)){
+ STRINGLIST **slpp;
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+
+ for(slpp = (flags & F_OR_FWD)
+ ? &pgm2->or->second->keyword
+ : &pgm2->or->second->unkeyword;
+ *slpp;
+ slpp = &(*slpp)->next)
+ ;
+
+ *slpp = mail_newstringlist();
+ (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
+ (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
+
+ pgm = pgm2;
+ }
+
+ if(flags & F_OR_RECENT){
+ SEARCHPGM *pgm2 = mail_newsearchpgm();
+ pgm2->or = mail_newsearchor();
+ pgm2->or->first = pgm;
+ pgm2->or->second = mail_newsearchpgm();
+ pgm2->or->second->recent = 1;
+ pgm = pgm2;
+ }
+
+ pgm->msgno = full_set;
+
+ pine_mail_search_full(mm_search_stream = stream, NULL,
+ pgm, SE_NOPREFETCH | SE_FREE);
+
+ if(!(flags & F_NOFILT) && new != sp_new_mail_count(stream)){
+ process_filter_patterns(stream, sp_msgmap(stream),
+ sp_new_mail_count(stream));
+
+ flag_search(stream, flags, set_start, set_msgmap, ping);
+ }
+ }
+ else{
+ if(full_set){
+ /* sequence bits of interesting msgs set */
+ mail_free_searchset(&full_set);
+ }
+ else{
+ /* light sequence bits of interesting msgs */
+ for(i = 1L;
+ (n = flag_search_sequence(stream, set_msgmap, i, MH_ANYTHD)) >= 0L;
+ i++)
+ if(n > 0L && n <= stream->nmsgs
+ && (mc = mail_elt(stream, n)) && !mc->valid)
+ mc->sequence = 1;
+ else
+ mc->sequence = 0;
+ }
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = 0;
+
+ if((seq = build_sequence(stream, NULL, NULL)) != NULL){
+ pine_mail_fetch_flags(stream, seq, 0L);
+ fs_give((void **) &seq);
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ count messages on stream with specified system flag attributes
+
+ Args: stream -- The stream/folder to look at message status
+ flags -- flags on folder/stream to examine
+
+ Result: count of messages flagged as requested
+
+ Task: return count of message flagged as requested while being
+ as server/network friendly as possible.
+
+ Strategy: run thru flags to make sure they're all valid. If any
+ invalid, do a search starting with the invalid message.
+ If all valid, ping the server to let it know we're
+ receptive to flag updates. At this
+
+ ----------------------------------------------------------------------*/
+long
+count_flagged(MAILSTREAM *stream, long int flags)
+{
+ long n, count;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(0L);
+
+ flag_search(stream, flags, 1, NULL, pine_mail_ping);
+
+ /* Paw thru once more since all should be updated */
+ for(n = 1L, count = 0L; n <= stream->nmsgs; n++)
+ if((((mc = mail_elt(stream, n)) && mc->searched)
+ || (mc && mc->valid && FLAG_MATCH(flags, mc, stream)))
+ && !get_lflag(stream, NULL, n, MN_EXLD)){
+ mc->searched = 1; /* caller may be interested! */
+ count++;
+ }
+
+ return(count);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Find the first message with the specified flags set
+
+ Args: flags -- Flags in messagecache to match on
+ stream -- The stream/folder to look at message status
+
+ Result: Message number of first message with specified flags set or the
+ number of the last message if none found.
+ ----------------------------------------------------------------------*/
+MsgNo
+first_sorted_flagged(long unsigned int flags, MAILSTREAM *stream, long int set_start, int opts)
+{
+ MsgNo i, n, start_with, winner = 0L;
+ MESSAGECACHE *mc;
+ int last;
+ MSGNO_S *msgmap;
+
+ msgmap = sp_msgmap(stream);
+
+ last = (opts & FSF_LAST);
+
+ /* set_start only affects which search bits we light */
+ start_with = set_start ? set_start
+ : (flags & F_SRCHBACK)
+ ? mn_get_total(msgmap) : 1L;
+ flag_search(stream, flags, start_with, msgmap, NULL);
+
+ for(i = start_with;
+ (n = flag_search_sequence(stream, msgmap, i,
+ (opts & FSF_SKIP_CHID) ? 0 : MH_ANYTHD)) >= 0L;
+ (flags & F_SRCHBACK) ? i-- : i++)
+ if(n > 0L && n <= stream->nmsgs
+ && (((mc = mail_elt(stream, n)) && mc->searched)
+ || (mc && mc->valid && FLAG_MATCH(flags, mc, stream)))){
+ winner = i;
+ if(!last)
+ break;
+ }
+
+ if(winner == 0L && flags != F_UNDEL && flags != F_NONE){
+ dprint((4,
+ "First_sorted_flagged didn't find a winner, look for undeleted\n"));
+ winner = first_sorted_flagged(F_UNDEL, stream, 0L,
+ opts | (mn_get_revsort(msgmap) ? 0 : FSF_LAST));
+ }
+
+ if(winner == 0L && flags != F_NONE){
+ dprint((4,
+ "First_sorted_flagged didn't find an undeleted, look for visible\n"));
+ winner = first_sorted_flagged(F_NONE, stream, 0L,
+ opts | (mn_get_revsort(msgmap) ? 0 : FSF_LAST));
+ }
+
+ dprint((4,
+ "First_sorted_flagged returning winner = %ld\n", winner));
+ return(winner ? winner
+ : (mn_get_revsort(msgmap)
+ ? 1L : mn_get_total(msgmap)));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Find the next message with specified flags set
+
+ Args: flags -- Flags in messagecache to match on
+ stream -- The stream/folder to look at message status
+ start -- Start looking after this message
+ opts -- These bits are both input and output. On input the bit
+ NSF_TRUST_FLAGS tells us whether we need to ping or not.
+ On input, the bit NSF_SEARCH_BACK tells us that we want to
+ know about matches <= start if we don't find any > start.
+ On output, NSF_FLAG_MATCH is set if we matched a message.
+ Returns: Message number of the matched message, if any; else the start # or
+ the max_msgno if the mailbox changed dramatically.
+ ----------------------------------------------------------------------*/
+MsgNo
+next_sorted_flagged(long unsigned int flags, MAILSTREAM *stream, long int start, int *opts)
+{
+ MsgNo i, n, dir;
+ MESSAGECACHE *mc;
+ int rev, fss_flags = 0;
+ MSGNO_S *msgmap;
+
+ msgmap = sp_msgmap(stream);
+
+ /*
+ * Search for the next thing the caller's interested in...
+ */
+
+ fss_flags = (opts && *opts & NSF_SKIP_CHID) ? 0 : MH_ANYTHD;
+ rev = (opts && *opts & NSF_SEARCH_BACK);
+ dir = (rev ? -1L : 1L);
+
+ flag_search(stream, flags | (rev ? F_SRCHBACK : 0), start + dir,
+ msgmap,
+ (opts && ((*opts) & NSF_TRUST_FLAGS)) ? NULL : pine_mail_ping);
+
+ for(i = start + dir;
+ (n = flag_search_sequence(stream, msgmap,
+ i, fss_flags)) >= 0L;
+ i += dir)
+ if(n > 0L && n <= stream->nmsgs
+ && (((mc = mail_elt(stream, n)) && mc->searched)
+ || (mc && mc->valid && FLAG_MATCH(flags, mc, stream)))){
+ /* actually found a msg matching the flags */
+ if(opts)
+ (*opts) |= NSF_FLAG_MATCH;
+
+ return(i);
+ }
+
+
+ return(MIN(start, mn_get_total(msgmap)));
+}
+
+
+
+/*----------------------------------------------------------------------
+ get the requested LOCAL flag bits for the given pine message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - message number to get
+ f - bitmap of interesting flags
+ Returns: non-zero if flag set, 0 if not set or no elt (error?)
+
+ NOTE: this can be used to test system flags
+ ----*/
+int
+get_lflag(MAILSTREAM *stream, MSGNO_S *msgs, long int n, int f)
+{
+ MESSAGECACHE *mc;
+ PINELT_S *pelt;
+ unsigned long rawno;
+
+ rawno = msgs ? mn_m2raw(msgs, n) : n;
+ if(!stream || rawno < 1L || rawno > stream->nmsgs)
+ return(0);
+
+ mc = mail_elt(stream, rawno);
+ if(!mc || (pelt = (PINELT_S *) mc->sparep) == NULL)
+ return(f ? 0 : 1);
+
+ return((!f)
+ ? !(pelt->hidden || pelt->excluded || pelt->selected ||
+ pelt->colhid || pelt->collapsed || pelt->searched)
+ : (((f & MN_HIDE) ? pelt->hidden : 0)
+ || ((f & MN_EXLD) ? pelt->excluded : 0)
+ || ((f & MN_SLCT) ? pelt->selected : 0)
+ || ((f & MN_STMP) ? pelt->tmp : 0)
+ || ((f & MN_USOR) ? pelt->unsorted : 0)
+ || ((f & MN_COLL) ? pelt->collapsed : 0)
+ || ((f & MN_CHID) ? pelt->colhid : 0)
+ || ((f & MN_CHID2) ? pelt->colhid2 : 0)
+ || ((f & MN_SRCH) ? pelt->searched : 0)));
+}
+
+
+
+/*----------------------------------------------------------------------
+ set the requested LOCAL flag bits for the given pine message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - message number to set
+ f - bitmap of interesting flags
+ v - value (on or off) flag should get
+ Returns: our index number of first
+
+ NOTE: this isn't to be used for setting IMAP system flags
+ ----*/
+int
+set_lflag(MAILSTREAM *stream, MSGNO_S *msgs, long int n, int f, int v)
+{
+ MESSAGECACHE *mc;
+ long rawno = 0L;
+ PINETHRD_S *thrd, *topthrd = NULL;
+ PINELT_S **peltp, *pelt;
+
+ if(n < 1L || n > mn_get_total(msgs))
+ return(0L);
+
+ if((rawno=mn_m2raw(msgs, n)) > 0L && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))){
+ int was_invisible, is_invisible;
+ int chk_thrd_cnt = 0, thrd_was_visible, was_hidden, is_hidden;
+
+ if((*(peltp = (PINELT_S **) &mc->sparep) == NULL)){
+ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
+ memset(*peltp, 0, sizeof(PINELT_S));
+ }
+
+ pelt = (*peltp);
+
+ was_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0;
+
+ if((chk_thrd_cnt = ((msgs->visible_threads >= 0L)
+ && THRD_INDX_ENABLED() && (f & MN_HIDE) && (pelt->hidden != v))) != 0){
+ thrd = fetch_thread(stream, rawno);
+ if(thrd && thrd->top){
+ if(thrd->top == thrd->rawno)
+ topthrd = thrd;
+ else
+ topthrd = fetch_thread(stream, thrd->top);
+ }
+
+ if(topthrd){
+ thrd_was_visible = thread_has_some_visible(stream, topthrd);
+ was_hidden = pelt->hidden ? 1 : 0;
+ }
+ }
+
+ if((f & MN_HIDE) && pelt->hidden != v){
+ pelt->hidden = v;
+ msgs->flagged_hid += (v) ? 1L : -1L;
+
+ if(pelt->hidden && THREADING() && !THRD_INDX()
+ && stream == ps_global->mail_stream
+ && ps_global->thread_disp_style == THREAD_MUTTLIKE)
+ clear_index_cache_for_thread(stream, fetch_thread(stream, rawno),
+ sp_msgmap(stream));
+ }
+
+ if((f & MN_CHID) && pelt->colhid != v){
+ pelt->colhid = v;
+ msgs->flagged_chid += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_CHID2) && pelt->colhid2 != v){
+ pelt->colhid2 = v;
+ msgs->flagged_chid2 += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_COLL) && pelt->collapsed != v){
+ pelt->collapsed = v;
+ msgs->flagged_coll += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_USOR) && pelt->unsorted != v){
+ pelt->unsorted = v;
+ msgs->flagged_usor += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_EXLD) && pelt->excluded != v){
+ pelt->excluded = v;
+ msgs->flagged_exld += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_SLCT) && pelt->selected != v){
+ pelt->selected = v;
+ msgs->flagged_tmp += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_SRCH) && pelt->searched != v){
+ pelt->searched = v;
+ msgs->flagged_srch += (v) ? 1L : -1L;
+ }
+
+ if((f & MN_STMP) && pelt->tmp != v){
+ pelt->tmp = v;
+ msgs->flagged_stmp += (v) ? 1L : -1L;
+ }
+
+ is_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0;
+
+ if(was_invisible != is_invisible)
+ msgs->flagged_invisible += (v) ? 1L : -1L;
+
+ /*
+ * visible_threads keeps track of how many of the max_thrdno threads
+ * are visible and how many are MN_HIDE-hidden.
+ */
+ if(chk_thrd_cnt && topthrd
+ && (was_hidden != (is_hidden = pelt->hidden ? 1 : 0))){
+ if(!thrd_was_visible && !is_hidden){
+ /* it is visible now, increase count by one */
+ msgs->visible_threads++;
+ }
+ else if(thrd_was_visible && is_hidden){
+ /* thread may have been hidden, check */
+ if(!thread_has_some_visible(stream, topthrd))
+ msgs->visible_threads--;
+ }
+ /* else no change */
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * Copy value of flag from to flag to.
+ */
+void
+copy_lflags(MAILSTREAM *stream, MSGNO_S *msgmap, int from, int to)
+{
+ unsigned long i;
+ int hide;
+
+ hide = ((to == MN_SLCT) && (any_lflagged(msgmap, MN_HIDE) > 0L));
+
+ set_lflags(stream, msgmap, to, 0);
+
+ if(any_lflagged(msgmap, from))
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ if(get_lflag(stream, msgmap, i, from))
+ set_lflag(stream, msgmap, i, to, 1);
+ else if(hide)
+ set_lflag(stream, msgmap, i, MN_HIDE, 1);
+}
+
+
+/*
+ * Set flag f to value v in all message.
+ */
+void
+set_lflags(MAILSTREAM *stream, MSGNO_S *msgmap, int f, int v)
+{
+ unsigned long i;
+
+ if((v == 0 && any_lflagged(msgmap, f)) || v )
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ set_lflag(stream, msgmap, i, f, v);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return whether the given flag is set somewhere in the folder
+
+ Accepts: msgs - pointer to message manipulation struct
+ f - flag bitmap to act on
+ Returns: number of messages with the given flag set.
+ NOTE: the sum, if multiple flags tested, is bogus
+ ----*/
+long
+any_lflagged(MSGNO_S *msgs, int f)
+{
+ if(!msgs)
+ return(0L);
+
+ if(f == MN_NONE)
+ return(!(msgs->flagged_hid || msgs->flagged_exld || msgs->flagged_tmp ||
+ msgs->flagged_coll || msgs->flagged_chid || msgs->flagged_srch));
+ else if(f == (MN_HIDE | MN_CHID))
+ return(msgs->flagged_invisible); /* special non-bogus case */
+ else
+ return(((f & MN_HIDE) ? msgs->flagged_hid : 0L)
+ + ((f & MN_EXLD) ? msgs->flagged_exld : 0L)
+ + ((f & MN_SLCT) ? msgs->flagged_tmp : 0L)
+ + ((f & MN_SRCH) ? msgs->flagged_srch : 0L)
+ + ((f & MN_STMP) ? msgs->flagged_stmp : 0L)
+ + ((f & MN_COLL) ? msgs->flagged_coll : 0L)
+ + ((f & MN_USOR) ? msgs->flagged_usor : 0L)
+ + ((f & MN_CHID) ? msgs->flagged_chid : 0L)
+ + ((f & MN_CHID2) ? msgs->flagged_chid2 : 0L));
+}
diff --git a/pith/flag.h b/pith/flag.h
new file mode 100644
index 00000000..0e96e116
--- /dev/null
+++ b/pith/flag.h
@@ -0,0 +1,155 @@
+/*
+ * $Id: flag.h 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_FLAG_INCLUDED
+#define PITH_FLAG_INCLUDED
+
+
+#include "../pith/msgno.h"
+
+
+/*
+ * Useful def's to specify interesting flags
+ */
+#define F_NONE 0x00000000
+#define F_SEEN 0x00000001
+#define F_UNSEEN 0x00000002
+#define F_DEL 0x00000004
+#define F_UNDEL 0x00000008
+#define F_FLAG 0x00000010
+#define F_UNFLAG 0x00000020
+#define F_ANS 0x00000040
+#define F_UNANS 0x00000080
+#define F_FWD 0x00000100
+#define F_UNFWD 0x00000200
+#define F_RECENT 0x00000400
+#define F_UNRECENT 0x00000800
+#define F_DRAFT 0x00001000
+#define F_UNDRAFT 0x00002000
+
+#define F_SRCHBACK 0x00004000 /* search backwards instead of forw */
+#define F_NOFILT 0x00008000 /* defer processing filters */
+
+#define F_OR_SEEN 0x00010000
+#define F_OR_UNSEEN 0x00020000
+#define F_OR_DEL 0x00040000
+#define F_OR_UNDEL 0x00080000
+#define F_OR_FLAG 0x00100000
+#define F_OR_UNFLAG 0x00200000
+#define F_OR_ANS 0x00400000
+#define F_OR_UNANS 0x00800000
+#define F_OR_FWD 0x01000000
+#define F_OR_UNFWD 0x02000000
+#define F_OR_RECENT 0x04000000
+#define F_OR_UNRECENT 0x08000000
+
+#define F_KEYWORD 0x10000000
+#define F_UNKEYWORD 0x20000000
+
+#define F_COMMENT 0x40000000
+
+
+/*
+ * Useful flag checking macro
+ */
+#define FLAG_MATCH(F,M,S) (((((F)&F_SEEN) ? (M)->seen \
+ : ((F)&F_UNSEEN) ? !(M)->seen : 1) \
+ && (((F)&F_DEL) ? (M)->deleted \
+ : ((F)&F_UNDEL) ? !(M)->deleted : 1) \
+ && (((F)&F_ANS) ? (M)->answered \
+ : ((F)&F_UNANS) ? !(M)->answered : 1) \
+ && (((F)&F_FWD) ? ((S) && user_flag_is_set((S),(M)->msgno,FORWARDED_FLAG)) \
+ : ((F)&F_UNFWD) ? ((S) && !user_flag_is_set((S),(M)->msgno,FORWARDED_FLAG)) : 1) \
+ && (((F)&F_FLAG) ? (M)->flagged \
+ : ((F)&F_UNFLAG) ? !(M)->flagged : 1) \
+ && (((F)&F_RECENT) ? (M)->recent \
+ : ((F)&F_UNRECENT) ? !(M)->recent : 1)) \
+ || ((((F)&F_OR_SEEN) ? (M)->seen \
+ : ((F)&F_OR_UNSEEN) ? !(M)->seen : 0) \
+ || (((F)&F_OR_DEL) ? (M)->deleted \
+ : ((F)&F_OR_UNDEL) ? !(M)->deleted : 0) \
+ || (((F)&F_OR_ANS) ? (M)->answered \
+ : ((F)&F_OR_UNANS) ? !(M)->answered : 0) \
+ || (((F)&F_OR_FWD) ? ((S) && user_flag_is_set((S),(M)->msgno,FORWARDED_FLAG)) \
+ : ((F)&F_OR_UNFWD) ? !((S) && user_flag_is_set((S),(M)->msgno,FORWARDED_FLAG)) : 0) \
+ || (((F)&F_OR_FLAG)? (M)->flagged \
+ : ((F)&F_OR_UNFLAG) ? !(M)->flagged : 0) \
+ || (((F)&F_OR_RECENT)? (M)->recent \
+ : ((F)&F_OR_UNRECENT) ? !(M)->recent : 0)))
+
+
+/*
+ * These are def's to help manage local, private flags pine uses
+ * to maintain it's mapping table (see MSGNO_S def). The local flags
+ * are actually stored in spare bits in c-client's per-message
+ * MESSAGECACHE struct. But they're private, you ask. Since the flags
+ * are tied to the actual message (independent of the mapping), storing
+ * them in the c-client means we don't have to worry about them during
+ * sorting and such. See {set,get}_lflags for more on local flags.
+ *
+ * MN_HIDE hides messages which are not visible due to Zooming.
+ * MN_EXLD hides messages which have been filtered away.
+ * MN_SLCT marks messages which have been Selected.
+ * MN_SRCH marks messages that are the result of a search
+ * MN_COLL marks a point in the thread tree where the view has been
+ * collapsed, hiding the messages below that point
+ * MN_CHID hides messages which are collapsed out of view
+ * MN_CHID2 is similar to CHID and is introduced for performance reasons.
+ * When using the separate-thread-index the toplevel messages are
+ * MN_COLL and everything else is MN_CHID. However, if we view a
+ * single thread from there, instead of marking all of those top
+ * level threads MN_HIDE (or something) we change the semantics
+ * of the flags. When viewing a single thread we mark the messages
+ * of the thread with MN_CHID2 for performance reasons.
+ */
+#define MN_NONE 0x0000 /* No Pine specific flags */
+#define MN_HIDE 0x0001 /* Pine specific hidden */
+#define MN_EXLD 0x0002 /* Pine specific excluded */
+#define MN_SLCT 0x0004 /* Pine specific selected */
+#define MN_COLL 0x0008 /* Pine specific collapsed */
+#define MN_CHID 0x0010 /* A parent somewhere above us is collapsed */
+#define MN_CHID2 0x0020 /* performance related */
+#define MN_USOR 0x0040 /* New message which hasn't been sorted yet */
+#define MN_STMP 0x0080 /* Temporary storage for a per-message bit */
+#define MN_SRCH 0x0100 /* Search result */
+
+
+/* next_sorted_flagged options */
+#define NSF_TRUST_FLAGS 0x01 /* input flag, don't need to ping */
+#define NSF_SKIP_CHID 0x02 /* input flag, skip MN_CHID messages */
+#define NSF_SEARCH_BACK 0x04 /* input flag, search backward if none forw */
+#define NSF_FLAG_MATCH 0x08 /* return flag, actually got a match */
+
+/* first_sorted_flagged options */
+#define FSF_LAST 0x01 /* last instead of first */
+#define FSF_SKIP_CHID 0x02 /* skip MN_CHID messages */
+
+
+typedef long MsgNo;
+
+
+/* exported protoypes */
+long count_flagged(MAILSTREAM *, long);
+MsgNo first_sorted_flagged(unsigned long, MAILSTREAM *, long, int);
+MsgNo next_sorted_flagged(unsigned long, MAILSTREAM *, long, int *);
+int get_lflag(MAILSTREAM *, MSGNO_S *, long, int);
+int set_lflag(MAILSTREAM *, MSGNO_S *, long, int, int);
+void copy_lflags(MAILSTREAM *, MSGNO_S *, int, int);
+void set_lflags(MAILSTREAM *, MSGNO_S *, int, int);
+long any_lflagged(MSGNO_S *, int);
+
+
+#endif /* PITH_FLAG_INCLUDED */
diff --git a/pith/folder.c b/pith/folder.c
new file mode 100644
index 00000000..d07604a0
--- /dev/null
+++ b/pith/folder.c
@@ -0,0 +1,2759 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: folder.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../c-client/utf8aux.h"
+#include "../pith/folder.h"
+#include "../pith/state.h"
+#include "../pith/context.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/stream.h"
+#include "../pith/imap.h"
+#include "../pith/util.h"
+#include "../pith/flag.h"
+#include "../pith/status.h"
+#include "../pith/busy.h"
+#include "../pith/mailindx.h"
+
+
+typedef struct _build_folder_list_data {
+ long mask; /* bitmap of responses to ignore */
+ LISTARGS_S args;
+ LISTRES_S response;
+ int is_move_folder;
+ FLIST *list;
+} BFL_DATA_S;
+
+
+#define FCHUNK 64
+
+
+/*
+ * Internal prototypes
+ */
+void mail_list_exists(MAILSTREAM *, char *, int, long, void *, unsigned);
+void init_incoming_folder_list(struct pine *, CONTEXT_S *);
+void mail_list_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
+void mail_lsub_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
+int mail_list_in_collection(char **, char *, char *, char *);
+char *folder_last_cmpnt(char *, int);
+void free_folder_entries(FLIST **);
+int folder_insert_sorted(int, int, int, FOLDER_S *, FLIST *,
+ int (*)(FOLDER_S *, FOLDER_S *));
+void folder_insert_index(FOLDER_S *, int, FLIST *);
+void resort_folder_list(FLIST *flist);
+int compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
+int compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
+int compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2);
+int compare_names(const qsort_t *, const qsort_t *);
+void init_incoming_unseen_data(struct pine *, FOLDER_S *f);
+
+
+char *
+folder_lister_desc(CONTEXT_S *cntxt, FDIR_S *fdp)
+{
+ char *p, *q;
+ unsigned char *fname;
+
+ q = ((p = strstr(cntxt->context, "%s")) && !*(p+2)
+ && !strncmp(fdp->ref, cntxt->context, p - cntxt->context))
+ ? fdp->ref + (p - cntxt->context) : fdp->ref;
+ fname = folder_name_decoded((unsigned char *) q);
+ /* Provide context in new collection header */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Dir: %s", fname ? (char *) fname : q);
+ if(fname) fs_give((void **)&fname);
+
+ return(cpystr(tmp_20k_buf));
+}
+
+
+void
+reset_context_folders(CONTEXT_S *cntxt)
+{
+ CONTEXT_S *tc;
+
+ for(tc = cntxt; tc && tc->prev ; tc = tc->prev)
+ ; /* start at beginning */
+
+ for( ; tc ; tc = tc->next){
+ free_folder_list(tc);
+
+ while(tc->dir->prev){
+ FDIR_S *tp = tc->dir->prev;
+ free_fdir(&tc->dir, 0);
+ tc->dir = tp;
+ }
+ }
+}
+
+
+/*
+ * next_folder_dir - return a directory structure with the folders it
+ * contains
+ */
+FDIR_S *
+next_folder_dir(CONTEXT_S *context, char *new_dir, int build_list, MAILSTREAM **streamp)
+{
+ char tmp[MAILTMPLEN], dir[3];
+ FDIR_S *tmp_fp, *fp;
+
+ fp = (FDIR_S *) fs_get(sizeof(FDIR_S));
+ memset(fp, 0, sizeof(FDIR_S));
+ (void) context_apply(tmp, context, new_dir, MAILTMPLEN);
+ dir[0] = context->dir->delim;
+ dir[1] = '\0';
+ strncat(tmp, dir, sizeof(tmp)-1-strlen(tmp));
+ fp->ref = cpystr(tmp);
+ fp->delim = context->dir->delim;
+ fp->view.internal = cpystr(NEWS_TEST(context) ? "*" : "%");
+ fp->folders = init_folder_entries();
+ fp->status = CNTXT_NOFIND;
+ tmp_fp = context->dir; /* temporarily rebind */
+ context->dir = fp;
+
+ if(build_list)
+ build_folder_list(streamp, context, NULL, NULL,
+ NEWS_TEST(context) ? BFL_LSUB : BFL_NONE);
+
+ context->dir = tmp_fp;
+ return(fp);
+}
+
+
+/*
+ * Return which pinerc incoming folder #index is in.
+ */
+EditWhich
+config_containing_inc_fldr(FOLDER_S *folder)
+{
+ char **t;
+ int i, keep_going = 1, inheriting = 0;
+ struct variable *v = &ps_global->vars[V_INCOMING_FOLDERS];
+
+ if(v->is_fixed)
+ return(None);
+
+ /* is it in exceptions config? */
+ if(v->post_user_val.l){
+ for(i = 0, t=v->post_user_val.l; t[i]; i++){
+ if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0)){
+ keep_going = 0;
+
+ if(!strcmp(tmp_20k_buf, INHERIT))
+ inheriting = 1;
+ else if(folder->varhash == line_hash(tmp_20k_buf))
+ return(Post);
+ }
+ }
+ }
+
+ if(inheriting)
+ keep_going = 1;
+
+ /* is it in main config? */
+ if(keep_going && v->main_user_val.l){
+ for(i = 0, t=v->main_user_val.l; t[i]; i++){
+ if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0) &&
+ folder->varhash == line_hash(tmp_20k_buf))
+ return(Main);
+ }
+ }
+
+ return(None);
+}
+
+
+/*----------------------------------------------------------------------
+ Format the given folder name for display for the user
+
+ Args: folder -- The folder name to fix up
+
+Not sure this always makes it prettier. It could do nice truncation if we
+passed in a length. Right now it adds the path name of the mail
+subdirectory if appropriate.
+ ----*/
+
+char *
+pretty_fn(char *folder)
+{
+ if(!strucmp(folder, ps_global->inbox_name))
+ return(ps_global->inbox_name);
+ else
+ return(folder);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the path delimiter for the given folder on the given server
+
+ Args: folder -- folder type for delimiter
+
+ ----*/
+int
+get_folder_delimiter(char *folder)
+{
+ MM_LIST_S ldata;
+ LISTRES_S response;
+ int ourstream = 0;
+
+ memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
+ ldata.filter = mail_list_response;
+ memset(ldata.data = &response, 0, sizeof(LISTRES_S));
+
+ if(*folder == '{'
+ && !(ldata.stream = sp_stream_get(folder, SP_MATCH))
+ && !(ldata.stream = sp_stream_get(folder, SP_SAME))){
+ if((ldata.stream = pine_mail_open(NULL,folder,
+ OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE,
+ NULL)) != NULL){
+ ourstream++;
+ }
+ else{
+ return(FEX_ERROR);
+ }
+ }
+
+ pine_mail_list(ldata.stream, folder, "", NULL);
+
+ if(ourstream)
+ pine_mail_close(ldata.stream);
+
+ return(response.delim);
+}
+
+
+/*----------------------------------------------------------------------
+ Check to see if folder exists in given context
+
+ Args: cntxt -- context inwhich to interpret "file" arg
+ file -- name of folder to check
+
+ Result: returns FEX_ISFILE if the folder exists and is a folder
+ FEX_ISDIR if the folder exists and is a directory
+ FEX_NOENT if it doesn't exist
+ FEX_ERROR on error
+
+ The two existence return values above may be logically OR'd
+
+ Uses mail_list to sniff out the existence of the requested folder.
+ The context string is just here for convenience. Checking for
+ folder's existence within a given context is probably more efficiently
+ handled outside this function for now using build_folder_list().
+
+ ----*/
+int
+folder_exists(CONTEXT_S *cntxt, char *file)
+{
+ return(folder_name_exists(cntxt, file, NULL));
+}
+
+
+/*----------------------------------------------------------------------
+ Check to see if folder exists in given context
+
+ Args: cntxt -- context in which to interpret "file" arg
+ file -- name of folder to check
+ name -- name of folder folder with context applied
+
+ Result: returns FEX_ISFILE if the folder exists and is a folder
+ FEX_ISDIR if the folder exists and is a directory
+ FEX_NOENT if it doesn't exist
+ FEX_ERROR on error
+
+ The two existence return values above may be logically OR'd
+
+ Uses mail_list to sniff out the existence of the requested folder.
+ The context string is just here for convenience. Checking for
+ folder's existence within a given context is probably more efficiently
+ handled outside this function for now using build_folder_list().
+
+ ----*/
+int
+folder_name_exists(CONTEXT_S *cntxt, char *file, char **fullpath)
+{
+ MM_LIST_S ldata;
+ EXISTDATA_S parms;
+ int we_cancel = 0, res;
+ char *p, reference[MAILTMPLEN], tmp[MAILTMPLEN], *tfolder = NULL;
+
+ /*
+ * No folder means "inbox".
+ */
+ if(*file == '{' && (p = strchr(file, '}')) && (!*(p+1))){
+ size_t l;
+
+ l = strlen(file)+strlen("inbox");
+ tfolder = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(tfolder, l+1, "%s%s", file, "inbox");
+ file = tfolder;
+ cntxt = NULL;
+ }
+
+ mm_list_info = &ldata; /* tie down global reference */
+ memset(&ldata, 0, sizeof(ldata));
+ ldata.filter = mail_list_exists;
+
+ ldata.stream = sp_stream_get(context_apply(tmp, cntxt, file, sizeof(tmp)),
+ SP_SAME);
+
+ memset(ldata.data = &parms, 0, sizeof(EXISTDATA_S));
+
+ /*
+ * If no preset reference string, must be at top of context
+ */
+ if(cntxt && context_isambig(file)){
+ /* inbox in first context is the real inbox */
+ if(ps_global->context_list == cntxt && !strucmp(file, ps_global->inbox_name)){
+ reference[0] = '\0';
+ parms.args.reference = reference;
+ }
+ else if(!(parms.args.reference = cntxt->dir->ref)){
+ char *p;
+
+ if((p = strstr(cntxt->context, "%s")) != NULL){
+ strncpy(parms.args.reference = reference,
+ cntxt->context,
+ MIN(p - cntxt->context, sizeof(reference)-1));
+ reference[MIN(p - cntxt->context, sizeof(reference)-1)] = '\0';
+ if(*(p += 2))
+ parms.args.tail = p;
+ }
+ else
+ parms.args.reference = cntxt->context;
+ }
+
+ parms.fullname = fullpath;
+ }
+
+ ps_global->mm_log_error = 0;
+ ps_global->noshow_error = 1;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ parms.args.name = file;
+
+ res = pine_mail_list(ldata.stream, parms.args.reference, parms.args.name,
+ &ldata.options);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ ps_global->noshow_error = 0;
+
+ if(cntxt && cntxt->dir && parms.response.delim)
+ cntxt->dir->delim = parms.response.delim;
+
+ if(tfolder)
+ fs_give((void **)&tfolder);
+ return(((res == FALSE) || ps_global->mm_log_error)
+ ? FEX_ERROR
+ : (((parms.response.isfile)
+ ? FEX_ISFILE : 0 )
+ | ((parms.response.isdir)
+ ? FEX_ISDIR : 0)
+ | ((parms.response.ismarked)
+ ? FEX_ISMARKED : 0)
+ | ((parms.response.unmarked)
+ ? FEX_UNMARKED : 0)));
+}
+
+
+void
+mail_list_exists(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
+ void *data, unsigned int options)
+{
+ if(delim)
+ ((EXISTDATA_S *) data)->response.delim = delim;
+
+ if(mailbox && *mailbox){
+ if(!(attribs & LATT_NOSELECT)){
+ ((EXISTDATA_S *) data)->response.isfile = 1;
+ ((EXISTDATA_S *) data)->response.count += 1;
+ }
+
+ if(!(attribs & LATT_NOINFERIORS)){
+ ((EXISTDATA_S *) data)->response.isdir = 1;
+ ((EXISTDATA_S *) data)->response.count += 1;
+ }
+
+ if(attribs & LATT_MARKED)
+ ((EXISTDATA_S *) data)->response.ismarked = 1;
+
+ /* don't mark #move folders unmarked */
+ if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
+ ((EXISTDATA_S *) data)->response.unmarked = 1;
+
+ if(attribs & LATT_HASCHILDREN)
+ ((EXISTDATA_S *) data)->response.haschildren = 1;
+
+ if(attribs & LATT_HASNOCHILDREN)
+ ((EXISTDATA_S *) data)->response.hasnochildren = 1;
+
+ if(((EXISTDATA_S *) data)->fullname
+ && ((((EXISTDATA_S *) data)->args.reference[0] != '\0')
+ ? struncmp(((EXISTDATA_S *) data)->args.reference, mailbox,
+ strlen(((EXISTDATA_S *)data)->args.reference))
+ : struncmp(((EXISTDATA_S *) data)->args.name, mailbox,
+ strlen(((EXISTDATA_S *) data)->args.name)))){
+ char *p;
+ size_t alloclen;
+ size_t len = (((stream && stream->mailbox)
+ ? strlen(stream->mailbox) : 0)
+ + strlen(((EXISTDATA_S *) data)->args.reference)
+ + strlen(((EXISTDATA_S *) data)->args.name)
+ + strlen(mailbox)) * sizeof(char);
+
+ /*
+ * Fully qualify (in the c-client name structure sense)
+ * anything that's not in the context of the "reference"...
+ */
+ if(*((EXISTDATA_S *) data)->fullname)
+ fs_give((void **) ((EXISTDATA_S *) data)->fullname);
+
+ alloclen = len;
+ *((EXISTDATA_S *) data)->fullname = (char *) fs_get(alloclen);
+ if(*mailbox != '{'
+ && stream && stream->mailbox && *stream->mailbox == '{'
+ && (p = strindex(stream->mailbox, '}'))){
+ len = (p - stream->mailbox) + 1;
+ strncpy(*((EXISTDATA_S *) data)->fullname,
+ stream->mailbox, MIN(len,alloclen));
+ p = *((EXISTDATA_S *) data)->fullname + len;
+ }
+ else
+ p = *((EXISTDATA_S *) data)->fullname;
+
+ strncpy(p, mailbox, alloclen-(p-(*((EXISTDATA_S *) data)->fullname)));
+ (*((EXISTDATA_S *) data)->fullname)[alloclen-1] = '\0';;
+ }
+ }
+}
+
+
+void
+mail_list_response(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
+ void *data, unsigned int options)
+{
+ int counted = 0;
+
+ if(delim)
+ ((LISTRES_S *) data)->delim = delim;
+
+ if(mailbox && *mailbox){
+ if(!(attribs & LATT_NOSELECT)){
+ counted++;
+ ((LISTRES_S *) data)->isfile = 1;
+ ((LISTRES_S *) data)->count += 1;
+ }
+
+ if(!(attribs & LATT_NOINFERIORS)){
+ ((LISTRES_S *) data)->isdir = 1;
+
+ if(!counted)
+ ((LISTRES_S *) data)->count += 1;
+ }
+
+ if(attribs & LATT_HASCHILDREN)
+ ((LISTRES_S *) data)->haschildren = 1;
+
+ if(attribs & LATT_HASNOCHILDREN)
+ ((LISTRES_S *) data)->hasnochildren = 1;
+ }
+}
+
+
+char *
+folder_as_breakout(CONTEXT_S *cntxt, char *name)
+{
+ if(context_isambig(name)){ /* if simple check doesn't pan out */
+ char tmp[2*MAILTMPLEN], *p, *f; /* look harder */
+
+ if(!cntxt->dir->delim){
+ (void) context_apply(tmp, cntxt, "", sizeof(tmp)/2);
+ cntxt->dir->delim = get_folder_delimiter(tmp);
+ }
+
+ if((p = strindex(name, cntxt->dir->delim)) != NULL){
+ if(p == name){ /* assumption 6,321: delim is root */
+ if(cntxt->context[0] == '{'
+ && (p = strindex(cntxt->context, '}'))){
+ strncpy(tmp, cntxt->context,
+ MIN((p - cntxt->context) + 1, sizeof(tmp)/2));
+ tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)] = '\0';
+ strncpy(&tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)],
+ name, sizeof(tmp)/2-strlen(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ return(cpystr(tmp));
+ }
+ else
+ return(cpystr(name));
+ }
+ else{ /* assumption 6,322: no create ~foo */
+ strncpy(tmp, name, MIN(p - name, MAILTMPLEN));
+ /* lop off trailingpath */
+ tmp[MIN(p - name, sizeof(tmp)/2)] = '\0';
+ f = NULL;
+ (void)folder_name_exists(cntxt, tmp, &f);
+ if(f){
+ snprintf(tmp, sizeof(tmp), "%s%s",f,p);
+ tmp[sizeof(tmp)-1] = '\0';
+ fs_give((void **) &f);
+ return(cpystr(tmp));
+ }
+ }
+ }
+ }
+
+ return(NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Initialize global list of contexts for folder collections.
+
+ Interprets collections defined in the pinerc and orders them for
+ pine's use. Parses user-provided context labels and sets appropriate
+ use flags and the default prototype for that collection.
+ (See find_folders for how the actual folder list is found).
+
+ ----*/
+void
+init_folders(struct pine *ps)
+{
+ CONTEXT_S *tc, *top = NULL, **clist;
+ int i, prime = 0;
+
+ clist = &top;
+
+ /*
+ * If no incoming folders are config'd, but the user asked for
+ * them via feature, make sure at least "inbox" ends up there...
+ */
+ if(F_ON(F_ENABLE_INCOMING, ps) && !ps->VAR_INCOMING_FOLDERS){
+ ps->VAR_INCOMING_FOLDERS = (char **)fs_get(2 * sizeof(char *));
+ ps->VAR_INCOMING_FOLDERS[0] = cpystr(ps->inbox_name);
+ ps->VAR_INCOMING_FOLDERS[1] = NULL;
+ }
+
+ /*
+ * Build context that's a list of folders the user's defined
+ * as receiveing new messages. At some point, this should
+ * probably include adding a prefix with the new message count.
+ * fake new context...
+ */
+ if(ps->VAR_INCOMING_FOLDERS && ps->VAR_INCOMING_FOLDERS[0]
+ && (tc = new_context("Incoming-Folders []", NULL))){
+ tc->dir->status &= ~CNTXT_NOFIND;
+ tc->use |= CNTXT_INCMNG; /* mark this as incoming collection */
+ if(tc->label)
+ fs_give((void **) &tc->label);
+
+ /* TRANSLATORS: a label */
+ tc->label = cpystr(_("Incoming Message Folders"));
+
+ *clist = tc;
+ clist = &tc->next;
+
+ init_incoming_folder_list(ps, tc);
+ }
+
+ /*
+ * Build list of folder collections. Because of the way init.c
+ * works, we're guaranteed at least a default. Also write any
+ * "bogus format" messages...
+ */
+ for(i = 0; ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[i] ; i++)
+ if((tc = new_context(ps->VAR_FOLDER_SPEC[i], &prime)) != NULL){
+ *clist = tc; /* add it to list */
+ clist = &tc->next; /* prepare for next */
+ tc->var.v = &ps->vars[V_FOLDER_SPEC];
+ tc->var.i = i;
+ }
+
+
+ /*
+ * Whoah cowboy!!! Guess we couldn't find a valid folder
+ * collection???
+ */
+ if(!prime)
+ panic(_("No folder collections defined"));
+
+ /*
+ * At this point, insert the INBOX mapping as the leading
+ * folder entry of the first collection...
+ */
+ init_inbox_mapping(ps->VAR_INBOX_PATH, top);
+
+ set_news_spec_current_val(TRUE, TRUE);
+
+ /*
+ * If news groups, loop thru list adding to collection list
+ */
+ for(i = 0; ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[i] ; i++)
+ if(ps->VAR_NEWS_SPEC[i][0]
+ && (tc = new_context(ps->VAR_NEWS_SPEC[i], NULL))){
+ *clist = tc; /* add it to list */
+ clist = &tc->next; /* prepare for next */
+ tc->var.v = &ps->vars[V_NEWS_SPEC];
+ tc->var.i = i;
+ }
+
+ ps->context_list = top; /* init pointers */
+ ps->context_current = (top->use & CNTXT_INCMNG) ? top->next : top;
+ ps->context_last = NULL;
+ /* Tie up all the previous pointers */
+ for(; top; top = top->next)
+ if(top->next)
+ top->next->prev = top;
+
+#ifdef DEBUG
+ dump_contexts();
+#endif
+}
+
+
+/*
+ * Add incoming list of folders to context.
+ */
+void
+init_incoming_folder_list(struct pine *ps, CONTEXT_S *cntxt)
+{
+ int i;
+ char *folder_string, *nickname;
+ FOLDER_S *f;
+
+ for(i = 0; ps->VAR_INCOMING_FOLDERS[i] ; i++){
+ /*
+ * Parse folder line for nickname and folder name.
+ * No nickname on line is OK.
+ */
+ get_pair(ps->VAR_INCOMING_FOLDERS[i], &nickname, &folder_string,0,0);
+
+ /*
+ * Allow for inbox to be specified in the incoming list, but
+ * don't let it show up along side the one magically inserted
+ * above!
+ */
+ if(!folder_string || !strucmp(ps->inbox_name, folder_string)){
+ if(folder_string)
+ fs_give((void **)&folder_string);
+
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ continue;
+ }
+ else if(update_bboard_spec(folder_string, tmp_20k_buf, SIZEOF_20KBUF)){
+ fs_give((void **) &folder_string);
+ folder_string = cpystr(tmp_20k_buf);
+ }
+
+ f = new_folder(folder_string,
+ line_hash(ps->VAR_INCOMING_FOLDERS[i]));
+ f->isfolder = 1;
+ fs_give((void **)&folder_string);
+
+ if(nickname){
+ if(strucmp(ps->inbox_name, nickname)){
+ f->nickname = nickname;
+ f->name_len = strlen(f->nickname);
+ }
+ else
+ fs_give((void **)&nickname);
+ }
+
+ init_incoming_unseen_data(ps, f);
+
+ folder_insert(f->nickname
+ && (strucmp(f->nickname, ps->inbox_name) == 0)
+ ? -1 : folder_total(FOLDERS(cntxt)),
+ f, FOLDERS(cntxt));
+ }
+}
+
+
+void
+init_incoming_unseen_data(struct pine *ps, FOLDER_S *f)
+{
+ int j, check_this = 0;
+
+ if(!f)
+ return;
+
+ /* see if this folder is in the monitoring list */
+ if(F_ON(F_ENABLE_INCOMING_CHECKING, ps)){
+ if(!ps->VAR_INCCHECKLIST)
+ check_this++; /* everything in by default */
+ else{
+ for(j = 0; !check_this && ps->VAR_INCCHECKLIST[j]; j++){
+ if((f->nickname && !strucmp(ps->VAR_INCCHECKLIST[j],f->nickname))
+ || (f->name && !strucmp(ps->VAR_INCCHECKLIST[j],f->name)))
+ check_this++;
+ }
+ }
+ }
+
+ if(check_this)
+ f->last_unseen_update = LUU_INIT;
+ else
+ f->last_unseen_update = LUU_NEVERCHK;
+}
+
+
+void
+reinit_incoming_folder_list(struct pine *ps, CONTEXT_S *context)
+{
+ free_folder_entries(&(FOLDERS(context)));
+ FOLDERS(context) = init_folder_entries();
+ init_incoming_folder_list(ps_global, context);
+ init_inbox_mapping(ps_global->VAR_INBOX_PATH, context);
+}
+
+
+/*
+ *
+ */
+void
+init_inbox_mapping(char *path, CONTEXT_S *cntxt)
+{
+ FOLDER_S *f;
+ int check_this, j;
+
+ /*
+ * If mapping already exists, blast it and replace it below...
+ */
+ if((f = folder_entry(0, FOLDERS(cntxt)))
+ && f->nickname && !strcmp(f->nickname, ps_global->inbox_name))
+ folder_delete(0, FOLDERS(cntxt));
+
+ if(path){
+ f = new_folder(path, 0);
+ f->nickname = cpystr(ps_global->inbox_name);
+ f->name_len = strlen(f->nickname);
+ }
+ else
+ f = new_folder(ps_global->inbox_name, 0);
+
+ f->isfolder = 1;
+
+ /* see if this folder is in the monitoring list */
+ check_this = 0;
+ if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) && ps_global->VAR_INCOMING_FOLDERS && ps_global->VAR_INCOMING_FOLDERS[0]){
+ if(!ps_global->VAR_INCCHECKLIST)
+ check_this++; /* everything in by default */
+ else{
+ for(j = 0; !check_this && ps_global->VAR_INCCHECKLIST[j]; j++){
+ if((f->nickname && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->nickname))
+ || (f->name && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->name)))
+ check_this++;
+ }
+ }
+ }
+
+ if(check_this)
+ f->last_unseen_update = LUU_INIT;
+ else
+ f->last_unseen_update = LUU_NEVERCHK;
+
+ folder_insert(0, f, FOLDERS(cntxt));
+}
+
+
+FDIR_S *
+new_fdir(char *ref, char *view, int wildcard)
+{
+ FDIR_S *rv = (FDIR_S *) fs_get(sizeof(FDIR_S));
+
+ memset((void *) rv, 0, sizeof(FDIR_S));
+
+ /* Monkey with the view to make sure it has wildcard? */
+ if(view && *view){
+ rv->view.user = cpystr(view);
+
+ /*
+ * This is sorta hairy since, for simplicity we allow
+ * users to use '*' in the view, but for mail
+ * we really mean '%' as def'd in 2060...
+ */
+ if(wildcard == '*'){ /* must be #news. */
+ if(strindex(view, wildcard))
+ rv->view.internal = cpystr(view);
+ }
+ else{ /* must be mail */
+ char *p;
+
+ if((p = strpbrk(view, "*%")) != NULL){
+ rv->view.internal = p = cpystr(view);
+ while((p = strpbrk(p, "*%")) != NULL)
+ *p++ = '%'; /* convert everything to '%' */
+ }
+ }
+
+ if(!rv->view.internal){
+ size_t l;
+
+ l = strlen(view)+2;
+ rv->view.internal = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(rv->view.internal, l+1, "%c%s%c", wildcard, view, wildcard);
+ }
+ }
+ else{
+ rv->view.internal = (char *) fs_get(2 * sizeof(char));
+ snprintf(rv->view.internal, 2, "%c", wildcard);
+ }
+
+ if(ref)
+ rv->ref = ref;
+
+ rv->folders = init_folder_entries();
+ rv->status = CNTXT_NOFIND;
+ return(rv);
+}
+
+
+void
+free_fdir(FDIR_S **f, int recur)
+{
+ if(f && *f){
+ if((*f)->prev && recur)
+ free_fdir(&(*f)->prev, 1);
+
+ if((*f)->ref)
+ fs_give((void **)&(*f)->ref);
+
+ if((*f)->view.user)
+ fs_give((void **)&(*f)->view.user);
+
+ if((*f)->view.internal)
+ fs_give((void **)&(*f)->view.internal);
+
+ if((*f)->desc)
+ fs_give((void **)&(*f)->desc);
+
+ free_folder_entries(&(*f)->folders);
+ fs_give((void **) f);
+ }
+}
+
+
+int
+update_bboard_spec(char *bboard, char *buf, size_t buflen)
+{
+ char *p = NULL, *origbuf;
+ int nntp = 0, bracket = 0;
+
+ origbuf = buf;
+
+ if(*bboard == '*'
+ || (*bboard == '{' && (p = strindex(bboard, '}')) && *(p+1) == '*')){
+ /* server name ? */
+ if(p || (*(bboard+1) == '{' && (p = strindex(++bboard, '}'))))
+ while(bboard <= p && (buf-origbuf < buflen)) /* copy it */
+ if((*buf++ = *bboard++) == '/' && !strncmp(bboard, "nntp", 4))
+ nntp++;
+
+ if(*bboard == '*')
+ bboard++;
+
+ if(!nntp)
+ /*
+ * See if path portion looks newsgroup-ish while being aware
+ * of the "view" portion of the spec...
+ */
+ for(p = bboard; *p; p++)
+ if(*p == '['){
+ if(bracket) /* only one set allowed! */
+ break;
+ else
+ bracket++;
+ }
+ else if(*p == ']'){
+ if(bracket != 1) /* must be closing bracket */
+ break;
+ else
+ bracket++;
+ }
+ else if(!(isalnum((unsigned char) *p) || strindex(".-", *p)))
+ break;
+
+ snprintf(buf, buflen-(buf-origbuf), "%s%s%s",
+ (!nntp && *p) ? "#public" : "#news.",
+ (!nntp && *p && *bboard != '/') ? "/" : "",
+ bboard);
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * build_folder_list - call mail_list to fetch us a list of folders
+ * from the given context.
+ */
+void
+build_folder_list(MAILSTREAM **stream, CONTEXT_S *context, char *pat, char *content, int flags)
+{
+ MM_LIST_S ldata;
+ BFL_DATA_S response;
+ int local_open = 0, we_cancel = 0, resort = 0;
+ char reference[2*MAILTMPLEN], *p;
+
+ if(!(context->dir->status & CNTXT_NOFIND)
+ || (context->dir->status & CNTXT_PARTFIND))
+ return; /* find already done! */
+
+ dprint((7, "build_folder_list: %s %s\n",
+ context ? context->context : "NULL",
+ pat ? pat : "NULL"));
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ /*
+ * Set up the pattern of folder name's to match within the
+ * given context.
+ */
+ if(!pat || ((*pat == '*' || *pat == '%') && *(pat+1) == '\0')){
+ context->dir->status &= ~CNTXT_NOFIND; /* let'em know we tried */
+ pat = context->dir->view.internal;
+ }
+ else
+ context->use |= CNTXT_PARTFIND; /* or are in a partial find */
+
+ memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
+ ldata.filter = (NEWS_TEST(context)) ? mail_lsub_filter : mail_list_filter;
+ memset(ldata.data = &response, 0, sizeof(BFL_DATA_S));
+ response.list = FOLDERS(context);
+
+ if(flags & BFL_FLDRONLY)
+ response.mask = LATT_NOSELECT;
+
+ /*
+ * if context is associated with a server, prepare a stream for
+ * sending our request.
+ */
+ if(*context->context == '{'){
+ /*
+ * Try using a stream we've already got open...
+ */
+ if(stream && *stream
+ && !(ldata.stream = same_stream(context->context, *stream))){
+ pine_mail_close(*stream);
+ *stream = NULL;
+ }
+
+ if(!ldata.stream)
+ ldata.stream = sp_stream_get(context->context, SP_MATCH);
+
+ if(!ldata.stream)
+ ldata.stream = sp_stream_get(context->context, SP_SAME);
+
+ /* gotta open a new one? */
+ if(!ldata.stream){
+ ldata.stream = mail_cmd_stream(context, &local_open);
+ if(stream)
+ *stream = ldata.stream;
+ }
+
+ dprint((ldata.stream ? 7 : 1, "build_folder_list: mail_open(%s) %s.\n",
+ context->server ? context->server : "?",
+ ldata.stream ? "OK" : "FAILED"));
+
+ if(!ldata.stream){
+ context->use &= ~CNTXT_PARTFIND; /* unset partial find bit */
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return;
+ }
+ }
+ else if(stream && *stream){ /* no server, simple case */
+ if(!sp_flagged(*stream, SP_LOCKED))
+ pine_mail_close(*stream);
+
+ *stream = NULL;
+ }
+
+ /*
+ * If preset reference string, we're somewhere in the hierarchy.
+ * ELSE we must be at top of context...
+ */
+ response.args.name = pat;
+ if(!(response.args.reference = context->dir->ref)){
+ if((p = strstr(context->context, "%s")) != NULL){
+ strncpy(response.args.reference = reference,
+ context->context,
+ MIN(p - context->context, sizeof(reference)/2));
+ reference[MIN(p - context->context, sizeof(reference)/2)] = '\0';
+ if(*(p += 2))
+ response.args.tail = p;
+ }
+ else
+ response.args.reference = context->context;
+ }
+
+ if(flags & BFL_SCAN)
+ mail_scan(ldata.stream, response.args.reference,
+ response.args.name, content);
+ else if(flags & BFL_LSUB)
+ mail_lsub(ldata.stream, response.args.reference, response.args.name);
+ else{
+ set_read_predicted(1);
+ pine_mail_list(ldata.stream, response.args.reference, response.args.name,
+ &ldata.options);
+ set_read_predicted(0);
+ }
+
+ if(context->dir && response.response.delim)
+ context->dir->delim = response.response.delim;
+
+ if(!(flags & (BFL_LSUB|BFL_SCAN)) && F_ON(F_QUELL_EMPTY_DIRS, ps_global)){
+ LISTRES_S listres;
+ FOLDER_S *f;
+ int i;
+
+ for(i = 0; i < folder_total(response.list); i++)
+ if((f = folder_entry(i, FOLDERS(context)))->isdir){
+ /*
+ * we don't need to do a list if we know already
+ * whether there are children or not.
+ */
+ if(!f->haschildren && !f->hasnochildren){
+ memset(ldata.data = &listres, 0, sizeof(LISTRES_S));
+ ldata.filter = mail_list_response;
+
+ if(context->dir->ref)
+ snprintf(reference, sizeof(reference), "%s%s", context->dir->ref, f->name);
+ else
+ context_apply(reference, context, f->name, sizeof(reference)/2);
+
+ /* append the delimiter to the reference */
+ for(p = reference; *p; p++)
+ ;
+
+ *p++ = context->dir->delim;
+ *p = '\0';
+
+ pine_mail_list(ldata.stream, reference, "%", NULL);
+
+ /* anything interesting inside? */
+ f->hasnochildren = (listres.count <= 1L);
+ f->haschildren = !f->hasnochildren;;
+ }
+
+ if(f->hasnochildren){
+ if(f->isfolder){
+ f->isdir = 0;
+ if(ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST
+ || ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
+ resort = 1;
+ }
+ /*
+ * We don't want to hide directories
+ * that are only directories even if they are
+ * empty. We only want to hide the directory
+ * piece of a dual-use folder when there are
+ * no children in the directory (and the user
+ * most likely thinks of it as a folder instead
+ * of a folder and a directory).
+ */
+ else if(f->isdir && f->isdual){
+ folder_delete(i, FOLDERS(context));
+ i--;
+ }
+ }
+ }
+ }
+
+ if(resort)
+ resort_folder_list(response.list);
+
+ if(local_open && !stream)
+ pine_mail_close(ldata.stream);
+
+ if(context->use & CNTXT_PRESRV)
+ folder_select_restore(context);
+
+ context->use &= ~CNTXT_PARTFIND; /* unset partial list bit */
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * Validate LIST response to command issued from build_folder_list
+ *
+ */
+void
+mail_list_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, void *data, unsigned int options)
+{
+ BFL_DATA_S *ld = (BFL_DATA_S *) data;
+ FOLDER_S *new_f = NULL, *dual_f = NULL;
+ int suppress_folder_add = 0;
+
+ if(!ld)
+ return;
+
+ if(delim)
+ ld->response.delim = delim;
+
+ /* test against mask of DIS-allowed attributes */
+ if((ld->mask & attribs)){
+ dprint((3, "mail_list_filter: failed attribute test"));
+ return;
+ }
+
+ /*
+ * First, make sure response fits our "reference" arg
+ * NOTE: build_folder_list can't supply breakout?
+ */
+ if(!mail_list_in_collection(&mailbox, ld->args.reference,
+ ld->args.name, ld->args.tail))
+ return;
+
+ /* ignore dotfolders unless told not to */
+ if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global) && *mailbox == '.'){
+ dprint((3, "mail_list_filter: dotfolder disallowed"));
+ return;
+ }
+
+ /*
+ * If this response is INBOX we have to handle it specially.
+ * The special cases are:
+ *
+ * Incoming folders are enabled and this INBOX is in the Incoming
+ * folders list already. We don't want to list it here, as well,
+ * unless it is also a directory.
+ *
+ * Incoming folders are not enabled, but we inserted INBOX into
+ * this primary collection (with init_inbox_mapping()) so it is
+ * already accounted for unless it is also a directory.
+ *
+ * Incoming folders are not enabled, but we inserted INBOX into
+ * the primary collection (which we are not looking at here) so
+ * it is already accounted for there. We'll add it as a directory
+ * as well here if that is what is called for.
+ */
+ if(!strucmp(mailbox, ps_global->inbox_name)){
+ int ftotal, i;
+ FOLDER_S *f;
+ char fullname[1000], tmp1[1000], tmp2[1000], *l1, *l2;
+
+ /* fullname is the name of the mailbox in the list response */
+ if(ld->args.reference){
+ strncpy(fullname, ld->args.reference, sizeof(fullname)-1);
+ fullname[sizeof(fullname)-1] = '\0';
+ }
+ else
+ fullname[0] = '\0';
+
+ strncat(fullname, mailbox, sizeof(fullname)-strlen(fullname)-1);
+ fullname[sizeof(fullname)-1] = '\0';
+
+ /* check if Incoming Folders are enabled */
+ if(ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG){
+ int this_inbox_is_in_incoming = 0;
+
+ /*
+ * Figure out if this INBOX is already in the Incoming list.
+ */
+
+ /* compare fullname to each incoming folder */
+ ftotal = folder_total(FOLDERS(ps_global->context_list));
+ for(i = 0; i < ftotal && !this_inbox_is_in_incoming; i++){
+ f = folder_entry(i, FOLDERS(ps_global->context_list));
+ if(f && f->name){
+ if(same_remote_mailboxes(fullname, f->name)
+ || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
+ && (l1=mailboxfile(tmp1,fullname))
+ && (l2=mailboxfile(tmp2,f->name))
+ && !strcmp(l1,l2)))
+ this_inbox_is_in_incoming++;
+ }
+ }
+
+ if(this_inbox_is_in_incoming){
+ /*
+ * Don't add a folder for this, only a directory if called for.
+ * If it isn't a directory, skip it.
+ */
+ if(!(delim && !(attribs & LATT_NOINFERIORS)))
+ return;
+
+ suppress_folder_add++;
+ }
+ }
+ else{
+ int inbox_is_in_this_collection = 0;
+
+ /* is INBOX already in this folder list? */
+ ftotal = folder_total(ld->list);
+ for(i = 0; i < ftotal && !inbox_is_in_this_collection; i++){
+ f = folder_entry(i, ld->list);
+ if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
+ inbox_is_in_this_collection++;
+ }
+
+ if(inbox_is_in_this_collection){
+
+ /*
+ * Inbox is already inserted in this collection. Unless
+ * it is also a directory, we are done.
+ */
+ if(!(delim && !(attribs & LATT_NOINFERIORS)))
+ return;
+
+ /*
+ * Since it is also a directory, what we do depends on
+ * the F_SEP feature. If that feature is not set we just
+ * want to mark the existing entry dual-use. If F_SEP is
+ * set we want to add a new directory entry.
+ */
+ if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
+ f->isdir = 0;
+ f->haschildren = 0;
+ f->hasnochildren = 0;
+ /* fall through and add a new directory */
+ }
+ else{
+ /* mark existing entry dual-use and return */
+ ld->response.count++;
+ ld->response.isdir = 1;
+ f->isdir = 1;
+ if(attribs & LATT_HASCHILDREN)
+ f->haschildren = 1;
+
+ if(attribs & LATT_HASNOCHILDREN)
+ f->hasnochildren = 1;
+
+ return;
+ }
+
+ suppress_folder_add++;
+ }
+ else{
+ int found_it = 0;
+ int this_inbox_is_primary_inbox = 0;
+
+ /*
+ * See if this INBOX is the same as the INBOX we inserted
+ * in the primary collection.
+ */
+
+ /* first find the existing INBOX entry */
+ ftotal = folder_total(FOLDERS(ps_global->context_list));
+ for(i = 0; i < ftotal && !found_it; i++){
+ f = folder_entry(i, FOLDERS(ps_global->context_list));
+ if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
+ found_it++;
+ }
+
+ if(found_it && f && f->name){
+ if(same_remote_mailboxes(fullname, f->name)
+ || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
+ && (l1=mailboxfile(tmp1,fullname))
+ && (l2=mailboxfile(tmp2,f->name))
+ && !strcmp(l1,l2)))
+ this_inbox_is_primary_inbox++;
+ }
+
+ if(this_inbox_is_primary_inbox){
+ /*
+ * Don't add a folder for this, only a directory if called for.
+ * If it isn't a directory, skip it.
+ */
+ if(!(delim && !(attribs & LATT_NOINFERIORS)))
+ return;
+
+ suppress_folder_add++;
+ }
+ }
+ }
+ }
+
+ /* is it a mailbox? */
+ if(!(attribs & LATT_NOSELECT) && !suppress_folder_add){
+ ld->response.count++;
+ ld->response.isfile = 1;
+ new_f = new_folder(mailbox, 0);
+ new_f->isfolder = 1;
+
+ if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
+ folder_insert(-1, new_f, ld->list);
+ dual_f = new_f;
+ new_f = NULL;
+ }
+ }
+
+ /* directory? */
+ if(delim && !(attribs & LATT_NOINFERIORS)){
+ ld->response.count++;
+ ld->response.isdir = 1;
+
+ if(!new_f)
+ new_f = new_folder(mailbox, 0);
+
+ new_f->isdir = 1;
+ if(attribs & LATT_HASCHILDREN)
+ new_f->haschildren = 1;
+ if(attribs & LATT_HASNOCHILDREN)
+ new_f->hasnochildren = 1;
+
+ /*
+ * When we have F_SEPARATE_FLDR_AS_DIR we still want to know
+ * whether the name really represents both so that we don't
+ * inadvertently delete both when the user meant one or the
+ * other.
+ */
+ if(dual_f){
+ if(attribs & LATT_HASCHILDREN)
+ dual_f->haschildren = 1;
+
+ if(attribs & LATT_HASNOCHILDREN)
+ dual_f->hasnochildren = 1;
+
+ dual_f->isdual = 1;
+ new_f->isdual = 1;
+ }
+ }
+
+ if(new_f)
+ folder_insert(-1, new_f, ld->list);
+
+ if(attribs & LATT_MARKED)
+ ld->response.ismarked = 1;
+
+ /* don't mark #move folders unmarked */
+ if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
+ ld->response.unmarked = 1;
+
+ if(attribs & LATT_HASCHILDREN)
+ ld->response.haschildren = 1;
+
+ if(attribs & LATT_HASNOCHILDREN)
+ ld->response.hasnochildren = 1;
+}
+
+
+/*
+ * Validate LSUB response to command issued from build_folder_list
+ *
+ */
+void
+mail_lsub_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
+ void *data, unsigned int options)
+{
+ BFL_DATA_S *ld = (BFL_DATA_S *) data;
+ FOLDER_S *new_f = NULL;
+ char *ref;
+
+ if(delim)
+ ld->response.delim = delim;
+
+ /* test against mask of DIS-allowed attributes */
+ if((ld->mask & attribs)){
+ dprint((3, "mail_lsub_filter: failed attribute test"));
+ return;
+ }
+
+ /* Normalize mailbox and reference strings re: namespace */
+ if(!strncmp(mailbox, "#news.", 6))
+ mailbox += 6;
+
+ if(!strncmp(ref = ld->args.reference, "#news.", 6))
+ ref += 6;
+
+ if(!mail_list_in_collection(&mailbox, ref, ld->args.name, ld->args.tail))
+ return;
+
+ if(!(attribs & LATT_NOSELECT)){
+ ld->response.count++;
+ ld->response.isfile = 1;
+ new_f = new_folder(mailbox, 0);
+ new_f->isfolder = 1;
+ folder_insert(F_ON(F_READ_IN_NEWSRC_ORDER, ps_global)
+ ? folder_total(ld->list) : -1,
+ new_f, ld->list);
+ }
+
+ /* We don't support directories in #news */
+}
+
+/* human readable name for a folder. memory freed by caller
+ * This is a jacket to conversion from modified utf7 to utf8.
+ */
+unsigned char *folder_name_decoded(unsigned char *mailbox)
+{
+ unsigned char *s;
+ s = (unsigned char *) utf8_from_mutf7((unsigned char *) mailbox);
+ if (s == NULL) s = (unsigned char *) cpystr(mailbox);
+ return s;
+}
+
+int
+mail_list_in_collection(char **mailbox, char *ref, char *name, char *tail)
+{
+ int boxlen, reflen, taillen;
+ char *p;
+
+ boxlen = strlen(*mailbox);
+ reflen = ref ? strlen(ref) : 0;
+ taillen = tail ? strlen(tail) : 0;
+
+ if(boxlen
+ && (reflen ? !struncmp(*mailbox, ref, reflen)
+ : (p = strpbrk(name, "%*"))
+ ? !struncmp(*mailbox, name, p - name)
+ : !strucmp(*mailbox,name))
+ && (!taillen
+ || (taillen < boxlen - reflen
+ && !strucmp(&(*mailbox)[boxlen - taillen], tail)))){
+ if(taillen)
+ (*mailbox)[boxlen - taillen] = '\0';
+
+ if(*(*mailbox += reflen))
+ return(TRUE);
+ }
+ /*
+ * else don't worry about context "breakouts" since
+ * build_folder_list doesn't let the user introduce
+ * one...
+ */
+
+ return(FALSE);
+}
+
+
+/*
+ * rebuild_folder_list -- free up old list and re-issue commands to build
+ * a new list.
+ */
+void
+refresh_folder_list(CONTEXT_S *context, int nodirs, int startover, MAILSTREAM **streamp)
+{
+ if(startover)
+ free_folder_list(context);
+
+ build_folder_list(streamp, context, NULL, NULL,
+ (NEWS_TEST(context) ? BFL_LSUB : BFL_NONE)
+ | ((nodirs) ? BFL_FLDRONLY : BFL_NONE));
+}
+
+
+/*
+ * free_folder_list - loop thru the context's lists of folders
+ * clearing all entries without nicknames
+ * (as those were user provided) AND reset the
+ * context's find flag.
+ *
+ * NOTE: if fetched() information (e.g., like message counts come back
+ * in bboard collections), we may want to have a check before
+ * executing the loop and setting the FIND flag.
+ */
+void
+free_folder_list(CONTEXT_S *cntxt)
+{
+ int n, i;
+
+ /*
+ * In this case, don't blast the list as it was given to us by the
+ * user and not the result of a mail_list call...
+ */
+ if(cntxt->use & CNTXT_INCMNG)
+ return;
+
+ if(cntxt->use & CNTXT_PRESRV)
+ folder_select_preserve(cntxt);
+
+ for(n = folder_total(FOLDERS(cntxt)), i = 0; n > 0; n--)
+ if(folder_entry(i, FOLDERS(cntxt))->nickname)
+ i++; /* entry wasn't from LIST */
+ else
+ folder_delete(i, FOLDERS(cntxt));
+
+ cntxt->dir->status |= CNTXT_NOFIND; /* do find next time... */
+ /* or add the fake entry */
+ cntxt->use &= ~(CNTXT_PSEUDO | CNTXT_PRESRV | CNTXT_ZOOM);
+}
+
+
+/*
+ * default_save_context - return the default context for saved messages
+ */
+CONTEXT_S *
+default_save_context(CONTEXT_S *cntxt)
+{
+ while(cntxt)
+ if((cntxt->use) & CNTXT_SAVEDFLT)
+ return(cntxt);
+ else
+ cntxt = cntxt->next;
+
+ return(NULL);
+}
+
+
+
+/*
+ * folder_complete - foldername completion routine
+ *
+ * Result: returns 0 if the folder doesn't have a any completetion
+ * 1 if the folder has a completion (*AND* "name" is
+ * replaced with the completion)
+ *
+ */
+int
+folder_complete(CONTEXT_S *context, char *name, size_t namelen, int *completions)
+{
+ return(folder_complete_internal(context, name, namelen, completions, FC_NONE));
+}
+
+
+/*
+ *
+ */
+int
+folder_complete_internal(CONTEXT_S *context, char *name, size_t namelen,
+ int *completions, int flags)
+{
+ int i, match = -1, ftotal;
+ char tmp[MAXFOLDER+2], *a, *b, *fn, *pat;
+ FOLDER_S *f;
+
+ if(completions)
+ *completions = 0;
+
+ if(*name == '\0' || !context_isambig(name))
+ return(0);
+
+ if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))){
+ /*
+ * Build the folder list from scratch since we may need to
+ * traverse hierarchy...
+ */
+
+ free_folder_list(context);
+ snprintf(tmp, sizeof(tmp), "%s%c", name, NEWS_TEST(context) ? '*' : '%');
+ build_folder_list(NULL, context, tmp, NULL,
+ (NEWS_TEST(context) & !(flags & FC_FORCE_LIST))
+ ? BFL_LSUB : BFL_NONE);
+ }
+
+ *tmp = '\0'; /* find uniq substring */
+ ftotal = folder_total(FOLDERS(context));
+ for(i = 0; i < ftotal; i++){
+ f = folder_entry(i, FOLDERS(context));
+ fn = FLDR_NAME(f);
+ pat = name;
+ if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))){
+ fn = folder_last_cmpnt(fn, context->dir->delim);
+ pat = folder_last_cmpnt(pat, context->dir->delim);
+ }
+
+ if(!strncmp(fn, pat, strlen(pat))){
+ if(match != -1){ /* oh well, do best we can... */
+ a = fn;
+ if(match >= 0){
+ f = folder_entry(match, FOLDERS(context));
+ fn = FLDR_NAME(f);
+ if(!NEWS_TEST(context))
+ fn = folder_last_cmpnt(fn, context->dir->delim);
+
+ strncpy(tmp, fn, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ match = -2;
+ b = tmp; /* remember largest common text */
+ while(*a && *b && *a == *b)
+ *b++ = *a++;
+
+ *b = '\0';
+ }
+ else
+ match = i; /* bingo?? */
+ }
+ }
+
+ if(match >= 0){ /* found! */
+ f = folder_entry(match, FOLDERS(context));
+ fn = FLDR_NAME(f);
+ if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG)))
+ fn = folder_last_cmpnt(fn, context->dir->delim);
+
+ strncpy(pat, fn, namelen-(pat-name));
+ name[namelen-1] = '\0';
+ if(f->isdir && !f->isfolder){
+ name[i = strlen(name)] = context->dir->delim;
+ name[i+1] = '\0';
+ }
+ }
+ else if(match == -2){ /* closest we could find */
+ strncpy(pat, tmp, namelen-(pat-name));
+ name[namelen-1] = '\0';
+ }
+
+ if(completions)
+ *completions = ftotal;
+
+ if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context)))
+ free_folder_list(context);
+
+ return((match >= 0) ? ftotal : 0);
+}
+
+
+char *
+folder_last_cmpnt(char *s, int d)
+{
+ register char *p;
+
+ if(d)
+ for(p = s; (p = strindex(p, d)); s = ++p)
+ ;
+
+ return(s);
+}
+
+
+/*
+ * init_folder_entries - return a piece of memory suitable for attaching
+ * a list of folders...
+ *
+ */
+FLIST *
+init_folder_entries(void)
+{
+ FLIST *flist = (FLIST *) fs_get(sizeof(FLIST));
+ flist->folders = (FOLDER_S **) fs_get(FCHUNK * sizeof(FOLDER_S *));
+ memset((void *)flist->folders, 0, (FCHUNK * sizeof(FOLDER_S *)));
+ flist->allocated = FCHUNK;
+ flist->used = 0;
+ return(flist);
+}
+
+
+/*
+ * new_folder - return a brand new folder entry, with the given name
+ * filled in.
+ *
+ * NOTE: THIS IS THE ONLY WAY TO PUT A NAME INTO A FOLDER ENTRY!!!
+ * STRCPY WILL NOT WORK!!!
+ */
+FOLDER_S *
+new_folder(char *name, long unsigned int hash)
+{
+ FOLDER_S *tmp;
+ size_t l = strlen(name);
+
+ tmp = (FOLDER_S *)fs_get(sizeof(FOLDER_S) + (l * sizeof(char)));
+ memset((void *)tmp, 0, sizeof(FOLDER_S));
+ strncpy(tmp->name, name, l);
+ tmp->name[l] = '\0';
+ tmp->name_len = (unsigned char) l;
+ tmp->varhash = hash;
+ return(tmp);
+}
+
+
+/*
+ * folder_entry - folder struct access routine. Permit reference to
+ * folder structs via index number. Serves two purposes:
+ * 1) easy way for callers to access folder data
+ * conveniently
+ * 2) allows for a custom manager to limit memory use
+ * under certain rather limited "operating systems"
+ * who shall renameless, but whose initials are DOS
+ *
+ *
+ */
+FOLDER_S *
+folder_entry(int i, FLIST *flist)
+{
+ return((i >= flist->used) ? NULL:flist->folders[i]);
+}
+
+
+/*
+ * free_folder_entries - release all resources associated with the given
+ * list of folder entries
+ */
+void
+free_folder_entries(FLIST **flist)
+{
+ register int i;
+
+ if(!(flist && *flist))
+ return;
+
+ i = (*flist)->used;
+ while(i--){
+ if((*flist)->folders[i]->nickname)
+ fs_give((void **) &(*flist)->folders[i]->nickname);
+
+ fs_give((void **) &((*flist)->folders[i]));
+ }
+
+ fs_give((void **) &((*flist)->folders));
+ fs_give((void **) flist);
+}
+
+
+/*
+ * return the number of folders associated with the given folder list
+ */
+int
+folder_total(FLIST *flist)
+{
+ return((int) flist->used);
+}
+
+
+/*
+ * return the index number of the given name in the given folder list
+ */
+int
+folder_index(char *name, CONTEXT_S *cntxt, int flags)
+{
+ register int i = 0;
+ FOLDER_S *f;
+ char *fname;
+
+ for(i = 0; (f = folder_entry(i, FOLDERS(cntxt))); i++)
+ if(((flags & FI_FOLDER) && (f->isfolder || (cntxt->use & CNTXT_INCMNG)))
+ || ((flags & FI_DIR) && f->isdir)){
+ fname = FLDR_NAME(f);
+#if defined(DOS) || defined(OS2)
+ if(flags & FI_RENAME){ /* case-dependent for rename */
+ if(*name == *fname && strcmp(name, fname) == 0)
+ return(i);
+ }
+ else{
+ if(toupper((unsigned char)(*name))
+ == toupper((unsigned char)(*fname)) && strucmp(name, fname) == 0)
+ return(i);
+ }
+#else
+ if(*name == *fname && strcmp(name, fname) == 0)
+ return(i);
+#endif
+ }
+
+ return(-1);
+}
+
+
+/*
+ * folder_is_nick - check to see if the given name is a nickname
+ * for some folder in the given context...
+ *
+ * NOTE: no need to check if mm_list_names has been done as
+ * nicknames can only be set by configuration...
+ */
+char *
+folder_is_nick(char *nickname, FLIST *flist, int flags)
+{
+ register int i = 0;
+ FOLDER_S *f;
+
+ if(!(nickname && *nickname && flist))
+ return(NULL);
+
+ while((f = folder_entry(i, flist)) != NULL){
+ /*
+ * The second part of the OR is checking in a case-indep
+ * way for INBOX. It should be restricted to the context
+ * to which we add the INBOX folder, which would be either
+ * the Incoming Folders collection or the first collection
+ * if there is no Incoming collection. We don't need to check
+ * the collection because nickname assignment has already
+ * done that for us. Most folders don't have nicknames, only
+ * incoming folders and folders like inbox if not in incoming.
+ */
+ if(f->nickname
+ && (!strcmp(nickname, f->nickname)
+ || (!strucmp(nickname, f->nickname)
+ && !strucmp(nickname, ps_global->inbox_name)))){
+ char source[MAILTMPLEN], *target = NULL;
+
+ /*
+ * If f is a maildrop, then we want to return the
+ * destination folder, not the whole #move thing.
+ */
+ if(!(flags & FN_WHOLE_NAME)
+ && check_for_move_mbox(f->name, source, sizeof(source), &target))
+ return(target);
+ else
+ return(f->name);
+ }
+ else
+ i++;
+ }
+
+ return(NULL);
+}
+
+
+char *
+folder_is_target_of_nick(char *longname, CONTEXT_S *cntxt)
+{
+ register int i = 0;
+ FOLDER_S *f;
+ FLIST *flist = NULL;
+
+ if(cntxt && cntxt == ps_global->context_list)
+ flist = FOLDERS(cntxt);
+
+ if(!(longname && *longname && flist))
+ return(NULL);
+
+ while((f = folder_entry(i, flist)) != NULL){
+ if(f->nickname && f->name && !strcmp(longname, f->name))
+ return(f->nickname);
+ else
+ i++;
+ }
+
+ return(NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Insert the given folder name into the sorted folder list
+ associated with the given context. Only allow ambiguous folder
+ names IF associated with a nickname.
+
+ Args: index -- Index to insert at, OR insert in sorted order if -1
+ folder -- folder structure to insert into list
+ flist -- folder list to insert folder into
+
+ **** WARNING ****
+ DON'T count on the folder pointer being valid after this returns
+ *** ALL FOLDER ELEMENT READS SHOULD BE THRU folder_entry() ***
+
+ ----*/
+int
+folder_insert(int index, FOLDER_S *folder, FLIST *flist)
+{
+ /* requested index < 0 means add to sorted list */
+ if(index < 0 && (index = folder_total(flist)) > 0)
+ index = folder_insert_sorted(index / 2, 0, index, folder, flist,
+ (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
+ ? compare_folders_dir_alpha
+ : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
+ ? compare_folders_alpha_dir
+ : compare_folders_alpha);
+
+ folder_insert_index(folder, index, flist);
+ return(index);
+}
+
+
+/*
+ * folder_insert_sorted - Insert given folder struct into given list
+ * observing sorting specified by given
+ * comparison function
+ */
+int
+folder_insert_sorted(int index, int min_index, int max_index, FOLDER_S *folder,
+ FLIST *flist, int (*compf)(FOLDER_S *, FOLDER_S *))
+{
+ int i;
+
+ return(((i = (*compf)(folder_entry(index, flist), folder)) == 0)
+ ? index
+ : (i < 0)
+ ? ((++index >= max_index)
+ ? max_index
+ : ((*compf)(folder_entry(index, flist), folder) > 0)
+ ? index
+ : folder_insert_sorted((index + max_index) / 2, index,
+ max_index, folder, flist, compf))
+ : ((index <= min_index)
+ ? min_index
+ : folder_insert_sorted((min_index + index) / 2, min_index, index,
+ folder, flist, compf)));
+}
+
+
+/*
+ * folder_insert_index - Insert the given folder struct into the global list
+ * at the given index.
+ */
+void
+folder_insert_index(FOLDER_S *folder, int index, FLIST *flist)
+{
+ register FOLDER_S **flp, **iflp;
+
+ /* if index is beyond size, place at end of list */
+ index = MIN(index, flist->used);
+
+ /* grow array ? */
+ if(flist->used + 1 > flist->allocated){
+ flist->allocated += FCHUNK;
+ fs_resize((void **)&(flist->folders),
+ flist->allocated * sizeof(FOLDER_S *));
+ }
+
+ /* shift array left */
+ iflp = &((flist->folders)[index]);
+ for(flp = &((flist->folders)[flist->used]);
+ flp > iflp; flp--)
+ flp[0] = flp[-1];
+
+ flist->folders[index] = folder;
+ flist->used += 1;
+}
+
+
+void
+resort_folder_list(FLIST *flist)
+{
+ if(flist && folder_total(flist) > 1 && flist->folders)
+ qsort(flist->folders, folder_total(flist), sizeof(flist->folders[0]),
+ (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
+ ? compare_folders_dir_alpha_qsort
+ : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
+ ? compare_folders_alpha_dir_qsort
+ : compare_folders_alpha_qsort);
+}
+
+
+/*----------------------------------------------------------------------
+ Removes a folder at the given index in the given context's
+ list.
+
+Args: index -- Index in folder list of folder to be removed
+ flist -- folder list
+ ----*/
+void
+folder_delete(int index, FLIST *flist)
+{
+ register int i;
+ FOLDER_S *f;
+
+ if(flist->used
+ && (index < 0 || index >= flist->used))
+ return; /* bogus call! */
+
+ if((f = folder_entry(index, flist))->nickname)
+ fs_give((void **)&(f->nickname));
+
+ fs_give((void **) &(flist->folders[index]));
+ for(i = index; i < flist->used - 1; i++)
+ flist->folders[i] = flist->folders[i+1];
+
+
+ flist->used -= 1;
+}
+
+
+/*----------------------------------------------------------------------
+ compare two names for qsort, case independent
+
+ Args: pointers to strings to compare
+
+ Result: integer result of strcmp of the names. Uses simple
+ efficiency hack to speed the string comparisons up a bit.
+
+ ----------------------------------------------------------------------*/
+int
+compare_names(const qsort_t *x, const qsort_t *y)
+{
+ char *a = *(char **)x, *b = *(char **)y;
+ int r;
+#define CMPI(X,Y) ((X)[0] - (Y)[0])
+#define UCMPI(X,Y) ((isupper((unsigned char)((X)[0])) \
+ ? (X)[0] - 'A' + 'a' : (X)[0]) \
+ - (isupper((unsigned char)((Y)[0])) \
+ ? (Y)[0] - 'A' + 'a' : (Y)[0]))
+
+ /*---- Inbox always sorts to the top ----*/
+ if(UCMPI(a, ps_global->inbox_name) == 0
+ && strucmp(a, ps_global->inbox_name) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
+ else if((UCMPI(b, ps_global->inbox_name)) == 0
+ && strucmp(b, ps_global->inbox_name) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
+
+ /*----- The sent-mail folder, is always next unless... ---*/
+ else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
+ && CMPI(a, ps_global->VAR_DEFAULT_FCC) == 0
+ && strcmp(a, ps_global->VAR_DEFAULT_FCC) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
+ else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
+ && CMPI(b, ps_global->VAR_DEFAULT_FCC) == 0
+ && strcmp(b, ps_global->VAR_DEFAULT_FCC) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
+
+ /*----- The saved-messages folder, is always next unless... ---*/
+ else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
+ && CMPI(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
+ && strcmp(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
+ else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
+ && CMPI(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
+ && strcmp(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
+ return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
+
+ else
+ return((r = CMPI(a, b)) ? r : strcmp(a, b));
+}
+
+
+/*----------------------------------------------------------------------
+ compare two folder structs for ordering alphabetically
+
+ Args: pointers to folder structs to compare
+
+ Result: integer result of dir-bit and strcmp of the folders.
+ ----------------------------------------------------------------------*/
+int
+compare_folders_alpha(FOLDER_S *f1, FOLDER_S *f2)
+{
+ int i;
+ char *f1name = FLDR_NAME(f1),
+ *f2name = FLDR_NAME(f2);
+
+ return(((i = compare_names(&f1name, &f2name)) != 0)
+ ? i : (f2->isdir - f1->isdir));
+}
+
+
+int
+compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
+{
+ FOLDER_S *f1 = *((FOLDER_S **) a1);
+ FOLDER_S *f2 = *((FOLDER_S **) a2);
+
+ return(compare_folders_alpha(f1, f2));
+}
+
+
+/*----------------------------------------------------------------------
+ compare two folder structs alphabetically with dirs first
+
+ Args: pointers to folder structs to compare
+
+ Result: integer result of dir-bit and strcmp of the folders.
+ ----------------------------------------------------------------------*/
+int
+compare_folders_dir_alpha(FOLDER_S *f1, FOLDER_S *f2)
+{
+ int i;
+
+ if((i = (f2->isdir - f1->isdir)) == 0){
+ char *f1name = FLDR_NAME(f1),
+ *f2name = FLDR_NAME(f2);
+
+ return(compare_names(&f1name, &f2name));
+ }
+
+ return(i);
+}
+
+
+int
+compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
+{
+ FOLDER_S *f1 = *((FOLDER_S **) a1);
+ FOLDER_S *f2 = *((FOLDER_S **) a2);
+
+ return(compare_folders_dir_alpha(f1, f2));
+}
+
+
+/*----------------------------------------------------------------------
+ compare two folder structs alphabetically with dirs last
+
+ Args: pointers to folder structs to compare
+
+ Result: integer result of dir-bit and strcmp of the folders.
+ ----------------------------------------------------------------------*/
+int
+compare_folders_alpha_dir(FOLDER_S *f1, FOLDER_S *f2)
+{
+ int i;
+
+ if((i = (f1->isdir - f2->isdir)) == 0){
+ char *f1name = FLDR_NAME(f1),
+ *f2name = FLDR_NAME(f2);
+
+ return(compare_names(&f1name, &f2name));
+ }
+
+ return(i);
+}
+
+
+int
+compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2)
+{
+ FOLDER_S *f1 = *((FOLDER_S **) a1);
+ FOLDER_S *f2 = *((FOLDER_S **) a2);
+
+ return(compare_folders_alpha_dir(f1, f2));
+}
+
+
+/*
+ * Find incoming folders and update the unseen counts
+ * if necessary.
+ */
+void
+folder_unseen_count_updater(unsigned long flags)
+{
+ CONTEXT_S *ctxt;
+ time_t oldest, started_checking;
+ int ftotal, i, first = -1;
+ FOLDER_S *f;
+
+ /*
+ * We would only do this if there is an incoming collection, the
+ * user wants us to monitor, and we're in the folder screen.
+ */
+ if(ps_global->in_folder_screen && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
+ && (ftotal = folder_total(FOLDERS(ctxt)))){
+ /*
+ * Search through the last_unseen_update times to find
+ * the one that was updated longest ago, and start with
+ * that one. We don't want to delay long doing these
+ * checks so may only check some of them each time we
+ * get called. An update time equal to 1 means don't check
+ * this folder at all.
+ */
+ for(i = 0; i < ftotal; i++){
+ f = folder_entry(i, FOLDERS(ctxt));
+ if(f && LUU_YES(f->last_unseen_update)){
+ first = i;
+ oldest = f->last_unseen_update;
+ break;
+ }
+ }
+
+ /*
+ * Now first is the first in the list that
+ * should ever be checked. Next find the
+ * one that should be checked next, the one
+ * that was checked longest ago.
+ */
+ if(first >= 0){
+ for(i = 1; i < ftotal; i++){
+ f = folder_entry(i, FOLDERS(ctxt));
+ if(f && LUU_YES(f->last_unseen_update) && f->last_unseen_update < oldest){
+ first = i;
+ oldest = f->last_unseen_update;
+ }
+ }
+ }
+
+ /* now first is the next one to be checked */
+
+ started_checking = time(0);
+
+ for(i = first; i < ftotal; i++){
+ /* update the next one */
+ f = folder_entry(i, FOLDERS(ctxt));
+ if(f && LUU_YES(f->last_unseen_update)
+ && (flags & UFU_FORCE
+ /* or it's been long enough and we've not been in this function too long */
+ || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
+ && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
+ update_folder_unseen(f, ctxt, flags, NULL);
+ }
+
+ for(i = 0; i < first; i++){
+ f = folder_entry(i, FOLDERS(ctxt));
+ if(f && LUU_YES(f->last_unseen_update)
+ && (flags & UFU_FORCE
+ || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
+ && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
+ update_folder_unseen(f, ctxt, flags, NULL);
+ }
+ }
+}
+
+
+/*
+ * Update the count of unseen in the FOLDER_S struct
+ * for this folder. This will update if the time
+ * interval has passed or if the FORCE flag is set.
+ */
+void
+update_folder_unseen(FOLDER_S *f, CONTEXT_S *ctxt, unsigned long flags,
+ MAILSTREAM *this_is_the_stream)
+{
+ time_t now;
+ int orig_valid;
+ int use_imap_interval = 0;
+ int stream_is_open = 0;
+ unsigned long orig_unseen, orig_new, orig_tot;
+ char mailbox_name[MAILTMPLEN];
+ char *target = NULL;
+ DRIVER *d;
+
+ if(!f || !LUU_YES(f->last_unseen_update))
+ return;
+
+ now = time(0);
+ context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
+
+ if(!mailbox_name[0])
+ return;
+
+ if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
+ MAILSTREAM *strm;
+
+ /*
+ * If this maildrop is the currently open stream use that.
+ * I'm not altogether sure that this is a good way to
+ * check this.
+ */
+ if(target
+ && ((strm=ps_global->mail_stream)
+ && strm->snarf.name
+ && (!strcmp(target,strm->mailbox)
+ || !strcmp(target,strm->original_mailbox)))){
+ stream_is_open++;
+ }
+ }
+ else{
+ MAILSTREAM *m = NULL;
+
+ stream_is_open = (this_is_the_stream
+ || (m=sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
+ || ((m=ps_global->mail_stream) && !sp_dead_stream(m)
+ && same_stream_and_mailbox(mailbox_name,m))
+ || (!IS_REMOTE(mailbox_name)
+ && (m=already_open_stream(mailbox_name, AOS_NONE)))) ? 1 : 0;
+
+ if(stream_is_open){
+ if(!this_is_the_stream)
+ this_is_the_stream = m;
+ }
+ else{
+ /*
+ * If it's IMAP or local we use a shorter interval.
+ */
+ d = mail_valid(NIL, mailbox_name, (char *) NIL);
+ if((d && !strcmp(d->name, "imap")) || !IS_REMOTE(mailbox_name))
+ use_imap_interval++;
+ }
+ }
+
+ /*
+ * Update if forced, or if it's been a while, or if we have a
+ * stream open to this mailbox already.
+ */
+ if(flags & UFU_FORCE
+ || stream_is_open
+ || ((use_imap_interval
+ && (now - f->last_unseen_update) >= ps_global->inc_check_interval)
+ || ((now - f->last_unseen_update) >= ps_global->inc_second_check_interval))){
+ unsigned long tot, uns, new;
+ unsigned long *totp = NULL, *unsp = NULL, *newp = NULL;
+
+ orig_valid = f->unseen_valid;
+ orig_unseen = f->unseen;
+ orig_new = f->new;
+ orig_tot = f->total;
+
+ if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
+ newp = &new;
+ else
+ unsp = &uns;
+
+ if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
+ totp = &tot;
+
+ f->unseen_valid = 0;
+
+ dprint((9, "update_folder_unseen(%s)", FLDR_NAME(f)));
+ if(get_recent_in_folder(mailbox_name, newp, unsp, totp, this_is_the_stream)){
+ f->last_unseen_update = time(0);
+ f->unseen_valid = 1;
+ if(unsp)
+ f->unseen = uns;
+
+ if(newp)
+ f->new = new;
+
+ if(totp)
+ f->total = tot;
+
+ if(!orig_valid){
+ dprint((9, "update_folder_unseen(%s): original: %s%s%s%s",
+ FLDR_NAME(f),
+ F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
+ F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
+ F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
+ F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
+ }
+
+ if(orig_valid
+ && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
+ && orig_new != f->new)
+ ||
+ (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
+ && orig_unseen != f->unseen)
+ ||
+ (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
+ && orig_tot != f->total))){
+
+ if(ps_global->in_folder_screen)
+ ps_global->noticed_change_in_unseen = 1;
+
+ dprint((9, "update_folder_unseen(%s): changed: %s%s%s%s",
+ FLDR_NAME(f),
+ F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
+ F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
+ F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
+ F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
+
+ if(flags & UFU_ANNOUNCE
+ && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
+ && orig_new < f->new)
+ ||
+ (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
+ && orig_unseen < f->unseen))){
+ if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
+ q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
+ FLDR_NAME(f), comatose(f->new),
+ _("new"));
+ else
+ q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
+ FLDR_NAME(f), comatose(f->unseen),
+ _("unseen"));
+ }
+ }
+ }
+ else
+ f->last_unseen_update = LUU_NOMORECHK; /* no further checking */
+ }
+}
+
+
+void
+update_folder_unseen_by_stream(MAILSTREAM *strm, unsigned long flags)
+{
+ CONTEXT_S *ctxt;
+ int ftotal, i;
+ char mailbox_name[MAILTMPLEN];
+ char *cn, tmp[MAILTMPLEN];
+ FOLDER_S *f;
+
+ /*
+ * Attempt to figure out which incoming folder this stream
+ * is open to, if any, so we can update the unseen counters.
+ */
+ if(strm
+ && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
+ && (ftotal = folder_total(FOLDERS(ctxt)))){
+ for(i = 0; i < ftotal; i++){
+ f = folder_entry(i, FOLDERS(ctxt));
+ context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
+ if(same_stream_and_mailbox(mailbox_name, strm)
+ || (!IS_REMOTE(mailbox_name) && (cn=mailboxfile(tmp,mailbox_name)) && (*cn) && (!strcmp(cn, strm->mailbox) || !strcmp(cn, strm->original_mailbox)))){
+ /* if we failed earlier on this one, give it another go */
+ if(f->last_unseen_update == LUU_NOMORECHK)
+ init_incoming_unseen_data(ps_global, f);
+
+ update_folder_unseen(f, ctxt, flags, strm);
+ return;
+ }
+ }
+ }
+}
+
+
+/*
+ * Find the number of new, unseen, and the total number of
+ * messages in mailbox_name.
+ * If the corresponding arg is NULL it will skip the work
+ * necessary for that flag.
+ *
+ * Returns 1 if successful, 0 if not.
+ */
+int
+get_recent_in_folder(char *mailbox_name, long unsigned int *new,
+ long unsigned int *unseen, long unsigned int *total,
+ MAILSTREAM *this_is_the_stream)
+{
+ MAILSTREAM *strm = NIL;
+ unsigned long tot, nw, uns;
+ int gotit = 0;
+ int maildrop = 0;
+ char *target = NULL;
+ MSGNO_S *msgmap;
+ long excluded, flags;
+ extern MAILSTATUS mm_status_result;
+
+ dprint((9, "get_recent_in_folder(%s)", mailbox_name ? mailbox_name : "?"));
+
+ if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
+
+ maildrop++;
+
+ /*
+ * If this maildrop is the currently open stream use that.
+ */
+ if(target
+ && ((strm=ps_global->mail_stream)
+ && strm->snarf.name
+ && (!strcmp(target,strm->mailbox)
+ || !strcmp(target,strm->original_mailbox)))){
+ gotit++;
+ msgmap = sp_msgmap(strm);
+ excluded = any_lflagged(msgmap, MN_EXLD);
+
+ tot = strm->nmsgs - excluded;
+ if(tot){
+ if(new){
+ if(sp_recent_since_visited(strm) == 0)
+ nw = 0;
+ else
+ nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
+ }
+
+ if(unseen)
+ uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
+ }
+ else{
+ nw = 0;
+ uns = 0;
+ }
+ }
+ /* else fall through to just open it case */
+ }
+
+ /* do we already have it selected? */
+ if(!gotit
+ && ((strm = this_is_the_stream)
+ || (strm = sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
+ || (!IS_REMOTE(mailbox_name)
+ && (strm = already_open_stream(mailbox_name, AOS_NONE))))){
+ gotit++;
+
+ /*
+ * Unfortunately, we have to worry about excluded
+ * messages. The user doesn't want to have
+ * excluded messages count in the totals, especially
+ * recent excluded messages.
+ */
+
+ msgmap = sp_msgmap(strm);
+ excluded = any_lflagged(msgmap, MN_EXLD);
+
+ tot = strm->nmsgs - excluded;
+ if(tot){
+ if(new){
+ if(sp_recent_since_visited(strm) == 0)
+ nw = 0;
+ else
+ nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
+ }
+
+ if(unseen)
+ uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
+ }
+ else{
+ nw = 0;
+ uns = 0;
+ }
+ }
+ /*
+ * No, but how about another stream to same server which
+ * could be used for a STATUS command?
+ */
+ else if(!gotit && (strm = sp_stream_get(mailbox_name, SP_SAME))
+ && modern_imap_stream(strm)){
+
+ flags = 0L;
+ if(total)
+ flags |= SA_MESSAGES;
+
+ if(new)
+ flags |= SA_RECENT;
+
+ if(unseen)
+ flags |= SA_UNSEEN;
+
+ mm_status_result.flags = 0L;
+
+ pine_mail_status(strm, mailbox_name, flags);
+ if(total){
+ if(mm_status_result.flags & SA_MESSAGES){
+ tot = mm_status_result.messages;
+ gotit++;
+ }
+ }
+
+ if(!(total && !gotit)){
+ if(new){
+ if(mm_status_result.flags & SA_RECENT){
+ nw = mm_status_result.recent;
+ gotit++;
+ }
+ else
+ gotit = 0;
+ }
+ }
+
+ if(!((total || new) && !gotit)){
+ if(unseen){
+ if(mm_status_result.flags & SA_UNSEEN){
+ uns = mm_status_result.unseen;
+ gotit++;
+ }
+ else
+ gotit = 0;
+ }
+ }
+ }
+
+ /* Let's just Select it. */
+ if(!gotit){
+ long saved_timeout;
+ long openflags;
+
+ /*
+ * Traditional unix folders don't notice new mail if
+ * they are opened readonly. So maildrops with unix folder
+ * targets will snarf to the file but the stream that is
+ * opened won't see the new mail. So make all maildrop
+ * opens non-readonly here.
+ */
+ openflags = SP_USEPOOL | SP_TEMPUSE | (maildrop ? 0 : OP_READONLY);
+
+ saved_timeout = (long) mail_parameters(NULL, GET_OPENTIMEOUT, NULL);
+ mail_parameters(NULL, SET_OPENTIMEOUT, (void *) (long) ps_global->inc_check_timeout);
+ strm = pine_mail_open(NULL, mailbox_name, openflags, NULL);
+ mail_parameters(NULL, SET_OPENTIMEOUT, (void *) saved_timeout);
+
+ if(strm){
+ gotit++;
+ msgmap = sp_msgmap(strm);
+ excluded = any_lflagged(msgmap, MN_EXLD);
+
+ tot = strm->nmsgs - excluded;
+ if(tot){
+ if(new){
+ if(sp_recent_since_visited(strm) == 0)
+ nw = 0;
+ else
+ nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
+ }
+
+ if(unseen)
+ uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
+ }
+ else{
+ nw = 0;
+ uns = 0;
+ }
+
+ pine_mail_close(strm);
+ }
+ }
+
+ if(gotit){
+ if(new)
+ *new = nw;
+
+ if(unseen)
+ *unseen = uns;
+
+ if(total)
+ *total = tot;
+ }
+
+ return(gotit);
+}
+
+
+void
+clear_incoming_valid_bits(void)
+{
+ CONTEXT_S *ctxt;
+ int ftotal, i;
+ FOLDER_S *f;
+
+ if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
+ && (ftotal = folder_total(FOLDERS(ctxt))))
+ for(i = 0; i < ftotal; i++){
+ f = folder_entry(i, FOLDERS(ctxt));
+ init_incoming_unseen_data(ps_global, f);
+ }
+}
+
+
+int
+selected_folders(CONTEXT_S *context)
+{
+ int i, n, total;
+
+ n = folder_total(FOLDERS(context));
+ for(total = i = 0; i < n; i++)
+ if(folder_entry(i, FOLDERS(context))->selected)
+ total++;
+
+ return(total);
+}
+
+
+SELECTED_S *
+new_selected(void)
+{
+ SELECTED_S *selp;
+
+ selp = (SELECTED_S *)fs_get(sizeof(SELECTED_S));
+ selp->sub = NULL;
+ selp->reference = NULL;
+ selp->folders = NULL;
+ selp->zoomed = 0;
+
+ return selp;
+}
+
+
+/*
+ * Free the current selected struct and all of the
+ * following structs in the list
+ */
+void
+free_selected(SELECTED_S **selp)
+{
+ if(!selp || !(*selp))
+ return;
+ if((*selp)->sub)
+ free_selected(&((*selp)->sub));
+
+ free_strlist(&(*selp)->folders);
+ if((*selp)->reference)
+ fs_give((void **) &(*selp)->reference);
+
+ fs_give((void **) selp);
+}
+
+
+void
+folder_select_preserve(CONTEXT_S *context)
+{
+ if(context
+ && !(context->use & CNTXT_PARTFIND)){
+ FOLDER_S *fp;
+ STRLIST_S **slpp;
+ SELECTED_S *selp = &context->selected;
+ int i, folder_n;
+
+ if(!context->dir->ref){
+ if(!context->selected.folders)
+ slpp = &context->selected.folders;
+ else
+ return;
+ }
+ else{
+ if(!selected_folders(context))
+ return;
+ else{
+ while(selp->sub){
+ selp = selp->sub;
+ if(!strcmp(selp->reference, context->dir->ref))
+ return;
+ }
+ selp->sub = new_selected();
+ selp = selp->sub;
+ slpp = &(selp->folders);
+ }
+ }
+ folder_n = folder_total(FOLDERS(context));
+
+ for(i = 0; i < folder_n; i++)
+ if((fp = folder_entry(i, FOLDERS(context)))->selected){
+ *slpp = new_strlist(fp->name);
+ slpp = &(*slpp)->next;
+ }
+
+ /* Only remember "ref" if any folders were selected */
+ if(selp->folders && context->dir->ref)
+ selp->reference = cpystr(context->dir->ref);
+
+ selp->zoomed = (context->use & CNTXT_ZOOM) != 0;
+ }
+}
+
+
+int
+folder_select_restore(CONTEXT_S *context)
+{
+ int rv = 0;
+
+ if(context
+ && !(context->use & CNTXT_PARTFIND)){
+ STRLIST_S *slp;
+ SELECTED_S *selp, *pselp = NULL;
+ int i, found = 0;
+
+ selp = &(context->selected);
+
+ if(context->dir->ref){
+ pselp = selp;
+ selp = selp->sub;
+ while(selp && strcmp(selp->reference, context->dir->ref)){
+ pselp = selp;
+ selp = selp->sub;
+ }
+ if (selp)
+ found = 1;
+ }
+ else
+ found = selp->folders != 0;
+ if(found){
+ for(slp = selp->folders; slp; slp = slp->next)
+ if(slp->name
+ && (i = folder_index(slp->name, context, FI_FOLDER)) >= 0){
+ folder_entry(i, FOLDERS(context))->selected = 1;
+ rv++;
+ }
+
+ /* Used, always clean them up */
+ free_strlist(&selp->folders);
+ if(selp->reference)
+ fs_give((void **) &selp->reference);
+
+ if(selp->zoomed){
+ context->use |= CNTXT_ZOOM;
+ selp->zoomed = 0;
+ }
+ if(!(selp == &context->selected)){
+ if(pselp){
+ pselp->sub = selp->sub;
+ fs_give((void **) &selp);
+ }
+ }
+ }
+ }
+
+ return(rv);
+}
+
diff --git a/pith/folder.h b/pith/folder.h
new file mode 100644
index 00000000..dfec2ca2
--- /dev/null
+++ b/pith/folder.h
@@ -0,0 +1,134 @@
+/*
+ * $Id: folder.h 880 2007-12-18 00:57:56Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_FOLDER_INCLUDED
+#define PITH_FOLDER_INCLUDED
+
+
+#include "../pith/foldertype.h"
+#include "../pith/conftype.h"
+#include "../pith/context.h"
+#include "../pith/state.h"
+
+
+/*
+ * Structs to ease c-client LIST/LSUB interaction
+ */
+typedef struct _listargs {
+ char *reference, /* IMAP LIST "reference" arg */
+ *name, /* IMAP LIST "name" arg */
+ *tail; /* Pine "context" after "name" part */
+} LISTARGS_S;
+
+typedef struct _listresponse {
+ long count;
+ int delim;
+ unsigned isfile:1,
+ isdir:1,
+ ismarked:1,
+ unmarked:1,
+ haschildren:1,
+ hasnochildren:1;
+} LISTRES_S;
+
+
+typedef struct _existdata {
+ LISTARGS_S args;
+ LISTRES_S response;
+ char **fullname;
+ int is_move_folder;
+} EXISTDATA_S;
+
+
+#define FEX_NOENT 0x0000 /* file_exists: doesn't exist */
+#define FEX_ISFILE 0x0001 /* file_exists: name is a file */
+#define FEX_ISDIR 0x0002 /* file_exists: name is a dir */
+#define FEX_ISMARKED 0x0004 /* file_exists: is interesting */
+#define FEX_UNMARKED 0x0008 /* file_exists: known UNinteresting */
+#define FEX_ERROR 0x1000 /* file_exists: error occured */
+
+#define BFL_NONE 0x00 /* build_folder_list: no flag */
+#define BFL_FLDRONLY 0x01 /* ignore directories */
+#define BFL_LSUB 0x02 /* use mail_lsub vs mail_list */
+#define BFL_SCAN 0x04 /* use mail_scan vs mail_list */
+#define BFL_CHILDREN 0x08 /* make sure haschildren is accurate */
+
+#define FI_FOLDER 0x01 /* folder_index flags */
+#define FI_DIR 0x02
+#define FI_RENAME 0x04
+#define FI_ANY (FI_FOLDER | FI_DIR)
+
+#define FN_NONE 0x00 /* flags modifying folder_is_nick */
+#define FN_WHOLE_NAME 0x01 /* return long name if #move */
+
+#define FC_NONE 0 /* flags for folder_complete */
+#define FC_FORCE_LIST 1
+
+#define UFU_NONE 0x00 /* flags for update_folder_unseen */
+#define UFU_FORCE 0x01
+#define UFU_ANNOUNCE 0x02 /* announce increases with q_status */
+
+
+/* exported protoypes */
+char *folder_lister_desc(CONTEXT_S *, FDIR_S *);
+void reset_context_folders(CONTEXT_S *);
+FDIR_S *next_folder_dir(CONTEXT_S *, char *, int, MAILSTREAM **);
+EditWhich config_containing_inc_fldr(FOLDER_S *);
+char *pretty_fn(char *);
+int get_folder_delimiter(char *);
+int folder_exists(CONTEXT_S *, char *);
+int folder_name_exists(CONTEXT_S *, char *, char **);
+char *folder_as_breakout(CONTEXT_S *, char *);
+void init_folders(struct pine *);
+void init_inbox_mapping(char *, CONTEXT_S *);
+void reinit_incoming_folder_list(struct pine *, CONTEXT_S *);
+FDIR_S *new_fdir(char *, char *, int);
+void free_fdir(FDIR_S **, int);
+void build_folder_list(MAILSTREAM **, CONTEXT_S *, char *, char *, int);
+void free_folder_list(CONTEXT_S *);
+CONTEXT_S *default_save_context(CONTEXT_S *);
+int folder_complete(CONTEXT_S *, char *, size_t, int *);
+FOLDER_S *new_folder(char *, unsigned long);
+FOLDER_S *folder_entry(int, FLIST *);
+int folder_total(FLIST *);
+int folder_index(char *, CONTEXT_S *, int);
+char *folder_is_nick(char *, FLIST *, int);
+char *folder_is_target_of_nick(char *, CONTEXT_S *);
+int folder_insert(int, FOLDER_S *, FLIST *);
+FLIST *init_folder_entries(void);
+int compare_folders_alpha(FOLDER_S *, FOLDER_S *);
+int compare_folders_dir_alpha(FOLDER_S *, FOLDER_S *);
+int compare_folders_alpha_dir(FOLDER_S *, FOLDER_S *);
+void mail_list_response(MAILSTREAM *, char *, int, long, void *, unsigned);
+void folder_seen_count_updater(void *);
+void folder_unseen_count_updater(unsigned long);
+void update_folder_unseen(FOLDER_S *, CONTEXT_S *, unsigned long, MAILSTREAM *);
+void update_folder_unseen_by_stream(MAILSTREAM *, unsigned long);
+int get_recent_in_folder(char *, unsigned long *, unsigned long *,
+ unsigned long *, MAILSTREAM *);
+void clear_incoming_valid_bits(void);
+int selected_folders(CONTEXT_S *);
+SELECTED_S *new_selected(void);
+void free_selected(SELECTED_S **);
+void folder_select_preserve(CONTEXT_S *);
+int folder_select_restore(CONTEXT_S *);
+int update_bboard_spec(char *, char *, size_t);
+void refresh_folder_list(CONTEXT_S *, int, int, MAILSTREAM **);
+int folder_complete_internal(CONTEXT_S *, char *, size_t, int *, int);
+void folder_delete(int, FLIST *);
+unsigned char *folder_name_decoded(unsigned char *);
+
+#endif /* PITH_FOLDER_INCLUDED */
diff --git a/pith/foldertype.h b/pith/foldertype.h
new file mode 100644
index 00000000..95d8b14a
--- /dev/null
+++ b/pith/foldertype.h
@@ -0,0 +1,163 @@
+/*
+ * $Id: foldertype.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_FOLDERTYPE_INCLUDED
+#define PITH_FOLDERTYPE_INCLUDED
+
+
+#include "../pith/conftype.h"
+#include "../pith/string.h"
+
+
+/*
+ * Type definitions for folder and context structures.
+ */
+
+
+/*------------------------------
+ Used for displaying as well as
+ keeping track of folders.
+ ----*/
+typedef struct folder {
+ unsigned char name_len; /* name length */
+ unsigned isfolder:1; /* is it a folder? */
+ unsigned isdir:1; /* is it a directory? */
+ /* isdual is only set if the user has the separate
+ directory and folder display option set. Otherwise,
+ we can tell it's dual use because both isfolder
+ and isdir will be set. */
+ unsigned isdual:1; /* dual use */
+ unsigned haschildren:1; /* dir known to have children */
+ unsigned hasnochildren:1; /* known not to have children */
+ unsigned scanned:1; /* scanned by c-client */
+ unsigned selected:1; /* selected by user */
+ unsigned subscribed:1; /* selected by user */
+ unsigned unseen_valid:1; /* unseen count is valid */
+ unsigned long varhash; /* hash of var for incoming */
+ imapuid_t uidvalidity; /* only for #move folder */
+ imapuid_t uidnext; /* only for #move folder */
+ char *nickname; /* folder's short name */
+ unsigned long unseen; /* for monitoring unseen */
+ unsigned long new; /* for monitoring unseen */
+ unsigned long total; /* for monitoring unseen */
+ time_t last_unseen_update; /* see LUU_ constants below */
+ char name[1]; /* folder's name */
+} FOLDER_S;
+
+
+/* special values stored in last_unseen_update */
+#define LUU_INIT ((time_t) 0) /* before first check is done */
+#define LUU_NEVERCHK ((time_t) 1) /* don't check this folder */
+#define LUU_NOMORECHK ((time_t) 2) /* check failed so stop checking */
+
+/* this value is eligible for checking */
+#define LUU_YES(luu) (((luu) != LUU_NEVERCHK) && ((luu) != LUU_NOMORECHK))
+
+
+/*
+ * Folder List Structure - provides for two ways to access and manage
+ * folder list data. One as an array of pointers
+ * to folder structs or
+ */
+typedef struct folder_list {
+ unsigned used;
+ unsigned allocated;
+ FOLDER_S **folders; /* array of pointers to folder structs */
+} FLIST;
+
+
+/*
+ * digested form of context including pointer to the parent
+ * level of hierarchy...
+ */
+typedef struct folder_dir {
+ char *ref, /* collection location */
+ *desc, /* Optional description */
+ delim, /* dir/file delimiter */
+ status; /* folder data's status */
+ struct {
+ char *user,
+ *internal;
+ } view; /* file's within dir */
+
+ FLIST *folders; /* folder data */
+ struct folder_dir *prev; /* parent directory */
+} FDIR_S;
+
+
+typedef struct selected_s {
+ char *reference; /* location of selected */
+ STRLIST_S *folders; /* list of selected */
+ unsigned zoomed:1; /* zoomed state */
+ struct selected_s *sub;
+} SELECTED_S;
+
+
+/*------------------------------
+ Stucture to keep track of the various folder collections being
+ dealt with.
+ ----*/
+typedef struct context {
+ FDIR_S *dir; /* directory stack */
+ char *context, /* raw context string */
+ *server, /* server name/parms */
+ *nickname, /* user provided nickname */
+ *label, /* Description */
+ *comment, /* Optional comment */
+ last_folder[MAXFOLDER+1]; /* last folder used */
+ struct {
+ struct variable *v; /* variable where defined */
+ short i; /* index into config list */
+ } var;
+
+ unsigned short use, /* use flags (see below) */
+ d_line; /* display line for labels */
+ SELECTED_S selected;
+ struct context *next, /* next context struct */
+ *prev; /* previous context struct */
+} CONTEXT_S;
+
+/*
+ * Flags to indicate context (i.e., folder collection) use
+ */
+#define CNTXT_PSEUDO 0x0001 /* fake folder entry exists */
+#define CNTXT_INCMNG 0x0002 /* inbox collection */
+#define CNTXT_SAVEDFLT 0x0004 /* default save collection */
+#define CNTXT_PARTFIND 0x0008 /* partial find done */
+#define CNTXT_NOFIND 0x0010 /* no find done in context */
+#define CNTXT_FINDALL 0x0020 /* Do a find_all on context */
+#define CNTXT_NEWS 0x0040 /* News namespace collection */
+#define CNTXT_SUBDIR 0x0080 /* subdirectory within col'n */
+#define CNTXT_PRESRV 0x0100 /* preserve/restore selected */
+#define CNTXT_ZOOM 0x0200 /* context display narrowed */
+#define CNTXT_INHERIT 0x1000
+
+
+/*
+ * Macros to help users of above two structures...
+ */
+#define NEWS_TEST(c) ((c) && ((c)->use & CNTXT_NEWS))
+
+#define FOLDERS(c) ((c)->dir->folders)
+#define FLDR_NAME(X) ((X) ? ((X)->nickname ? (X)->nickname : (X)->name) :"")
+#define ALL_FOUND(X) (((X)->dir->status & CNTXT_NOFIND) == 0 && \
+ ((X)->dir->status & CNTXT_PARTFIND) == 0)
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_FOLDERTYPE_INCLUDED */
diff --git a/pith/handle.c b/pith/handle.c
new file mode 100644
index 00000000..cf6fec57
--- /dev/null
+++ b/pith/handle.c
@@ -0,0 +1,169 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: handle.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/handle.h"
+#include "../pith/mailview.h"
+
+
+HANDLE_S *
+get_handle(HANDLE_S *handles, int key)
+{
+ HANDLE_S *h;
+
+ if((h = handles) != NULL){
+ for( ; h ; h = h->next)
+ if(h->key == key)
+ return(h);
+
+ for(h = handles->prev ; h ; h = h->prev)
+ if(h->key == key)
+ return(h);
+ }
+
+ return(NULL);
+}
+
+
+void
+init_handles(HANDLE_S **handlesp)
+{
+ if(handlesp)
+ *handlesp = NULL;
+
+ (void) url_external_specific_handler(NULL, 0);
+}
+
+
+
+HANDLE_S *
+new_handle(HANDLE_S **handlesp)
+{
+ HANDLE_S *hp, *h = NULL;
+
+ if(handlesp){
+ h = (HANDLE_S *) fs_get(sizeof(HANDLE_S));
+ memset(h, 0, sizeof(HANDLE_S));
+
+ /* Put it in the list */
+ if((hp = *handlesp) != NULL){
+ while(hp->next)
+ hp = hp->next;
+
+ h->key = hp->key + 1;
+ hp->next = h;
+ h->prev = hp;
+ }
+ else{
+ /* Assumption #2,340: There are NO ZERO KEY HANDLES */
+ h->key = 1;
+ *handlesp = h;
+ }
+ }
+
+ return(h);
+}
+
+
+/*
+ * Normally we ignore the is_used bit in HANDLE_S. However, if we are
+ * using the delete_quotes filter, we pay attention to it. All of the is_used
+ * bits are off by default, and the delete_quotes filter turns them on
+ * if it is including lines with those handles.
+ *
+ * This is a bit of a crock, since it depends heavily on the order of the
+ * filters. Notice that the charset_editorial filter, which comes after
+ * delete_quotes and adds a handle, has to explicitly set the is_used bit!
+ */
+void
+delete_unused_handles(HANDLE_S **handlesp)
+{
+ HANDLE_S *h, *nexth;
+
+ if(handlesp && *handlesp && (*handlesp)->using_is_used){
+ for(h = *handlesp; h && h->prev; h = h->prev)
+ ;
+
+ for(; h; h = nexth){
+ nexth = h->next;
+ if(h->is_used == 0){
+ if(h == *handlesp)
+ *handlesp = nexth;
+
+ free_handle(&h);
+ }
+ }
+ }
+}
+
+
+void
+free_handle(HANDLE_S **h)
+{
+ if(h){
+ if((*h)->next) /* clip from list */
+ (*h)->next->prev = (*h)->prev;
+
+ if((*h)->prev)
+ (*h)->prev->next = (*h)->next;
+
+ if((*h)->type == URL){ /* destroy malloc'd data */
+ if((*h)->h.url.path)
+ fs_give((void **) &(*h)->h.url.path);
+
+ if((*h)->h.url.tool)
+ fs_give((void **) &(*h)->h.url.tool);
+
+ if((*h)->h.url.name)
+ fs_give((void **) &(*h)->h.url.name);
+ }
+
+ free_handle_locations(&(*h)->loc);
+
+ fs_give((void **) h);
+ }
+}
+
+
+void
+free_handles(HANDLE_S **handlesp)
+{
+ HANDLE_S *h;
+
+ if(handlesp && *handlesp){
+ while((h = (*handlesp)->next) != NULL)
+ free_handle(&h);
+
+ while((h = (*handlesp)->prev) != NULL)
+ free_handle(&h);
+
+ free_handle(handlesp);
+ }
+}
+
+
+void
+free_handle_locations(POSLIST_S **l)
+{
+ if(*l){
+ free_handle_locations(&(*l)->next);
+ fs_give((void **) l);
+ }
+}
+
+
diff --git a/pith/handle.h b/pith/handle.h
new file mode 100644
index 00000000..a6f87ada
--- /dev/null
+++ b/pith/handle.h
@@ -0,0 +1,90 @@
+/*
+ * $Id: handle.h 814 2007-11-14 18:39:28Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_HANDLE_INCLUDED
+#define PITH_HANDLE_INCLUDED
+
+
+#include "../pith/context.h"
+#include "../pith/msgno.h"
+#include "../pith/atttype.h"
+#include "../pith/util.h"
+
+
+typedef struct screen_position_list {
+ Pos where;
+ struct screen_position_list *next;
+} POSLIST_S;
+
+
+/*
+ * Struct to help manage embedded urls (and anythin' else we might embed)
+ */
+typedef struct handle_s {
+ int key; /* tag number embedded in text */
+ enum {URL, Attach, Folder, Function, IMG} type;
+ unsigned force_display:1; /* Don't ask before launching */
+ unsigned using_is_used:1; /* bit below is being used */
+ unsigned is_used:1; /* if not, remove it from list */
+ unsigned color_unseen:1; /* we're coloring folders with unseen */
+ unsigned is_dual_do_open:1; /* choosing this handle means open */
+ union {
+ struct { /* URL corresponding to this handle */
+ char *path, /* Actual url string */
+ *tool, /* displaying application */
+ *name; /* URL's NAME attribute */
+ } url; /* stuff to describe URL handle */
+ struct {
+ char *src, /* src of image (CID: only?) */
+ *alt; /* image alternate text */
+ } img; /* stuff to describe img */
+ ATTACH_S *attach; /* Attachment struct for this handle */
+ struct {
+ int index; /* folder's place in context's list */
+ CONTEXT_S *context; /* description of folders */
+ } f; /* stuff to describe Folder handle */
+ struct {
+ struct { /* function and args to pass it */
+ MAILSTREAM *stream;
+ MSGNO_S *msgmap;
+ long msgno;
+ } args;
+ void (*f)(MAILSTREAM *, MSGNO_S *, long);
+ } func;
+ } h;
+ POSLIST_S *loc; /* list of places it exists in text */
+ struct handle_s *next, *prev; /* next and previous in the list */
+} HANDLE_S ;
+
+
+
+/*
+ * Function used to dispatch locally handled URL's
+ */
+typedef int (*url_tool_t)(char *);
+
+
+/* exported protoypes */
+HANDLE_S *get_handle(HANDLE_S *, int);
+void init_handles(HANDLE_S **);
+HANDLE_S *new_handle(HANDLE_S **);
+void delete_unused_handles(HANDLE_S **);
+void free_handle(HANDLE_S **);
+void free_handles(HANDLE_S **);
+void free_handle_locations(POSLIST_S **);
+
+
+#endif /* PITH_HANDLE_INCLUDED */
diff --git a/pith/headers.h b/pith/headers.h
new file mode 100644
index 00000000..f1fe5df7
--- /dev/null
+++ b/pith/headers.h
@@ -0,0 +1,63 @@
+/*
+ * $Id: headers.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#ifndef PITH_HEADERS_INCLUDED
+#define PITH_HEADERS_INCLUDED
+
+
+/*----------------------------------------------------------------------
+ Include files
+ ----*/
+#include <system.h> /* os-dep defs/includes */
+#include <general.h> /* generally useful definitions */
+
+#include "../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../c-client/osdep.h"
+#include "../c-client/rfc822.h" /* for soutr_t and such */
+#include "../c-client/misc.h" /* for cpystr proto */
+#include "../c-client/utf8.h" /* for CHARSET and such*/
+#include "../c-client/imap4r1.h"
+
+/* include osdep protos and def'ns */
+#include "osdep/bldpath.h"
+#include "osdep/canaccess.h"
+#include "osdep/canonicl.h"
+#include "osdep/collate.h"
+#include "osdep/color.h"
+#include "osdep/coredump.h"
+#include "osdep/creatdir.h"
+#include "osdep/debugtime.h"
+#include "osdep/domnames.h"
+#include "osdep/err_desc.h"
+#include "osdep/fgetpos.h"
+#include "osdep/filesize.h"
+#include "osdep/fnexpand.h"
+#include "osdep/hostname.h"
+#include "osdep/lstcmpnt.h"
+#include "osdep/mimedisp.h"
+#include "osdep/pipe.h"
+#include "osdep/pithosd.h"
+#include "osdep/pw_stuff.h"
+#include "osdep/rename.h"
+#include "osdep/tempfile.h"
+#include "osdep/temp_nam.h"
+#include "osdep/writ_dir.h"
+#include "charconv/utf8.h"
+#include "charconv/filesys.h"
+
+#include "debug.h"
+
+#endif /* PITH_HEADERS_INCLUDED */
diff --git a/pith/help.c b/pith/help.c
new file mode 100644
index 00000000..4e8ed61a
--- /dev/null
+++ b/pith/help.c
@@ -0,0 +1,369 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: help.c 900 2008-01-05 01:13:26Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/help.h"
+#include "../pith/flag.h"
+#include "../pith/conf.h"
+#include "../pith/sort.h"
+
+
+REV_MSG_S rmjoarray[RMJLEN]; /* For regular journal */
+REV_MSG_S rmloarray[RMLLEN]; /* debug 0-4 */
+REV_MSG_S rmhiarray[RMHLEN]; /* debug 5-9 */
+int rmjofirst = -1, rmjolast = -1;
+int rmlofirst = -1, rmlolast = -1;
+int rmhifirst = -1, rmhilast = -1;
+int rm_not_right_now;
+
+
+
+HelpType
+help_name2section(char *url, int url_len)
+{
+ char name[256];
+ HelpType newhelp = NO_HELP;
+ struct help_texts *t;
+
+ snprintf(name, sizeof(name), "%.*s", MIN(url_len,sizeof(name)), url);
+
+ for(t = h_texts; t->help_text != NO_HELP; t++)
+ if(!strucmp(t->tag, name)){
+ newhelp = t->help_text;
+ break;
+ }
+
+ return(newhelp);
+}
+
+
+char *
+get_alpine_revision_string(char *buf, size_t nbuf)
+{
+ char ourbuf[100], *p;
+ char *rev = NULL;
+
+ buf[0] = '\0';
+ ourbuf[0] = '\0';
+
+ /* HelpType (the type of h_revision) is assumed to be char ** */
+ if(h_revision && h_revision[0] && h_revision[0][0]){
+ strncpy(ourbuf, h_revision[0], sizeof(ourbuf)-1);
+ ourbuf[sizeof(ourbuf)-1] = '\0';
+ }
+
+ if(ourbuf[0]){
+ /* move to revision number */
+ for(p = ourbuf; *p && !isdigit((unsigned char) (*p)); p++)
+ ;
+
+ if(*p)
+ rev = p;
+
+ if(rev){
+ /* skip to following space */
+ for(; *p && !isspace((unsigned char) (*p)); p++)
+ ;
+
+ /* skip whitespace */
+ for(; *p && isspace((unsigned char) (*p)); p++)
+ ;
+
+ /* skip over date to following space */
+ for(; *p && !isspace((unsigned char) (*p)); p++)
+ ;
+
+ strncpy(buf, rev, MIN(p-rev, nbuf-1));
+ buf[MIN(p-rev,nbuf-1)] = '\0';
+ }
+ }
+
+ return(buf);
+}
+
+
+char *
+get_alpine_revision_number(char *buf, size_t nbuf)
+{
+ char ourbuf[100], *p;
+ char *rev = NULL;
+
+ buf[0] = '\0';
+ ourbuf[0] = '\0';
+
+ /* HelpType (the type of h_revision) is assumed to be char ** */
+ if(h_revision && h_revision[0] && h_revision[0][0]){
+ strncpy(ourbuf, h_revision[0], sizeof(ourbuf)-1);
+ ourbuf[sizeof(ourbuf)-1] = '\0';
+ }
+
+ if(ourbuf[0]){
+ /* move to revision number */
+ for(p = ourbuf; *p && !isdigit((unsigned char) (*p)); p++)
+ ;
+
+ if(*p)
+ rev = p;
+
+ if(rev){
+ /* skip to following space */
+ for(; *p && !isspace((unsigned char) (*p)); p++)
+ ;
+
+ strncpy(buf, rev, MIN(p-rev, nbuf-1));
+ buf[MIN(p-rev,nbuf-1)] = '\0';
+ }
+ }
+
+ return(buf);
+}
+
+
+#ifdef DEBUG
+
+void
+debugjournal_to_file(FILE *dfile)
+{
+ int donejo, donelo, donehi, jo, lo, hi;
+ RMCat rmcat;
+
+ if(dfile && (rmjofirst >= 0 || rmlofirst >= 0 || rmhifirst >= 0)
+ && rmjofirst < RMJLEN && rmjolast < RMJLEN
+ && rmlofirst < RMLLEN && rmlolast < RMLLEN
+ && rmhifirst < RMHLEN && rmhilast < RMHLEN
+ && (rmjofirst < 0 || rmjolast >= 0)
+ && (rmlofirst < 0 || rmlolast >= 0)
+ && (rmhifirst < 0 || rmhilast >= 0)){
+
+ donejo = donehi = donelo = 0;
+ jo = rmjofirst;
+ if(jo < 0)
+ donejo = 1;
+
+ lo = rmlofirst;
+ if(lo < 0)
+ donelo = 1;
+
+ hi = rmhifirst;
+ if(hi < 0)
+ donehi = 1;
+
+ while(!(donejo && donelo && donehi)){
+ REV_MSG_S *pjo, *plo, *phi, *p;
+
+ if(!donejo)
+ pjo = &rmjoarray[jo];
+ else
+ pjo = NULL;
+
+ if(!donelo)
+ plo = &rmloarray[lo];
+ else
+ plo = NULL;
+
+ if(!donehi)
+ phi = &rmhiarray[hi];
+ else
+ phi = NULL;
+
+ if(pjo && (!plo || pjo->seq <= plo->seq)
+ && (!phi || pjo->seq <= phi->seq))
+ rmcat = Jo;
+ else if(plo && (!phi || plo->seq <= phi->seq))
+ rmcat = Lo;
+ else if(phi)
+ rmcat = Hi;
+ else
+ rmcat = No;
+
+ if(rmcat == Jo){
+ p = pjo;
+ if(jo == rmjofirst &&
+ (((rmjolast + 1) % RMJLEN) == rmjofirst) &&
+ fputs("*** Level -1 entries prior to this are deleted", dfile) == EOF)
+ break;
+ }
+ else if(rmcat == Lo){
+ p = plo;
+ if(lo == rmlofirst &&
+ (((rmlolast + 1) % RMLLEN) == rmlofirst) &&
+ fputs("*** Level 0-4 entries prior to this are deleted", dfile) == EOF)
+ break;
+ }
+ else if(rmcat == Hi){
+ p = phi;
+ if(hi == rmhifirst &&
+ (((rmhilast + 1) % RMHLEN) == rmhifirst) &&
+ fputs("*** Level 5-9 entries prior to this are deleted", dfile) == EOF)
+ break;
+ }
+ else if(rmcat == No){
+ p = NULL;
+ }
+
+ if(p){
+ if(p->timestamp && p->timestamp[0]
+ && (fputs(p->timestamp, dfile) == EOF
+ || fputs(": ", dfile) == EOF))
+ break;
+
+ if(p->message && p->message[0]
+ && (fputs(p->message, dfile) == EOF
+ || fputs("\n", dfile) == EOF))
+ break;
+ }
+
+ switch(rmcat){
+ case Jo:
+ if(jo == rmjolast)
+ donejo++;
+ else
+ jo = (jo + 1) % RMJLEN;
+
+ break;
+
+ case Lo:
+ if(lo == rmlolast)
+ donelo++;
+ else
+ lo = (lo + 1) % RMLLEN;
+
+ break;
+
+ case Hi:
+ if(hi == rmhilast)
+ donehi++;
+ else
+ hi = (hi + 1) % RMHLEN;
+
+ break;
+
+ default:
+ donejo++;
+ donelo++;
+ donehi++;
+ break;
+ }
+ }
+ }
+}
+
+#endif /* DEBUG */
+
+
+/*----------------------------------------------------------------------
+ Add a message to the circular status message review buffer
+
+ Args: message -- The message to add
+ -----*/
+void
+add_review_message(char *message, int level)
+{
+ int next_is_continuation = 0, cur_is_continuation = 0;
+ char *p, *q;
+ static unsigned long rmseq = 0L;
+
+ if(rm_not_right_now || !(message && *message))
+ return;
+
+ /*
+ * Debug output can have newlines in it, so split up each newline piece
+ * by hand and make them separate messages.
+ */
+ rm_not_right_now = 1;
+ for(p = message; *p; p = (*q && !next_is_continuation) ? q+1 : q){
+ for(q = p; *q && *q != '\n' && (q-p) < RMMSGLEN; q++)
+ ;
+
+ if(p == q)
+ continue;
+
+ cur_is_continuation = next_is_continuation;
+
+ if((q-p) == RMMSGLEN && *q && *q != '\n')
+ next_is_continuation = 1;
+ else
+ next_is_continuation = 0;
+
+ if(level < 0){
+ if(rmjofirst < 0){
+ rmjofirst = 0;
+ rmjolast = 0;
+ }
+ else{
+ rmjolast = (rmjolast + 1) % RMJLEN;
+ if(rmjolast == rmjofirst)
+ rmjofirst = (rmjofirst + 1) % RMJLEN;
+ }
+
+ rmjoarray[rmjolast].level = (short) level;
+ rmjoarray[rmjolast].seq = rmseq++;
+ rmjoarray[rmjolast].continuation = cur_is_continuation ? 1 : 0;
+ memset(rmjoarray[rmjolast].message, 0, (RMMSGLEN+1)*sizeof(char));
+ strncpy(rmjoarray[rmjolast].message, p, MIN(q-p,RMMSGLEN));
+#ifdef DEBUG
+ memset(rmjoarray[rmjolast].timestamp, 0, (RMTIMLEN+1)*sizeof(char));
+ strncpy(rmjoarray[rmjolast].timestamp, debug_time(0,1), RMTIMLEN);
+#endif
+ }
+ else if(level <= 4){
+ if(rmlofirst < 0){
+ rmlofirst = 0;
+ rmlolast = 0;
+ }
+ else{
+ rmlolast = (rmlolast + 1) % RMLLEN;
+ if(rmlolast == rmlofirst)
+ rmlofirst = (rmlofirst + 1) % RMLLEN;
+ }
+
+ rmloarray[rmlolast].level = (short) level;
+ rmloarray[rmlolast].seq = rmseq++;
+ rmloarray[rmlolast].continuation = cur_is_continuation ? 1 : 0;
+ memset(rmloarray[rmlolast].message, 0, (RMMSGLEN+1)*sizeof(char));
+ strncpy(rmloarray[rmlolast].message, p, MIN(q-p,RMMSGLEN));
+#ifdef DEBUG
+ memset(rmloarray[rmlolast].timestamp, 0, (RMTIMLEN+1)*sizeof(char));
+ strncpy(rmloarray[rmlolast].timestamp, debug_time(0,1), RMTIMLEN);
+#endif
+ }
+ else{
+ if(rmhifirst < 0){
+ rmhifirst = 0;
+ rmhilast = 0;
+ }
+ else{
+ rmhilast = (rmhilast + 1) % RMHLEN;
+ if(rmhilast == rmhifirst)
+ rmhifirst = (rmhifirst + 1) % RMHLEN;
+ }
+
+ rmhiarray[rmhilast].level = (short) level;
+ rmhiarray[rmhilast].seq = rmseq++;
+ rmhiarray[rmhilast].continuation = cur_is_continuation ? 1 : 0;
+ memset(rmhiarray[rmhilast].message, 0, (RMMSGLEN+1)*sizeof(char));
+ strncpy(rmhiarray[rmhilast].message, p, MIN(q-p,RMMSGLEN));
+#ifdef DEBUG
+ memset(rmhiarray[rmhilast].timestamp, 0, (RMTIMLEN+1)*sizeof(char));
+ strncpy(rmhiarray[rmhilast].timestamp, debug_time(0,1), RMTIMLEN);
+#endif
+ }
+ }
+
+ rm_not_right_now = 0;
+}
diff --git a/pith/help.h b/pith/help.h
new file mode 100644
index 00000000..fb3bcd24
--- /dev/null
+++ b/pith/help.h
@@ -0,0 +1,61 @@
+/*
+ * $Id: help.h 900 2008-01-05 01:13:26Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_HELP_INCLUDED
+#define PITH_HELP_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/filttype.h"
+#include "../pith/helptext.h"
+
+
+#define RMMSGLEN 120
+#define RMTIMLEN 15
+#define RMJLEN 500
+#define RMLLEN 2000
+#define RMHLEN 2000
+
+typedef struct _rev_msg {
+ unsigned long seq;
+ short level; /* -1 for journal, debuglevel for dprint */
+ unsigned continuation:1;
+ char message[RMMSGLEN+1];
+ char timestamp[RMTIMLEN+1];
+} REV_MSG_S;
+
+
+typedef enum {No, Jo, Lo, Hi} RMCat;
+
+extern REV_MSG_S rmjoarray[RMJLEN]; /* For regular journal */
+extern REV_MSG_S rmloarray[RMLLEN]; /* debug 0-4 */
+extern REV_MSG_S rmhiarray[RMHLEN]; /* debug 5-9 */
+extern int rmjofirst, rmjolast;
+extern int rmlofirst, rmlolast;
+extern int rmhifirst, rmhilast;
+extern int rm_not_right_now;
+
+
+/* exported protoypes */
+HelpType help_name2section(char *, int);
+void debugjournal_to_file(FILE *);
+void add_review_message(char *, int);
+int gripe_gripe_to(char *);
+char *get_alpine_revision_string(char *, size_t);
+char *get_alpine_revision_number(char *, size_t);
+
+
+#endif /* PITH_HELP_INCLUDED */
diff --git a/pith/help_c_gen.c b/pith/help_c_gen.c
new file mode 100644
index 00000000..77ae2ed9
--- /dev/null
+++ b/pith/help_c_gen.c
@@ -0,0 +1,291 @@
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+
+typedef struct helplist {
+ char *name;
+ struct helplist *next;
+} HELPLIST_S;
+
+
+HELPLIST_S *help_list;
+
+
+void preamble(FILE *ofp);
+void body(FILE *ifp, FILE *ofp);
+char *quote_clean(char *rawline);
+int only_tags(char *line);
+int append_to_help_list(HELPLIST_S **, char *new);
+void print_help_list(HELPLIST_S *, FILE *fp);
+
+
+int
+main(int argc, char **argv)
+{
+ preamble(stdout);
+ body(stdin, stdout);
+ exit(0);
+}
+
+
+void
+preamble(FILE *ofp)
+{
+ fprintf(ofp, "\n\t\t/*\n");
+ fprintf(ofp, "\t\t * AUTMATICALLY GENERATED FILE!\n");
+ fprintf(ofp, "\t\t * DO NOT EDIT!!\n");
+ fprintf(ofp, "\t\t * See help_c_gen.c.\n\t\t */\n\n\n");
+ fprintf(ofp, "#include <stdio.h>\n#include \"headers.h\"\n#include \"helptext.h\"\n\n");
+}
+
+
+void
+body(FILE *ifp, FILE *ofp)
+{
+ char rawline[10000];
+ char *line;
+#define SPACE ' '
+ char *p, *helpname;
+ int in_text = 0, new_topic = 0, first_one = 1, justtags;
+
+ while(fgets(rawline, sizeof(rawline), ifp) != NULL){
+ if(rawline[0] == '#')
+ continue;
+
+ line = quote_clean(rawline);
+
+ if(!line){
+ /*
+ * Put errors in result so that it will cause a compile
+ * error and be noticed.
+ */
+ fprintf(ofp, "Error: quote_clean returns NULL for help line\n %s\n", rawline);
+ exit(-1);
+ }
+
+ justtags = 0;
+ if(!strncmp(line, "====", 4)){
+ p = line;
+ /* skip to first space */
+ while(*p && *p != SPACE)
+ p++;
+
+ if(!*p){
+ fprintf(ofp, "Error: help input line\n %s\n No space after ====\n", rawline);
+ exit(-1);
+ }
+
+ /* skip spaces */
+ while(*p && *p == SPACE)
+ p++;
+
+ if(!*p){
+ fprintf(ofp, "Error: help input line\n %s\n Missing helpname after ====\n", rawline);
+ exit(-1);
+ }
+
+ helpname = p;
+
+ /* skip to next space */
+ while(*p && *p != SPACE)
+ p++;
+
+ *p = '\0'; /* tie off helpname */
+
+ /* finish previous one */
+ if(in_text)
+ fprintf(ofp, "NULL\n};\n\n\n");
+
+ in_text = new_topic = 1;
+
+ fprintf(ofp, "char *%s[] = {\n", helpname);
+
+ if(append_to_help_list(&help_list, helpname) < 0){
+ fprintf(ofp, "Error: Can't allocate memory for help_list after line\n %s\n", rawline);
+ exit(-1);
+ }
+ }
+ else if(line[0] == '\0'){
+ if(in_text)
+ fprintf(ofp, "\" \",\n"); /* why the space? */
+ }
+ else if(only_tags(line)){
+ if(in_text){
+ fprintf(ofp, "\"%s\",\n", line);
+ justtags = 1;
+ }
+ }
+
+ if(line[0] && line[0] != '='){
+ if(in_text && !justtags){
+ if(first_one){
+ first_one = 0;
+ fprintf(ofp, "/*\n");
+ fprintf(ofp, "TRANSLATORS: The translation strings for pith/helptext.c\n");
+ fprintf(ofp, "are automatically generated by a script from the help\n");
+ fprintf(ofp, "text in pith/pine.hlp. This means that the translation job for\n");
+ fprintf(ofp, "the help text is particularly difficult.\n");
+ fprintf(ofp, "This is HTML source so please leave the text inside HTML tags untranslated.\n");
+ fprintf(ofp, "HTML tags like <LI> or <TITLE> should, of course, be left untranslated.\n");
+ fprintf(ofp, "Special HTML characters like &lt; (less than character) should be left alone.\n");
+ fprintf(ofp, "Alpine option names are short phrases with the words separated by\n");
+ fprintf(ofp, "dashes. An example of an option name is Quell-Extra-Post-Prompt.\n");
+ fprintf(ofp, "Option names should not be translated.\n");
+ fprintf(ofp, "The file pith/helptext.c contains many separate help topics.\n");
+ fprintf(ofp, "Some of them are very short and some are long. If left unsorted the\n");
+ fprintf(ofp, "text for a single topic is together in the translation file. The start\n");
+ fprintf(ofp, "of each new topic is marked by the comment\n");
+ fprintf(ofp, "TRANSLATORS: Start of new help topic.\n");
+ fprintf(ofp, "*/\n");
+ }
+ else if(new_topic){
+ new_topic = 0;
+ fprintf(ofp, "/* TRANSLATORS: Start of new help topic. */\n");
+ }
+
+ fprintf(ofp, "N_(\"%s\"),\n", line);
+ }
+ else{
+ ; /* skip leading cruft */
+ }
+ }
+ }
+
+ if(in_text)
+ fprintf(ofp, "NULL\n};\n\n\n");
+
+ print_help_list(help_list, ofp);
+}
+
+
+char *
+quote_clean(char *rawline)
+{
+ char *p, *q, *cleaned = NULL;
+ size_t len;
+
+ if(rawline){
+ len = strlen(rawline);
+ cleaned = (char *) malloc((2*len+1) * sizeof(char));
+
+ if(cleaned){
+ p = rawline;
+ q = cleaned;
+
+ while(*p && *p != '\n'){
+ if(*p == '"' && !(p > rawline && *(p-1) == '\\'))
+ *q++ = '\\';
+
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+ }
+ }
+
+ return cleaned;
+}
+
+
+int
+only_tags(char *line)
+{
+ char *p;
+ int is_tags = 1; /* only tags seen so far */
+
+ if(!line)
+ return 0;
+
+ p = line;
+
+ while(is_tags && *p){
+ /* leading space before a tag */
+ while(*p && isspace(*p))
+ p++;
+
+ if(*p == '<'){
+ p++;
+ /* skip through interior of tag */
+ while(*p && *p != '<' && *p != '>')
+ p++;
+
+ if(*p == '>'){
+ p++;
+ /* trailing space after tag */
+ while(*p && isspace(*p))
+ p++;
+ }
+ else
+ is_tags = 0;
+ }
+ else if(*p)
+ is_tags = 0;
+ }
+
+ return is_tags;
+}
+
+
+int
+append_to_help_list(HELPLIST_S **head, char *name)
+{
+ HELPLIST_S *new, *h;
+ size_t len;
+
+ if(!(name && *name && head))
+ return 0;
+
+ new = (HELPLIST_S *) malloc(sizeof(*new));
+ if(!new)
+ return -1;
+
+ memset(new, 0, sizeof(*new));
+ len = strlen(name);
+ new->name = (char *) malloc((len+1) * sizeof(char));
+ strncpy(new->name, name, len);
+ new->name[len] = '\0';
+
+ if(*head){
+ for(h = *head; h->next; h = h->next)
+ ;
+
+ h->next = new;
+ }
+ else
+ *head = new;
+
+ return 0;
+}
+
+
+void
+print_help_list(HELPLIST_S *head, FILE *fp)
+{
+ HELPLIST_S *h;
+
+ if(head){
+ fprintf(fp, "struct help_texts h_texts[] = {\n");
+
+ for(h = head; h; h = h->next)
+ if(h->name && h->name[0])
+ fprintf(fp, "{%s,\"%s\"},\n", h->name, h->name);
+
+ fprintf(fp, "{NO_HELP, NULL}\n};\n");
+ }
+}
diff --git a/pith/help_h_gen.c b/pith/help_h_gen.c
new file mode 100644
index 00000000..d858b0fb
--- /dev/null
+++ b/pith/help_h_gen.c
@@ -0,0 +1,92 @@
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+void preamble(FILE *ofp);
+void body(FILE *ifp, FILE *ofp);
+void postamble(FILE *ofp);
+
+
+int
+main(int argc, char **argv)
+{
+ preamble(stdout);
+ body(stdin, stdout);
+ postamble(stdout);
+ exit(0);
+}
+
+
+void
+preamble(FILE *ofp)
+{
+ fprintf(ofp, "\n\t\t/*\n");
+ fprintf(ofp, "\t\t * AUTMATICALLY GENERATED FILE!\n");
+ fprintf(ofp, "\t\t * DO NOT EDIT!!\n");
+ fprintf(ofp, "\t\t * See help_h_gen.c.\n\t\t */\n\n\n");
+ fprintf(ofp, "#ifndef PITH_HELPTEXT_INCLUDED\n");
+ fprintf(ofp, "#define PITH_HELPTEXT_INCLUDED\n\n\n");
+ fprintf(ofp, "#define\tHelpType\tchar **\n");
+ fprintf(ofp, "#define\tNO_HELP\t((char **) NULL)\n\n");
+ fprintf(ofp, "struct help_texts {\n");
+ fprintf(ofp, " HelpType help_text;\n");
+ fprintf(ofp, " char *tag;\n};\n\n");
+}
+
+
+void
+body(FILE *ifp, FILE *ofp)
+{
+ char line[10000];
+ char *space = " ";
+ char *p;
+
+ while(fgets(line, sizeof(line), ifp) != NULL){
+ if(!strncmp(line, "====", 4)){
+ p = strtok(line, space);
+ if(p){
+ p = strtok(NULL, space);
+ if(p){
+ if(isalpha(*p))
+ fprintf(ofp, "extern char *%s[];\n", p);
+ else{
+ fprintf(ofp, "Error: help input line\n %s\nis bad\n", line);
+ exit(-1);
+ }
+ }
+ else{
+ fprintf(ofp, "Error: help input\n %scontains ==== without following helpname\n", line);
+ exit(-1);
+ }
+
+ }
+ else{
+ fprintf(ofp, "Error: help input\n %scontains ==== without following space\n", line);
+ exit(-1);
+ }
+ }
+ }
+}
+
+
+void
+postamble(FILE *ofp)
+{
+ fprintf(ofp, "\nextern struct help_texts h_texts[];\n\n\n");
+ fprintf(ofp, "#endif /* PITH_HELPTEXT_INCLUDED */\n");
+}
diff --git a/pith/helpindx.c b/pith/helpindx.c
new file mode 100644
index 00000000..20cb9273
--- /dev/null
+++ b/pith/helpindx.c
@@ -0,0 +1,132 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: helpindx.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * very short, very specialized
+ *
+ *
+ *
+ */
+
+#include "system.h"
+
+#define HELP_KEY_MAX 64 /* maximum length of a key */
+
+struct hindx {
+ char key[HELP_KEY_MAX]; /* name of help section */
+ long offset; /* where help text starts */
+ short lines; /* how many lines there are */
+};
+
+int
+main(int argc, char **argv)
+{
+ char *p, s[1024];
+ long index;
+ int section,
+ len,
+ line,
+ i;
+ FILE *hp,
+ *hip, /* help index ptr */
+ *hhp; /* help header ptr */
+ struct hindx irec;
+
+ if(argc < 4){
+ fprintf(stderr,
+ "usage: helpindx <help_file> <index_file> <header_file>\n");
+ exit(-1);
+ }
+
+ if((hp = fopen(argv[1], "rb")) == NULL){ /* problems */
+ perror(argv[1]);
+ exit(-1);
+ }
+
+ if((hip = fopen(argv[2], "wb")) == NULL){ /* problems */
+ perror(argv[2]);
+ exit(-1);
+ }
+
+ if((hhp = fopen(argv[3], "w")) == NULL){ /* problems */
+ perror(argv[3]);
+ exit(-1);
+ }
+
+ fprintf(hhp,"/*\n * Alpine Help text header file\n */\n");
+ fprintf(hhp,"\n#ifndef PITH_HELPTEXT_INCLUDED\n#define PITH_HELPTEXT_INCLUDED\n");
+ fprintf(hhp,"\n#define\tHELP_KEY_MAX\t%d\n", HELP_KEY_MAX);
+ fprintf(hhp,"\ntypedef\tshort\tHelpType;\n");
+ fprintf(hhp,"\n#define\tNO_HELP\t(-1)\n");
+ fprintf(hhp,"struct hindx {\n char key[HELP_KEY_MAX];");
+ fprintf(hhp,"\t\t/* name of help section */\n");
+ fprintf(hhp," long offset;\t\t\t/* where help text starts */\n");
+ fprintf(hhp," short lines;\t\t\t/* how many lines there are */\n");
+ fprintf(hhp,"};\n\n\n/*\n * defs for help section titles\n */\n");
+
+ index = 0L;
+ line = section = 0;
+
+ while(fgets(s, sizeof(s) - 1, hp) != NULL){
+ line++;
+ len = strlen(s);
+ if(s[0] == '='){ /* new section? */
+ i = 0;
+ while((s[i] == '=' || isspace((unsigned char)s[i])) && i < len)
+ i++;
+
+ if(section)
+ fwrite(&irec, sizeof(struct hindx), 1, hip);
+
+ irec.offset = index + (long)i; /* save where name starts */
+ irec.lines = 0;
+ p = &irec.key[0]; /* save name field */
+ while(!isspace((unsigned char)s[i]) && i < len)
+ *p++ = s[i++];
+ *p = '\0';
+
+ if(irec.key[0] == '\0'){
+ fprintf(stderr,"Invalid help line %d: %s", line, s);
+ exit(-1);
+ }
+ else
+ fprintf(hhp, "#define\t%s\t%d\n", irec.key, section++);
+
+ }
+ else if(s[0] == '#' && section){
+ fprintf(stderr,"Comments not allowed in help text: line %d", line);
+ exit(-1);
+ }
+ else{
+ irec.lines++;
+ }
+ index += len;
+ }
+
+ if(section) /* write last entry */
+ fwrite(&irec, sizeof(struct hindx), 1, hip);
+
+ fprintf(hhp, "#define\tLASTHELP\t%d\n", section);
+
+ fprintf(hhp,"\n#endif /* PITH_HELPTEXT_INCLUDED */\n");
+
+ fclose(hp);
+ fclose(hip);
+ fclose(hhp);
+ exit(0);
+}
diff --git a/pith/hist.c b/pith/hist.c
new file mode 100644
index 00000000..18a132a1
--- /dev/null
+++ b/pith/hist.c
@@ -0,0 +1,218 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: hist.c 807 2007-11-09 01:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#include "../pith/headers.h"
+#include "../pith/conf.h"
+#include "../pith/hist.h"
+
+
+void
+init_hist(HISTORY_S **history, int histsize)
+{
+ size_t l;
+
+ if(!history)
+ return;
+
+ if(!*history){
+ l = sizeof(**history) + histsize * sizeof(ONE_HIST_S);
+ *history = (HISTORY_S *) fs_get(l);
+ memset(*history, 0, l);
+ (*history)->histsize = histsize;
+ (*history)->origindex = histsize - 1;
+ add_to_histlist(history);
+ }
+
+ (*history)->curindex = (*history)->origindex;
+}
+
+
+void
+free_hist(HISTORY_S **history)
+{
+ int i;
+
+ if(history && *history){
+
+ for(i = 0; i < (*history)->histsize; i++)
+ if((*history)->hist[i] && (*history)->hist[i]->str)
+ fs_give((void **) &(*history)->hist[i]->str);
+
+ fs_give((void **) history);
+ }
+}
+
+
+char *
+get_prev_hist(HISTORY_S *history, char *savethis, unsigned saveflags, void *cntxt)
+{
+ int nextcurindex;
+ size_t l;
+
+ if(!(history && history->histsize > 0))
+ return NULL;
+
+ nextcurindex = (history->curindex + 1) % history->histsize;
+
+ /* already at start of history */
+ if(nextcurindex == history->origindex
+ || !(history->hist[nextcurindex] && history->hist[nextcurindex]->str
+ && history->hist[nextcurindex]->str[0]))
+ return NULL;
+
+ /* save what user typed */
+ if(history->curindex == history->origindex){
+ if(!savethis)
+ savethis = "";
+
+ if(!history->hist[history->origindex]){
+ history->hist[history->origindex] = (ONE_HIST_S *) fs_get(sizeof(ONE_HIST_S));
+ memset(history->hist[history->origindex], 0, sizeof(ONE_HIST_S));
+ }
+
+ if(history->hist[history->origindex]->str){
+ if(strlen(history->hist[history->origindex]->str) < (l=strlen(savethis)))
+ fs_resize((void **) &history->hist[history->origindex]->str, l+1);
+
+ strncpy(history->hist[history->origindex]->str, savethis, l+1);
+ history->hist[history->origindex]->str[l] = '\0';
+ }
+ else
+ history->hist[history->origindex]->str = cpystr(savethis);
+
+ history->hist[history->origindex]->flags = saveflags;
+
+ history->hist[history->origindex]->cntxt = cntxt;
+ }
+
+ history->curindex = nextcurindex;
+
+ return((history->hist[history->curindex] && history->hist[history->curindex]->str)
+ ? history->hist[history->curindex]->str : NULL);
+}
+
+
+char *
+get_next_hist(HISTORY_S *history, char *savethis, unsigned saveflags, void *cntxt)
+{
+ if(!(history && history->histsize > 0))
+ return NULL;
+
+ /* already at end (most recent) of history */
+ if(history->curindex == history->origindex)
+ return NULL;
+
+ history->curindex = (history->curindex + history->histsize - 1) % history->histsize;
+
+ return((history->hist[history->curindex] && history->hist[history->curindex]->str)
+ ? history->hist[history->curindex]->str : NULL);
+}
+
+
+void
+save_hist(HISTORY_S *history, char *savethis, unsigned saveflags, void *cntxt)
+{
+ size_t l;
+ int plusone;
+
+ if(!(history && history->histsize > 0))
+ return;
+
+ plusone = (history->origindex + 1) % history->histsize;
+
+ if(!history->hist[history->origindex]){
+ history->hist[history->origindex] = (ONE_HIST_S *) fs_get(sizeof(ONE_HIST_S));
+ memset(history->hist[history->origindex], 0, sizeof(ONE_HIST_S));
+ }
+
+ if(savethis && savethis[0]
+ && (!history->hist[history->origindex]->str
+ || strcmp(history->hist[history->origindex]->str, savethis)
+ || history->hist[history->origindex]->flags != saveflags
+ || history->hist[history->origindex]->cntxt != cntxt)
+ && !(history->hist[plusone] && history->hist[plusone]->str
+ && !strcmp(history->hist[plusone]->str, savethis)
+ && history->hist[history->origindex]->flags == saveflags
+ && history->hist[history->origindex]->cntxt == cntxt)){
+ if(history->hist[history->origindex]->str){
+ if(strlen(history->hist[history->origindex]->str) < (l=strlen(savethis)))
+ fs_resize((void **) &history->hist[history->origindex]->str, l+1);
+
+ strncpy(history->hist[history->origindex]->str, savethis, l+1);
+ history->hist[history->origindex]->str[l] = '\0';
+ }
+ else{
+ history->hist[history->origindex]->str = cpystr(savethis);
+ }
+
+ history->hist[history->origindex]->flags = saveflags;
+ history->hist[history->origindex]->cntxt = cntxt;
+
+ history->origindex = (history->origindex + history->histsize - 1) % history->histsize;
+ if(history->hist[history->origindex] && history->hist[history->origindex]->str)
+ history->hist[history->origindex]->str[0] = '\0';
+ }
+}
+
+
+/*
+ * Returns count of items entered into history.
+ */
+int
+items_in_hist(HISTORY_S *history)
+{
+ int i, cnt = 0;
+
+ if(history && history->histsize > 0)
+ for(i = 0; i < history->histsize; i++)
+ if(history->hist[i] && history->hist[i]->str)
+ cnt++;
+
+ return(cnt);
+}
+
+
+static HISTORY_S **histlist[100];
+
+void
+add_to_histlist(HISTORY_S **history)
+{
+ int i;
+
+ if(history){
+ /* find empty slot */
+ for(i = 0; i < 100; i++)
+ if(!histlist[i])
+ break;
+
+ if(i < 100)
+ histlist[i] = history;
+ }
+}
+
+
+void
+free_histlist(void)
+{
+ int i;
+
+ for(i = 0; i < 100; i++)
+ if(histlist[i])
+ free_hist(histlist[i]);
+}
diff --git a/pith/hist.h b/pith/hist.h
new file mode 100644
index 00000000..b3a2b421
--- /dev/null
+++ b/pith/hist.h
@@ -0,0 +1,53 @@
+/*
+ * $Id: hist.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_HIST_INCLUDED
+#define PITH_HIST_INCLUDED
+
+
+#define HISTSIZE (20+1)
+
+typedef struct one_hist {
+ char *str;
+ unsigned flags;
+ void *cntxt;
+} ONE_HIST_S;
+
+
+typedef struct history_s {
+ int histsize;
+ int origindex;
+ int curindex;
+ ONE_HIST_S *hist[1]; /* has size histsize */
+} HISTORY_S;
+
+
+#define HISTORY_UP_KEYNAME "Up"
+#define HISTORY_DOWN_KEYNAME "Down"
+#define HISTORY_KEYLABEL N_("History")
+
+
+void init_hist(HISTORY_S **, int);
+void free_hist(HISTORY_S **);
+char *get_prev_hist(HISTORY_S *, char *, unsigned, void *);
+char *get_next_hist(HISTORY_S *, char *, unsigned, void *);
+void save_hist(HISTORY_S *, char *, unsigned, void *);
+int items_in_hist(HISTORY_S *);
+void add_to_histlist(HISTORY_S **);
+void free_histlist(void);
+
+
+#endif /* PITH_HIST_INCLUDED */
diff --git a/pith/icache.c b/pith/icache.c
new file mode 100644
index 00000000..fc2a1eb8
--- /dev/null
+++ b/pith/icache.c
@@ -0,0 +1,452 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: icache.c 874 2007-12-15 02:51:06Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/icache.h"
+#include "../pith/mailindx.h"
+#include "../pith/flag.h"
+#include "../pith/msgno.h"
+#include "../pith/status.h"
+#include "../pith/pineelt.h"
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * * * * Index entry cache manager * * *
+ */
+
+
+/*
+ * Erase a particular entry in the cache.
+ */
+void
+clear_index_cache_ent(MAILSTREAM *stream, long int msgno, unsigned int flags)
+{
+ long rawno = -1L;
+ PINELT_S **peltp;
+ MESSAGECACHE *mc;
+
+ if(stream){
+ if(flags && IC_USE_RAW_MSGNO)
+ rawno = msgno;
+ else
+ rawno = mn_m2raw(sp_msgmap(stream), msgno);
+
+ if(rawno > 0L && rawno <= stream->nmsgs){
+ mc = mail_elt(stream, rawno);
+ if(mc && mc->sparep){
+ peltp = (PINELT_S **) &mc->sparep;
+ if((*peltp)->ice){
+ /*
+ * This is intended to be a lightweight reset of
+ * just the widths and print_format strings. For example,
+ * the width of the screen changed and nothing else.
+ * We simply unset the widths_done bit and it
+ * is up to the drawer to free and recalculate the
+ * print_format strings and to reset the widths.
+ *
+ * The else case is a clear of the entire cache entry
+ * leaving behind only the empty structure.
+ */
+ if(flags & IC_CLEAR_WIDTHS_DONE){
+ (*peltp)->ice->widths_done = 0;
+
+ /* also zero out hash value */
+ (*peltp)->ice->id = 0;
+
+ if((*peltp)->ice->tice){
+ (*peltp)->ice->tice->widths_done = 0;
+
+ /* also zero out hash value */
+ (*peltp)->ice->tice->id = 0;
+ }
+ }
+ else
+ clear_ice(&(*peltp)->ice);
+ }
+ }
+ }
+ }
+}
+
+
+void
+clear_index_cache(MAILSTREAM *stream, unsigned int flags)
+{
+ long rawno;
+
+ if(stream){
+ set_need_format_setup(stream);
+ for(rawno = 1L; rawno <= stream->nmsgs; rawno++)
+ clear_index_cache_ent(stream, rawno, flags | IC_USE_RAW_MSGNO);
+ }
+}
+
+
+void
+clear_index_cache_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap)
+{
+ unsigned long msgno;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return;
+
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+
+ clear_index_cache_ent(stream, msgno, 0);
+
+ if(thrd->next)
+ clear_index_cache_for_thread(stream, fetch_thread(stream, thrd->next),
+ msgmap);
+
+ if(thrd->branch)
+ clear_index_cache_for_thread(stream, fetch_thread(stream, thrd->branch),
+ msgmap);
+}
+
+
+void
+clear_icache_flags(MAILSTREAM *stream)
+{
+ sp_set_icache_flags(stream, 0);
+}
+
+
+void
+set_need_format_setup(MAILSTREAM *stream)
+{
+ sp_set_icache_flags(stream, sp_icache_flags(stream) | SP_NEED_FORMAT_SETUP);
+}
+
+
+int
+need_format_setup(MAILSTREAM *stream)
+{
+ return(sp_icache_flags(stream) & SP_NEED_FORMAT_SETUP);
+}
+
+
+void
+set_format_includes_msgno(MAILSTREAM *stream)
+{
+ sp_set_icache_flags(stream, sp_icache_flags(stream) | SP_FORMAT_INCLUDES_MSGNO);
+}
+
+
+int
+format_includes_msgno(MAILSTREAM *stream)
+{
+ return(sp_icache_flags(stream) & SP_FORMAT_INCLUDES_MSGNO);
+}
+
+
+void
+set_format_includes_smartdate(MAILSTREAM *stream)
+{
+ sp_set_icache_flags(stream, sp_icache_flags(stream) | SP_FORMAT_INCLUDES_SMARTDATE);
+}
+
+
+int
+format_includes_smartdate(MAILSTREAM *stream)
+{
+ return(sp_icache_flags(stream) & SP_FORMAT_INCLUDES_SMARTDATE);
+}
+
+
+/*
+ * Almost a free_ice, but we leave the memory there for the ICE_S.
+ */
+void
+clear_ice(ICE_S **ice)
+{
+ if(ice && *ice){
+ free_ifield(&(*ice)->ifield);
+
+ if((*ice)->linecolor)
+ free_color_pair(&(*ice)->linecolor);
+
+ if((*ice)->tice)
+ clear_ice(&(*ice)->tice);
+
+ /* do these one at a time so we don't clear tice */
+ (*ice)->color_lookup_done = 0;
+ (*ice)->to_us = 0;
+ (*ice)->cc_us = 0;
+ (*ice)->plus = 0;
+ (*ice)->id = 0;
+ }
+}
+
+
+void
+free_ice(ICE_S **ice)
+{
+ if(ice && *ice){
+
+ if((*ice)->tice)
+ free_ice(&(*ice)->tice);
+
+ clear_ice(ice);
+
+ fs_give((void **) ice);
+ }
+}
+
+
+void
+free_ifield(IFIELD_S **ifld)
+{
+ if(ifld && *ifld){
+ free_ifield(&(*ifld)->next);
+ free_ielem(&(*ifld)->ielem);
+ fs_give((void **) ifld);
+ }
+}
+
+
+void
+free_ielem(IELEM_S **il)
+{
+ if(il && *il){
+ free_ielem(&(*il)->next);
+ if((*il)->freeprintf && (*il)->print_format)
+ fs_give((void **) &(*il)->print_format);
+
+ if((*il)->freecolor && (*il)->color)
+ free_color_pair(&(*il)->color);
+
+ if((*il)->freedata && (*il)->data)
+ fs_give((void **) &(*il)->data);
+
+ fs_give((void **) il);
+ }
+}
+
+
+/*
+ * Returns the index cache entry associated with this message.
+ * If it doesn't already exist it is instantiated.
+ */
+ICE_S *
+fetch_ice(MAILSTREAM *stream, long unsigned int rawno)
+{
+ PINELT_S **peltp;
+ MESSAGECACHE *mc;
+
+ if(!stream || rawno < 1L || rawno > stream->nmsgs)
+ return NULL;
+
+ if(!(mc = mail_elt(stream, rawno)))
+ return NULL;
+
+ /*
+ * any private elt data yet?
+ */
+ if((*(peltp = (PINELT_S **) &mc->sparep) == NULL)){
+ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
+ memset(*peltp, 0, sizeof(PINELT_S));
+ }
+
+ if((*peltp)->ice == NULL)
+ (*peltp)->ice = new_ice();
+
+ if(need_format_setup(stream) && setup_header_widths)
+ (*setup_header_widths)(stream);
+
+ return((*peltp)->ice);
+}
+
+
+ICE_S **
+fetch_ice_ptr(MAILSTREAM *stream, long unsigned int rawno)
+{
+ PINELT_S **peltp;
+ MESSAGECACHE *mc;
+
+ if(!stream || rawno < 1L || rawno > stream->nmsgs)
+ return NULL;
+
+ if(!(mc = mail_elt(stream, rawno)))
+ return NULL;
+
+ /*
+ * any private elt data yet?
+ */
+ if((*(peltp = (PINELT_S **) &mc->sparep) == NULL)){
+ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
+ memset(*peltp, 0, sizeof(PINELT_S));
+ }
+
+ return(&(*peltp)->ice);
+}
+
+
+ICE_S *
+copy_ice(ICE_S *src)
+{
+ ICE_S *head = NULL;
+
+ if(src){
+ head = new_ice();
+
+ head->color_lookup_done = src->color_lookup_done;
+ head->widths_done = src->widths_done;
+ head->to_us = src->to_us;
+ head->cc_us = src->cc_us;
+ head->plus = src->plus;
+ head->id = src->id;
+
+ if(src->linecolor)
+ head->linecolor = new_color_pair(src->linecolor->fg, src->linecolor->bg);
+
+ if(src->ifield)
+ head->ifield = copy_ifield(src->ifield);
+
+ if(src->tice)
+ head->tice = copy_ice(src->tice);
+ }
+
+ return(head);
+}
+
+
+IFIELD_S *
+copy_ifield(IFIELD_S *src)
+{
+ IFIELD_S *head = NULL;
+
+ if(src){
+ head = new_ifield(NULL);
+
+ if(src->next)
+ head->next = copy_ifield(src->next);
+
+ head->ctype = src->ctype;
+ head->width = src->width;
+ head->leftadj = src->leftadj;
+
+ if(src->ielem)
+ head->ielem = copy_ielem(src->ielem);
+ }
+
+ return(head);
+}
+
+
+IELEM_S *
+copy_ielem(IELEM_S *src)
+{
+ IELEM_S *head = NULL;
+
+ if(src){
+ head = new_ielem(NULL);
+
+ if(src->next)
+ head->next = copy_ielem(src->next);
+
+ head->type = src->type;
+ head->wid = src->wid;
+
+ if(src->color){
+ head->color = new_color_pair(src->color->fg, src->color->bg);
+ head->freecolor = 1;
+ }
+
+ if(src->data){
+ head->data = cpystr(src->data);
+ head->datalen = strlen(head->data);
+ head->freedata = 1;
+ }
+
+ if(src->print_format){
+ head->print_format = cpystr(src->print_format);
+ head->freeprintf = strlen(head->print_format) + 1;
+ }
+ }
+
+ return(head);
+}
+
+
+ICE_S *
+new_ice(void)
+{
+ ICE_S *ice;
+
+ ice = (ICE_S *) fs_get(sizeof(ICE_S));
+ memset(ice, 0, sizeof(ICE_S));
+ return(ice);
+}
+
+
+/*
+ * Create new IFIELD_S, zero it out, and insert it at end.
+ */
+IFIELD_S *
+new_ifield(IFIELD_S **ifieldp)
+{
+ IFIELD_S *ifield, *ip;
+
+ ifield = (IFIELD_S *) fs_get(sizeof(*ifield));
+ memset(ifield, 0, sizeof(*ifield));
+
+ if(ifieldp){
+ ip = *ifieldp;
+ if(ip){
+ for(ip = (*ifieldp); ip && ip->next; ip = ip->next)
+ ;
+
+ ip->next = ifield;
+ }
+ else
+ *ifieldp = ifield;
+ }
+
+ return(ifield);
+}
+
+
+/*
+ * Create new IELEM_S, zero it out, and insert it at end.
+ */
+IELEM_S *
+new_ielem(IELEM_S **ielemp)
+{
+ IELEM_S *ielem, *ip;
+
+ ielem = (IELEM_S *) fs_get(sizeof(*ielem));
+ memset(ielem, 0, sizeof(*ielem));
+
+ if(ielemp){
+ ip = *ielemp;
+ if(ip){
+ for(ip = (*ielemp); ip && ip->next; ip = ip->next)
+ ;
+
+ ip->next = ielem;
+ }
+ else
+ *ielemp = ielem;
+ }
+
+ return(ielem);
+}
diff --git a/pith/icache.h b/pith/icache.h
new file mode 100644
index 00000000..3d9b9a7a
--- /dev/null
+++ b/pith/icache.h
@@ -0,0 +1,56 @@
+/*
+ * $Id: icache.h 874 2007-12-15 02:51:06Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ICACHE_INCLUDED
+#define PITH_ICACHE_INCLUDED
+
+
+#include "../pith/msgno.h"
+#include "../pith/thread.h"
+#include "../pith/indxtype.h"
+
+
+/* flags for clear_index_cache() */
+#define IC_USE_RAW_MSGNO 0x01
+#define IC_CLEAR_WIDTHS_DONE 0x02
+
+
+/* exported protoypes */
+void clear_index_cache_ent(MAILSTREAM *, long, unsigned);
+void clear_index_cache(MAILSTREAM *, unsigned);
+void clear_index_cache_for_thread(MAILSTREAM *, PINETHRD_S *, MSGNO_S *);
+void clear_icache_flags(MAILSTREAM *);
+void set_need_format_setup(MAILSTREAM *);
+int need_format_setup(MAILSTREAM *);
+void set_format_includes_msgno(MAILSTREAM *);
+int format_includes_msgno(MAILSTREAM *);
+void set_format_includes_smartdate(MAILSTREAM *);
+int format_includes_smartdate(MAILSTREAM *);
+void free_ice(ICE_S **);
+void clear_ice(ICE_S **);
+void free_ifield(IFIELD_S **);
+void free_ielem(IELEM_S **);
+ICE_S *fetch_ice(MAILSTREAM *, unsigned long);
+ICE_S **fetch_ice_ptr(MAILSTREAM *, unsigned long);
+ICE_S *copy_ice(ICE_S *);
+IFIELD_S *copy_ifield(IFIELD_S *);
+IELEM_S *copy_ielem(IELEM_S *);
+ICE_S *new_ice(void);
+IFIELD_S *new_ifield(IFIELD_S **);
+IELEM_S *new_ielem(IELEM_S **);
+
+
+#endif /* PITH_ICACHE_INCLUDED */
diff --git a/pith/imap.c b/pith/imap.c
new file mode 100644
index 00000000..ea4c5b1f
--- /dev/null
+++ b/pith/imap.c
@@ -0,0 +1,1111 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: imap.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/imap.h"
+#include "../pith/msgno.h"
+#include "../pith/state.h"
+#include "../pith/flag.h"
+#include "../pith/pineelt.h"
+#include "../pith/status.h"
+#include "../pith/conftype.h"
+#include "../pith/context.h"
+#include "../pith/thread.h"
+#include "../pith/mailview.h"
+#include "../pith/mailpart.h"
+#include "../pith/mailindx.h"
+#include "../pith/mailcmd.h"
+#include "../pith/save.h"
+#include "../pith/util.h"
+#include "../pith/stream.h"
+#include "../pith/newmail.h"
+#include "../pith/icache.h"
+#include "../pith/options.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+long imap_seq_exec(MAILSTREAM *, char *, long (*)(MAILSTREAM *, long, void *), void *);
+long imap_seq_exec_append(MAILSTREAM *, long, void *);
+char *ps_get(size_t);
+
+
+/*
+ * Exported globals setup by searching functions to tell mm_searched
+ * where to put message numbers that matched the search criteria,
+ * and to allow mm_searched to return number of matches.
+ */
+MAILSTREAM *mm_search_stream;
+long mm_search_count = 0L;
+MAILSTATUS mm_status_result;
+
+MM_LIST_S *mm_list_info;
+
+MMLOGIN_S *mm_login_list = NULL;
+MMLOGIN_S *cert_failure_list = NULL;
+
+/*
+ * Instead of storing cached passwords in free storage, store them in this
+ * private space. This makes it easier and more reliable when we want
+ * to zero this space out. We only store passwords here (char *) so we
+ * don't need to worry about alignment.
+ */
+static volatile char private_store[1024];
+
+static int critical_depth = 0;
+
+/* hook to hang callback on "current" message expunge */
+void (*pith_opt_current_expunged)(long unsigned int);
+
+#ifdef SIGINT
+RETSIGTYPE (*hold_int)(int);
+#endif
+
+#ifdef SIGTERM
+RETSIGTYPE (*hold_term)(int);
+#endif
+
+#ifdef SIGHUP
+RETSIGTYPE (*hold_hup)(int);
+#endif
+
+#ifdef SIGUSR2
+RETSIGTYPE (*hold_usr2)(int);
+#endif
+
+
+
+/*----------------------------------------------------------------------
+ receive notification that search found something
+
+ Input: mail stream and message number of located item
+
+ Result: nothing, not used by pine
+ ----*/
+void
+mm_searched(MAILSTREAM *stream, long unsigned int rawno)
+{
+ MESSAGECACHE *mc;
+
+ if(rawno > 0L && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))){
+ mc->searched = 1;
+ if(stream == mm_search_stream)
+ mm_search_count++;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ receive notification of new mail from imap daemon
+
+ Args: stream -- The stream the message count report is for.
+ number -- The number of messages now in folder.
+
+ Result: Sets value in pine state indicating new mailbox size
+
+ Called when the number of messages in the mailbox goes up. This
+ may also be called as a result of an expunge. It increments the
+ new_mail_count based on a the difference between the current idea of
+ the maximum number of messages and what mm_exists claims. The new mail
+ notification is done in newmail.c
+
+ Only worry about the cases when the number grows, as mm_expunged
+ handles shrinkage...
+
+ ----*/
+void
+mm_exists(MAILSTREAM *stream, long unsigned int number)
+{
+ long new_this_call, n;
+ int exbits = 0, lflags = 0;
+ MSGNO_S *msgmap;
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 1 || ps_global->debugmem)
+ dprint((3, "=== mm_exists(%lu,%s) called ===\n", number,
+ !stream ? "(no stream)" : !stream->mailbox ? "(null)" : stream->mailbox));
+#endif
+
+ msgmap = sp_msgmap(stream);
+ if(!msgmap)
+ return;
+
+ if(mn_get_nmsgs(msgmap) != (long) number){
+ sp_set_mail_box_changed(stream, 1);
+ /* titlebar will be affected */
+ if(ps_global->mail_stream == stream)
+ ps_global->mangled_header = 1;
+ }
+
+ if(mn_get_nmsgs(msgmap) < (long) number){
+ new_this_call = (long) number - mn_get_nmsgs(msgmap);
+ sp_set_new_mail_count(stream,
+ sp_new_mail_count(stream) + new_this_call);
+ sp_set_recent_since_visited(stream,
+ sp_recent_since_visited(stream) + new_this_call);
+
+ mn_add_raw(msgmap, new_this_call);
+
+ /*
+ * Set local "recent" and "hidden" bits...
+ */
+ for(n = 0; n < new_this_call; n++, number--){
+ if(msgno_exceptions(stream, number, "0", &exbits, FALSE))
+ exbits |= MSG_EX_RECENT;
+ else
+ exbits = MSG_EX_RECENT;
+
+ msgno_exceptions(stream, number, "0", &exbits, TRUE);
+
+ if(SORT_IS_THREADED(msgmap))
+ lflags |= MN_USOR;
+
+ /*
+ * If we're zoomed, then hide this message too since
+ * it couldn't have possibly been selected yet...
+ */
+ lflags |= (any_lflagged(msgmap, MN_HIDE) ? MN_HIDE : 0);
+ if(lflags)
+ set_lflag(stream, msgmap, mn_get_total(msgmap) - n, lflags, 1);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Receive notification from IMAP that a single message has been expunged
+
+ Args: stream -- The stream/folder the message is expunged from
+ rawno -- The raw message number that was expunged
+
+mm_expunged is always called on an expunge. Simply remove all
+reference to the expunged message, shifting internal mappings as
+necessary.
+ ----*/
+void
+mm_expunged(MAILSTREAM *stream, long unsigned int rawno)
+{
+ MESSAGECACHE *mc;
+ long i;
+ int is_current = 0;
+ MSGNO_S *msgmap;
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 1 || ps_global->debugmem)
+ dprint((3, "mm_expunged(%s,%lu)\n",
+ stream
+ ? (stream->mailbox
+ ? stream->mailbox
+ : "(no stream)")
+ : "(null)", rawno));
+#endif
+
+ msgmap = sp_msgmap(stream);
+ if(!msgmap)
+ return;
+
+ if(ps_global->mail_stream == stream)
+ is_current++;
+
+ if((i = mn_raw2m(msgmap, (long) rawno)) != 0L){
+ dprint((7, "mm_expunged: rawno=%lu msgno=%ld nmsgs=%ld max_msgno=%ld flagged_exld=%ld\n", rawno, i, mn_get_nmsgs(msgmap), mn_get_total(msgmap), msgmap->flagged_exld));
+
+ sp_set_mail_box_changed(stream, 1);
+ sp_set_expunge_count(stream, sp_expunge_count(stream) + 1);
+
+ if(is_current){
+ reset_check_point(stream);
+ ps_global->mangled_header = 1;
+
+ /* flush invalid cache entries */
+ while(i <= mn_get_total(msgmap))
+ clear_index_cache_ent(stream, i++, 0);
+
+ /* let app know what happened */
+ if(pith_opt_current_expunged)
+ (*pith_opt_current_expunged)(rawno);
+ }
+ }
+ else{
+ dprint((7,
+ "mm_expunged: rawno=%lu was excluded, flagged_exld was %d\n",
+ rawno, msgmap->flagged_exld));
+ dprint((7, " nmsgs=%ld max_msgno=%ld\n",
+ mn_get_nmsgs(msgmap), mn_get_total(msgmap)));
+ if(rawno > 0L && rawno <= stream->nmsgs)
+ mc = mail_elt(stream, rawno);
+
+ if(!mc){
+ dprint((7, " cannot get mail_elt(%lu)\n",
+ rawno));
+ }
+ else if(!mc->sparep){
+ dprint((7, " mail_elt(%lu)->sparep is NULL\n",
+ rawno));
+ }
+ else{
+ dprint((7, " mail_elt(%lu)->sparep->excluded=%d\n",
+ rawno, (int) (((PINELT_S *) mc->sparep)->excluded)));
+ }
+ }
+
+ if(SORT_IS_THREADED(msgmap)
+ && (SEP_THRDINDX()
+ || ps_global->thread_disp_style != THREAD_NONE)){
+ long cur;
+
+ /*
+ * When we're sorting with a threaded method an expunged
+ * message may cause the rest of the sort to be wrong. This
+ * isn't so bad if we're just looking at the index. However,
+ * it also causes the thread tree (PINETHRD_S) to become
+ * invalid, so if we're using a threading view we need to
+ * sort in order to fix the tree and to protect fetch_thread().
+ */
+ sp_set_need_to_rethread(stream, 1);
+
+ /*
+ * If we expunged the current message which was a member of the
+ * viewed thread, and the adjustment to current will take us
+ * out of that thread, fix it if we can, by backing current up
+ * into the thread. We'd like to just check after mn_flush_raw
+ * below but the problem is that the elts won't change until
+ * after we return from mm_expunged. So we have to manually
+ * check the other messages for CHID2 flags instead of thinking
+ * that we can expunge the current message and then check. It won't
+ * work because the elt will still refer to the expunged message.
+ */
+ if(sp_viewing_a_thread(stream)
+ && get_lflag(stream, NULL, rawno, MN_CHID2)
+ && mn_total_cur(msgmap) == 1
+ && mn_is_cur(msgmap, mn_raw2m(msgmap, (long) rawno))
+ && (cur = mn_get_cur(msgmap)) > 1L
+ && cur < mn_get_total(msgmap)
+ && !get_lflag(stream, msgmap, cur + 1L, MN_CHID2)
+ && get_lflag(stream, msgmap, cur - 1L, MN_CHID2))
+ mn_set_cur(msgmap, cur - 1L);
+ }
+
+ /*
+ * Keep on top of our special flag counts.
+ *
+ * NOTE: This is allowed since mail_expunged releases
+ * data for this message after the callback.
+ */
+ if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){
+ PINELT_S *pelt = (PINELT_S *) mc->sparep;
+
+ if(pelt){
+ if(pelt->hidden)
+ msgmap->flagged_hid--;
+
+ if(pelt->excluded)
+ msgmap->flagged_exld--;
+
+ if(pelt->selected)
+ msgmap->flagged_tmp--;
+
+ if(pelt->colhid)
+ msgmap->flagged_chid--;
+
+ if(pelt->colhid2)
+ msgmap->flagged_chid2--;
+
+ if(pelt->collapsed)
+ msgmap->flagged_coll--;
+
+ if(pelt->tmp)
+ msgmap->flagged_stmp--;
+
+ if(pelt->unsorted)
+ msgmap->flagged_usor--;
+
+ if(pelt->searched)
+ msgmap->flagged_srch--;
+
+ if(pelt->hidden || pelt->colhid)
+ msgmap->flagged_invisible--;
+
+ free_pine_elt(&mc->sparep);
+ }
+ }
+
+ /*
+ * if it's in the sort array, flush it, otherwise
+ * decrement raw sequence numbers greater than "rawno"
+ */
+ mn_flush_raw(msgmap, (long) rawno);
+}
+
+
+void
+mm_flags(MAILSTREAM *stream, long unsigned int rawno)
+{
+ /*
+ * The idea here is to clean up any data pine might have cached
+ * that has anything to do with the indicated message number.
+ */
+ if(stream == ps_global->mail_stream){
+ long msgno, t;
+ PINETHRD_S *thrd;
+
+ if(scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP)
+ clear_msg_score(stream, rawno);
+
+ msgno = mn_raw2m(sp_msgmap(stream), (long) rawno);
+
+ /* if in thread index */
+ if(THRD_INDX()){
+ if((thrd = fetch_thread(stream, rawno))
+ && thrd->top
+ && (thrd = fetch_thread(stream, thrd->top))
+ && thrd->rawno
+ && (t = mn_raw2m(sp_msgmap(stream), thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+ }
+ else if(THREADING()){
+ if(msgno > 0L)
+ clear_index_cache_ent(stream, msgno, 0);
+
+ /*
+ * If a parent is collapsed, clear that parent's
+ * index cache entry.
+ */
+ if((thrd = fetch_thread(stream, rawno)) && thrd->parent){
+ thrd = fetch_thread(stream, thrd->parent);
+ while(thrd){
+ if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
+ && (t = mn_raw2m(sp_msgmap(stream), (long) thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+
+ if(thrd->parent)
+ thrd = fetch_thread(stream, thrd->parent);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ else if(msgno > 0L)
+ clear_index_cache_ent(stream, msgno, 0);
+
+ if(msgno && mn_is_cur(sp_msgmap(stream), msgno))
+ ps_global->mangled_header = 1;
+ }
+
+ /*
+ * We count up flag changes here. The
+ * dont_count_flagchanges variable tries to prevent us from
+ * counting when we're just fetching flags.
+ */
+ if(!(ps_global->dont_count_flagchanges
+ && stream == ps_global->mail_stream)){
+ int exbits;
+
+ check_point_change(stream);
+
+ /* we also note flag changes for filtering purposes */
+ if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
+ exbits |= MSG_EX_STATECHG;
+ else
+ exbits = MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
+ }
+}
+
+
+void
+mm_list(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
+{
+#ifdef DEBUG
+ if(ps_global->debug_imap > 2 || ps_global->debugmem)
+ dprint((5, "mm_list \"%s\": delim: '%c', %s%s%s%s%s%s\n",
+ mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
+ (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
+ (attributes & LATT_NOSELECT) ? ", no select" : "",
+ (attributes & LATT_MARKED) ? ", marked" : "",
+ (attributes & LATT_UNMARKED) ? ", unmarked" : "",
+ (attributes & LATT_HASCHILDREN) ? ", has children" : "",
+ (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
+#endif
+
+ if(!mm_list_info->stream || stream == mm_list_info->stream)
+ (*mm_list_info->filter)(stream, mailbox, delimiter,
+ attributes, mm_list_info->data,
+ mm_list_info->options);
+}
+
+
+void
+mm_lsub(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
+{
+#ifdef DEBUG
+ if(ps_global->debug_imap > 2 || ps_global->debugmem)
+ dprint((5, "LSUB \"%s\": delim: '%c', %s%s%s%s%s%s\n",
+ mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
+ (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
+ (attributes & LATT_NOSELECT) ? ", no select" : "",
+ (attributes & LATT_MARKED) ? ", marked" : "",
+ (attributes & LATT_UNMARKED) ? ", unmarked" : "",
+ (attributes & LATT_HASCHILDREN) ? ", has children" : "",
+ (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
+#endif
+
+ if(!mm_list_info->stream || stream == mm_list_info->stream)
+ (*mm_list_info->filter)(stream, mailbox, delimiter,
+ attributes, mm_list_info->data,
+ mm_list_info->options);
+}
+
+
+void
+mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status)
+{
+ /*
+ * We implement mail_status for the #move namespace by adding a wrapper
+ * routine, pine_mail_status. It may have to call the real mail_status
+ * twice for #move folders and combine the results. It sets
+ * pine_cached_status to point to a local status variable to store the
+ * intermediate results.
+ */
+ if(status){
+ if(pine_cached_status != NULL)
+ *pine_cached_status = *status;
+ else
+ mm_status_result = *status;
+ }
+
+#ifdef DEBUG
+ if(status){
+ if(pine_cached_status)
+ dprint((2,
+ "mm_status: Preliminary pass for #move\n"));
+
+ dprint((2, "mm_status: Mailbox \"%s\"",
+ mailbox ? mailbox : "?"));
+ if(status->flags & SA_MESSAGES)
+ dprint((2, ", %lu messages", status->messages));
+
+ if(status->flags & SA_RECENT)
+ dprint((2, ", %lu recent", status->recent));
+
+ if(status->flags & SA_UNSEEN)
+ dprint((2, ", %lu unseen", status->unseen));
+
+ if(status->flags & SA_UIDVALIDITY)
+ dprint((2, ", %lu UID validity", status->uidvalidity));
+
+ if(status->flags & SA_UIDNEXT)
+ dprint((2, ", %lu next UID", status->uidnext));
+
+ dprint((2, "\n"));
+ }
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Write imap debugging information into log file
+
+ Args: strings -- the string for the debug file
+
+ Result: message written to the debug log file
+ ----*/
+void
+mm_dlog(char *string)
+{
+ char *p, *q = NULL, save, *continued;
+ int more = 1;
+
+#ifdef _WINDOWS
+ mswin_imaptelemetry(string);
+#endif
+#ifdef DEBUG
+ continued = "";
+ p = string;
+#ifdef DEBUGJOURNAL
+ /* because string can be really long and we don't want to lose any of it */
+ if(p)
+ more = 1;
+
+ while(more){
+ if(q){
+ *q = save;
+ p = q;
+ continued = "(Continuation line) ";
+ }
+
+ if(strlen(p) > 63000){
+ q = p + 60000;
+ save = *q;
+ *q = '\0';
+ }
+ else
+ more = 0;
+#endif
+ dprint(((ps_global->debug_imap >= 4 && debug < 4) ? debug : 4,
+ "IMAP DEBUG %s%s: %s\n",
+ continued ? continued : "",
+ debug_time(1, ps_global->debug_timestamp), p ? p : "?"));
+#ifdef DEBUGJOURNAL
+ }
+#endif
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Ignore signals when imap is running through critical code
+
+ Args: stream -- The stream on which critical operation is proceeding
+ ----*/
+
+void
+mm_critical(MAILSTREAM *stream)
+{
+ stream = stream; /* For compiler complaints that this isn't used */
+
+ if(++critical_depth == 1){
+
+#ifdef SIGHUP
+ hold_hup = signal(SIGHUP, SIG_IGN);
+#endif
+#ifdef SIGUSR2
+ hold_usr2 = signal(SIGUSR2, SIG_IGN);
+#endif
+#ifdef SIGINT
+ hold_int = signal(SIGINT, SIG_IGN);
+#endif
+#ifdef SIGTERM
+ hold_term = signal(SIGTERM, SIG_IGN);
+#endif
+ }
+
+ dprint((9, "IMAP critical (depth now %d) on %s\n",
+ critical_depth,
+ (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
+}
+
+
+/*----------------------------------------------------------------------
+ Reset signals after critical imap code
+ ----*/
+void
+mm_nocritical(MAILSTREAM *stream)
+{
+ stream = stream; /* For compiler complaints that this isn't used */
+
+ if(--critical_depth == 0){
+
+#ifdef SIGHUP
+ (void)signal(SIGHUP, hold_hup);
+#endif
+#ifdef SIGUSR2
+ (void)signal(SIGUSR2, hold_usr2);
+#endif
+#ifdef SIGINT
+ (void)signal(SIGINT, hold_int);
+#endif
+#ifdef SIGTERM
+ (void)signal(SIGTERM, hold_term);
+#endif
+ }
+
+ critical_depth = MAX(critical_depth, 0);
+
+ dprint((9, "Done with IMAP critical (depth now %d) on %s\n",
+ critical_depth,
+ (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
+}
+
+
+void
+mm_fatal(char *message)
+{
+ panic(message);
+}
+
+
+char *
+imap_referral(MAILSTREAM *stream, char *ref, long int code)
+{
+ char *buf = NULL;
+
+ if(ref && !struncmp(ref, "imap://", 7)){
+ char *folder = NULL;
+ imapuid_t uid_val, uid;
+ int rv;
+
+ rv = url_imap_folder(ref, &folder, &uid, &uid_val, NULL, 1);
+ switch(code){
+ case REFAUTHFAILED :
+ case REFAUTH :
+ if((rv & URL_IMAP_IMAILBOXLIST) && (rv & URL_IMAP_ISERVERONLY))
+ buf = cpystr(folder);
+
+ break;
+
+ case REFSELECT :
+ case REFCREATE :
+ case REFDELETE :
+ case REFRENAME :
+ case REFSUBSCRIBE :
+ case REFUNSUBSCRIBE :
+ case REFSTATUS :
+ case REFCOPY :
+ case REFAPPEND :
+ if(rv & URL_IMAP_IMESSAGELIST)
+ buf = cpystr(folder);
+
+ break;
+
+ default :
+ break;
+ }
+
+ if(folder)
+ fs_give((void **) &folder);
+ }
+
+ return(buf);
+}
+
+
+long
+imap_proxycopy(MAILSTREAM *stream, char *sequence, char *mailbox, long int flags)
+{
+ SE_APP_S args;
+ long ret;
+
+ args.folder = mailbox;
+ args.flags = flags;
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ ret = imap_seq_exec(stream, sequence, imap_seq_exec_append, &args);
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ return(ret);
+}
+
+
+long
+imap_seq_exec(MAILSTREAM *stream, char *sequence,
+ long int (*func)(MAILSTREAM *, long int, void *),
+ void *args)
+{
+ unsigned long i,j,x;
+
+ while (*sequence) { /* while there is something to parse */
+ if (*sequence == '*') { /* maximum message */
+ if(!(i = stream->nmsgs)){
+ mm_log ("No messages, so no maximum message number",ERROR);
+ return(0L);
+ }
+
+ sequence++; /* skip past * */
+ }
+ else if (!(i = strtoul ((const char *) sequence,&sequence,10))
+ || (i > stream->nmsgs)){
+ mm_log ("Sequence invalid",ERROR);
+ return(0L);
+ }
+
+ switch (*sequence) { /* see what the delimiter is */
+ case ':': /* sequence range */
+ if (*++sequence == '*') { /* maximum message */
+ if (stream->nmsgs) j = stream->nmsgs;
+ else {
+ mm_log ("No messages, so no maximum message number",ERROR);
+ return NIL;
+ }
+
+ sequence++; /* skip past * */
+ }
+ /* parse end of range */
+ else if (!(j = strtoul ((const char *) sequence,&sequence,10)) ||
+ (j > stream->nmsgs)) {
+ mm_log ("Sequence range invalid",ERROR);
+ return NIL;
+ }
+
+ if (*sequence && *sequence++ != ',') {
+ mm_log ("Sequence range syntax error",ERROR);
+ return NIL;
+ }
+
+ if (i > j) { /* swap the range if backwards */
+ x = i; i = j; j = x;
+ }
+
+ while (i <= j)
+ if(!(*func)(stream, i++, args))
+ return(0L);
+
+ break;
+ case ',': /* single message */
+ ++sequence; /* skip the delimiter, fall into end case */
+ case '\0': /* end of sequence */
+ if(!(*func)(stream, i, args))
+ return(0L);
+
+ break;
+ default: /* anything else is a syntax error! */
+ mm_log ("Sequence syntax error",ERROR);
+ return NIL;
+ }
+ }
+
+ return T; /* successfully parsed sequence */
+}
+
+
+long
+imap_seq_exec_append(MAILSTREAM *stream, long int msgno, void *args)
+{
+ char *save_folder, *flags = NULL, date[64];
+ CONTEXT_S *cntxt = NULL;
+ int our_stream = 0;
+ long rv = 0L;
+ MAILSTREAM *save_stream;
+ SE_APP_S *sa = (SE_APP_S *) args;
+ MESSAGECACHE *mc;
+ STORE_S *so;
+
+ save_folder = (strucmp(sa->folder, ps_global->inbox_name) == 0)
+ ? ps_global->VAR_INBOX_PATH : sa->folder;
+
+ save_stream = save_msg_stream(cntxt, save_folder, &our_stream);
+
+ if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
+ /* store flags before the fetch so UNSEEN bit isn't flipped */
+ mc = (msgno > 0L && stream && msgno <= stream->nmsgs)
+ ? mail_elt(stream, msgno) : NULL;
+
+ flags = flag_string(stream, msgno, F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD);
+ if(mc && mc->day)
+ mail_date(date, mc);
+ else
+ *date = '\0';
+
+ rv = save_fetch_append(stream, msgno, NULL,
+ save_stream, save_folder, NULL,
+ mc ? mc->rfc822_size : 0L, flags, date, so);
+ if(flags)
+ fs_give((void **) &flags);
+
+ if(rv < 0 || sp_expunge_count(stream)){
+ cmd_cancelled("Attached message Save");
+ rv = 0L;
+ }
+ /* else whatever broke in save_fetch_append shoulda bitched */
+
+ so_give(&so);
+ }
+ else{
+ dprint((1, "Can't allocate store for save: %s\n",
+ error_description(errno)));
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem creating space for message text.");
+ }
+
+ if(our_stream)
+ mail_close(save_stream);
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Get login and password from user for IMAP login
+
+ Args: mb -- The mail box property struct
+ user -- Buffer to return the user name in
+ passwd -- Buffer to return the passwd in
+ trial -- The trial number or number of attempts to login
+ user is at least size NETMAXUSER
+ passwd is apparently at least MAILTMPLEN, but mrc has asked us to
+ use a max size of about 100 instead
+
+ Result: username and password passed back to imap
+ ----*/
+void
+mm_login(NETMBX *mb, char *user, char *pwd, long int trial)
+{
+ mm_login_work(mb, user, pwd, trial, NULL, NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Exported method to retrieve logged in user name associated with stream
+
+ Args: host -- host to find associated login name with.
+
+ Result:
+ ----*/
+char *
+cached_user_name(char *host)
+{
+ MMLOGIN_S *l;
+ STRLIST_S *h;
+
+ if((l = mm_login_list) && host)
+ do
+ for(h = l->hosts; h; h = h->next)
+ if(!strucmp(host, h->name))
+ return(l->user);
+ while((l = l->next) != NULL);
+
+ return(NULL);
+}
+
+
+int
+imap_same_host(STRLIST_S *hl1, STRLIST_S *hl2)
+{
+ STRLIST_S *lp;
+
+ for( ; hl1; hl1 = hl1->next)
+ for(lp = hl2; lp; lp = lp->next)
+ if(!strucmp(hl1->name, lp->name))
+ return(TRUE);
+
+ return(FALSE);
+}
+
+
+/*
+ * For convenience, we use the same m_list structure (but a different
+ * instance) for storing a list of hosts we've asked the user about when
+ * SSL validation fails. If this function returns TRUE, that means we
+ * have previously asked the user about this host. Ok_novalidate == 1 means
+ * the user said yes, it was ok. Ok_novalidate == 0 means the user
+ * said no. Warned means we warned them already.
+ */
+int
+imap_get_ssl(MMLOGIN_S *m_list, STRLIST_S *hostlist, int *ok_novalidate, int *warned)
+{
+ MMLOGIN_S *l;
+
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist)){
+ if(ok_novalidate)
+ *ok_novalidate = l->ok_novalidate;
+
+ if(warned)
+ *warned = l->warned;
+
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+
+/*
+ * Just trying to guess the username the user might want to use on this
+ * host, the user will confirm.
+ */
+char *
+imap_get_user(MMLOGIN_S *m_list, STRLIST_S *hostlist)
+{
+ MMLOGIN_S *l;
+
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist))
+ return(l->user);
+
+ return(NULL);
+}
+
+
+/*
+ * If we have a matching hostname, username, and altflag in our cache,
+ * attempt to login with the password from the cache.
+ */
+int
+imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist, int altflag)
+{
+ MMLOGIN_S *l;
+
+ dprint((9,
+ "imap_get_passwd: checking user=%s alt=%d host=%s%s%s\n",
+ user ? user : "(null)",
+ altflag,
+ hostlist->name ? hostlist->name : "",
+ (hostlist->next && hostlist->next->name) ? ", " : "",
+ (hostlist->next && hostlist->next->name) ? hostlist->next->name
+ : ""));
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist)
+ && *user
+ && !strcmp(user, l->user)
+ && l->altflag == altflag){
+ if(passwd){
+ strncpy(passwd, l->passwd, NETMAXPASSWD);
+ passwd[NETMAXPASSWD-1] = '\0';
+ }
+ dprint((9, "imap_get_passwd: match\n"));
+ dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n",
+ passwd ? passwd : "?"));
+ return(TRUE);
+ }
+
+ dprint((9, "imap_get_passwd: no match\n"));
+ return(FALSE);
+}
+
+
+
+void
+imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
+ int altflag, int ok_novalidate, int warned)
+{
+ STRLIST_S **listp;
+ size_t len;
+
+ dprint((9, "imap_set_passwd\n"));
+ for(; *l; l = &(*l)->next)
+ if(imap_same_host((*l)->hosts, hostlist)
+ && !strcmp(user, (*l)->user)
+ && altflag == (*l)->altflag){
+ if(strcmp(passwd, (*l)->passwd) ||
+ (*l)->ok_novalidate != ok_novalidate ||
+ (*l)->warned != warned)
+ break;
+ else
+ return;
+ }
+
+ if(!*l){
+ *l = (MMLOGIN_S *)fs_get(sizeof(MMLOGIN_S));
+ memset(*l, 0, sizeof(MMLOGIN_S));
+ }
+
+ len = strlen(passwd);
+ if(!(*l)->passwd || strlen((*l)->passwd) < len)
+ (*l)->passwd = ps_get(len+1);
+
+ strncpy((*l)->passwd, passwd, len+1);
+
+ (*l)->altflag = altflag;
+ (*l)->ok_novalidate = ok_novalidate;
+ (*l)->warned = warned;
+
+ if(!(*l)->user)
+ (*l)->user = cpystr(user);
+
+ dprint((9, "imap_set_passwd: user=%s altflag=%d\n",
+ (*l)->user ? (*l)->user : "?",
+ (*l)->altflag));
+
+ for( ; hostlist; hostlist = hostlist->next){
+ for(listp = &(*l)->hosts;
+ *listp && strucmp((*listp)->name, hostlist->name);
+ listp = &(*listp)->next)
+ ;
+
+ if(!*listp){
+ *listp = new_strlist(hostlist->name);
+ dprint((9, "imap_set_passwd: host=%s\n",
+ (*listp)->name ? (*listp)->name : "?"));
+ }
+ }
+
+ dprint((10, "imap_set_passwd: passwd=\"%s\"\n",
+ passwd ? passwd : "?"));
+}
+
+
+
+void
+imap_flush_passwd_cache(int dumpcache)
+{
+ size_t len;
+ volatile char *p;
+
+ /* equivalent of memset but can't be optimized away */
+ len = sizeof(private_store);
+ p = private_store;
+ while(len-- > 0)
+ *p++ = '\0';
+
+ if(dumpcache){
+ MMLOGIN_S *l;
+
+ while((l = mm_login_list) != NULL){
+ mm_login_list = mm_login_list->next;
+ if(l->user)
+ fs_give((void **) &l->user);
+
+ free_strlist(&l->hosts);
+
+ fs_give((void **) &l);
+ }
+
+ while((l = cert_failure_list) != NULL){
+ cert_failure_list = cert_failure_list->next;
+ if(l->user)
+ fs_give((void **) &l->user);
+
+ free_strlist(&l->hosts);
+
+ fs_give((void **) &l);
+ }
+ }
+}
+
+
+/*
+ * Mimics fs_get except it only works for char * (no alignment hacks), it
+ * stores in a static array so it is easy to zero it out (that's the whole
+ * purpose), allocations always happen at the end (no free).
+ * If we go past array limit, we don't break, we just use free storage.
+ * Should be awfully rare, though.
+ */
+char *
+ps_get(size_t size)
+{
+ static char *last = (char *) private_store;
+ char *block = NULL;
+
+ /* there is enough space */
+ if(size <= sizeof(private_store) - (last - (char *) private_store)){
+ block = last;
+ last += size;
+ }
+ else{
+ dprint((2,
+ "Out of password caching space in private_store\n"));
+ dprint((2,
+ "Using free storage instead\n"));
+ block = fs_get(size);
+ }
+
+ return(block);
+}
diff --git a/pith/imap.h b/pith/imap.h
new file mode 100644
index 00000000..86a0b533
--- /dev/null
+++ b/pith/imap.h
@@ -0,0 +1,135 @@
+/*
+ * $Id: imap.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_IMAP_INCLUDED
+#define PITH_IMAP_INCLUDED
+
+
+#include "../pith/string.h"
+
+
+#define NETMAXPASSWD 100
+
+
+/*
+ * struct used to keep track of password/host/user triples.
+ * The problem is we only want to try user names and passwords if
+ * we've already tried talking to this host before.
+ *
+ */
+typedef struct _mmlogin_s {
+ char *user,
+ *passwd;
+ unsigned altflag:1;
+ unsigned ok_novalidate:1;
+ unsigned warned:1;
+ STRLIST_S *hosts;
+ struct _mmlogin_s *next;
+} MMLOGIN_S;
+
+
+typedef struct _se_app_s {
+ char *folder;
+ long flags;
+} SE_APP_S;
+
+
+/*
+ * struct to help manage mail_list calls/callbacks
+ */
+typedef struct mm_list_s {
+ MAILSTREAM *stream;
+ unsigned options;
+ void (*filter)(MAILSTREAM *, char *, int, long, void *, unsigned);
+ void *data;
+} MM_LIST_S;
+
+
+/*
+ * return values for IMAP URL parser
+ */
+#define URL_IMAP_MASK 0x0007
+#define URL_IMAP_ERROR 0
+#define URL_IMAP_IMAILBOXLIST 0x0001
+#define URL_IMAP_IMESSAGELIST 0x0002
+#define URL_IMAP_IMESSAGEPART 0x0004
+#define URL_IMAP_IMBXLSTLSUB 0x0010
+#define URL_IMAP_ISERVERONLY 0x0020
+
+
+/*
+ * Exported globals setup by searching functions to tell mm_searched
+ * where to put message numbers that matched the search criteria,
+ * and to allow mm_searched to return number of matches.
+ */
+extern MAILSTREAM *mm_search_stream;
+extern long mm_search_count;
+extern MAILSTATUS mm_status_result;
+
+extern MM_LIST_S *mm_list_info;
+
+
+extern MMLOGIN_S *mm_login_list;
+extern MMLOGIN_S *cert_failure_list;
+
+
+/*
+ * These are declared in c-client and implemented in ../pith/imap.c
+ */
+#if 0
+ void mm_searched (MAILSTREAM *stream,unsigned long number);
+ void mm_exists (MAILSTREAM *stream,unsigned long number);
+ void mm_expunged (MAILSTREAM *stream,unsigned long number);
+ void mm_flags (MAILSTREAM *stream,unsigned long number);
+ void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes);
+ void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes);
+ void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status);
+ void mm_dlog (char *string);
+ void mm_critical (MAILSTREAM *stream);
+ void mm_nocritical (MAILSTREAM *stream);
+ void mm_fatal (char *string);
+#endif
+
+/*
+ * These are declared in c-client and must be implemented in application
+ */
+#if 0
+ void mm_notify (MAILSTREAM *stream,char *string,long errflg);
+ void mm_log (char *string,long errflg);
+ void mm_login (NETMBX *mb,char *user,char *pwd,long trial);
+ long mm_diskerror (MAILSTREAM *stream,long errcode,long serious);
+#endif
+
+
+/* exported protoypes */
+char *imap_referral(MAILSTREAM *, char *, long);
+long imap_proxycopy(MAILSTREAM *, char *, char *, long);
+char *cached_user_name(char *);
+int imap_same_host(STRLIST_S *, STRLIST_S *);
+int imap_get_ssl(MMLOGIN_S *, STRLIST_S *, int *, int *);
+char *imap_get_user(MMLOGIN_S *, STRLIST_S *);
+int imap_get_passwd(MMLOGIN_S *, char *, char *, STRLIST_S *, int);
+void imap_set_passwd(MMLOGIN_S **, char *, char *, STRLIST_S *, int, int, int);
+void imap_flush_passwd_cache(int);
+
+
+/* currently mandatory to implement stubs */
+
+/* called by build_folder_list(), ok if it does nothing */
+void set_read_predicted(int);
+void mm_login_work (NETMBX *mb,char *user,char *pwd,long trial,char *usethisprompt, char *altuserforcache);
+
+
+#endif /* PITH_IMAP_INCLUDED */
diff --git a/pith/indxtype.h b/pith/indxtype.h
new file mode 100644
index 00000000..ee01a9bb
--- /dev/null
+++ b/pith/indxtype.h
@@ -0,0 +1,249 @@
+/*
+ * $Id: indxtype.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_INDXTYPE_INCLUDED
+#define PITH_INDXTYPE_INCLUDED
+
+#include "../pith/osdep/color.h"
+
+#include "../pith/msgno.h"
+#include "../pith/charset.h"
+
+
+/*
+ * Flags for msgline_hidden.
+ *
+ * MH_NONE means we should only consider messages which are or would be
+ * visible in the index view. That is, messages which are not hidden due
+ * to zooming, not hidden because they are in a collapsed part of a
+ * thread, and not hidden because we are in one thread and they are in
+ * another and the view we are using only shows one thread.
+ * MH_THISTHD changes that a little bit. It considers more messages
+ * to be visible. In particular, messages in this thread which are
+ * hidden due to collapsing are considered to be visible instead of hidden.
+ * This is useful if we are viewing a message and hit Next, and want
+ * to see the next message in the thread even if it was in a
+ * collapsed thread. This only makes sense when using the separate
+ * thread index and viewing a thread. "This thread" is the thread that
+ * is being viewed, not the thread that msgno is part of. So it is the
+ * thread that has been marked MN_CHID2.
+ * MH_ANYTHD adds more visibility. It doesn't matter if the message is in
+ * the same thread or if it is collapsed or not. If a message is not
+ * hidden due to zooming, then it is not hidden. Notice that ANYTHD
+ * implies THISTHD.
+ */
+#define MH_NONE 0x0
+#define MH_THISTHD 0x1
+#define MH_ANYTHD 0x2
+
+
+typedef enum {iNothing, iStatus, iFStatus, iIStatus, iSIStatus,
+ iDate, iLDate, iS1Date, iS2Date, iS3Date, iS4Date, iSDate,
+ iDateIso, iDateIsoS,
+ iSDateIso, iSDateIsoS, iSDateS1, iSDateS2, iSDateS3, iSDateS4,
+ iSDateTime,
+ iSDateTimeIso, iSDateTimeIsoS, iSDateTimeS1,
+ iSDateTimeS2, iSDateTimeS3, iSDateTimeS4,
+ iSDateTime24,
+ iSDateTimeIso24, iSDateTimeIsoS24, iSDateTimeS124,
+ iSDateTimeS224, iSDateTimeS324, iSDateTimeS424,
+ iRDate, iTimezone,
+ iTime24, iTime12,
+ iCurDate, iCurDateIso, iCurDateIsoS, iCurTime24, iCurTime12,
+ iCurDay, iCurDay2Digit, iCurDayOfWeek, iCurDayOfWeekAbb,
+ iCurMon, iCurMon2Digit, iCurMonLong, iCurMonAbb,
+ iCurYear, iCurYear2Digit,
+ iLstMon, iLstMon2Digit, iLstMonLong, iLstMonAbb,
+ iLstMonYear, iLstMonYear2Digit,
+ iLstYear, iLstYear2Digit,
+ iMessNo, iAtt, iMsgID,
+ iSubject, iSubjKey, iSubjKeyInit,
+ iSubjectText, iSubjKeyText, iSubjKeyInitText,
+ iOpeningText, iOpeningTextNQ,
+ iKey, iKeyInit,
+ iPrefDate, iPrefTime, iPrefDateTime,
+ iCurPrefDate, iCurPrefTime, iCurPrefDateTime,
+ iSize, iSizeComma, iSizeNarrow, iDescripSize,
+ iNewsAndTo, iToAndNews, iNewsAndRecips, iRecipsAndNews,
+ iFromTo, iFromToNotNews, iFrom, iTo, iSender, iCc, iNews, iRecips,
+ iCurNews, iArrow,
+ iMailbox, iAddress, iInit, iCursorPos,
+ iDay2Digit, iMon2Digit, iYear2Digit,
+ iSTime, iKSize,
+ iRoleNick, iNewLine,
+ iHeader, iText,
+ iPrio, iPrioAlpha, iPrioBang,
+ iScore, iDayOfWeekAbb, iDayOfWeek,
+ iDay, iDayOrdinal, iMonAbb, iMonLong, iMon, iYear} IndexColType;
+
+typedef enum {AllAuto, Fixed, Percent, WeCalculate, Special} WidthType;
+
+typedef enum {eText = 0, eKeyWord, eThreadCount, eThreadInfo, eTypeCol} ElemType;
+
+typedef enum {Left, Right} ColAdj;
+
+typedef struct index_parse_tokens {
+ char *name;
+ IndexColType ctype;
+ int what_for;
+} INDEX_PARSE_T;
+
+
+/* these are flags for the what_for field in INDEX_PARSE_T */
+#define FOR_NOTHING 0x00
+#define FOR_INDEX 0x01
+#define FOR_REPLY_INTRO 0x02
+#define FOR_TEMPLATE 0x04 /* or for signature */
+#define FOR_FILT 0x08
+#define DELIM_USCORE 0x10
+#define DELIM_PAREN 0x20
+#define DELIM_COLON 0x40
+
+
+#define DEFAULT_REPLY_INTRO "default"
+
+
+typedef struct hdr_token_description {
+ char *hdrname;
+ int fieldnum;
+ int fieldsepcnt;
+ ColAdj adjustment;
+ char *fieldseps;
+} HEADER_TOK_S;
+
+typedef struct col_description {
+ IndexColType ctype;
+ WidthType wtype;
+ int req_width;
+ int width;
+ int actual_length;
+ int monabb_width; /* hack */
+ ColAdj adjustment;
+ HEADER_TOK_S *hdrtok;
+} INDEX_COL_S;
+
+
+/*
+ * Ensure that all the data in an index_elem (in the data member)
+ * is stored in UTF-8. The writing routines (paint_index_line() for pine)
+ * should then assume UTF-8 when using the data. The process of putting
+ * data into an index_elem will likely involve a conversion into UTF-8.
+ * For example, the Subject or From fields may have data in some arbitrary
+ * character set.
+ */
+typedef struct index_elem {
+ struct index_elem *next;
+ char *print_format;
+ COLOR_PAIR *color; /* color, if any, for this element */
+ char *data; /* ptr to full text for this element */
+ unsigned int datalen; /* not counting terminating null */
+ ElemType type; /* part of field data represents */
+ unsigned freedata:1; /* free data when done */
+ unsigned freecolor:1; /* free color when done */
+ unsigned freeprintf:8; /* how much alloced for print_format */
+ unsigned wid:16; /* redundant, width from print_format */
+} IELEM_S;
+
+
+/*
+ * 3 is room for '%', '.', and 's'. You need to add one for '\0';
+ * The format string looks like:
+ *
+ * %13.13s or %-13.13s
+ *
+ * The '-' is there if left is set and the 13 is the 'width' in the call.
+ */
+#define PRINT_FORMAT_LEN(width,left) (3 + ((left) ? 1 : 0) + 2 * ((width) > 999 ? 4 : (width) > 99 ? 3 : (width) > 9 ? 2 : 1))
+
+
+/*
+ * Each field consists of a list of elements. The leftadj bit means we
+ * should left adjust the whole thing in the field width, padding on the
+ * right with spaces or truncating on the right. We haven't yet fully
+ * implemented right adjust, except that it is easy for a single element
+ * field. So be careful with multi-element, right-adjusted fields.
+ */
+typedef struct index_field {
+ struct index_field *next;
+ IndexColType ctype;
+ unsigned width:16; /* width of whole field */
+ unsigned leftadj:1; /* left adjust elements in field */
+ IELEM_S *ielem; /* list of elements in this field */
+} IFIELD_S;
+
+
+/*
+ * If the index_cache_entry has an ifield list, it is assumed that the
+ * ifields have had their ielement lists filled in.
+ * The widths_done bit is set after the widths and print formats are setup
+ * to do the correct width. A width change could be done by unsetting
+ * the widths_done bit and then recalculating the widths in the ifields
+ * and resetting the print_format strings in the ielems.
+ */
+typedef struct index_cache_entry {
+ IFIELD_S *ifield; /* list of fields */
+ COLOR_PAIR *linecolor;
+ unsigned color_lookup_done:1; /* efficiency hacks */
+ unsigned widths_done:1;
+ unsigned to_us:1;
+ unsigned cc_us:1;
+ int plus;
+ unsigned long id; /* hash value */
+ struct index_cache_entry *tice; /* thread index header line */
+} ICE_S;
+
+
+/*
+ * Pieces needed to construct a valid folder index entry, and to
+ * control what can be fetched when (due to callbacks and such)
+ */
+typedef struct index_data {
+ MAILSTREAM *stream;
+ ADDRESS *from, /* always valid */
+ *to, /* check valid bit, fetch as req'd */
+ *cc, /* check valid bit, fetch as req'd */
+ *sender; /* check valid bit, fetch as req'd */
+ char *newsgroups, /* check valid bit, fetch as req'd */
+ *subject, /* always valid */
+ *date; /* always valid */
+ long msgno, /* tells us what we're looking at */
+ rawno,
+ size; /* always valid */
+ unsigned no_fetch:1, /* lit when we're in a callback */
+ bogus:2, /* lit when there were problems */
+ valid_to:1, /* trust struct's "to" pointer */
+ valid_cc:1, /* trust struct's "cc" pointer */
+ valid_sender:1, /* trust struct's "sender" pointer */
+ valid_news:1, /* trust struct's "news" pointer */
+ valid_resent_to:1, /* trust struct's "resent-to" ptr */
+ resent_to_us:1; /* lit when we know its true */
+} INDEXDATA_S;
+
+
+/*
+ * Line_hash can't return LINE_HASH_N, so we use it in a couple places as
+ * a special value that we can assign knowing it can't be a real hash value.
+ */
+#define LINE_HASH_N 0xffffffff
+
+
+typedef enum {NoKW, KW, KWInit} SubjKW;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_INDXTYPE_INCLUDED */
diff --git a/pith/init.c b/pith/init.c
new file mode 100644
index 00000000..d7942dcb
--- /dev/null
+++ b/pith/init.c
@@ -0,0 +1,546 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: init.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ init.c
+ Routines for pine start up and initialization
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/folder.h"
+
+
+/*
+ * Internal prototypes
+ */
+int compare_sm_files(const qsort_t *, const qsort_t *);
+
+
+
+/*----------------------------------------------------------------------
+ Sets login, full_username and home_dir
+
+ Args: ps -- The Pine structure to put the user name, etc in
+
+ Result: sets the fullname, login and home_dir field of the pine structure
+ returns 0 on success, -1 if not.
+ ----*/
+
+int
+init_username(struct pine *ps)
+{
+ char *expanded;
+ int rv;
+
+ rv = 0;
+ expanded = NULL;
+#if defined(DOS) || defined(OS2)
+ if(ps->COM_USER_ID)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->COM_USER_ID, 0);
+
+ if(!expanded && ps->vars[V_USER_ID].post_user_val.p)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->vars[V_USER_ID].post_user_val.p, 0);
+
+ if(!expanded && ps->vars[V_USER_ID].main_user_val.p)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->vars[V_USER_ID].main_user_val.p, 0);
+
+ if(!expanded)
+ ps->blank_user_id = 1;
+
+ ps->VAR_USER_ID = cpystr(expanded ? expanded : "");
+#else
+ ps->VAR_USER_ID = cpystr(ps->ui.login);
+ if(!ps->VAR_USER_ID[0]){
+ fprintf(stderr, "Who are you? (Unable to look up login name)\n");
+ rv = -1;
+ }
+#endif
+
+ expanded = NULL;
+ if(ps->vars[V_PERSONAL_NAME].is_fixed){
+ if(ps->FIX_PERSONAL_NAME){
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->FIX_PERSONAL_NAME, 0);
+ }
+ if(ps->vars[V_PERSONAL_NAME].main_user_val.p ||
+ ps->vars[V_PERSONAL_NAME].post_user_val.p){
+ ps_global->give_fixed_warning = 1;
+ ps_global->fix_fixed_warning = 1;
+ }
+ else if(ps->COM_PERSONAL_NAME)
+ ps_global->give_fixed_warning = 1;
+ }
+ else{
+ if(ps->COM_PERSONAL_NAME)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->COM_PERSONAL_NAME, 0);
+
+ if(!expanded && ps->vars[V_PERSONAL_NAME].post_user_val.p)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->vars[V_PERSONAL_NAME].post_user_val.p, 0);
+
+ if(!expanded && ps->vars[V_PERSONAL_NAME].main_user_val.p)
+ expanded = expand_variables(tmp_20k_buf, SIZEOF_20KBUF,
+ ps->vars[V_PERSONAL_NAME].main_user_val.p, 0);
+ }
+
+ if(!expanded){
+ expanded = ps->ui.fullname;
+#if defined(DOS) || defined(OS2)
+ ps->blank_personal_name = 1;
+#endif
+ }
+
+ ps->VAR_PERSONAL_NAME = cpystr(expanded ? expanded : "");
+
+ dprint((1, "Userid: %s\nFullname: \"%s\"\n",
+ ps->VAR_USER_ID, ps->VAR_PERSONAL_NAME));
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Sets home_dir
+
+ Args: ps -- The Pine structure to put the user name, etc in
+
+ Result: sets the home_dir field of the pine structure
+ returns 0 on success, -1 if not.
+ ----*/
+
+int
+init_userdir(struct pine *ps)
+{
+ char fld_dir[MAXPATH+1];
+
+ if(strlen(ps->home_dir) + strlen(ps->VAR_MAIL_DIRECTORY)+2 > MAXPATH){
+ printf(_("Folders directory name is longer than %d\n"), MAXPATH);
+ printf(_("Directory name: \"%s/%s\"\n"),ps->home_dir,
+ ps->VAR_MAIL_DIRECTORY);
+ return(-1);
+ }
+#if defined(DOS) || defined(OS2)
+ if(ps->VAR_MAIL_DIRECTORY[1] == ':'){
+ strncpy(fld_dir, ps->VAR_MAIL_DIRECTORY, sizeof(fld_dir)-1);
+ fld_dir[sizeof(fld_dir)-1] = '\0';
+ }
+ else
+#endif
+ build_path(fld_dir, ps->home_dir, ps->VAR_MAIL_DIRECTORY, sizeof(fld_dir));
+ ps->folders_dir = cpystr(fld_dir);
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Fetch the hostname of the current system and put it in pine struct
+
+ Args: ps -- The pine structure to put the hostname, etc in
+
+ Result: hostname, localdomain, userdomain and maildomain are set
+
+
+** Pine uses the following set of names:
+ hostname - The fully-qualified hostname. Obtained with
+ gethostbyname() which reads /etc/hosts or does a DNS
+ lookup. This may be blank.
+ localdomain - The domain name without the host. Obtained from the
+ above hostname if it has a "." in it. Removes first
+ segment. If hostname has no "." in it then the hostname
+ is used. This may be blank.
+ userdomain - Explicitly configured domainname. This is read out of the
+ global pine.conf or user's .pinerc. The user's entry in the
+ .pinerc overrides.
+
+** Pine has the following uses for such names:
+
+ 1. On outgoing messages in the From: line
+ Uses userdomain if there is one. If not uses, uses
+ hostname unless Pine has been configured to use localdomain.
+
+ 2. When expanding/fully-qualifying unqualified addresses during
+ composition
+ (same as 1)
+
+ 3. When expanding/fully-qualifying unqualified addresses during
+ composition when a local entry in the password file exists for
+ name.
+ If no userdomain is given, then this lookup is always done
+ and the hostname is used unless Pine's been configured to
+ use the localdomain. If userdomain is defined, it is used,
+ but no local lookup is done. We can't assume users on the
+ local host are valid in the given domain (and, for simplicity,
+ have chosen to ignore the cases userdomain matches localdomain
+ or localhost). Setting user-lookup-even-if-domain-mismatch
+ feature will tell pine to override this behavior and perform
+ the local lookup anyway. The problem of a global "even-if"
+ set and a .pinerc-defined user-domain of something odd causing
+ the local lookup, but this will only effect the personal name,
+ and is not judged to be a significant problem.
+
+ 4. In determining if an address is that of the current pine user for
+ formatting index and filtering addresses when replying
+ If a userdomain is specified the address must match the
+ userdomain exactly. If a userdomain is not specified or the
+ userdomain is the same as the hostname or domainname, then
+ an address will be considered the users if it matches either
+ the domainname or the hostname. Of course, the userid must
+ match too.
+
+ 5. In Message ID's
+ The fully-qualified hostname is always users here.
+
+
+** Setting the domain names
+ To set the domain name for all Pine users on the system to be
+different from what Pine figures out from DNS, set the domain name in
+the "user-domain" variable in pine.conf. To set the domain name for an
+individual user, set the "user-domain" variable in his .pinerc.
+The .pinerc setting overrides any other setting.
+ ----*/
+int
+init_hostname(struct pine *ps)
+{
+ char hostname[MAX_ADDRESS+1], domainname[MAX_ADDRESS+1];
+
+ getdomainnames(hostname, sizeof(hostname)-1,
+ domainname, sizeof(domainname)-1);
+
+ if(ps->hostname)
+ fs_give((void **)&ps->hostname);
+
+ ps->hostname = cpystr(hostname);
+
+ if(ps->localdomain)
+ fs_give((void **)&ps->localdomain);
+
+ ps->localdomain = cpystr(domainname);
+ ps->userdomain = NULL;
+
+ if(ps->VAR_USER_DOMAIN && ps->VAR_USER_DOMAIN[0]){
+ ps->maildomain = ps->userdomain = ps->VAR_USER_DOMAIN;
+ }else{
+#if defined(DOS) || defined(OS2)
+ if(ps->VAR_USER_DOMAIN)
+ ps->blank_user_domain = 1; /* user domain set to null string! */
+
+ ps->maildomain = ps->localdomain[0] ? ps->localdomain : ps->hostname;
+#else
+ ps->maildomain = strucmp(ps->VAR_USE_ONLY_DOMAIN_NAME, "yes")
+ ? ps->hostname : ps->localdomain;
+#endif
+ }
+
+ /*
+ * Tell c-client what domain to use when completing unqualified
+ * addresses it finds in local mailboxes. Remember, it won't
+ * affect what's to the right of '@' for unqualified addresses in
+ * remote folders...
+ */
+ mail_parameters(NULL, SET_LOCALHOST, (void *) ps->maildomain);
+ if(F_OFF(F_QUELL_MAILDOMAIN_WARNING, ps) && !strchr(ps->maildomain, '.')){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Incomplete maildomain \"%s\"."),
+ ps->maildomain);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ strncpy(tmp_20k_buf,
+ _("Return address in mail you send may be incorrect."), SIZEOF_20KBUF);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+
+ dprint((1,"User domain name being used \"%s\"\n",
+ ps->userdomain == NULL ? "" : ps->userdomain));
+ dprint((1,"Local Domain name being used \"%s\"\n",
+ ps->localdomain ? ps->localdomain : "?"));
+ dprint((1,"Host name being used \"%s\"\n",
+ ps->hostname ? ps->hostname : "?"));
+ dprint((1,
+ "Mail Domain name being used (by c-client too) \"%s\"\n",
+ ps->maildomain ? ps->maildomain : "?"));
+
+ if(!ps->maildomain || !ps->maildomain[0]){
+#if defined(DOS) || defined(OS2)
+ if(ps->blank_user_domain)
+ return(0); /* prompt for this in send.c:dos_valid_from */
+#endif
+ fprintf(stderr, _("No host name or domain name set\n"));
+ return(-1);
+ }
+ else
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Make sure the default save folders exist in the default
+ save context.
+ ----*/
+void
+init_save_defaults(void)
+{
+ CONTEXT_S *save_cntxt;
+
+ if(!ps_global->VAR_DEFAULT_FCC ||
+ !*ps_global->VAR_DEFAULT_FCC ||
+ !ps_global->VAR_DEFAULT_SAVE_FOLDER ||
+ !*ps_global->VAR_DEFAULT_SAVE_FOLDER)
+ return;
+
+ if(!(save_cntxt = default_save_context(ps_global->context_list)))
+ save_cntxt = ps_global->context_list;
+
+ if(!(folder_exists(save_cntxt, ps_global->VAR_DEFAULT_FCC) & FEX_ISFILE))
+ context_create(save_cntxt, NULL, ps_global->VAR_DEFAULT_FCC);
+
+ if(!(folder_exists(save_cntxt, ps_global->VAR_DEFAULT_SAVE_FOLDER) &
+ FEX_ISFILE))
+ context_create(save_cntxt, NULL, ps_global->VAR_DEFAULT_SAVE_FOLDER);
+
+ free_folder_list(save_cntxt);
+}
+
+
+/*----------------------------------------------------------------------
+ Put sent-mail files in date order
+
+ Args: a, b -- The names of two files. Expects names to be sent-mail-mmm-yy
+ Other names will sort in order and come before those
+ in above format.
+ ----*/
+int
+compare_sm_files(const qsort_t *aa, const qsort_t *bb)
+{
+ struct sm_folder *a = (struct sm_folder *)aa,
+ *b = (struct sm_folder *)bb;
+
+ if(a->month_num == -1 && b->month_num == -1 && a->name && b->name)
+ return(strucmp(a->name, b->name));
+ if(a->month_num == -1) return(-1);
+ if(b->month_num == -1) return(1);
+
+ return(a->month_num - b->month_num);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Create an ordered list of sent-mail folders and their month numbers
+
+ Args: dir -- The directory to find the list of files in
+
+ Result: Pointer to list of files is returned.
+
+This list includes all files that start with "sent-mail", but not "sent-mail"
+itself.
+ ----*/
+struct sm_folder *
+get_mail_list(CONTEXT_S *list_cntxt, char *folder_base)
+{
+ register struct sm_folder *sm = NULL;
+ struct sm_folder *sml = NULL;
+ char *filename;
+ int i, folder_base_len;
+ int max_files;
+ char searchname[MAXPATH+1];
+
+ if((folder_base_len = strlen(folder_base)) == 0 || !list_cntxt){
+ sml = (struct sm_folder *) fs_get(sizeof(struct sm_folder));
+ memset((void *)sml, 0, sizeof(struct sm_folder));
+ return(sml);
+ }
+
+#ifdef DOS
+ if(*list_cntxt->context != '{'){ /* NOT an IMAP collection! */
+ snprintf(searchname, sizeof(searchname), "%4.4s*", folder_base);
+ folder_base_len = strlen(searchname) - 1;
+ }
+ else
+#endif
+ snprintf(searchname, sizeof(searchname), "%.*s*", sizeof(searchname)-2, folder_base);
+
+ build_folder_list(NULL, list_cntxt, searchname, NULL, BFL_FLDRONLY);
+
+ max_files = MIN(MAX(0, folder_total(FOLDERS(list_cntxt))), 5000);
+ sml = sm = (struct sm_folder *) fs_get(sizeof(struct sm_folder)*(max_files+1));
+ memset((void *)sml, 0, sizeof(struct sm_folder) * (max_files+1));
+
+ for(i = 0; i < folder_total(FOLDERS(list_cntxt)); i++){
+ filename = folder_entry(i, FOLDERS(list_cntxt))->name;
+#ifdef DOS
+ if(struncmp(filename, folder_base, folder_base_len) == 0
+ && strucmp(filename, folder_base)){
+
+ if(*list_cntxt->context != '{'){
+ int j;
+ for(j = 0; j < 4; j++)
+ if(!isdigit((unsigned char)filename[folder_base_len + j]))
+ break;
+
+ if(j < 4) /* not proper date format! */
+ continue; /* keep trying */
+ }
+#else
+#ifdef OS2
+ if(strnicmp(filename, folder_base, folder_base_len) == 0
+ && stricmp(filename, folder_base)){
+#else
+ if(strncmp(filename, folder_base, folder_base_len) == 0
+ && strcmp(filename, folder_base)){
+#endif
+#endif
+ sm->name = cpystr(filename);
+#ifdef DOS
+ if(*list_cntxt->context != '{'){ /* NOT an IMAP collection! */
+ sm->month_num = (sm->name[folder_base_len] - '0') * 10;
+ sm->month_num += sm->name[folder_base_len + 1] - '0';
+ }
+ else
+#endif
+ sm->month_num = month_num(sm->name + (size_t)folder_base_len + 1);
+ sm++;
+ if(sm >= &sml[max_files])
+ break; /* Too many files, ignore the rest ; shouldn't occur */
+ }
+ }
+
+ /* anything to sort?? */
+ if(sml->name && *(sml->name) && (sml+1)->name && *((sml+1)->name)){
+ qsort(sml,
+ sm - sml,
+ sizeof(struct sm_folder),
+ compare_sm_files);
+ }
+
+ return(sml);
+}
+
+
+
+int
+check_prune_time(time_t *now, struct tm **tm_now)
+{
+ char tmp[50];
+
+ *now = time((time_t *) 0);
+ *tm_now = localtime(now);
+
+ /*
+ * If the last time we did this is blank (as if pine's run for
+ * first time), don't go thru list asking, but just note it for
+ * the next time...
+ */
+ if(ps_global->VAR_LAST_TIME_PRUNE_QUESTION == NULL){
+ ps_global->last_expire_year = (*tm_now)->tm_year;
+ ps_global->last_expire_month = (*tm_now)->tm_mon;
+ snprintf(tmp, sizeof(tmp), "%d.%d", ps_global->last_expire_year,
+ ps_global->last_expire_month + 1);
+ set_variable(V_LAST_TIME_PRUNE_QUESTION, tmp, 1, 1, Main);
+ return(0);
+ }
+
+ if(ps_global->last_expire_year != -1 &&
+ ((*tm_now)->tm_year < ps_global->last_expire_year ||
+ ((*tm_now)->tm_year == ps_global->last_expire_year &&
+ (*tm_now)->tm_mon <= ps_global->last_expire_month)))
+ return(0);
+
+ return(1);
+}
+
+
+int
+first_run_of_month(void)
+{
+ time_t now;
+ struct tm *tm_now;
+
+ now = time((time_t *) 0);
+ tm_now = localtime(&now);
+
+ if(ps_global->last_expire_year == -1 ||
+ (tm_now->tm_year < ps_global->last_expire_year ||
+ (tm_now->tm_year == ps_global->last_expire_year &&
+ tm_now->tm_mon <= ps_global->last_expire_month)))
+ return(0);
+
+ return(1);
+}
+
+
+int
+first_run_of_year(void)
+{
+ time_t now;
+ struct tm *tm_now;
+
+ now = time((time_t *) 0);
+ tm_now = localtime(&now);
+
+ if(ps_global->last_expire_year == -1 ||
+ (tm_now->tm_year <= ps_global->last_expire_year))
+ return(0);
+
+ return(1);
+}
+
+
+/*
+ * prune_move_folder - rename folder in context and delete old copy
+ * Returns -1 if unsuccessful.
+ */
+int
+prune_move_folder(char *oldpath, char *newpath, CONTEXT_S *prune_cntxt)
+{
+ char spath[MAXPATH+1];
+
+ strncpy(spath, oldpath, sizeof(spath)-1);
+ spath[sizeof(spath)-1] = '\0';
+
+ /*--- User says OK to rename ---*/
+ dprint((5, "rename \"%s\" to \"%s\"\n",
+ spath ? spath : "?", newpath ? newpath : "?"));
+ q_status_message1(SM_ORDER, 1, 3,
+ /* TRANSLATORS: arg is a filename */
+ _("Renaming \"%s\" at start of month"),
+ pretty_fn(spath ? spath : "?"));
+
+ if(!context_rename(prune_cntxt, NULL, spath, newpath)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: 1st arg is filename, 2nd is error message */
+ _("Error renaming \"%s\": %s"),
+ pretty_fn(spath ? spath : "?"),
+ error_description(errno));
+ dprint((1, "Error renaming %s to %s: %s\n",
+ spath ? spath : "?", newpath ? newpath : "?",
+ error_description(errno)));
+ display_message('x');
+ return -1;
+ }
+
+ context_create(prune_cntxt, NULL, spath);
+
+ return 0;
+}
diff --git a/pith/init.h b/pith/init.h
new file mode 100644
index 00000000..243b53e7
--- /dev/null
+++ b/pith/init.h
@@ -0,0 +1,42 @@
+/*
+ * $Id: init.h 900 2008-01-05 01:13:26Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_INIT_INCLUDED
+#define PITH_INIT_INCLUDED
+
+#include "../pith/state.h"
+#include "../pith/conftype.h"
+#include "../pith/context.h"
+
+
+#define ALPINE_VERSION PACKAGE_VERSION
+
+#define LEGAL_NOTICE \
+ "For Copyright information press \"?\""
+
+/* exported protoypes */
+int init_username(struct pine *);
+int init_userdir(struct pine *);
+int init_hostname(struct pine *);
+void init_save_defaults(void);
+int check_prune_time(time_t *, struct tm **);
+int prune_move_folder(char *, char *, CONTEXT_S *);
+int first_run_of_month(void);
+int first_run_of_year(void);
+struct sm_folder *get_mail_list(CONTEXT_S *, char *);
+
+
+#endif /* PITH_INIT_INCLUDED */
diff --git a/pith/keyword.c b/pith/keyword.c
new file mode 100644
index 00000000..2251e91d
--- /dev/null
+++ b/pith/keyword.c
@@ -0,0 +1,441 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: keyword.c 807 2007-11-09 01:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/keyword.h"
+#include "../pith/state.h"
+#include "../pith/flag.h"
+#include "../pith/string.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * Read the keywords array into a KEYWORD_S structure.
+ * Make sure that all of the strings are UTF-8.
+ */
+KEYWORD_S *
+init_keyword_list(char **keywordarray)
+{
+ char **t, *nickname, *keyword;
+ KEYWORD_S *head = NULL, **tail;
+
+ tail = &head;
+ for(t = keywordarray; t && *t && **t; t++){
+ nickname = keyword = NULL;
+ get_pair(*t, &nickname, &keyword, 0, 0);
+ *tail = new_keyword_s(keyword, nickname);
+ tail = &(*tail)->next;
+
+ if(keyword)
+ fs_give((void **) &keyword);
+
+ if(nickname)
+ fs_give((void **) &nickname);
+ }
+
+ return(head);
+}
+
+
+KEYWORD_S *
+new_keyword_s(char *keyword, char *nickname)
+{
+ KEYWORD_S *kw = NULL;
+
+ kw = (KEYWORD_S *) fs_get(sizeof(*kw));
+ memset(kw, 0, sizeof(*kw));
+
+ if(keyword && *keyword)
+ kw->kw = cpystr(keyword);
+
+ if(nickname && *nickname)
+ kw->nick = cpystr(nickname);
+
+ return(kw);
+}
+
+
+void
+free_keyword_list(KEYWORD_S **kl)
+{
+ if(kl && *kl){
+ if((*kl)->next)
+ free_keyword_list(&(*kl)->next);
+
+ if((*kl)->kw)
+ fs_give((void **) &(*kl)->kw);
+
+ if((*kl)->nick)
+ fs_give((void **) &(*kl)->nick);
+
+ fs_give((void **) kl);
+ }
+}
+
+
+/*
+ * Return a pointer to the keyword associated with a nickname, or the
+ * input itself if no match.
+ */
+char *
+nick_to_keyword(char *nick)
+{
+ KEYWORD_S *kw;
+ char *ret;
+
+ ret = nick;
+ for(kw = ps_global->keywords; kw; kw = kw->next)
+ if(!strcmp(nick, kw->nick ? kw->nick : kw->kw ? kw->kw : "")){
+ if(kw->nick)
+ ret = kw->kw;
+
+ break;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Return a pointer to the nickname associated with a keyword, or the
+ * input itself if no match.
+ */
+char *
+keyword_to_nick(char *keyword)
+{
+ KEYWORD_S *kw;
+ char *ret;
+
+ ret = keyword;
+ for(kw = ps_global->keywords; kw; kw = kw->next)
+ if(!strcmp(keyword, kw->kw ? kw->kw : "")){
+ if(kw->nick)
+ ret = kw->nick;
+
+ break;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Return a pointer to the keyword associated with an initial, or the
+ * input itself if no match.
+ * Only works for ascii initials.
+ */
+char *
+initial_to_keyword(char *initial)
+{
+ KEYWORD_S *kw;
+ char *ret;
+ int init;
+ int kwinit;
+
+ ret = initial;
+ if(initial[0] && initial[1] == '\0'){
+ init = initial[0];
+ if(isascii(init) && isupper(init))
+ init = tolower((unsigned char) init);
+
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(kw->nick)
+ kwinit = kw->nick[0];
+ else if(kw->kw)
+ kwinit = kw->kw[0];
+
+ if(isascii(kwinit) && isupper(kwinit))
+ kwinit = tolower((unsigned char) kwinit);
+
+ if(kwinit == init){
+ ret = kw->kw;
+ break;
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+int
+user_flag_is_set(MAILSTREAM *stream, long unsigned int rawno, char *keyword)
+{
+ int j, is_set = 0;
+ MESSAGECACHE *mc;
+
+ if(stream && keyword && keyword[0]){
+ if(rawno > 0L && stream
+ && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno)) != NULL){
+ j = user_flag_index(stream, keyword);
+ if(j >= 0 && j < NUSERFLAGS && ((1 << j) & mc->user_flags))
+ is_set++;
+ }
+ }
+
+ return(is_set);
+}
+
+
+/*
+ * Returns the bit position of the keyword in stream, else -1.
+ */
+int
+user_flag_index(MAILSTREAM *stream, char *keyword)
+{
+ int i, retval = -1;
+ char *p;
+
+ if(stream && keyword && keyword[0]){
+ for(i = 0; i < NUSERFLAGS; i++)
+ if((p=stream_to_user_flag_name(stream, i)) && !strucmp(keyword, p)){
+ retval = i;
+ break;
+ }
+ }
+
+ return(retval);
+}
+
+
+char *
+stream_to_user_flag_name(MAILSTREAM *stream, int k)
+{
+ if(stream && stream->user_flags && k >= 0 && k < NUSERFLAGS)
+ return(stream->user_flags[k]);
+
+ return(NULL);
+}
+
+
+int
+some_user_flags_defined(MAILSTREAM *stream)
+{
+ int k;
+
+ if(stream)
+ for(k = 0; k < NUSERFLAGS; k++)
+ if(stream_to_user_flag_name(stream, k))
+ return 1;
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------
+ Build flags string based on requested flags and what's set in messagecache
+
+ Args: flags -- flags to test
+
+ Result: allocated, space-delimited flags string is returned
+ ----*/
+char *
+flag_string(MAILSTREAM *stream, long rawno, long int flags)
+{
+ MESSAGECACHE *mc;
+ char *p, *q, *returned_flags = NULL;
+ size_t len = 0;
+ int k;
+
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(!mc)
+ return returned_flags;
+
+ if((flags & F_DEL) && mc->deleted)
+ len += strlen("\\DELETED") + 1;
+
+ if((flags & F_ANS) && mc->answered)
+ len += strlen("\\ANSWERED") + 1;
+
+ if((flags & F_FWD) && user_flag_is_set(stream, rawno, FORWARDED_FLAG))
+ len += strlen(FORWARDED_FLAG) + 1;
+
+ if((flags & F_FLAG) && mc->flagged)
+ len += strlen("\\FLAGGED") + 1;
+
+ if((flags & F_SEEN) && mc->seen)
+ len += strlen("\\SEEN") + 1;
+
+ if((flags & F_KEYWORD) && stream->user_flags){
+ for(k = 0; k < NUSERFLAGS; k++){
+ if((q=stream_to_user_flag_name(stream, k))
+ && user_flag_is_set(stream, rawno, q)){
+ len += strlen(q) + 1;
+ }
+ }
+ }
+
+ returned_flags = (char *) fs_get((len+1) * sizeof(char));
+ p = returned_flags;
+ *p = '\0';
+
+ if((flags & F_DEL) && mc->deleted)
+ sstrncpy(&p, "\\DELETED ", len+1-(p-returned_flags));
+
+ if((flags & F_ANS) && mc->answered)
+ sstrncpy(&p, "\\ANSWERED ", len+1-(p-returned_flags));
+
+ if((flags & F_FWD) && user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
+ sstrncpy(&p, FORWARDED_FLAG, len+1-(p-returned_flags));
+ sstrncpy(&p, " ", len+1-(p-returned_flags));
+ }
+
+ if((flags & F_FLAG) && mc->flagged)
+ sstrncpy(&p, "\\FLAGGED ", len+1-(p-returned_flags));
+
+ if((flags & F_SEEN) && mc->seen)
+ sstrncpy(&p, "\\SEEN ", len+1-(p-returned_flags));
+
+ if((flags & F_KEYWORD) && stream->user_flags){
+ for(k = 0; k < NUSERFLAGS; k++){
+ if((q=stream_to_user_flag_name(stream, k))
+ && user_flag_is_set(stream, rawno, q)){
+ sstrncpy(&p, q, len+1-(p-returned_flags));
+ sstrncpy(&p, " ", len+1-(p-returned_flags));
+ len += strlen(p) + 1;
+ }
+ }
+ }
+
+ if(p != returned_flags && (len+1-(p-returned_flags)>0))
+ *--p = '\0';
+
+ return returned_flags;
+}
+
+
+long
+get_msgno_by_msg_id(MAILSTREAM *stream, char *message_id, MSGNO_S *msgmap)
+{
+ SEARCHPGM *pgm = NULL;
+ long hint = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ long newmsgno = -1L;
+ int iter = 0;
+ MESSAGECACHE *mc;
+ extern MAILSTREAM *mm_search_stream;
+ extern long mm_search_count;
+
+ if(!(message_id && message_id[0]) || stream->nmsgs < 1L)
+ return(newmsgno);
+
+ mm_search_count = 0L;
+ mm_search_stream = stream;
+ while(mm_search_count == 0L && iter++ < 3
+ && (pgm = mail_newsearchpgm()) != NULL){
+ pgm->message_id = mail_newstringlist();
+ pgm->message_id->text.data = (unsigned char *) cpystr(message_id);
+ pgm->message_id->text.size = strlen(message_id);
+
+ if(iter > 1 || hint > stream->nmsgs)
+ iter++;
+
+ if(iter == 1){
+ /* restrict to hint message on first try */
+ pgm->msgno = mail_newsearchset();
+ pgm->msgno->first = pgm->msgno->last = hint;
+ }
+ else if(iter == 2){
+ /* restrict to last 50 messages on 2nd try */
+ pgm->msgno = mail_newsearchset();
+ if(stream->nmsgs > 100L)
+ pgm->msgno->first = stream->nmsgs-50L;
+ else{
+ pgm->msgno->first = 1L;
+ iter++;
+ }
+
+ pgm->msgno->last = stream->nmsgs;
+ }
+
+ pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
+
+ if(mm_search_count){
+ for(newmsgno=stream->nmsgs; newmsgno > 0L; newmsgno--)
+ if((mc = mail_elt(stream, newmsgno)) && mc->searched)
+ break;
+ }
+ }
+
+ return(mn_raw2m(msgmap, newmsgno));
+}
+
+
+/*
+ * These chars are not allowed in keywords.
+ *
+ * Returns 0 if ok, 1 if not.
+ * Returns an allocated error message on error.
+ */
+int
+keyword_check(char *kw, char **error)
+{
+ register char *t;
+ char buf[100], *p;
+
+ if(!kw || !kw[0])
+ return 1;
+
+ kw = nick_to_keyword(kw);
+
+ for(p = kw; p && *p; p++)
+ if(!isascii(*p)){
+ if(error)
+ *error = cpystr("Keywords must be all ASCII characters");
+
+ return 1;
+ }
+
+ if((t = strindex(kw, SPACE)) ||
+ (t = strindex(kw, '{')) ||
+ (t = strindex(kw, '(')) ||
+ (t = strindex(kw, ')')) ||
+ (t = strindex(kw, ']')) ||
+ (t = strindex(kw, '%')) ||
+ (t = strindex(kw, '"')) ||
+ (t = strindex(kw, '\\')) ||
+ (t = strindex(kw, '*'))){
+ char s[4];
+ s[0] = '"';
+ s[1] = *t;
+ s[2] = '"';
+ s[3] = '\0';
+ if(error){
+ snprintf(buf, sizeof(buf), "%s not allowed in keywords",
+ *t == SPACE ?
+ "Spaces" :
+ *t == '"' ?
+ "Quotes" :
+ *t == '%' ?
+ "Percents" :
+ s);
+ *error = cpystr(buf);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/pith/keyword.h b/pith/keyword.h
new file mode 100644
index 00000000..915de83c
--- /dev/null
+++ b/pith/keyword.h
@@ -0,0 +1,48 @@
+/*
+ * $Id: keyword.h 807 2007-11-09 01:21:33Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_KEYWORD_INCLUDED
+#define PITH_KEYWORD_INCLUDED
+
+
+#include "../pith/msgno.h"
+
+
+/* these are always UTF-8 */
+typedef struct keywords {
+ char *kw;
+ char *nick;
+ struct keywords *next;
+} KEYWORD_S;
+
+
+/* exported protoypes */
+KEYWORD_S *init_keyword_list(char **);
+KEYWORD_S *new_keyword_s(char *, char *);
+void free_keyword_list(KEYWORD_S **);
+char *nick_to_keyword(char *);
+char *keyword_to_nick(char *);
+char *initial_to_keyword(char *);
+int user_flag_is_set(MAILSTREAM *, unsigned long, char *);
+int user_flag_index(MAILSTREAM *, char *);
+char *stream_to_user_flag_name(MAILSTREAM *, int);
+int some_user_flags_defined(MAILSTREAM *);
+char *flag_string(MAILSTREAM *, long, long);
+long get_msgno_by_msg_id(MAILSTREAM *, char *, MSGNO_S *);
+int keyword_check(char *, char **);
+
+
+#endif /* PITH_KEYWORD_INCLUDED */
diff --git a/pith/ldap.c b/pith/ldap.c
new file mode 100644
index 00000000..d40641e4
--- /dev/null
+++ b/pith/ldap.c
@@ -0,0 +1,1798 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: ldap.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/ldap.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+#include "../pith/imap.h"
+#include "../pith/busy.h"
+#include "../pith/signal.h"
+#include "../pith/ablookup.h"
+
+
+/*
+ * Until we can think of a better way to do this. If user exits from an
+ * ldap address selection screen we want to return -1 so that call_builder
+ * will stay on the same line. Might want to use another return value
+ * for the builder so that call_builder more fully understands what we're
+ * up to.
+ */
+int wp_exit;
+int wp_nobail;
+
+
+#ifdef ENABLE_LDAP
+/*
+ * Hook to allow user input on whether or not to save chosen LDAP result
+ */
+void (*pith_opt_save_ldap_entry)(struct pine *, LDAP_CHOOSE_S *, int);
+
+
+/*
+ * Internal prototypes
+ */
+LDAP_SERV_RES_S *ldap_lookup(LDAP_SERV_S *, char *, CUSTOM_FILT_S *, WP_ERR_S *, int);
+LDAP_SERV_S *copy_ldap_serv_info(LDAP_SERV_S *);
+int our_ldap_get_lderrno(LDAP *, char **, char **);
+int our_ldap_set_lderrno(LDAP *, int, char *, char *);
+#endif /* ENABLE_LDAP */
+
+
+/*
+ * This function does white pages lookups.
+ *
+ * Args string -- the string to use in the lookup
+ *
+ * Returns NULL -- lookup failed
+ * Address -- A single address is returned if lookup was successfull.
+ */
+ADDRESS *
+wp_lookups(char *string, WP_ERR_S *wp_err, int recursing)
+{
+ ADDRESS *ret_a = NULL;
+ char ebuf[200];
+#ifdef ENABLE_LDAP
+ LDAP_SERV_RES_S *free_when_done = NULL;
+ LDAPLookupStyle style;
+ LDAP_CHOOSE_S *winning_e = NULL;
+ LDAP_SERV_S *info = NULL;
+ static char *fakedomain = "@";
+ char *tmp_a_string;
+ int auwe_rv = 0;
+
+ /*
+ * Runtime ldap lookup of addrbook entry.
+ */
+ if(!strncmp(string, RUN_LDAP, LEN_RL)){
+ LDAP_SERV_RES_S *head_of_result_list;
+
+ info = break_up_ldap_server(string+LEN_RL);
+ head_of_result_list = ldap_lookup(info, "", NULL, wp_err, 1);
+
+ if(head_of_result_list){
+ if(!wp_exit)
+ auwe_rv = ask_user_which_entry(head_of_result_list, string,
+ &winning_e,
+ wp_err,
+ wp_err->wp_err_occurred
+ ? DisplayIfOne : DisplayIfTwo);
+
+ if(auwe_rv != -5)
+ free_when_done = head_of_result_list;
+ }
+ else{
+ wp_err->wp_err_occurred = 1;
+ if(wp_err->error){
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ fs_give((void **)&wp_err->error);
+ }
+
+ /* try using backup email address */
+ if(info && info->mail && *info->mail){
+ tmp_a_string = cpystr(info->mail);
+ rfc822_parse_adrlist(&ret_a, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+
+ wp_err->error =
+ cpystr(_("Directory lookup failed, using backup email address"));
+ }
+ else{
+ /*
+ * Do this so the awful LDAP: ... string won't show up
+ * in the composer. This shouldn't actually happen in
+ * real life, so we're not too concerned about it. If we
+ * were we'd want to recover the nickname we started with
+ * somehow, or something like that.
+ */
+ ret_a = mail_newaddr();
+ ret_a->mailbox = cpystr("missing-username");
+ wp_err->error = cpystr(_("Directory lookup failed, no backup email address available"));
+ }
+
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+ }
+ else{
+ style = F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
+ ? DisplayIfOne : DisplayIfTwo;
+ auwe_rv = ldap_lookup_all(string, as.n_serv, recursing, style, NULL,
+ &winning_e, wp_err, &free_when_done);
+ }
+
+ if(winning_e && auwe_rv != -5){
+ ret_a = address_from_ldap(winning_e);
+
+ if(pith_opt_save_ldap_entry && ret_a && F_ON(F_ADD_LDAP_TO_ABOOK, ps_global) && !info)
+ (*pith_opt_save_ldap_entry)(ps_global, winning_e, 1);
+
+
+ fs_give((void **)&winning_e);
+ }
+
+ /* Info's only set in the RUN_LDAP case */
+ if(info){
+ if(ret_a && ret_a->host){
+ ADDRESS *backup = NULL;
+
+ if(info->mail && *info->mail)
+ rfc822_parse_adrlist(&backup, info->mail, fakedomain);
+
+ if(!backup || !address_is_same(ret_a, backup)){
+ if(wp_err->error){
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ fs_give((void **)&wp_err->error);
+ }
+
+ snprintf(ebuf, sizeof(ebuf),
+ _("Warning: current address different from saved address (%s)"),
+ info->mail);
+ wp_err->error = cpystr(ebuf);
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+
+ if(backup)
+ mail_free_address(&backup);
+ }
+
+ free_ldap_server_info(&info);
+ }
+
+ if(free_when_done)
+ free_ldap_result_list(&free_when_done);
+#endif /* ENABLE_LDAP */
+
+ if(ret_a){
+ if(ret_a->mailbox){ /* indicates there was a MAIL attribute */
+ if(!ret_a->host || !ret_a->host[0]){
+ if(ret_a->host)
+ fs_give((void **)&ret_a->host);
+
+ ret_a->host = cpystr("missing-hostname");
+ wp_err->wp_err_occurred = 1;
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(_("Missing hostname in LDAP address"));
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+
+ if(!ret_a->mailbox[0]){
+ if(ret_a->mailbox)
+ fs_give((void **)&ret_a->mailbox);
+
+ ret_a->mailbox = cpystr("missing-username");
+ wp_err->wp_err_occurred = 1;
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(_("Missing username in LDAP address"));
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+ }
+ else{
+ wp_err->wp_err_occurred = 1;
+
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ snprintf(ebuf, sizeof(ebuf), _("No email address available for \"%s\""),
+ (ret_a->personal && *ret_a->personal)
+ ? ret_a->personal
+ : "selected entry");
+ wp_err->error = cpystr(ebuf);
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ mail_free_address(&ret_a);
+ ret_a = NULL;
+ }
+ }
+
+ return(ret_a);
+}
+
+
+#ifdef ENABLE_LDAP
+
+/*
+ * Goes through all servers looking up string.
+ *
+ * Args string -- String to search for
+ * who -- Which servers to look on
+ * style -- Sometimes we want to display no matter what, sometimes
+ * only if more than one entry, sometimes only if email
+ * addresses, ...
+ *
+ * The possible styles are:
+ * AlwaysDisplayAndMailRequired: This happens when user ^T's from
+ * composer to address book screen, and
+ * then queries directory server.
+ * AlwaysDisplay: This happens when user is browsing
+ * in address book maintenance screen and
+ * then queries directory server.
+ * DisplayIfOne: These two come from implicit lookups
+ * DisplayIfTwo: from build_address. If the compose rejects
+ * unqualified feature is on we get the
+ * DisplayIfOne, otherwise IfTwo.
+ *
+ * cust -- Use this custom filter instead of configured filters
+ * winning_e -- Return value
+ * wp_err -- Error handling
+ * free_when_done -- Caller needs to free this
+ *
+ * Returns -- value returned by ask_user_which_entry
+ */
+int
+ldap_lookup_all(char *string, int who, int recursing, LDAPLookupStyle style,
+ CUSTOM_FILT_S *cust, LDAP_CHOOSE_S **winning_e,
+ WP_ERR_S *wp_err, LDAP_SERV_RES_S **free_when_done)
+{
+ int retval = -1;
+ LDAP_SERV_RES_S *head_of_result_list = NULL;
+
+ wp_exit = wp_nobail = 0;
+ if(free_when_done)
+ *free_when_done = NULL;
+
+ head_of_result_list = ldap_lookup_all_work(string, who, recursing,
+ cust, wp_err);
+
+ if(!wp_exit)
+ retval = ask_user_which_entry(head_of_result_list, string, winning_e,
+ wp_err,
+ (wp_err->wp_err_occurred &&
+ style == DisplayIfTwo) ? DisplayIfOne
+ : style);
+
+ /*
+ * Because winning_e probably points into the result list
+ * we need to leave the result list alone and have the caller
+ * free it after they are done with winning_e.
+ */
+ if(retval != -5 && free_when_done)
+ *free_when_done = head_of_result_list;
+
+ return(retval);
+}
+
+
+/*
+ * Goes through all servers looking up string.
+ *
+ * Args string -- String to search for
+ * who -- Which servers to look on
+ * cust -- Use this custom filter instead of configured filters
+ * wp_err -- Error handling
+ *
+ * Returns -- list of results that needs to be freed by caller
+ */
+LDAP_SERV_RES_S *
+ldap_lookup_all_work(char *string, int who, int recursing,
+ CUSTOM_FILT_S *cust, WP_ERR_S *wp_err)
+{
+ int i;
+ LDAP_SERV_RES_S *serv_res;
+ LDAP_SERV_RES_S *rr, *head_of_result_list = NULL;
+
+ /* If there is at least one server */
+ if(ps_global->VAR_LDAP_SERVERS && ps_global->VAR_LDAP_SERVERS[0] &&
+ ps_global->VAR_LDAP_SERVERS[0][0]){
+ int how_many_servers;
+
+ for(i = 0; ps_global->VAR_LDAP_SERVERS[i] &&
+ ps_global->VAR_LDAP_SERVERS[i][0]; i++)
+ ;
+
+ how_many_servers = i;
+
+ /* For each server in list */
+ for(i = 0; !wp_exit && ps_global->VAR_LDAP_SERVERS[i] &&
+ ps_global->VAR_LDAP_SERVERS[i][0]; i++){
+ LDAP_SERV_S *info;
+
+ dprint((6, "ldap_lookup_all_work: lookup on server (%.256s)\n",
+ ps_global->VAR_LDAP_SERVERS[i]));
+ info = NULL;
+ if(who == -1 || who == i || who == as.n_serv)
+ info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[i]);
+
+ /*
+ * Who tells us which servers to look on.
+ * Who == -1 means all servers.
+ * Who == 0 means server[0].
+ * Who == 1 means server[1].
+ * Who == as.n_serv means query on those with impl set.
+ */
+ if(!(who == -1 || who == i ||
+ (who == as.n_serv && !recursing && info && info->impl) ||
+ (who == as.n_serv && recursing && info && info->rhs))){
+
+ if(info)
+ free_ldap_server_info(&info);
+
+ continue;
+ }
+
+ dprint((6, "ldap_lookup_all_work: ldap_lookup (server: %.20s...)(string: %s)\n",
+ ps_global->VAR_LDAP_SERVERS[i], string));
+ serv_res = ldap_lookup(info, string, cust,
+ wp_err, how_many_servers > 1);
+ if(serv_res){
+ /* Add new one to end of list so they come in the right order */
+ for(rr = head_of_result_list; rr && rr->next; rr = rr->next)
+ ;
+
+ if(rr)
+ rr->next = serv_res;
+ else
+ head_of_result_list = serv_res;
+ }
+
+ if(info)
+ free_ldap_server_info(&info);
+ }
+ }
+
+ return(head_of_result_list);
+}
+
+
+/*
+ * Do an LDAP lookup to the server described in the info argument.
+ *
+ * Args info -- LDAP info for server.
+ * string -- String to lookup.
+ * cust -- Possible custom filter description.
+ * wp_err -- We set this is we get a white pages error.
+ * name_in_error -- Caller sets this if they want us to include the server
+ * name in error messages.
+ *
+ * Returns Results of lookup, NULL if lookup failed.
+ */
+LDAP_SERV_RES_S *
+ldap_lookup(LDAP_SERV_S *info, char *string, CUSTOM_FILT_S *cust,
+ WP_ERR_S *wp_err, int name_in_error)
+{
+ char ebuf[900];
+ char buf[900];
+ char *serv, *base, *serv_errstr;
+ char *mailattr, *snattr, *gnattr, *cnattr;
+ int we_cancel = 0, we_turned_on = 0;
+ LDAP_SERV_RES_S *serv_res = NULL;
+ LDAP *ld;
+ long pwdtrial = 0L;
+ int ld_errnum;
+ char *ld_errstr;
+
+
+ if(!info)
+ return(serv_res);
+
+ serv = cpystr((info->serv && *info->serv) ? info->serv : "?");
+
+ if(name_in_error)
+ snprintf(ebuf, sizeof(ebuf), " (%s)",
+ (info->nick && *info->nick) ? info->nick : serv);
+ else
+ ebuf[0] = '\0';
+
+ serv_errstr = cpystr(ebuf);
+ base = cpystr(info->base ? info->base : "");
+
+ if(info->port < 0)
+ info->port = LDAP_PORT;
+
+ if(info->type < 0)
+ info->type = DEF_LDAP_TYPE;
+
+ if(info->srch < 0)
+ info->srch = DEF_LDAP_SRCH;
+
+ if(info->time < 0)
+ info->time = DEF_LDAP_TIME;
+
+ if(info->size < 0)
+ info->size = DEF_LDAP_SIZE;
+
+ if(info->scope < 0)
+ info->scope = DEF_LDAP_SCOPE;
+
+ mailattr = (info->mailattr && info->mailattr[0]) ? info->mailattr
+ : DEF_LDAP_MAILATTR;
+ snattr = (info->snattr && info->snattr[0]) ? info->snattr
+ : DEF_LDAP_SNATTR;
+ gnattr = (info->gnattr && info->gnattr[0]) ? info->gnattr
+ : DEF_LDAP_GNATTR;
+ cnattr = (info->cnattr && info->cnattr[0]) ? info->cnattr
+ : DEF_LDAP_CNATTR;
+
+ /*
+ * We may want to keep ldap handles open, but at least for
+ * now, re-open them every time.
+ */
+
+ dprint((3, "ldap_lookup(%s,%d)\n", serv ? serv : "?", info->port));
+
+ snprintf(ebuf, sizeof(ebuf), "Searching%s%s%s on %s",
+ (string && *string) ? " for \"" : "",
+ (string && *string) ? string : "",
+ (string && *string) ? "\"" : "",
+ serv);
+ we_turned_on = intr_handling_on(); /* this erases keymenu */
+ we_cancel = busy_cue(ebuf, NULL, 0);
+ if(wp_err->mangled)
+ *(wp_err->mangled) = 1;
+
+#ifdef _SOLARIS_SDK
+ if(info->tls || info->tlsmust)
+ ldapssl_client_init(NULL, NULL);
+ if((ld = ldap_init(serv, info->port)) == NULL)
+#else
+#if (LDAPAPI >= 11)
+ if((ld = ldap_init(serv, info->port)) == NULL)
+#else
+ if((ld = ldap_open(serv, info->port)) == NULL)
+#endif
+#endif
+ {
+ /* TRANSLATORS: All of the three args together are an error message */
+ snprintf(ebuf, sizeof(ebuf), _("Access to LDAP server failed: %s%s(%s)"),
+ errno ? error_description(errno) : "",
+ errno ? " " : "",
+ serv);
+ wp_err->wp_err_occurred = 1;
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(ebuf);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ dprint((2, "%s\n", ebuf));
+ }
+ else if(!ps_global->intr_pending){
+ int proto = 3, tlsmustbail = 0;
+ char pwd[NETMAXPASSWD], user[NETMAXUSER];
+ char *passwd = NULL;
+ char hostbuf[1024];
+ NETMBX mb;
+#ifndef _WINDOWS
+ int rc;
+#endif
+
+ memset(&mb, 0, sizeof(mb));
+
+#ifdef _SOLARIS_SDK
+ if(info->tls || info->tlsmust)
+ rc = ldapssl_install_routines(ld);
+#endif
+
+ if(ldap_v3_is_supported(ld) &&
+ our_ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) == 0){
+ dprint((5, "ldap: using version 3 protocol\n"));
+ }
+
+ /*
+ * If we don't set RESTART then the select() waiting for the answer
+ * in libldap will be interrupted and stopped by our busy_cue.
+ */
+ our_ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
+
+ /*
+ * If we need to authenticate, get the password. We are not
+ * supporting SASL authentication, just LDAP simple.
+ */
+ if(info->binddn && info->binddn[0]){
+ char pmt[500];
+ char *space;
+
+ snprintf(hostbuf, sizeof(hostbuf), "{%s}dummy", info->serv ? info->serv : "?");
+
+ /*
+ * We don't handle multiple space-delimited hosts well.
+ * We don't know which we're asking for a password for.
+ * We're not connected yet so we can't know.
+ */
+ if((space=strindex(hostbuf, ' ')) != NULL)
+ *space = '\0';
+
+ mail_valid_net_parse_work(hostbuf, &mb, "ldap");
+ mb.port = info->port;
+ mb.tlsflag = (info->tls || info->tlsmust) ? 1 : 0;
+
+try_password_again:
+
+ if(mb.tlsflag
+ && (pwdtrial > 0 ||
+#ifndef _WINDOWS
+#ifdef _SOLARIS_SDK
+ (rc == LDAP_SUCCESS)
+#else /* !_SOLARIS_SDK */
+ ((rc=ldap_start_tls_s(ld, NULL, NULL)) == LDAP_SUCCESS)
+#endif /* !_SOLARIS_SDK */
+#else /* _WINDOWS */
+ 0 /* TODO: find a way to do this in Windows */
+#endif /* _WINDOWS */
+ ))
+ mb.tlsflag = 1;
+ else
+ mb.tlsflag = 0;
+
+ if((info->tls || info->tlsmust) && !mb.tlsflag){
+ q_status_message(SM_ORDER, 3, 5, "Not able to start TLS encryption for LDAP server");
+ if(info->tlsmust)
+ tlsmustbail++;
+ }
+
+ if(!tlsmustbail){
+ snprintf(pmt, sizeof(pmt), " %s", (info->nick && *info->nick) ? info->nick : serv);
+ mm_login_work(&mb, user, pwd, pwdtrial, pmt, info->binddn);
+ if(pwd && pwd[0])
+ passwd = pwd;
+ }
+ }
+
+
+ /*
+ * LDAPv2 requires the bind. v3 doesn't require it but we want
+ * to tell the server we're v3 if the server supports v3, and if the
+ * server doesn't support v3 the bind is required.
+ */
+ if(tlsmustbail || ldap_simple_bind_s(ld, info->binddn, passwd) != LDAP_SUCCESS){
+ wp_err->wp_err_occurred = 1;
+
+ ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);
+
+ if(!tlsmustbail && info->binddn && info->binddn[0] && pwdtrial < 2L
+ && ld_errnum == LDAP_INVALID_CREDENTIALS){
+ pwdtrial++;
+ q_status_message(SM_ORDER, 3, 5, _("Invalid password"));
+ goto try_password_again;
+ }
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP server failed: %s%s%s%s"),
+ ldap_err2string(ld_errnum),
+ serv_errstr,
+ (ld_errstr && *ld_errstr) ? ": " : "",
+ (ld_errstr && *ld_errstr) ? ld_errstr : "");
+
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ ldap_unbind(ld);
+ wp_err->error = cpystr(ebuf);
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ dprint((2, "%s\n", ebuf));
+ }
+ else if(!ps_global->intr_pending){
+ int srch_res, args, slen, flen;
+#define TEMPLATELEN 512
+ char filt_template[TEMPLATELEN + 1];
+ char filt_format[2*TEMPLATELEN + 1];
+ char filter[2*TEMPLATELEN + 1];
+ char scp[2*TEMPLATELEN + 1];
+ char *p, *q;
+ LDAPMessage *res = NULL;
+ int intr_happened = 0;
+ int tl;
+
+ tl = (info->time == 0) ? info->time : info->time + 10;
+
+ our_ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &tl);
+ our_ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &info->size);
+
+ /*
+ * If a custom filter has been passed in and it doesn't include a
+ * request to combine it with the configured filter, then replace
+ * any configured filter with the passed in filter.
+ */
+ if(cust && cust->filt && !cust->combine){
+ if(info->cust)
+ fs_give((void **)&info->cust);
+
+ info->cust = cpystr(cust->filt);
+ }
+
+ if(info->cust && *info->cust){ /* use custom filter if present */
+ strncpy(filt_template, info->cust, sizeof(filt_template));
+ filt_template[sizeof(filt_template)-1] = '\0';
+ }
+ else{ /* else use configured filter */
+ switch(info->type){
+ case LDAP_TYPE_SUR:
+ snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", snattr);
+ break;
+ case LDAP_TYPE_GIVEN:
+ snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", gnattr);
+ break;
+ case LDAP_TYPE_EMAIL:
+ snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", mailattr);
+ break;
+ case LDAP_TYPE_CN_EMAIL:
+ snprintf(filt_template, sizeof(filt_template), "(|(%s=%%s)(%s=%%s))", cnattr,
+ mailattr);
+ break;
+ case LDAP_TYPE_SUR_GIVEN:
+ snprintf(filt_template, sizeof(filt_template), "(|(%s=%%s)(%s=%%s))",
+ snattr, gnattr);
+ break;
+ case LDAP_TYPE_SEVERAL:
+ snprintf(filt_template, sizeof(filt_template),
+ "(|(%s=%%s)(%s=%%s)(%s=%%s)(%s=%%s))",
+ cnattr, mailattr, snattr, gnattr);
+ break;
+ default:
+ case LDAP_TYPE_CN:
+ snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", cnattr);
+ break;
+ }
+ }
+
+ /* just copy if custom */
+ if(info->cust && *info->cust)
+ info->srch = LDAP_SRCH_EQUALS;
+
+ p = filt_template;
+ q = filt_format;
+ memset((void *)filt_format, 0, sizeof(filt_format));
+ args = 0;
+ while(*p && (q - filt_format) + 4 < sizeof(filt_format)){
+ if(*p == '%' && *(p+1) == 's'){
+ args++;
+ switch(info->srch){
+ /* Exact match */
+ case LDAP_SRCH_EQUALS:
+ *q++ = *p++;
+ *q++ = *p++;
+ break;
+
+ /* Append wildcard after %s */
+ case LDAP_SRCH_BEGINS:
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = '*';
+ break;
+
+ /* Insert wildcard before %s */
+ case LDAP_SRCH_ENDS:
+ *q++ = '*';
+ *q++ = *p++;
+ *q++ = *p++;
+ break;
+
+ /* Put wildcard before and after %s */
+ default:
+ case LDAP_SRCH_CONTAINS:
+ *q++ = '*';
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = '*';
+ break;
+ }
+ }
+ else
+ *q++ = *p++;
+ }
+
+ if(q - filt_format < sizeof(filt_format))
+ *q = '\0';
+
+ filt_format[sizeof(filt_format)-1] = '\0';
+
+ /*
+ * If combine is lit we put the custom filter and the filt_format
+ * filter and combine them with an &.
+ */
+ if(cust && cust->filt && cust->combine){
+ char *combined;
+ size_t l;
+
+ l = strlen(filt_format) + strlen(cust->filt) + 3;
+ combined = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(combined, l+1, "(&%s%s)", cust->filt, filt_format);
+ strncpy(filt_format, combined, sizeof(filt_format));
+ filt_format[sizeof(filt_format)-1] = '\0';
+ fs_give((void **) &combined);
+ }
+
+ /*
+ * Ad hoc attempt to make "Steve Hubert" match
+ * Steven Hubert but not Steven Shubert.
+ * We replace a <SPACE> with * <SPACE> (not * <SPACE> *).
+ */
+ memset((void *)scp, 0, sizeof(scp));
+ if(info->nosub)
+ strncpy(scp, string, sizeof(scp));
+ else{
+ p = string;
+ q = scp;
+ while(*p && (q - scp) + 1 < sizeof(scp)){
+ if(*p == SPACE && *(p+1) != SPACE){
+ *q++ = '*';
+ *q++ = *p++;
+ }
+ else
+ *q++ = *p++;
+ }
+ }
+
+ scp[sizeof(scp)-1] = '\0';
+
+ slen = strlen(scp);
+ flen = strlen(filt_format);
+ /* truncate string if it will overflow filter */
+ if(args*slen + flen - 2*args > sizeof(filter)-1)
+ scp[(sizeof(filter)-1 - flen)/args] = '\0';
+
+ /*
+ * Replace %s's with scp.
+ */
+ switch(args){
+ case 0:
+ snprintf(filter, sizeof(filter), filt_format);
+ break;
+ case 1:
+ snprintf(filter, sizeof(filter), filt_format, scp);
+ break;
+ case 2:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp);
+ break;
+ case 3:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp);
+ break;
+ case 4:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp);
+ break;
+ case 5:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp);
+ break;
+ case 6:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp);
+ break;
+ case 7:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp);
+ break;
+ case 8:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
+ scp);
+ break;
+ case 9:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
+ scp, scp);
+ break;
+ case 10:
+ default:
+ snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
+ scp, scp, scp);
+ break;
+ }
+
+ /* replace double *'s with single *'s in filter */
+ for(p = q = filter; *p; p++)
+ if(*p != '*' || p == filter || *(p-1) != '*')
+ *q++ = *p;
+
+ *q = '\0';
+
+ (void) removing_double_quotes(base);
+ dprint((5, "about to ldap_search(\"%s\", %s)\n",
+ base ? base : "?", filter ? filter : "?"));
+ if(ps_global->intr_pending)
+ srch_res = LDAP_PROTOCOL_ERROR;
+ else{
+ int msgid;
+ time_t start_time;
+
+ start_time = time((time_t *)0);
+
+ dprint((6, "ldap_lookup: calling ldap_search\n"));
+ msgid = ldap_search(ld, base, info->scope, filter, NULL, 0);
+
+ if(msgid == -1)
+ srch_res = our_ldap_get_lderrno(ld, NULL, NULL);
+ else{
+ int lres;
+ /*
+ * Warning: struct timeval is not portable. However, since it is
+ * part of LDAP api it must be portable to all platforms LDAP
+ * has been ported to.
+ */
+ struct timeval t;
+
+ t.tv_sec = 1; t.tv_usec = 0;
+
+ do {
+ if(ps_global->intr_pending)
+ intr_happened = 1;
+
+ dprint((6, "ldap_result(id=%d): ", msgid));
+ if((lres=ldap_result(ld, msgid, LDAP_MSG_ALL, &t, &res)) == -1){
+ /* error */
+ srch_res = our_ldap_get_lderrno(ld, NULL, NULL);
+ dprint((6, "error (-1 returned): ld_errno=%d\n",
+ srch_res));
+ }
+ else if(lres == 0){ /* timeout, no results available */
+ if(intr_happened){
+ ldap_abandon(ld, msgid);
+ srch_res = LDAP_PROTOCOL_ERROR;
+ if(our_ldap_get_lderrno(ld, NULL, NULL) == LDAP_SUCCESS)
+ our_ldap_set_lderrno(ld, LDAP_PROTOCOL_ERROR, NULL, NULL);
+
+ dprint((6, "timeout, intr: srch_res=%d\n",
+ srch_res));
+ }
+ else if(info->time > 0 &&
+ ((long)time((time_t *)0) - start_time) > info->time){
+ /* try for partial results */
+ t.tv_sec = 0; t.tv_usec = 0;
+ lres = ldap_result(ld, msgid, LDAP_MSG_RECEIVED, &t, &res);
+ if(lres > 0 && lres != LDAP_RES_SEARCH_RESULT){
+ srch_res = LDAP_SUCCESS;
+ dprint((6, "partial result: lres=0x%x\n", lres));
+ }
+ else{
+ if(lres == 0)
+ ldap_abandon(ld, msgid);
+
+ srch_res = LDAP_TIMEOUT;
+ if(our_ldap_get_lderrno(ld, NULL, NULL) == LDAP_SUCCESS)
+ our_ldap_set_lderrno(ld, LDAP_TIMEOUT, NULL, NULL);
+
+ dprint((6,
+ "timeout, total_time (%d), srch_res=%d\n",
+ info->time, srch_res));
+ }
+ }
+ else{
+ dprint((6, "timeout\n"));
+ }
+ }
+ else{
+ srch_res = ldap_result2error(ld, res, 0);
+ dprint((6, "lres=0x%x, srch_res=%d\n", lres,
+ srch_res));
+ }
+ }while(lres == 0 &&
+ !(intr_happened ||
+ (info->time > 0 &&
+ ((long)time((time_t *)0) - start_time) > info->time)));
+ }
+ }
+
+ if(intr_happened){
+ wp_exit = 1;
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+ else{
+ q_status_message(SM_ORDER, 0, 1, "Interrupt");
+ display_message('x');
+ fflush(stdout);
+ }
+
+ if(res)
+ ldap_msgfree(res);
+ if(ld)
+ ldap_unbind(ld);
+
+ res = NULL; ld = NULL;
+ }
+ else if(srch_res != LDAP_SUCCESS &&
+ srch_res != LDAP_TIMELIMIT_EXCEEDED &&
+ srch_res != LDAP_RESULTS_TOO_LARGE &&
+ srch_res != LDAP_TIMEOUT &&
+ srch_res != LDAP_SIZELIMIT_EXCEEDED){
+ wp_err->wp_err_occurred = 1;
+
+ ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s%s%s%s"),
+ ldap_err2string(ld_errnum),
+ serv_errstr,
+ (ld_errstr && *ld_errstr) ? ": " : "",
+ (ld_errstr && *ld_errstr) ? ld_errstr : "");
+
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(ebuf);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ dprint((2, "%s\n", ebuf));
+ if(res)
+ ldap_msgfree(res);
+ if(ld)
+ ldap_unbind(ld);
+
+ res = NULL; ld = NULL;
+ }
+ else{
+ int cnt;
+
+ cnt = ldap_count_entries(ld, res);
+
+ if(cnt > 0){
+
+ if(srch_res == LDAP_TIMELIMIT_EXCEEDED ||
+ srch_res == LDAP_RESULTS_TOO_LARGE ||
+ srch_res == LDAP_TIMEOUT ||
+ srch_res == LDAP_SIZELIMIT_EXCEEDED){
+ wp_err->wp_err_occurred = 1;
+ ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP partial results: %s%s%s%s"),
+ ldap_err2string(ld_errnum),
+ serv_errstr,
+ (ld_errstr && *ld_errstr) ? ": " : "",
+ (ld_errstr && *ld_errstr) ? ld_errstr : "");
+ dprint((2, "%s\n", ebuf));
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(ebuf);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+
+ dprint((5, "Matched %d entries on %s\n",
+ cnt, serv ? serv : "?"));
+
+ serv_res = (LDAP_SERV_RES_S *)fs_get(sizeof(LDAP_SERV_RES_S));
+ memset((void *)serv_res, 0, sizeof(*serv_res));
+ serv_res->ld = ld;
+ serv_res->res = res;
+ serv_res->info_used = copy_ldap_serv_info(info);
+ /* Save by reference? */
+ if(info->ref){
+ snprintf(buf, sizeof(buf), "%s:%s", serv, comatose(info->port));
+ serv_res->serv = cpystr(buf);
+ }
+ else
+ serv_res->serv = NULL;
+
+ serv_res->next = NULL;
+ }
+ else{
+ if(srch_res == LDAP_TIMELIMIT_EXCEEDED ||
+ srch_res == LDAP_RESULTS_TOO_LARGE ||
+ srch_res == LDAP_TIMEOUT ||
+ srch_res == LDAP_SIZELIMIT_EXCEEDED){
+ wp_err->wp_err_occurred = 1;
+ wp_err->ldap_errno = srch_res;
+
+ ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s%s%s%s"),
+ ldap_err2string(ld_errnum),
+ serv_errstr,
+ (ld_errstr && *ld_errstr) ? ": " : "",
+ (ld_errstr && *ld_errstr) ? ld_errstr : "");
+
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(ebuf);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ dprint((2, "%s\n", ebuf));
+ }
+
+ dprint((5, "Matched 0 entries on %s\n",
+ serv ? serv : "?"));
+ if(res)
+ ldap_msgfree(res);
+ if(ld)
+ ldap_unbind(ld);
+
+ res = NULL; ld = NULL;
+ }
+ }
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ if(serv)
+ fs_give((void **)&serv);
+ if(base)
+ fs_give((void **)&base);
+ if(serv_errstr)
+ fs_give((void **)&serv_errstr);
+
+ return(serv_res);
+}
+
+
+/*
+ * Given a list of entries, present them to user so user may
+ * select one.
+ *
+ * Args head -- The head of the list of results
+ * orig -- The string the user was searching for
+ * result -- Returned pointer to chosen LDAP_SEARCH_WINNER or NULL
+ * wp_err -- Error handling
+ * style --
+ *
+ * Returns 0 ok
+ * -1 Exit chosen by user
+ * -2 None of matched entries had an email address
+ * -3 No matched entries
+ * -4 Goback to Abook List chosen by user
+ * -5 caller shouldn't free head
+ */
+int
+ask_user_which_entry(LDAP_SERV_RES_S *head, char *orig, LDAP_CHOOSE_S **result,
+ WP_ERR_S *wp_err, LDAPLookupStyle style)
+{
+ ADDR_CHOOSE_S ac;
+ char t[200];
+ int retval;
+
+ dprint((3, "ask_user_which(style=%s)\n",
+ style == AlwaysDisplayAndMailRequired ? "AlwaysDisplayAndMailRequired" :
+ style == AlwaysDisplay ? "AlwaysDisplay" :
+ style == DisplayIfTwo ? "DisplayIfTwo" :
+ style == DisplayForURL ? "DisplayForURL" :
+ style == DisplayIfOne ? "DisplayIfOne" : "?"));
+
+ /*
+ * Set up a screen for user to choose one entry.
+ */
+
+ if(style == AlwaysDisplay || style == DisplayForURL)
+ snprintf(t, sizeof(t), "SEARCH RESULTS INDEX");
+ else{
+ int len;
+
+ len = strlen(orig);
+ snprintf(t, sizeof(t), _("SELECT ONE ADDRESS%s%s%s"),
+ (orig && *orig && len < 40) ? " FOR \"" : "",
+ (orig && *orig && len < 40) ? orig : "",
+ (orig && *orig && len < 40) ? "\"" : "");
+ }
+
+ memset(&ac, 0, sizeof(ADDR_CHOOSE_S));
+ ac.title = cpystr(t);
+ ac.res_head = head;
+
+ retval = ldap_addr_select(ps_global, &ac, result, style, wp_err, orig);
+
+ switch(retval){
+ case 0: /* Ok */
+ break;
+
+ case -1: /* Exit chosen by user */
+ wp_exit = 1;
+ break;
+
+ case -4: /* GoBack to AbookList chosen by user */
+ break;
+
+ case -5:
+ wp_nobail = 1;
+ break;
+
+ case -2:
+ if(style != AlwaysDisplay){
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error =
+ cpystr(_("None of the names matched on directory server has an email address"));
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+
+ break;
+
+ case -3:
+ if(style == AlwaysDisplayAndMailRequired){
+ if(wp_err->error)
+ fs_give((void **)&wp_err->error);
+
+ wp_err->error = cpystr(_("No matches on directory server"));
+ q_status_message(SM_ORDER, 3, 5, wp_err->error);
+ display_message('x');
+ }
+
+ break;
+ }
+
+ fs_give((void **)&ac.title);
+
+ return(retval);
+}
+
+
+ADDRESS *
+address_from_ldap(LDAP_CHOOSE_S *winning_e)
+{
+ ADDRESS *ret_a = NULL;
+
+ if(winning_e){
+ char *a;
+ BerElement *ber;
+
+ ret_a = mail_newaddr();
+ for(a = ldap_first_attribute(winning_e->ld, winning_e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(winning_e->ld, winning_e->selected_entry, ber)){
+ int i;
+ char *p;
+ char **vals;
+
+ dprint((9, "attribute: %s\n", a ? a : "?"));
+ if(!ret_a->personal &&
+ strcmp(a, winning_e->info_used->cnattr) == 0){
+ dprint((9, "Got cnattr:"));
+ vals = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+ for(i = 0; vals[i] != NULL; i++)
+ dprint((9, " %s\n",
+ vals[i] ? vals[i] : "?"));
+
+ if(vals && vals[0])
+ ret_a->personal = cpystr(vals[0]);
+
+ ldap_value_free(vals);
+ }
+ else if(!ret_a->mailbox &&
+ strcmp(a, winning_e->info_used->mailattr) == 0){
+ dprint((9, "Got mailattr:"));
+ vals = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+ for(i = 0; vals[i] != NULL; i++)
+ dprint((9, " %s\n",
+ vals[i] ? vals[i] : "?"));
+
+ /* use first one */
+ if(vals && vals[0]){
+ if((p = strindex(vals[0], '@')) != NULL){
+ ret_a->host = cpystr(p+1);
+ *p = '\0';
+ }
+
+ ret_a->mailbox = cpystr(vals[0]);
+ }
+
+ ldap_value_free(vals);
+ }
+
+ our_ldap_memfree(a);
+ }
+ }
+
+ return(ret_a);
+}
+
+
+/*
+ * Break up the ldap-server string stored in the pinerc into its
+ * parts. The structure is allocated here and should be freed by the caller.
+ *
+ * The original string looks like
+ * <servername>[:port] <SPACE> "/base=<base>/impl=1/..."
+ *
+ * Args serv_str -- The original string from the pinerc to parse.
+ *
+ * Returns A pointer to a structure with filled in answers.
+ *
+ * Some of the members have defaults. If port is -1, that means to use
+ * the default LDAP_PORT. If base is NULL, use "". Type and srch have
+ * defaults defined in alpine.h. If cust is non-NULL, it overrides type and
+ * srch.
+ */
+LDAP_SERV_S *
+break_up_ldap_server(char *serv_str)
+{
+ char *lserv;
+ char *q, *p, *tail;
+ int i, only_one = 1;
+ LDAP_SERV_S *info = NULL;
+
+ if(!serv_str)
+ return(info);
+
+ info = (LDAP_SERV_S *)fs_get(sizeof(LDAP_SERV_S));
+
+ /*
+ * Initialize to defaults.
+ */
+ memset((void *)info, 0, sizeof(*info));
+ info->port = -1;
+ info->srch = -1;
+ info->type = -1;
+ info->time = -1;
+ info->size = -1;
+ info->scope = -1;
+
+ /* copy the whole string to work on */
+ lserv = cpystr(serv_str);
+ if(lserv)
+ removing_trailing_white_space(lserv);
+
+ if(!lserv || !*lserv || *lserv == '"'){
+ if(lserv)
+ fs_give((void **)&lserv);
+
+ if(info)
+ free_ldap_server_info(&info);
+
+ return(NULL);
+ }
+
+ tail = lserv;
+ while((tail = strindex(tail, SPACE)) != NULL){
+ tail++;
+ if(*tail == '"' || *tail == '/'){
+ *(tail-1) = '\0';
+ break;
+ }
+ else
+ only_one = 0;
+ }
+
+ /* tail is the part after server[:port] <SPACE> */
+ if(tail && *tail){
+ removing_leading_white_space(tail);
+ (void)removing_double_quotes(tail);
+ }
+
+ /* get the optional port number */
+ if(only_one && (q = strindex(lserv, ':')) != NULL){
+ int ldapport = -1;
+
+ *q = '\0';
+ if((ldapport = atoi(q+1)) >= 0)
+ info->port = ldapport;
+ }
+
+ /* use lserv for serv even though it has a few extra bytes alloced */
+ info->serv = lserv;
+
+ if(tail && *tail){
+ /* get the search base */
+ if((q = srchstr(tail, "/base=")) != NULL)
+ info->base = remove_backslash_escapes(q+6);
+
+ if((q = srchstr(tail, "/binddn=")) != NULL)
+ info->binddn = remove_backslash_escapes(q+8);
+
+ /* get the implicit parameter */
+ if((q = srchstr(tail, "/impl=1")) != NULL)
+ info->impl = 1;
+
+ /* get the rhs parameter */
+ if((q = srchstr(tail, "/rhs=1")) != NULL)
+ info->rhs = 1;
+
+ /* get the ref parameter */
+ if((q = srchstr(tail, "/ref=1")) != NULL)
+ info->ref = 1;
+
+ /* get the nosub parameter */
+ if((q = srchstr(tail, "/nosub=1")) != NULL)
+ info->nosub = 1;
+
+ /* get the tls parameter */
+ if((q = srchstr(tail, "/tls=1")) != NULL)
+ info->tls = 1;
+
+ /* get the tlsmust parameter */
+ if((q = srchstr(tail, "/tlsm=1")) != NULL)
+ info->tlsmust = 1;
+
+ /* get the search type value */
+ if((q = srchstr(tail, "/type=")) != NULL){
+ NAMEVAL_S *v;
+
+ q += 6;
+ if((p = strindex(q, '/')) != NULL)
+ *p = '\0';
+
+ for(i = 0; (v = ldap_search_types(i)); i++)
+ if(!strucmp(q, v->name)){
+ info->type = v->value;
+ break;
+ }
+
+ if(p)
+ *p = '/';
+ }
+
+ /* get the search rule value */
+ if((q = srchstr(tail, "/srch=")) != NULL){
+ NAMEVAL_S *v;
+
+ q += 6;
+ if((p = strindex(q, '/')) != NULL)
+ *p = '\0';
+
+ for(i = 0; (v = ldap_search_rules(i)); i++)
+ if(!strucmp(q, v->name)){
+ info->srch = v->value;
+ break;
+ }
+
+ if(p)
+ *p = '/';
+ }
+
+ /* get the scope */
+ if((q = srchstr(tail, "/scope=")) != NULL){
+ NAMEVAL_S *v;
+
+ q += 7;
+ if((p = strindex(q, '/')) != NULL)
+ *p = '\0';
+
+ for(i = 0; (v = ldap_search_scope(i)); i++)
+ if(!strucmp(q, v->name)){
+ info->scope = v->value;
+ break;
+ }
+
+ if(p)
+ *p = '/';
+ }
+
+ /* get the time limit */
+ if((q = srchstr(tail, "/time=")) != NULL){
+ q += 6;
+ if((p = strindex(q, '/')) != NULL)
+ *p = '\0';
+
+ /* This one's a number */
+ if(*q){
+ char *err;
+
+ err = strtoval(q, &i, 0, 500, 0, tmp_20k_buf, SIZEOF_20KBUF, "ldap timelimit");
+ if(err){
+ dprint((1, "%s\n", err ? err : "?"));
+ }
+ else
+ info->time = i;
+ }
+
+ if(p)
+ *p = '/';
+ }
+
+ /* get the size limit */
+ if((q = srchstr(tail, "/size=")) != NULL){
+ q += 6;
+ if((p = strindex(q, '/')) != NULL)
+ *p = '\0';
+
+ /* This one's a number */
+ if(*q){
+ char *err;
+
+ err = strtoval(q, &i, 0, 500, 0, tmp_20k_buf, SIZEOF_20KBUF, "ldap sizelimit");
+ if(err){
+ dprint((1, "%s\n", err ? err : "?"));
+ }
+ else
+ info->size = i;
+ }
+
+ if(p)
+ *p = '/';
+ }
+
+ /* get the custom search filter */
+ if((q = srchstr(tail, "/cust=")) != NULL)
+ info->cust = remove_backslash_escapes(q+6);
+
+ /* get the nickname */
+ if((q = srchstr(tail, "/nick=")) != NULL)
+ info->nick = remove_backslash_escapes(q+6);
+
+ /* get the mail attribute name */
+ if((q = srchstr(tail, "/matr=")) != NULL)
+ info->mailattr = remove_backslash_escapes(q+6);
+
+ /* get the sn attribute name */
+ if((q = srchstr(tail, "/satr=")) != NULL)
+ info->snattr = remove_backslash_escapes(q+6);
+
+ /* get the gn attribute name */
+ if((q = srchstr(tail, "/gatr=")) != NULL)
+ info->gnattr = remove_backslash_escapes(q+6);
+
+ /* get the cn attribute name */
+ if((q = srchstr(tail, "/catr=")) != NULL)
+ info->cnattr = remove_backslash_escapes(q+6);
+
+ /* get the backup mail address */
+ if((q = srchstr(tail, "/mail=")) != NULL)
+ info->mail = remove_backslash_escapes(q+6);
+ }
+
+ return(info);
+}
+
+
+void
+free_ldap_server_info(LDAP_SERV_S **info)
+{
+ if(info && *info){
+ if((*info)->serv)
+ fs_give((void **)&(*info)->serv);
+
+ if((*info)->base)
+ fs_give((void **)&(*info)->base);
+
+ if((*info)->cust)
+ fs_give((void **)&(*info)->cust);
+
+ if((*info)->binddn)
+ fs_give((void **)&(*info)->binddn);
+
+ if((*info)->nick)
+ fs_give((void **)&(*info)->nick);
+
+ if((*info)->mail)
+ fs_give((void **)&(*info)->mail);
+
+ if((*info)->mailattr)
+ fs_give((void **)&(*info)->mailattr);
+
+ if((*info)->snattr)
+ fs_give((void **)&(*info)->snattr);
+
+ if((*info)->gnattr)
+ fs_give((void **)&(*info)->gnattr);
+
+ if((*info)->cnattr)
+ fs_give((void **)&(*info)->cnattr);
+
+ fs_give((void **)info);
+ *info = NULL;
+ }
+}
+
+
+LDAP_SERV_S *
+copy_ldap_serv_info(LDAP_SERV_S *src)
+{
+ LDAP_SERV_S *info = NULL;
+
+ if(src){
+ info = (LDAP_SERV_S *) fs_get(sizeof(*info));
+
+ /*
+ * Initialize to defaults.
+ */
+ memset((void *)info, 0, sizeof(*info));
+
+ info->serv = src->serv ? cpystr(src->serv) : NULL;
+ info->base = src->base ? cpystr(src->base) : NULL;
+ info->cust = src->cust ? cpystr(src->cust) : NULL;
+ info->binddn = src->binddn ? cpystr(src->binddn) : NULL;
+ info->nick = src->nick ? cpystr(src->nick) : NULL;
+ info->mail = src->mail ? cpystr(src->mail) : NULL;
+ info->mailattr = cpystr((src->mailattr && src->mailattr[0])
+ ? src->mailattr : DEF_LDAP_MAILATTR);
+ info->snattr = cpystr((src->snattr && src->snattr[0])
+ ? src->snattr : DEF_LDAP_SNATTR);
+ info->gnattr = cpystr((src->gnattr && src->gnattr[0])
+ ? src->gnattr : DEF_LDAP_GNATTR);
+ info->cnattr = cpystr((src->cnattr && src->cnattr[0])
+ ? src->cnattr : DEF_LDAP_CNATTR);
+
+ info->port = (src->port < 0) ? LDAP_PORT : src->port;
+ info->time = (src->time < 0) ? DEF_LDAP_TIME : src->time;
+ info->size = (src->size < 0) ? DEF_LDAP_SIZE : src->size;
+ info->type = (src->type < 0) ? DEF_LDAP_TYPE : src->type;
+ info->srch = (src->srch < 0) ? DEF_LDAP_SRCH : src->srch;
+ info->scope = (src->scope < 0) ? DEF_LDAP_SCOPE : src->scope;
+ info->impl = src->impl;
+ info->rhs = src->rhs;
+ info->ref = src->ref;
+ info->nosub = src->nosub;
+ info->tls = src->tls;
+ }
+
+ return(info);
+}
+
+
+void
+free_ldap_result_list(LDAP_SERV_RES_S **r)
+{
+ if(r && *r){
+ free_ldap_result_list(&(*r)->next);
+ if((*r)->res)
+ ldap_msgfree((*r)->res);
+ if((*r)->ld)
+ ldap_unbind((*r)->ld);
+ if((*r)->info_used)
+ free_ldap_server_info(&(*r)->info_used);
+ if((*r)->serv)
+ fs_give((void **) &(*r)->serv);
+
+ fs_give((void **) r);
+ }
+}
+
+
+/*
+ * Mask API differences.
+ */
+void
+our_ldap_memfree(void *a)
+{
+#if (LDAPAPI >= 15)
+ if(a)
+ ldap_memfree(a);
+#endif
+}
+
+
+/*
+ * Mask API differences.
+ */
+void
+our_ldap_dn_memfree(void *a)
+{
+#if defined(_WINDOWS)
+ if(a)
+ ldap_memfree(a);
+#else
+#if (LDAPAPI >= 15)
+ if(a)
+ ldap_memfree(a);
+#else
+ if(a)
+ free(a);
+#endif
+#endif
+}
+
+
+/*
+ * More API masking.
+ */
+int
+our_ldap_get_lderrno(LDAP *ld, char **m, char **s)
+{
+ int ret = 0;
+
+#if (LDAPAPI >= 2000)
+ if(ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&ret) == 0){
+ if(s)
+ ldap_get_option(ld, LDAP_OPT_ERROR_STRING, (void *)s);
+ }
+#elif (LDAPAPI >= 15)
+ ret = ldap_get_lderrno(ld, m, s);
+#else
+ ret = ld->ld_errno;
+ if(s)
+ *s = ld->ld_error;
+#endif
+
+ return(ret);
+}
+
+
+/*
+ * More API masking.
+ */
+int
+our_ldap_set_lderrno(LDAP *ld, int e, char *m, char *s)
+{
+ int ret;
+
+#if (LDAPAPI >= 2000)
+ if(ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&e) == 0)
+ ret = LDAP_SUCCESS;
+ else
+ (void)ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&ret);
+#elif (LDAPAPI >= 15)
+ ret = ldap_set_lderrno(ld, e, m, s);
+#else
+ /* this is all we care about */
+ ld->ld_errno = e;
+ ret = LDAP_SUCCESS;
+#endif
+
+ return(ret);
+}
+
+
+/*
+ * More API masking.
+ */
+int
+our_ldap_set_option(LDAP *ld, int option, void *optdata)
+{
+ int ret;
+
+#if (LDAPAPI >= 15)
+ ret = ldap_set_option(ld, option, optdata);
+#else
+ switch(option){
+ case LDAP_OPT_TIMELIMIT:
+ ld->ld_timelimit = *(int *)optdata;
+ break;
+
+ case LDAP_OPT_SIZELIMIT:
+ ld->ld_sizelimit = *(int *)optdata;
+ break;
+
+ case LDAP_OPT_RESTART:
+ if((int)optdata)
+ ld->ld_options |= LDAP_OPT_RESTART;
+ else
+ ld->ld_options &= ~LDAP_OPT_RESTART;
+
+ break;
+
+ /*
+ * Does nothing here. There is only one protocol version supported.
+ */
+ case LDAP_OPT_PROTOCOL_VERSION:
+ ret = -1;
+ break;
+
+ default:
+ panic("LDAP function not implemented");
+ }
+#endif
+
+ return(ret);
+}
+
+
+/*
+ * Returns 1 if we can use LDAP version 3 protocol.
+ */
+int
+ldap_v3_is_supported(LDAP *ld)
+{
+ return(1);
+}
+
+struct tl_table {
+ char *ldap_ese;
+ char *translated;
+};
+
+static struct tl_table ldap_trans_table[]={
+ /*
+ * TRANSLATORS: This is a list of LDAP attributes with translations to present
+ * to the user. For example the attribute mail is Email Address and the attribute
+ * cn is Name.
+ */
+ {"mail", N_("Email Address")},
+#define LDAP_MAIL_ATTR 0
+ {"sn", N_("Surname")},
+#define LDAP_SN_ATTR 1
+ {"givenName", N_("Given Name")},
+#define LDAP_GN_ATTR 2
+ {"cn", N_("Name")},
+#define LDAP_CN_ATTR 3
+ {"electronicmail", N_("Email Address")},
+#define LDAP_EMAIL_ATTR 4
+ {"o", N_("Organization")},
+ {"ou", N_("Unit")},
+ {"c", N_("Country")},
+ {"st", N_("State or Province")},
+ {"l", N_("Locality")},
+ {"objectClass", N_("Object Class")},
+ {"title", N_("Title")},
+ {"departmentNumber", N_("Department")},
+ {"postalAddress", N_("Postal Address")},
+ {"homePostalAddress", N_("Home Address")},
+ {"mailStop", N_("Mail Stop")},
+ {"telephoneNumber", N_("Voice Telephone")},
+ {"homePhone", N_("Home Telephone")},
+ {"officePhone", N_("Office Telephone")},
+ {"facsimileTelephoneNumber", N_("FAX Telephone")},
+ {"mobile", N_("Mobile Telephone")},
+ {"pager", N_("Pager")},
+ {"roomNumber", N_("Room Number")},
+ {"uid", N_("User ID")},
+ {NULL, NULL}
+};
+
+char *
+ldap_translate(char *a, LDAP_SERV_S *info_used)
+{
+ int i;
+
+ if(info_used){
+ if(info_used->mailattr && strucmp(info_used->mailattr, a) == 0)
+ return(_(ldap_trans_table[LDAP_MAIL_ATTR].translated));
+ else if(info_used->snattr && strucmp(info_used->snattr, a) == 0)
+ return(_(ldap_trans_table[LDAP_SN_ATTR].translated));
+ else if(info_used->gnattr && strucmp(info_used->gnattr, a) == 0)
+ return(_(ldap_trans_table[LDAP_GN_ATTR].translated));
+ else if(info_used->cnattr && strucmp(info_used->cnattr, a) == 0)
+ return(_(ldap_trans_table[LDAP_CN_ATTR].translated));
+ }
+
+ for(i = 0; ldap_trans_table[i].ldap_ese; i++){
+ if(info_used)
+ switch(i){
+ case LDAP_MAIL_ATTR:
+ case LDAP_SN_ATTR:
+ case LDAP_GN_ATTR:
+ case LDAP_CN_ATTR:
+ case LDAP_EMAIL_ATTR:
+ continue;
+ }
+
+ if(strucmp(ldap_trans_table[i].ldap_ese, a) == 0)
+ return(_(ldap_trans_table[i].translated));
+ }
+
+ return(a);
+}
+
+
+#endif /* ENABLE_LDAP */
diff --git a/pith/ldap.h b/pith/ldap.h
new file mode 100644
index 00000000..2168e5f7
--- /dev/null
+++ b/pith/ldap.h
@@ -0,0 +1,186 @@
+/*
+ * $Id: ldap.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_LDAP_INCLUDED
+#define PITH_LDAP_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/adrbklib.h"
+
+
+#ifdef ENABLE_LDAP
+
+/*
+ * This is used to consolidate related information about a server. This
+ * information is all stored in the ldap-servers variable, per server.
+ */
+typedef struct ldap_serv {
+ char *serv, /* Server name */
+ *base, /* Search base */
+ *binddn, /* Bind DN if non-anonymous */
+ *cust, /* Custom search filter */
+ *nick, /* Nickname */
+ *mail, /* Backup email address */
+ *mailattr, /* "Mail" attribute name */
+ *snattr, /* "Surname" attribute name */
+ *gnattr, /* "Givenname" attribute name */
+ *cnattr; /* "CommonName" attribute name */
+ int port, /* Port number */
+ time, /* Time limit */
+ size, /* Size limit */
+ impl, /* Use implicitly feature */
+ rhs, /* Lookup contents feature */
+ ref, /* Save by reference feature */
+ nosub, /* Disable space sub feature */
+ tls, /* Attempt TLS */
+ tlsmust, /* Require TLS */
+ type, /* Search type (surname...) */
+ srch, /* Search rule (contains...) */
+ scope; /* Scope of search (base...) */
+} LDAP_SERV_S;
+
+
+/*
+ * Structures to control the LDAP address selection screen
+ *
+ * We may run into the problem of LDAP databases containing non-UTF-8 data
+ * because they are old. They should have all UTF-8 data and that is what
+ * we are assuming. If we wanted to accomodate these servers we could
+ * translate the data when we use it. LDAP data is only used in a few
+ * places so it might not be too hard to fix it. There are four calls
+ * into the LDAP library that produce character strings which are
+ * supposed to be UTF-8. They are
+ * ldap_get_dn
+ * ldap_first_attribute
+ * ldap_next_attribute
+ * ldap_get_values
+ * We call those from a half dozen functions. We could fix it by
+ * having a directory-character-set per server and passing that around
+ * in the LDAP_SERV_RES_S structure, I think. For now, let's go with
+ * the assumption that everything is already UTF-8.
+ */
+typedef struct ldap_serv_results {
+ LDAP *ld; /* LDAP handle */
+ LDAPMessage *res; /* LDAP search result */
+ LDAP_SERV_S *info_used;
+ char *serv;
+ struct ldap_serv_results *next;
+} LDAP_SERV_RES_S;
+
+
+typedef struct addr_choose {
+ LDAP_SERV_RES_S *res_head;
+ char *title;
+ LDAP *selected_ld; /* from which ld was entry selected */
+ LDAPMessage *selected_entry; /* which entry was selected */
+ LDAP_SERV_S *info_used;
+ char *selected_serv;
+} ADDR_CHOOSE_S;
+
+
+/*
+ * This is very similar to LDAP_SERV_RES_S, but selected_entry
+ * is a single entry instead of a result list.
+ */
+typedef struct ldap_choose_results {
+ LDAP *ld; /* LDAP handle */
+ LDAPMessage *selected_entry;
+ LDAP_SERV_S *info_used;
+ char *serv;
+} LDAP_CHOOSE_S;
+
+
+/*
+ * How the LDAP lookup should work.
+ */
+typedef enum {AlwaysDisplay,
+ AlwaysDisplayAndMailRequired,
+ DisplayIfTwo,
+ DisplayIfOne,
+ DisplayForURL
+ } LDAPLookupStyle;
+
+
+#define LDAP_TYPE_CN 0
+#define LDAP_TYPE_SUR 1
+#define LDAP_TYPE_GIVEN 2
+#define LDAP_TYPE_EMAIL 3
+#define LDAP_TYPE_CN_EMAIL 4
+#define LDAP_TYPE_SUR_GIVEN 5
+#define LDAP_TYPE_SEVERAL 6
+
+#define LDAP_SRCH_CONTAINS 0
+#define LDAP_SRCH_EQUALS 1
+#define LDAP_SRCH_BEGINS 2
+#define LDAP_SRCH_ENDS 3
+
+#define DEF_LDAP_TYPE 6
+#define DEF_LDAP_SRCH 2
+#define DEF_LDAP_TIME 30
+#define DEF_LDAP_SIZE 0
+#define DEF_LDAP_SCOPE LDAP_SCOPE_SUBTREE
+#define DEF_LDAP_MAILATTR "mail"
+#define DEF_LDAP_SNATTR "sn"
+#define DEF_LDAP_GNATTR "givenname"
+#define DEF_LDAP_CNATTR "cn"
+
+#endif /* ENABLE_LDAP */
+
+
+/*
+ * Error handling argument for white pages lookups.
+ */
+typedef struct wp_err {
+ char *error;
+ int wp_err_occurred;
+ int *mangled;
+ int ldap_errno;
+} WP_ERR_S;
+
+
+extern int wp_exit;
+extern int wp_nobail;
+
+
+/* exported protoypes */
+ADDRESS *wp_lookups(char *, WP_ERR_S *, int);
+#ifdef ENABLE_LDAP
+int ldap_lookup_all(char *, int, int, LDAPLookupStyle, CUSTOM_FILT_S *,
+ LDAP_CHOOSE_S **, WP_ERR_S *, LDAP_SERV_RES_S **);
+char *ldap_translate(char *, LDAP_SERV_S *);
+ADDRESS *address_from_ldap(LDAP_CHOOSE_S *);
+LDAP_SERV_S *break_up_ldap_server(char *);
+void free_ldap_server_info(LDAP_SERV_S **);
+void free_ldap_result_list(LDAP_SERV_RES_S **);
+void our_ldap_memfree(void *);
+void our_ldap_dn_memfree(void *);
+int our_ldap_set_option(LDAP *, int, void *);
+int ldap_v3_is_supported(LDAP *);
+int ask_user_which_entry(LDAP_SERV_RES_S *, char *,
+ LDAP_CHOOSE_S **, WP_ERR_S *, LDAPLookupStyle);
+LDAP_SERV_RES_S *ldap_lookup_all_work(char *, int, int, CUSTOM_FILT_S *, WP_ERR_S *);
+
+
+/*
+ * This must be defined in the application
+ */
+int ldap_addr_select(struct pine *, ADDR_CHOOSE_S *, LDAP_CHOOSE_S **,
+ LDAPLookupStyle, WP_ERR_S *, char *);
+#endif /* ENABLE_LDAP */
+
+
+#endif /* PITH_LDAP_INCLUDED */
diff --git a/pith/list.c b/pith/list.c
new file mode 100644
index 00000000..a862260a
--- /dev/null
+++ b/pith/list.c
@@ -0,0 +1,188 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: list.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ list.c
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/list.h"
+#include "../pith/string.h"
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * parse_list - takes a comma delimited list of "count" elements and
+ * returns an array of pointers to each element neatly
+ * malloc'd in its own array. Any errors are returned
+ * in the string pointed to by "error"
+ *
+ * If remove_surrounding_double_quotes is set, then double quotes around
+ * each element of the list are removed. We can't do this for all list
+ * variables. For example, incoming folders look like
+ * nickname foldername
+ * in the config file. Each of those may be quoted separately.
+ *
+ * NOTE: only recognizes escaped quotes
+ */
+char **
+parse_list(char *list, int count, int flags, char **error)
+{
+ char **lvalue, *p2, *p3, *p4;
+ int was_quoted = 0;
+ int remove_surrounding_double_quotes;
+ int commas_may_be_escaped;
+
+ remove_surrounding_double_quotes = (flags & PL_REMSURRQUOT);
+ commas_may_be_escaped = (flags & PL_COMMAQUOTE);
+
+ lvalue = (char **) fs_get((count+1) * sizeof(char *));
+ count = 0;
+ while(*list){ /* pick elements from list */
+ p2 = list; /* find end of element */
+ while(1){
+ if(*p2 == '"') /* ignore ',' if quoted */
+ was_quoted = (was_quoted) ? 0 : 1 ;
+
+ if(*p2 == '\\' && *(p2+1) == '"')
+ p2++; /* preserve escaped quotes, too */
+
+ if((*p2 == ',' && !was_quoted) || *p2 == '\0')
+ break;
+
+ if(commas_may_be_escaped && *p2 == '\\' && *(p2+1) == ',')
+ p2++;
+
+ p2++;
+ }
+
+ if(was_quoted){ /* unbalanced quotes! */
+ if(error)
+ *error = "Unbalanced quotes";
+
+ break;
+ }
+
+ /*
+ * if element found, eliminate trailing
+ * white space and tie into variable list
+ */
+ if(p2 != list){
+ for(p3 = p2 - 1; isspace((unsigned char) *p3) && list < p3; p3--)
+ ;
+
+ p4 = fs_get(((p3 - list) + 2) * sizeof(char));
+ lvalue[count] = p4;
+ while(list <= p3)
+ *p4++ = *list++;
+
+ *p4 = '\0';
+
+ if(remove_surrounding_double_quotes)
+ removing_double_quotes(lvalue[count]);
+
+ count++;
+ }
+
+ if(*(list = p2) != '\0'){ /* move to beginning of next val */
+ while(*list == ',' || isspace((unsigned char)*list))
+ list++;
+ }
+ }
+
+ lvalue[count] = NULL; /* tie off pointer list */
+ return(lvalue);
+}
+
+
+/*
+ * Free array of string pointers and associated strings
+ *
+ * Args: list -- array of char *'s
+ */
+void
+free_list_array(char ***list)
+{
+ char **p;
+
+ if(list && *list){
+ for(p = *list; *p; p++)
+ fs_give((void **) p);
+
+ fs_give((void **) list);
+ }
+}
+
+
+/*
+ * Copy array of string pointers and associated strings
+ *
+ * Args: list -- array of char *'s
+ *
+ * Returns: Allocated array of string pointers and allocated copies of strings.
+ * Caller should free the list array when done.
+ */
+char **
+copy_list_array(char **list)
+{
+ int i, cnt = 0;
+ char **p, **ret_list = NULL;
+
+ if(list){
+ while(list[cnt++])
+ ;
+
+ p = ret_list = (char **)fs_get((cnt+1) * sizeof(char *));
+ memset((void *) ret_list, 0, (cnt+1) * sizeof(char *));
+
+ for(i=0; list[i]; i++, p++)
+ *p = cpystr(list[i]);
+
+ *p = NULL;
+
+ }
+
+ return(ret_list);
+}
+
+
+int
+equal_list_arrays(char **list1, char **list2)
+{
+ int ret = 0;
+
+ if(list1 && list2){
+ while(*list1){
+ if(!*list2 || strcmp(*list1, *list2) != 0)
+ break;
+
+ list1++;
+ list2++;
+ }
+
+ if(*list1 == NULL && *list2 == NULL)
+ ret++;
+ }
+
+ return(ret);
+}
diff --git a/pith/list.h b/pith/list.h
new file mode 100644
index 00000000..abe32811
--- /dev/null
+++ b/pith/list.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: list.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_LIST_INCLUDED
+#define PITH_LIST_INCLUDED
+
+
+#define PL_NONE 0x00 /* flags modifying parse_list */
+#define PL_REMSURRQUOT 0x01 /* rm surrounding quotes */
+#define PL_COMMAQUOTE 0x02 /* backslash quotes comma */
+
+
+/* exported protoypes */
+char **parse_list(char *, int, int, char **);
+char **copy_list_array(char **);
+void free_list_array(char ***);
+int equal_list_arrays(char **, char **);
+
+
+#endif /* PITH_LIST_INCLUDED */
diff --git a/pith/mailcap.c b/pith/mailcap.c
new file mode 100644
index 00000000..34dce329
--- /dev/null
+++ b/pith/mailcap.c
@@ -0,0 +1,976 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailcap.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mailcap.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/mimetype.h"
+#include "../pith/mimedesc.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+#include "../pith/readfile.h"
+
+/*
+ * We've decided not to implement the RFC1524 standard minimum path, because
+ * some of us think it is harder to debug a problem when you may be misled
+ * into looking at the wrong mailcap entry. Likewise for MIME.Types files.
+ */
+#if defined(DOS) || defined(OS2)
+#define MC_PATH_SEPARATOR ';'
+#define MC_USER_FILE "MAILCAP"
+#define MC_STDPATH NULL
+#else /* !DOS */
+#define MC_PATH_SEPARATOR ':'
+#define MC_USER_FILE NULL
+#define MC_STDPATH \
+ ".mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"
+#endif /* !DOS */
+
+#ifdef _WINDOWS
+#define MC_ADD_TMP " %s"
+#else
+#define MC_ADD_TMP " < %s"
+#endif
+
+typedef struct mcap_entry {
+ struct mcap_entry *next;
+ int needsterminal;
+ char *contenttype;
+ char *command;
+ char *testcommand;
+ char *label; /* unused */
+ char *printcommand; /* unused */
+} MailcapEntry;
+
+struct mailcap_data {
+ MailcapEntry *head, **tail;
+ STRINGLIST *raw;
+} MailcapData;
+
+#define MC_TOKEN_MAX 64
+
+
+/*
+ * Internal prototypes
+ */
+void mc_init(void);
+void mc_process_file(char *);
+void mc_parse_file(char *);
+int mc_parse_line(char **, char **);
+int mc_comment(char **);
+int mc_token(char **, char **);
+void mc_build_entry(char **);
+int mc_sane_command(char *);
+MailcapEntry *mc_get_command(int, char *, BODY *, int, int *);
+int mc_ctype_match(int, char *, char *);
+int mc_passes_test(MailcapEntry *, int, char *, BODY *);
+char *mc_bld_test_cmd(char *, int, char *, BODY *);
+char *mc_cmd_bldr(char *, int, char *, BODY *, char *, char **);
+MailcapEntry *mc_new_entry(void);
+void mc_free_entry(MailcapEntry **);
+
+
+char *
+mc_conf_path(char *def_path, char *env_path, char *user_file, int separator, char *stdpath)
+{
+ char *path;
+
+ /* We specify MIMETYPES as a path override */
+ if(def_path)
+ /* there may need to be an override specific to pine */
+ path = cpystr(def_path);
+ else if(env_path)
+ path = cpystr(env_path);
+ else{
+#if defined(DOS) || defined(OS2)
+ char *s;
+
+ /*
+ * This gets interesting. Since we don't have any standard location
+ * for config/data files, look in the same directory as the PINERC
+ * and the same dir as PINE.EXE. This is similar to the UNIX
+ * situation with personal config info coming before
+ * potentially shared config data...
+ */
+ if(s = last_cmpnt(ps_global->pinerc)){
+ strncpy(tmp_20k_buf+1000, ps_global->pinerc, MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000));
+ tmp_20k_buf[1000+MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000-1)] = '\0';
+ }
+ else
+ strncpy(tmp_20k_buf+1000, ".\\", SIZEOF_20KBUF-1000);
+
+ /* pinerc directory version of file */
+ build_path(tmp_20k_buf+2000, tmp_20k_buf+1000, user_file, SIZEOF_20KBUF-2000);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /* pine.exe directory version of file */
+ build_path(tmp_20k_buf+3000, ps_global->pine_dir, user_file, SIZEOF_20KBUF-3000);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /* combine them */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%c%s", tmp_20k_buf+2000, separator, tmp_20k_buf+3000);
+
+#else /* !DOS */
+ build_path(tmp_20k_buf, ps_global->home_dir, stdpath, SIZEOF_20KBUF);
+#endif /* !DOS */
+ path = cpystr(tmp_20k_buf);
+ }
+
+ return(path);
+}
+
+
+/*
+ * mc_init - Run down the path gathering all the mailcap entries.
+ * Returns with the Mailcap list built.
+ */
+void
+mc_init(void)
+{
+ char *s,
+ *pathcopy,
+ *path,
+ image_viewer[MAILTMPLEN];
+
+ if(MailcapData.raw) /* already have the file? */
+ return;
+ else
+ MailcapData.tail = &MailcapData.head;
+
+ dprint((5, "- mc_init -\n"));
+
+ pathcopy = mc_conf_path(ps_global->VAR_MAILCAP_PATH, getenv("MAILCAPS"),
+ MC_USER_FILE, MC_PATH_SEPARATOR, MC_STDPATH);
+
+ path = pathcopy; /* overloaded "path" */
+
+ /*
+ * Insert an entry for the image-viewer variable from .pinerc, if present.
+ */
+ if(ps_global->VAR_IMAGE_VIEWER && *ps_global->VAR_IMAGE_VIEWER){
+ MailcapEntry *mc = mc_new_entry();
+
+ snprintf(image_viewer, sizeof(image_viewer), "%s %%s", ps_global->VAR_IMAGE_VIEWER);
+
+ MailcapData.raw = mail_newstringlist();
+ MailcapData.raw->text.data = (unsigned char *) cpystr(image_viewer);
+ mc->command = (char *) MailcapData.raw->text.data;
+ mc->contenttype = "image/*";
+ mc->label = "Alpine Image Viewer";
+ dprint((5, "mailcap: using image-viewer=%s\n",
+ ps_global->VAR_IMAGE_VIEWER
+ ? ps_global->VAR_IMAGE_VIEWER : "?"));
+ }
+
+ dprint((7, "mailcap: path: %s\n", path ? path : "?"));
+ while(path){
+ s = strindex(path, MC_PATH_SEPARATOR);
+ if(s)
+ *s++ = '\0';
+ mc_process_file(path);
+ path = s;
+ }
+
+ if(pathcopy)
+ fs_give((void **)&pathcopy);
+
+#ifdef DEBUG
+ if(debug >= 11){
+ MailcapEntry *mc;
+ int i = 0;
+
+ dprint((11, "Collected mailcap entries\n"));
+ for(mc = MailcapData.head; mc; mc = mc->next){
+
+ dprint((11, "%d: ", i++));
+ if(mc->label)
+ dprint((11, "%s\n", mc->label ? mc->label : "?"));
+ if(mc->contenttype)
+ dprint((11, " %s",
+ mc->contenttype ? mc->contenttype : "?"));
+ if(mc->command)
+ dprint((11, " command: %s\n",
+ mc->command ? mc->command : "?"));
+ if(mc->testcommand)
+ dprint((11, " testcommand: %s",
+ mc->testcommand ? mc->testcommand : "?"));
+ if(mc->printcommand)
+ dprint((11, " printcommand: %s",
+ mc->printcommand ? mc->printcommand : "?"));
+ dprint((11, " needsterminal %d\n", mc->needsterminal));
+ }
+ }
+#endif /* DEBUG */
+}
+
+
+/*
+ * Add all the entries from this file onto the Mailcap list.
+ */
+void
+mc_process_file(char *file)
+{
+ char filebuf[MAXPATH+1], *file_data;
+
+ dprint((5, "mailcap: process_file: %s\n", file ? file : "?"));
+
+ (void)strncpy(filebuf, file, MAXPATH);
+ filebuf[MAXPATH] = '\0';
+ file = fnexpand(filebuf, sizeof(filebuf));
+ dprint((7, "mailcap: processing file: %s\n", file ? file : "?"));
+ switch(is_writable_dir(file)){
+ case 0: case 1: /* is a directory */
+ dprint((1, "mailcap: %s is a directory, should be a file\n",
+ file ? file : "?"));
+ return;
+
+ case 2: /* ok */
+ break;
+
+ case 3: /* doesn't exist */
+ dprint((5, "mailcap: %s doesn't exist\n", file ? file : "?"));
+ return;
+
+ default:
+ panic("Programmer botch in mc_process_file");
+ /*NOTREACHED*/
+ }
+
+ if((file_data = read_file(file, READ_FROM_LOCALE)) != NULL){
+ STRINGLIST *newsl, **sl;
+
+ /* Create a new container */
+ newsl = mail_newstringlist();
+ newsl->text.data = (unsigned char *) file_data;
+
+ /* figure out where in the list it should go */
+ for(sl = &MailcapData.raw; *sl; sl = &((*sl)->next))
+ ;
+
+ *sl = newsl; /* Add it to the list */
+
+ mc_parse_file(file_data); /* the process mailcap data */
+ }
+ else
+ dprint((5, "mailcap: %s can't be read\n", file ? file : "?"));
+}
+
+
+void
+mc_parse_file(char *file)
+{
+ char *tokens[MC_TOKEN_MAX];
+
+ while(mc_parse_line(&file, tokens))
+ mc_build_entry(tokens);
+}
+
+
+int
+mc_parse_line(char **line, char **tokens)
+{
+ char **tokenp = tokens;
+
+ while(mc_comment(line)) /* skip comment lines */
+ ;
+
+ while(mc_token(tokenp, line)) /* collect ';' delim'd tokens */
+ if(++tokenp - tokens >= MC_TOKEN_MAX)
+ fatal("Ran out of tokens parsing mailcap file"); /* outch! */
+
+ *++tokenp = NULL; /* tie off list */
+ return(*tokens != NULL);
+}
+
+
+/*
+ * Retuns 1 if line is a comment, 0 otherwise
+ */
+int
+mc_comment(char **line)
+{
+ if(**line == '\n'){ /* blank line is a comment, too */
+ (*line)++;
+ return(1);
+ }
+
+ if(**line == '#'){
+ while(**line) /* !EOF */
+ if(*++(*line) == '\n'){ /* EOL? */
+ (*line)++;
+ break;
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * Retuns 0 if EOL, 1 otherwise
+ */
+int
+mc_token(char **token, char **line)
+{
+ int rv = 0;
+ char *start, *wsp = NULL;
+
+ *token = NULL; /* init the slot for this token */
+
+ /* skip leading white space */
+ while(**line && isspace((unsigned char) **line))
+ (*line)++;
+
+ start = *line;
+
+ /* Then see what's left */
+ while(1)
+ switch(**line){
+ case ';' : /* End-Of-Token */
+ rv = 1; /* let caller know more follows */
+ case '\n' : /* EOL */
+ if(wsp)
+ *wsp = '\0'; /* truncate white space? */
+ else
+ *start = '\0'; /* if we have a token, tie it off */
+
+ (*line)++; /* and get ready to parse next one */
+
+ if(rv == 1){ /* ignore trailing semicolon */
+ while(**line){
+ if(**line == '\n')
+ rv = 0;
+
+ if(isspace((unsigned char) **line))
+ (*line)++;
+ else
+ break;
+ }
+ }
+
+ case '\0' : /* EOF */
+ return(rv);
+
+ case '\\' : /* Quoted char */
+ (*line)++;
+#if defined(DOS) || defined(OS2)
+ /*
+ * RFC 1524 says that backslash is used to quote
+ * the next character, but since backslash is part of pathnames
+ * on DOS we're afraid people will not put double backslashes
+ * in their mailcap files. Therefore, we violate the RFC by
+ * looking ahead to the next character. If it looks like it
+ * is just part of a pathname, then we consider a single
+ * backslash to *not* be a quoting character, but a literal
+ * backslash instead.
+ *
+ * SO:
+ * If next char is any of these, treat the backslash
+ * that preceded it like a regular character.
+ */
+ if(**line && isascii(**line)
+ && (isalnum((unsigned char) **line) || strchr("_+-=~" , **line))){
+ *start++ = '\\';
+ wsp = NULL;
+ break;
+ }
+ else
+#endif /* !DOS */
+
+ if(**line == '\n'){ /* quoted line break */
+ *start = ' ';
+ (*line)++; /* just move on */
+ while(isspace((unsigned char) **line))
+ (*line)++;
+
+ break;
+ }
+ else if(**line == '%') /* quoted '%' becomes "%%" */
+ *--(*line) = '%'; /* overwrite '\' !! */
+
+ /* Fall thru and copy/advance pointers*/
+
+ default :
+ if(!*token)
+ *token = start;
+
+ *start = *(*line)++;
+ wsp = (isspace((unsigned char) *start) && !wsp) ? start : NULL;
+ start++;
+ break;
+ }
+}
+
+
+void
+mc_build_entry(char **tokens)
+{
+ MailcapEntry *mc;
+
+ if(!tokens[0]){
+ dprint((5, "mailcap: missing content type!\n"));
+ return;
+ }
+ else if(!tokens[1] || !mc_sane_command(tokens[1])){
+ dprint((5, "mailcap: missing/bogus command!\n"));
+ return;
+ }
+
+ mc = mc_new_entry();
+ mc->contenttype = *tokens++;
+ mc->command = *tokens++;
+
+ dprint((9, "mailcap: content type: %s\n command: %s\n",
+ mc->contenttype ? mc->contenttype : "?",
+ mc->command ? mc->command : "?"));
+
+ /* grok options */
+ for( ; *tokens; tokens++){
+ char *arg;
+
+ /* legit value? */
+ if(!isalnum((unsigned char) **tokens)){
+ dprint((5, "Unknown parameter = \"%s\"", *tokens));
+ continue;
+ }
+
+ if((arg = strindex(*tokens, '=')) != NULL){
+ *arg = ' ';
+ while(arg > *tokens && isspace((unsigned char) arg[-1]))
+ arg--;
+
+ *arg++ = '\0'; /* tie off parm arg */
+ while(*arg && isspace((unsigned char) *arg))
+ arg++;
+
+ if(!*arg)
+ arg = NULL;
+ }
+
+ if(!strucmp(*tokens, "needsterminal")){
+ mc->needsterminal = 1;
+ dprint((9, "mailcap: set needsterminal\n"));
+ }
+ else if(!strucmp(*tokens, "copiousoutput")){
+ mc->needsterminal = 2;
+ dprint((9, "mailcap: set copiousoutput\n"));
+ }
+ else if(arg && !strucmp(*tokens, "test")){
+ mc->testcommand = arg;
+ dprint((9, "mailcap: testcommand=%s\n",
+ mc->testcommand ? mc->testcommand : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "description")){
+ mc->label = arg;
+ dprint((9, "mailcap: label=%s\n",
+ mc->label ? mc->label : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "print")){
+ mc->printcommand = arg;
+ dprint((9, "mailcap: printcommand=%s\n",
+ mc->printcommand ? mc->printcommand : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "compose")){
+ /* not used */
+ dprint((9, "mailcap: not using compose=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "composetyped")){
+ /* not used */
+ dprint((9, "mailcap: not using composetyped=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "textualnewlines")){
+ /* not used */
+ dprint((9,
+ "mailcap: not using texttualnewlines=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "edit")){
+ /* not used */
+ dprint((9, "mailcap: not using edit=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "x11-bitmap")){
+ /* not used */
+ dprint((9, "mailcap: not using x11-bitmap=%s\n",
+ arg ? arg : "?"));
+ }
+ else
+ dprint((9, "mailcap: ignoring unknown flag: %s\n",
+ arg ? arg : "?"));
+ }
+}
+
+
+/*
+ * Tests for mailcap defined command's sanity
+ */
+int
+mc_sane_command(char *command)
+{
+ /* First, test that a command string actually exists */
+ if(command && *command){
+#ifdef LATER
+ /*
+ * NOTE: Maybe we'll do this later. The problem is when the
+ * mailcap's been misconfigured. We then end up supressing
+ * valuable output when the user actually tries to launch the
+ * spec'd viewer.
+ */
+
+ /* Second, Make sure we can get at it */
+ if(can_access_in_path(getenv("PATH"), command, EXECUTE_ACCESS) >= 0)
+#endif
+ return(1);
+ }
+
+ return(0); /* failed! */
+}
+
+
+/*
+ * Returns the mailcap entry for type/subtype from the successfull
+ * mailcap entry, or NULL if none. Command string still contains % stuff.
+ */
+MailcapEntry *
+mc_get_command(int type, char *subtype, BODY *body,
+ int check_extension, int *sp_handlingp)
+{
+ MailcapEntry *mc;
+ char tmp_subtype[256], tmp_ext[16], *ext = NULL;
+
+ dprint((5, "- mc_get_command(%s/%s) -\n",
+ body_type_names(type),
+ subtype ? subtype : "?"));
+
+ if(type == TYPETEXT
+ && (!subtype || !strucmp(subtype, "plain"))
+ && F_ON(F_SHOW_TEXTPLAIN_INT, ps_global))
+ return(NULL);
+
+ mc_init();
+
+ if(check_extension){
+ char *fname;
+ MT_MAP_T e2b;
+
+ /*
+ * Special handling for when we're looking at what's likely
+ * binary application data. Look for a file name extension
+ * that we might use to hook a helper app to.
+ *
+ * NOTE: This used to preclude an "app/o-s" mailcap entry
+ * since this took precedence. Now that there are
+ * typically two scans through the check_extension
+ * mechanism, the mailcap entry now takes precedence.
+ */
+ if((fname = get_filename_parameter(NULL, 0, body, &e2b.from.ext)) != NULL
+ && e2b.from.ext && e2b.from.ext[0]){
+ if(strlen(e2b.from.ext) < sizeof(tmp_ext) - 2){
+ strncpy(ext = tmp_ext, e2b.from.ext - 1, sizeof(tmp_ext)); /* remember it */
+ tmp_ext[sizeof(tmp_ext)-1] = '\0';
+ if(mt_srch_mime_type(mt_srch_by_ext, &e2b)){
+ type = e2b.to.mime.type; /* mapped type */
+ strncpy(subtype = tmp_subtype, e2b.to.mime.subtype,
+ sizeof(tmp_subtype)-1);
+ tmp_subtype[sizeof(tmp_subtype)-1] = '\0';
+ fs_give((void **) &e2b.to.mime.subtype);
+ body = NULL; /* the params no longer apply */
+ }
+ }
+
+ fs_give((void **) &fname);
+ }
+ else{
+ if(fname)
+ fs_give((void **) &fname);
+
+ return(NULL);
+ }
+ }
+
+ for(mc = MailcapData.head; mc; mc = mc->next)
+ if(mc_ctype_match(type, subtype, mc->contenttype)
+ && mc_passes_test(mc, type, subtype, body)){
+ dprint((9,
+ "mc_get_command: type=%s/%s, command=%s\n",
+ body_type_names(type),
+ subtype ? subtype : "?",
+ mc->command ? mc->command : "?"));
+ return(mc);
+ }
+
+ if(mime_os_specific_access()){
+ static MailcapEntry fake_mc;
+ static char fake_cmd[1024];
+ char tmp_mime_type[256];
+
+ memset(&fake_mc, 0, sizeof(MailcapEntry));
+ fake_cmd[0] = '\0';
+ fake_mc.command = fake_cmd;
+
+ snprintf(tmp_mime_type, sizeof(tmp_mime_type), "%s/%s", body_types[type], subtype);
+ if(mime_get_os_mimetype_command(tmp_mime_type, ext, fake_cmd,
+ sizeof(fake_cmd), check_extension, sp_handlingp))
+ return(&fake_mc);
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * Check whether the pattern "pat" matches this type/subtype.
+ * Returns 1 if it does, 0 if not.
+ */
+int
+mc_ctype_match(int type, char *subtype, char *pat)
+{
+ char *type_name = body_type_names(type);
+ int len = strlen(type_name);
+
+ dprint((5, "mc_ctype_match: %s == %s / %s ?\n",
+ pat ? pat : "?",
+ type_name ? type_name : "?",
+ subtype ? subtype : "?"));
+
+ return(!struncmp(type_name, pat, len)
+ && ((pat[len] == '/'
+ && (!pat[len+1] || pat[len+1] == '*'
+ || !strucmp(subtype, &pat[len+1])))
+ || !pat[len]));
+}
+
+
+/*
+ * Run the test command for entry mc to see if this entry currently applies to
+ * applies to this type/subtype.
+ *
+ * Returns 1 if it does pass test (exits with status 0), 0 otherwise.
+ */
+int
+mc_passes_test(MailcapEntry *mc, int type, char *subtype, BODY *body)
+{
+ char *cmd = NULL;
+ int rv;
+
+ dprint((5, "- mc_passes_test -\n"));
+
+ if(mc->testcommand
+ && *mc->testcommand
+ && !(cmd = mc_bld_test_cmd(mc->testcommand, type, subtype, body)))
+ return(FALSE); /* couldn't be built */
+
+ if(!mc->testcommand || !cmd || !*cmd){
+ if(cmd)
+ fs_give((void **)&cmd);
+
+ dprint((7, "no test command, so Pass\n"));
+ return 1;
+ }
+
+ rv = exec_mailcap_test_cmd(cmd);
+ dprint((7, "mc_passes_test: \"%s\" %s (rv=%d)\n",
+ cmd ? cmd : "?", rv ? "Failed" : "Passed", rv)) ;
+
+ fs_give((void **)&cmd);
+
+ return(!rv);
+}
+
+
+int
+mailcap_can_display(int type, char *subtype, BODY *body, int check_extension)
+{
+ dprint((5, "- mailcap_can_display -\n"));
+
+ return(mc_get_command(type, subtype, body,
+ check_extension, NULL) != NULL);
+}
+
+
+MCAP_CMD_S *
+mailcap_build_command(int type, char *subtype, BODY *body,
+ char *tmp_file, int *needsterm, int chk_extension)
+{
+ MailcapEntry *mc;
+ char *command, *err = NULL;
+ MCAP_CMD_S *mc_cmd = NULL;
+ int sp_handling = 0;
+
+ dprint((5, "- mailcap_build_command -\n"));
+
+ mc = mc_get_command(type, subtype, body, chk_extension, &sp_handling);
+ if(!mc){
+ q_status_message(SM_ORDER, 3, 4, "Error constructing viewer command");
+ dprint((1,
+ "mailcap_build_command: no command string for %s/%s\n",
+ body_type_names(type), subtype ? subtype : "?"));
+ return((MCAP_CMD_S *)NULL);
+ }
+
+ if(needsterm)
+ *needsterm = mc->needsterminal;
+
+ if(sp_handling)
+ command = cpystr(mc->command);
+ else if(!(command = mc_cmd_bldr(mc->command, type, subtype, body, tmp_file, &err)) && err && *err)
+ q_status_message(SM_ORDER, 5, 5, err);
+
+ dprint((5, "built command: %s\n", command ? command : "?"));
+
+ if(command){
+ mc_cmd = (MCAP_CMD_S *)fs_get(sizeof(MCAP_CMD_S));
+ mc_cmd->command = command;
+ mc_cmd->special_handling = sp_handling;
+ }
+ return(mc_cmd);
+}
+
+
+/*
+ * mc_bld_test_cmd - build the command to test if the given type flies
+ *
+ * mc_cmd_bldr's tmp_file argument is NULL as we're not going to
+ * decode and write each and every MIME segment's data to a temp file
+ * when no test's going to use the data anyway.
+ */
+char *
+mc_bld_test_cmd(char *controlstring, int type, char *subtype, BODY *body)
+{
+ return(mc_cmd_bldr(controlstring, type, subtype, body, NULL, NULL));
+}
+
+
+/*
+ * mc_cmd_bldr - construct a command string to execute
+ *
+ * If tmp_file is null, then the contents of the given MIME segment
+ * is not provided. This is useful for building the "test=" string
+ * as it doesn't operate on the segment's data.
+ *
+ * The return value is an alloc'd copy of the command to be executed.
+ */
+char *
+mc_cmd_bldr(char *controlstring, int type, char *subtype,
+ BODY *body, char *tmp_file, char **err)
+{
+ char *from, *to, *s, *parm;
+ int prefixed = 0, used_tmp_file = 0;
+
+ dprint((8, "- mc_cmd_bldr -\n"));
+
+ for(from = controlstring, to = tmp_20k_buf; *from; ++from){
+ if(prefixed){ /* previous char was % */
+ prefixed = 0;
+ switch(*from){
+ case '%': /* turned \% into this earlier */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '%';
+
+ break;
+
+ case 's': /* insert tmp_file name in cmd */
+ if(tmp_file){
+ used_tmp_file = 1;
+ sstrncpy(&to, tmp_file, SIZEOF_20KBUF-(to-tmp_20k_buf));
+ }
+ else
+ dprint((1,
+ "mc_cmd_bldr: %%s in cmd but not supplied!\n"));
+
+ break;
+
+ case 't': /* insert MIME type/subtype */
+ /* quote to prevent funny business */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\'';
+
+ sstrncpy(&to, body_type_names(type), SIZEOF_20KBUF-(to-tmp_20k_buf));
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '/';
+
+ sstrncpy(&to, subtype, SIZEOF_20KBUF-(to-tmp_20k_buf));
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\'';
+
+ break;
+
+ case '{': /* insert requested MIME param */
+ if(F_OFF(F_DO_MAILCAP_PARAM_SUBST, ps_global)){
+ int save;
+
+ dprint((2, "mc_cmd_bldr: param subs %s\n",
+ from ? from : "?"));
+ if(err){
+ if((s = strindex(from, '}')) != NULL){
+ save = *++s;
+ *s = '\0';
+ }
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Mailcap: see hidden feature %.200s (%%%.200s)",
+ feature_list_name(F_DO_MAILCAP_PARAM_SUBST), from);
+ *err = tmp_20k_buf;
+ if(s)
+ *s = save;
+ }
+
+ return(NULL);
+ }
+
+ s = strindex(from, '}');
+ if(!s){
+ q_status_message1(SM_ORDER, 0, 4,
+ "Ignoring ill-formed parameter reference in mailcap file: %.200s", from);
+ break;
+ }
+
+ *s = '\0';
+ ++from; /* from is the part inside the brackets now */
+
+ parm = parameter_val(body ? body->parameter : NULL, from);
+
+ dprint((9,
+ "mc_cmd_bldr: parameter %s = %s\n",
+ from ? from : "?", parm ? parm : "(not found)"));
+
+ /*
+ * Quote parameter values for /bin/sh.
+ * Put single quotes around the whole thing but every time
+ * there is an actual single quote put it outside of the
+ * single quotes with a backslash in front of it. So the
+ * parameter value fred's car
+ * turns into 'fred'\''s car'
+ */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\''; /* opening quote */
+
+ if(parm){
+ char *p;
+
+ /*
+ * Copy value, but quote single quotes for /bin/sh
+ * Backslash quote is ignored inside single quotes so
+ * have to put those outside of the single quotes.
+ * (The parm+1000 nonsense is to protect against
+ * malicious mail trying to overflow our buffer.)
+ *
+ * TCH - Change 2/8/1999
+ * Also quote the ` to prevent execution of arbitrary code
+ */
+ for(p = parm; *p && p < parm+1000; p++){
+ if((*p == '\'') || (*p == '`')){
+ if(to-tmp_20k_buf+4 < SIZEOF_20KBUF){
+ *to++ = '\''; /* closing quote */
+ *to++ = '\\';
+ *to++ = *p; /* quoted character */
+ *to++ = '\''; /* opening quote */
+ }
+ }
+ else if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = *p;
+ }
+
+ fs_give((void **) &parm);
+ }
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\''; /* closing quote for /bin/sh */
+
+ *s = '}'; /* restore */
+ from = s;
+ break;
+
+ /*
+ * %n and %F are used by metamail to support otherwise
+ * unrecognized multipart Content-Types. Pine does
+ * not use these since we're only dealing with the individual
+ * parts at this point.
+ */
+ case 'n':
+ case 'F':
+ default:
+ dprint((9,
+ "Ignoring %s format code in mailcap file: %%%c\n",
+ (*from == 'n' || *from == 'F') ? "unimplemented"
+ : "unrecognized",
+ *from));
+ break;
+ }
+ }
+ else if(*from == '%') /* next char is special */
+ prefixed = 1;
+ else if(to-tmp_20k_buf < SIZEOF_20KBUF) /* regular character, just copy */
+ *to++ = *from;
+ }
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to = '\0';
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /*
+ * file not specified, redirect to stdin
+ */
+ if(!used_tmp_file && tmp_file)
+ snprintf(to, SIZEOF_20KBUF-(to-tmp_20k_buf), MC_ADD_TMP, tmp_file);
+
+ return(cpystr(tmp_20k_buf));
+}
+
+
+/*
+ *
+ */
+MailcapEntry *
+mc_new_entry(void)
+{
+ MailcapEntry *mc = (MailcapEntry *) fs_get(sizeof(MailcapEntry));
+ memset(mc, 0, sizeof(MailcapEntry));
+ *MailcapData.tail = mc;
+ MailcapData.tail = &mc->next;
+ return(mc);
+}
+
+
+/*
+ * Free a list of mailcap entries
+ */
+void
+mc_free_entry(MailcapEntry **mc)
+{
+ if(mc && *mc){
+ mc_free_entry(&(*mc)->next);
+ fs_give((void **) mc);
+ }
+}
+
+
+void
+mailcap_free(void)
+{
+ mail_free_stringlist(&MailcapData.raw);
+ mc_free_entry(&MailcapData.head);
+}
diff --git a/pith/mailcap.h b/pith/mailcap.h
new file mode 100644
index 00000000..25ccd115
--- /dev/null
+++ b/pith/mailcap.h
@@ -0,0 +1,40 @@
+/*
+ * $Id: mailcap.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILCAP_INCLUDED
+#define PITH_MAILCAP_INCLUDED
+
+
+typedef struct mcap_cmd {
+ char *command; /* command to execute */
+ int special_handling; /* special os handling */
+} MCAP_CMD_S;
+
+
+/* exported protoypes */
+char *mc_conf_path(char *, char *, char *, int, char *);
+int mailcap_can_display(int, char *, BODY *, int);
+MCAP_CMD_S *mailcap_build_command(int, char *, BODY *, char *, int *, int);
+void mailcap_free(void);
+
+/* currently mandatory to implement stubs */
+
+/* return exit status of test command */
+int exec_mailcap_test_cmd(char *);
+
+
+
+#endif /* PITH_MAILCAP_INCLUDED */
diff --git a/pith/mailcmd.c b/pith/mailcmd.c
new file mode 100644
index 00000000..78ba98ad
--- /dev/null
+++ b/pith/mailcmd.c
@@ -0,0 +1,2741 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailcmd.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mailcmd.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/flag.h"
+#include "../pith/thread.h"
+#include "../pith/util.h"
+#include "../pith/folder.h"
+#include "../pith/sort.h"
+#include "../pith/newmail.h"
+#include "../pith/mailview.h"
+#include "../pith/mailindx.h"
+#include "../pith/save.h"
+#include "../pith/news.h"
+#include "../pith/sequence.h"
+#include "../pith/stream.h"
+#include "../pith/ldap.h"
+#include "../pith/options.h"
+#include "../pith/busy.h"
+#include "../pith/icache.h"
+#include "../pith/ablookup.h"
+#include "../pith/search.h"
+#include "../pith/charconv/utf8.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * optional function hooks
+ */
+int (*pith_opt_read_msg_prompt)(long, char *);
+int (*pith_opt_reopen_folder)(struct pine *, int *);
+int (*pith_opt_expunge_prompt)(MAILSTREAM *, char *, long);
+void (*pith_opt_begin_closing)(int, char *);
+void get_new_message_count(MAILSTREAM *, int, long *, long *);
+char *new_messages_string(MAILSTREAM *);
+void search_for_our_regex_addresses(MAILSTREAM *stream, char type,
+ int not, SEARCHSET *searchset);
+
+
+
+/*----------------------------------------------------------------------
+ Complain about command on empty folder
+
+ Args: map -- msgmap
+ type -- type of message that's missing
+ cmd -- string explaining command attempted
+
+ ----*/
+int
+any_messages(MSGNO_S *map, char *type, char *cmd)
+{
+ if(mn_get_total(map) <= 0L){
+ q_status_message5(SM_ORDER, 0, 2, "No %s%s%s%s%s",
+ type ? type : "",
+ type ? " " : "",
+ THRD_INDX() ? "threads" : "messages",
+ (!cmd || *cmd != '.') ? " " : "",
+ cmd ? cmd : "in folder");
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
+
+/*----------------------------------------------------------------------
+ test whether or not we have a valid stream to set flags on
+
+ Args: state -- pine state containing vital signs
+ cmd -- string explaining command attempted
+ permflag -- associated permanent flag state
+
+ Result: returns 1 if we can set flags, otw 0 and complains
+
+ ----*/
+int
+can_set_flag(struct pine *state, char *cmd, int permflag)
+{
+ if((!permflag && READONLY_FOLDER(state->mail_stream))
+ || sp_dead_stream(state->mail_stream)){
+ q_status_message2(SM_ORDER | (sp_dead_stream(state->mail_stream)
+ ? SM_DING : 0),
+ 0, 3,
+ "Can't %s message. Folder is %s.", cmd,
+ (sp_dead_stream(state->mail_stream)) ? "closed" : "read-only");
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
+
+/*----------------------------------------------------------------------
+ Complain about command on empty folder
+
+ Args: type -- type of message that's missing
+ cmd -- string explaining command attempted
+
+ ----*/
+void
+cmd_cancelled(char *cmd)
+{
+ /* TRANSLATORS: Arg is replaced with the command name or the word Command */
+ q_status_message1(SM_INFO, 0, 2, _("%s cancelled"), cmd ? cmd : _("Command"));
+}
+
+
+/*----------------------------------------------------------------------
+ Execute DELETE message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+
+ Result: with side effect of "current" message delete flag set
+
+ ----*/
+int
+cmd_delete(struct pine *state, MSGNO_S *msgmap, int copts,
+ char *(*cmd_action_f)(struct pine *, MSGNO_S *))
+{
+ int lastmsg, rv = 0;
+ long msgno, del_count = 0L, new;
+ char *sequence = NULL, prompt[128];
+
+ dprint((4, "\n - delete message -\n"));
+ if(!(any_messages(msgmap, NULL, "to Delete")
+ && can_set_flag(state, "delete", state->mail_stream->perm_deleted)))
+ return rv;
+
+ rv++;
+
+ if(sp_io_error_on_stream(state->mail_stream)){
+ sp_set_io_error_on_stream(state->mail_stream, 0);
+ pine_mail_check(state->mail_stream); /* forces write */
+ }
+
+ if(MCMD_ISAGG(copts)){
+ sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 0);
+ snprintf(prompt, sizeof(prompt), "%ld%s message%s marked for deletion",
+ del_count, (copts & MCMD_AGG_2) ? "" : " selected", plural(del_count));
+ }
+ else{
+ long rawno;
+
+ msgno = mn_get_cur(msgmap);
+ rawno = mn_m2raw(msgmap, msgno);
+ del_count = 1L; /* return current */
+ sequence = cpystr(long2string(rawno));
+ lastmsg = (msgno >= mn_get_total(msgmap));
+ snprintf(prompt, sizeof(prompt), "%s%s marked for deletion",
+ lastmsg ? "Last message" : "Message ",
+ lastmsg ? "" : long2string(msgno));
+ }
+
+ dprint((3, "DELETE: msg %s\n", sequence ? sequence : "?"));
+ new = sp_new_mail_count(state->mail_stream);
+ mail_flag(state->mail_stream, sequence, "\\DELETED", ST_SET);
+ fs_give((void **) &sequence);
+ if(new != sp_new_mail_count(state->mail_stream))
+ process_filter_patterns(state->mail_stream, state->msgmap,
+ sp_new_mail_count(state->mail_stream));
+
+ if(cmd_action_f){
+ char *rv;
+
+ if((rv = (*cmd_action_f)(state, msgmap)) != NULL)
+ strncat(prompt, rv, sizeof(prompt) - strlen(prompt)- 1);
+ }
+
+ if(!(copts & MCMD_SILENT))
+ q_status_message(SM_ORDER, 0, 3, prompt);
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Execute UNDELETE message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+
+ Result: with side effect of "current" message delete flag UNset
+
+ ----*/
+int
+cmd_undelete(struct pine *state, MSGNO_S *msgmap, int copts)
+{
+ long del_count;
+ char *sequence;
+ int wasdeleted = FALSE, rv = 0;
+ MESSAGECACHE *mc;
+
+ dprint((4, "\n - undelete -\n"));
+ if(!(any_messages(msgmap, NULL, "to Undelete")
+ && can_set_flag(state, "undelete", state->mail_stream->perm_deleted)))
+ return rv;
+
+ rv++;
+
+ if(MCMD_ISAGG(copts)){
+ del_count = 0L; /* return current */
+ sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 1);
+ wasdeleted = TRUE;
+ }
+ else{
+ long rawno;
+ int exbits = 0;
+
+ del_count = 1L; /* return current */
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ sequence = cpystr(long2string(rawno));
+ wasdeleted = (state->mail_stream
+ && rawno > 0L && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))
+ && mc->valid
+ && mc->deleted);
+ /*
+ * Mark this message manually flagged so we don't re-filter it
+ * with a filter which only sets flags.
+ */
+ if(msgno_exceptions(state->mail_stream, rawno, "0", &exbits, FALSE))
+ exbits |= MSG_EX_MANUNDEL;
+ else
+ exbits = MSG_EX_MANUNDEL;
+
+ msgno_exceptions(state->mail_stream, rawno, "0", &exbits, TRUE);
+ }
+
+ dprint((3, "UNDELETE: msg %s\n", sequence ? sequence : "?"));
+
+ mail_flag(state->mail_stream, sequence, "\\DELETED", 0L);
+ fs_give((void **) &sequence);
+
+ if((copts & MCMD_SILENT) == 0){
+ if(del_count == 1L && MCMD_ISAGG(copts) == 0){
+ q_status_message(SM_ORDER, 0, 3,
+ wasdeleted
+ ? _("Deletion mark removed, message won't be deleted")
+ : _("Message not marked for deletion; no action taken"));
+ }
+ else
+ q_status_message2(SM_ORDER, 0, 3,
+ _("Deletion mark removed from %s message%s"),
+ comatose(del_count), plural(del_count));
+ }
+
+ if(sp_io_error_on_stream(state->mail_stream)){
+ sp_set_io_error_on_stream(state->mail_stream, 0);
+ pine_mail_check(state->mail_stream); /* forces write */
+ }
+
+ return rv;
+}
+
+
+int
+cmd_expunge_work(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long old_max_msgno;
+ int rv = 0;
+
+ old_max_msgno = mn_get_total(msgmap);
+ delete_filtered_msgs(stream);
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(stream);
+ ps_global->expunge_in_progress = 0;
+
+ dprint((2,"expunge complete cur:%ld max:%ld\n",
+ mn_get_cur(msgmap), mn_get_total(msgmap)));
+ /*
+ * This is only actually necessary if this causes the width of the
+ * message number field to change. That is, it depends on the
+ * format the user is using as well as on the max_msgno. Since it
+ * should be rare, we'll just do it whenever it happens.
+ * Also have to check for an increase in max_msgno on new mail.
+ */
+ if((old_max_msgno >= 1000L && mn_get_total(msgmap) < 1000L)
+ || (old_max_msgno >= 10000L && mn_get_total(msgmap) < 10000L)
+ || (old_max_msgno >= 100000L && mn_get_total(msgmap) < 100000L)){
+ clear_index_cache(stream, 0);
+ rv = 1;
+ }
+
+ /*
+ * mm_exists and mm_expunge take care of updating max_msgno
+ * and selecting a new message should the selected get removed
+ */
+
+
+ reset_check_point(stream);
+
+ return(rv);
+}
+
+
+CONTEXT_S *
+broach_get_folder(CONTEXT_S *context, int *inbox, char **folder)
+{
+ CONTEXT_S *tc;
+
+ if(ps_global->goto_default_rule == GOTO_LAST_FLDR){
+ tc = context ? context : ps_global->context_current;
+ *inbox = 1; /* fill in last_folder below */
+ }
+ else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN){
+ tc = (ps_global->context_list->use & CNTXT_INCMNG)
+ ? ps_global->context_list->next : ps_global->context_list;
+ ps_global->last_unambig_folder[0] = '\0';
+ *inbox = 1; /* fill in last_folder below */
+ }
+ else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX){
+ tc = (ps_global->context_list->use & CNTXT_INCMNG)
+ ? ps_global->context_list->next : ps_global->context_list;
+ tc->last_folder[0] = '\0';
+ *inbox = 0;
+ ps_global->last_unambig_folder[0] = '\0';
+ }
+ else{
+ *inbox = (ps_global->cur_folder
+ && ps_global->inbox_name
+ && strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0
+ && (!ps_global->context_current
+ || ps_global->context_current->use & CNTXT_INCMNG
+ || (!(ps_global->context_list->use & CNTXT_INCMNG)
+ && ps_global->context_current == ps_global->context_list)));
+ if(!*inbox)
+ tc = ps_global->context_list; /* inbox's context */
+ else if(ps_global->goto_default_rule == GOTO_INBOX_FIRST_CLCTN){
+ tc = (ps_global->context_list->use & CNTXT_INCMNG)
+ ? ps_global->context_list->next : ps_global->context_list;
+ ps_global->last_unambig_folder[0] = '\0';
+ }
+ else
+ tc = context ? context : ps_global->context_current;
+ }
+
+ if(folder){
+ if(!*inbox){
+ *folder = ps_global->inbox_name;
+ }
+ else
+ *folder = (ps_global->last_unambig_folder[0])
+ ? ps_global->last_unambig_folder
+ : ((tc->last_folder[0]) ? tc->last_folder : NULL);
+ }
+
+ return(tc);
+}
+
+
+/*----------------------------------------------------------------------
+ Actually attempt to open given folder
+
+ Args: newfolder -- The folder name to open
+ streamp -- Candidate stream for recycling. This stream will either
+ be re-used, or it will be closed.
+
+ Result: 1 if the folder was successfully opened
+ 0 if the folder open failed and went back to old folder
+ -1 if open failed and no folder is left open
+
+ Attempt to open the folder name given. If the open of the new folder
+ fails then the previously open folder will remain open, unless
+ something really bad has happened. The designate inbox will always be
+ kept open, and when a request to open it is made the already open
+ stream will be used.
+ ----*/
+int
+do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp,
+ long unsigned int flags)
+{
+ MAILSTREAM *m, *strm, *stream = streamp ? *streamp : NULL;
+ int open_inbox, rv, old_tros, we_cancel = 0,
+ do_reopen = 0, n, was_dead = 0, cur_already_set = 0;
+ char expanded_file[MAX(MAXPATH,MAILTMPLEN)+1],
+ *old_folder, *old_path, *p, *report;
+ unsigned char *fname;
+ long openmode, rflags = 0L, pc = 0L, cur, raw;
+ ENVELOPE *env = NULL;
+ char status_msg[81];
+ SortOrder old_sort;
+ unsigned perfolder_startup_rule;
+ char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN], *lname, *mname;
+
+ openmode = SP_USERFLDR;
+
+ dprint((1, "About to open folder \"%s\" inbox is: \"%s\"\n",
+ newfolder ? newfolder : "?",
+ ps_global->inbox_name ? ps_global->inbox_name : "?"));
+
+ /*
+ *--- Set flag that we're opening the inbox, a special case.
+ *
+ * We want to know if inbox is being opened either by name OR
+ * fully qualified path...
+ */
+ if(strucmp(newfolder, ps_global->inbox_name) == 0)
+ open_inbox = (flags & DB_INBOXWOCNTXT || new_context == ps_global->context_list);
+ else{
+ open_inbox = (strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0
+ || same_remote_mailboxes(newfolder, ps_global->VAR_INBOX_PATH)
+ || (!IS_REMOTE(newfolder)
+ && (lname=mailboxfile(tmp1,newfolder))
+ && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
+ && !strcmp(lname,mname)));
+
+ /* further checking for inbox open */
+ if(!open_inbox && new_context && context_isambig(newfolder)){
+ if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
+ /*
+ * Check for an incoming folder other
+ * than INBOX that also point to INBOX.
+ */
+ open_inbox = (strucmp(p, ps_global->inbox_name) == 0
+ || strcmp(p, ps_global->VAR_INBOX_PATH) == 0
+ || same_remote_mailboxes(p, ps_global->VAR_INBOX_PATH)
+ || (!IS_REMOTE(p)
+ && (lname=mailboxfile(tmp1,p))
+ && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
+ && !strcmp(lname,mname)));
+ }
+ else if(!(new_context->use & CNTXT_INCMNG)){
+ char tmp3[MAILTMPLEN];
+
+ /*
+ * Check to see if we are opening INBOX using the folder name
+ * and a context. We won't have recognized this is the
+ * same as INBOX without applying the context first.
+ */
+ context_apply(tmp3, new_context, newfolder, sizeof(tmp3));
+ open_inbox = (strucmp(tmp3, ps_global->inbox_name) == 0
+ || strcmp(tmp3, ps_global->VAR_INBOX_PATH) == 0
+ || same_remote_mailboxes(tmp3, ps_global->VAR_INBOX_PATH)
+ || (!IS_REMOTE(tmp3)
+ && (lname=mailboxfile(tmp1,tmp3))
+ && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
+ && !strcmp(lname,mname)));
+ }
+ }
+ }
+
+ if(open_inbox)
+ new_context = ps_global->context_list; /* restore first context */
+
+ was_dead = sp_a_locked_stream_is_dead();
+
+ /*----- Little to do to if reopening same folder -----*/
+ if(new_context == ps_global->context_current && ps_global->mail_stream
+ && (strcmp(newfolder, ps_global->cur_folder) == 0
+ || (open_inbox && sp_flagged(ps_global->mail_stream, SP_INBOX)))){
+ if(stream){
+ pine_mail_close(stream); /* don't need it */
+ stream = NULL;
+ }
+
+ if(sp_dead_stream(ps_global->mail_stream))
+ do_reopen++;
+
+ /*
+ * If it is a stream which could probably discover newmail by
+ * reopening and user has YES set for those streams, or it
+ * is a stream which may discover newmail by reopening and
+ * user has YES set for those stream, then do_reopen.
+ */
+ if(!do_reopen
+ &&
+ (((ps_global->mail_stream->dtb
+ && ((ps_global->mail_stream->dtb->flags & DR_NONEWMAIL)
+ || (ps_global->mail_stream->rdonly
+ && ps_global->mail_stream->dtb->flags
+ & DR_NONEWMAILRONLY)))
+ && (ps_global->reopen_rule == REOPEN_YES_YES
+ || ps_global->reopen_rule == REOPEN_YES_ASK_Y
+ || ps_global->reopen_rule == REOPEN_YES_ASK_N
+ || ps_global->reopen_rule == REOPEN_YES_NO))
+ ||
+ ((ps_global->mail_stream->dtb
+ && ps_global->mail_stream->rdonly
+ && !(ps_global->mail_stream->dtb->flags & DR_LOCAL))
+ && (ps_global->reopen_rule == REOPEN_YES_YES))))
+ do_reopen++;
+
+ /*
+ * If it is a stream which could probably discover newmail by
+ * reopening and user has ASK set for those streams, or it
+ * is a stream which may discover newmail by reopening and
+ * user has ASK set for those stream, then ask.
+ */
+ if(!do_reopen
+ && pith_opt_reopen_folder
+ && (*pith_opt_reopen_folder)(ps_global, &do_reopen) < 0){
+ cmd_cancelled(NULL);
+ return(0);
+ }
+
+ if(do_reopen){
+ /*
+ * If it's not healthy or if the user explicitly wants to
+ * do a reopen, we reset things and fall thru
+ * to actually reopen it.
+ */
+ if(sp_dead_stream(ps_global->mail_stream)){
+ dprint((2, "Stream was dead, reopening \"%s\"\n",
+ newfolder ? newfolder : "?"));
+ }
+
+ /* clean up */
+ pine_mail_actually_close(ps_global->mail_stream);
+ ps_global->mangled_header = 1;
+ clear_index_cache(ps_global->mail_stream, 0);
+ }
+ else{
+ if(!(flags & DB_NOVISIT))
+ sp_set_recent_since_visited(ps_global->mail_stream, 0L);
+
+ return(1); /* successful open of same folder! */
+ }
+ }
+
+ /*
+ * If ambiguous foldername (not fully qualified), make sure it's
+ * not a nickname for a folder in the given context...
+ */
+
+ /* might get reset below */
+ strncpy(expanded_file, newfolder, sizeof(expanded_file));
+ expanded_file[sizeof(expanded_file)-1] = '\0';
+
+ if(!open_inbox && new_context && context_isambig(newfolder)){
+ if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
+ strncpy(expanded_file, p, sizeof(expanded_file));
+ expanded_file[sizeof(expanded_file)-1] = '\0';
+ dprint((2, "broach_folder: nickname for %s is %s\n",
+ expanded_file ? expanded_file : "?",
+ newfolder ? newfolder : "?"));
+ }
+ else if((new_context->use & CNTXT_INCMNG)
+ && (folder_index(newfolder, new_context, FI_FOLDER) < 0)
+ && !is_absolute_path(newfolder)){
+ fname = folder_name_decoded((unsigned char *)newfolder);
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Can't find Incoming Folder %s."), fname ? (char *) fname : newfolder);
+ if(stream)
+ pine_mail_close(stream);
+
+ if(fname)
+ fs_give((void **)&fname);
+
+ return(0);
+ }
+ }
+
+ /*--- Opening inbox, inbox has been already opened, the easy case ---*/
+ /*
+ * [ It is probably true that we could eliminate most of this special ]
+ * [ inbox stuff and just get the inbox stream back when we do the ]
+ * [ context_open below, but figuring that out hasn't been done. ]
+ */
+ if(open_inbox && (strm=sp_inbox_stream())){
+ if(sp_dead_stream(strm)){
+ /*
+ * if dead INBOX, just close it and let it be reopened.
+ * This is different from the do_reopen case above,
+ * because we're going from another open mail folder to the
+ * dead INBOX.
+ */
+ dprint((2, "INBOX was dead, closing before reopening\n"));
+ pine_mail_actually_close(strm);
+ }
+ else{
+ /*
+ * Clean up the mail_stream we're leaving.
+ */
+ if(ps_global->mail_stream
+ && (!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ || (sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && F_ON(F_EXPUNGE_INBOX, ps_global))
+ || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ && F_ON(F_EXPUNGE_STAYOPENS, ps_global))))
+ expunge_and_close(ps_global->mail_stream, NULL,
+ sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ ? EC_NO_CLOSE : EC_NONE);
+ else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
+ /*
+ * We want to save our position in the folder so that when we
+ * come back to this folder again, we can place the cursor on
+ * a reasonable message number.
+ */
+ sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
+
+ if(ps_global->mail_stream->nmsgs > 0L){
+ cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
+ raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
+ if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
+ env = pine_mail_fetchstructure(ps_global->mail_stream,
+ raw, NULL);
+
+ if(env && env->message_id && env->message_id[0])
+ sp_set_saved_cur_msg_id(ps_global->mail_stream,
+ env->message_id);
+ }
+ }
+
+ /*
+ * Make the already open inbox the current mailbox.
+ */
+ ps_global->mail_stream = strm;
+ ps_global->msgmap = sp_msgmap(strm);
+
+ if(was_dead && pith_opt_icon_text)
+ (*pith_opt_icon_text)(NULL, IT_MCLOSED);
+
+ dprint((7, "%ld %ld %x\n",
+ mn_get_cur(ps_global->msgmap),
+ mn_get_total(ps_global->msgmap),
+ ps_global->mail_stream));
+ /*
+ * remember last context and folder
+ */
+ if(context_isambig(ps_global->cur_folder)){
+ ps_global->context_last = ps_global->context_current;
+ snprintf(ps_global->context_current->last_folder,
+ sizeof(ps_global->context_current->last_folder),
+ "%s", ps_global->cur_folder);
+ ps_global->last_unambig_folder[0] = '\0';
+ }
+ else{
+ ps_global->context_last = NULL;
+ snprintf(ps_global->last_unambig_folder,
+ sizeof(ps_global->last_unambig_folder),
+ "%s", ps_global->cur_folder);
+ }
+
+ p = sp_fldr(ps_global->mail_stream) ? sp_fldr(ps_global->mail_stream)
+ : ps_global->inbox_name;
+ strncpy(ps_global->cur_folder, p, sizeof(ps_global->cur_folder)-1);
+ ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
+ ps_global->context_current = ps_global->context_list;
+ reset_index_format();
+ clear_index_cache(ps_global->mail_stream, 0);
+ /* MUST sort before restoring msgno! */
+ refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
+ report = new_messages_string(ps_global->mail_stream);
+ q_status_message3(SM_ORDER, 0, 3,
+ (mn_get_total(ps_global->msgmap) > 1)
+ ? _("Opened folder \"%s\" with %s messages%s")
+ : _("Opened folder \"%s\" with %s message%s"),
+ ps_global->inbox_name,
+ long2string(mn_get_total(ps_global->msgmap)),
+ report ? report : "");
+ if(report)
+ fs_give((void **)&report);
+
+#ifdef _WINDOWS
+ mswin_settitle(ps_global->inbox_name);
+#endif
+ if(stream)
+ pine_mail_close(stream);
+
+ if(!(flags & DB_NOVISIT))
+ sp_set_recent_since_visited(ps_global->mail_stream, 0L);
+
+ return(1);
+ }
+ }
+
+ if(!new_context && !expand_foldername(expanded_file,sizeof(expanded_file))){
+ if(stream)
+ pine_mail_close(stream);
+
+ return(0);
+ }
+
+ /*
+ * This is a safe time to clean up dead streams because nothing should
+ * be referencing them right now.
+ */
+ sp_cleanup_dead_streams();
+
+ old_folder = NULL;
+ old_path = NULL;
+ old_sort = SortArrival; /* old sort */
+ old_tros = 0; /* old reverse sort ? */
+ /*---- now close the old one we had open if there was one ----*/
+ if(ps_global->mail_stream != NULL){
+ old_folder = cpystr(ps_global->cur_folder);
+ old_path = cpystr(ps_global->mail_stream->original_mailbox
+ ? ps_global->mail_stream->original_mailbox
+ : ps_global->mail_stream->mailbox);
+ old_sort = mn_get_sort(ps_global->msgmap);
+ old_tros = mn_get_revsort(ps_global->msgmap);
+ if(!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ || (sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && F_ON(F_EXPUNGE_INBOX, ps_global))
+ || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ && F_ON(F_EXPUNGE_STAYOPENS, ps_global)))
+ expunge_and_close(ps_global->mail_stream, NULL,
+ sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
+ ? EC_NO_CLOSE : EC_NONE);
+ else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
+ && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
+ /*
+ * We want to save our position in the folder so that when we
+ * come back to this folder again, we can place the cursor on
+ * a reasonable message number.
+ */
+
+ sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
+
+ if(ps_global->mail_stream->nmsgs > 0L){
+ cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
+ raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
+ if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
+ env = pine_mail_fetchstructure(ps_global->mail_stream,
+ raw, NULL);
+
+ if(env && env->message_id && env->message_id[0])
+ sp_set_saved_cur_msg_id(ps_global->mail_stream,
+ env->message_id);
+ }
+ }
+
+ ps_global->mail_stream = NULL;
+ }
+
+ snprintf(status_msg, sizeof(status_msg), "%sOpening \"", do_reopen ? "Re-" : "");
+ fname = folder_name_decoded((unsigned char *)newfolder);
+ strncat(status_msg, pretty_fn(fname ? (char*) fname : newfolder),
+ sizeof(status_msg)-strlen(status_msg) - 2);
+ if(fname) fs_give((void **)&fname);
+ status_msg[sizeof(status_msg)-2] = '\0';
+ strncat(status_msg, "\"", sizeof(status_msg)-strlen(status_msg) - 1);
+ status_msg[sizeof(status_msg)-1] = '\0';
+ we_cancel = busy_cue(status_msg, NULL, 0);
+
+ /*
+ * if requested, make access to folder readonly (only once)
+ */
+ if(ps_global->open_readonly_on_startup){
+ openmode |= OP_READONLY;
+ ps_global->open_readonly_on_startup = 0;
+ }
+
+ if(!(flags & DB_NOVISIT))
+ ps_global->first_open_was_attempted = 1;
+
+ openmode |= SP_USEPOOL;
+
+ if(stream)
+ sp_set_first_unseen(stream, 0L);
+
+ /* in case we closed the old stream by cancelling the connection, do
+ * not let that interfere with opening the new stream.
+ */
+ ps_global->user_says_cancel = 0;
+
+ m = context_open((new_context && !open_inbox) ? new_context : NULL,
+ stream,
+ open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
+ openmode | (open_inbox ? SP_INBOX : 0),
+ &rflags);
+
+ /*
+ * We aren't in a situation where we want a single cancel to
+ * apply to multiple opens.
+ */
+ ps_global->user_says_cancel = 0;
+
+ if(streamp)
+ *streamp = m;
+
+
+ dprint((8, "Opened folder %p \"%s\" (context: \"%s\")\n",
+ m, (m && m->mailbox) ? m->mailbox : "nil",
+ (new_context && new_context->context)
+ ? new_context->context : "nil"));
+
+
+ /* Can get m != NULL if correct passwd for remote, but wrong name */
+ if(m == NULL || m->halfopen){
+ /*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
+ /* fall back to currently open mailbox */
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ ps_global->mail_stream = NULL;
+ if(m)
+ pine_mail_actually_close(m);
+
+ rv = 0;
+ dprint((8, "Old folder: \"%s\"\n",
+ old_folder == NULL ? "" : old_folder));
+ if(old_folder != NULL){
+ if(strcmp(old_folder, ps_global->inbox_name) == 0){
+ ps_global->mail_stream = sp_inbox_stream();
+ ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
+
+ dprint((8, "Reactivate inbox %ld %ld %p\n",
+ mn_get_cur(ps_global->msgmap),
+ mn_get_total(ps_global->msgmap),
+ ps_global->mail_stream));
+ p = sp_fldr(ps_global->mail_stream)
+ ? sp_fldr(ps_global->mail_stream)
+ : ps_global->inbox_name;
+ strncpy(ps_global->cur_folder, p,
+ sizeof(ps_global->cur_folder)-1);
+ ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
+ }
+ else{
+ ps_global->mail_stream = pine_mail_open(NULL, old_path,
+ openmode, &rflags);
+ /* mm_log will take care of error message here */
+ if(ps_global->mail_stream == NULL){
+ rv = -1;
+ }
+ else{
+ ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
+ mn_set_sort(ps_global->msgmap, old_sort);
+ mn_set_revsort(ps_global->msgmap, old_tros);
+ ps_global->mangled_header = 1;
+ reset_index_format();
+ clear_index_cache(ps_global->mail_stream, 0);
+
+ if(!(rflags & SP_MATCH)){
+ sp_set_expunge_count(ps_global->mail_stream, 0L);
+ sp_set_new_mail_count(ps_global->mail_stream, 0L);
+ sp_set_dead_stream(ps_global->mail_stream, 0);
+ sp_set_noticed_dead_stream(ps_global->mail_stream, 0);
+
+ reset_check_point(ps_global->mail_stream);
+ if(IS_NEWS(ps_global->mail_stream)
+ && ps_global->mail_stream->rdonly)
+ msgno_exclude_deleted(ps_global->mail_stream,
+ sp_msgmap(ps_global->mail_stream));
+
+ if(mn_get_total(ps_global->msgmap) > 0)
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_NONE,
+ ps_global->mail_stream,
+ 0L,
+ THREADING()
+ ? 0 : FSF_SKIP_CHID));
+
+ if(!(mn_get_sort(ps_global->msgmap) == SortArrival
+ && !mn_get_revsort(ps_global->msgmap)))
+ refresh_sort(ps_global->mail_stream,
+ ps_global->msgmap, SRT_NON);
+ }
+
+ fname = folder_name_decoded((unsigned char *)old_folder);
+ q_status_message1(SM_ORDER, 0, 3,
+ "Folder \"%s\" reopened", fname ? (char *)fname : old_folder);
+ if(fname)
+ fs_give((void **)&fname);
+ }
+ }
+
+ if(rv == 0)
+ mn_set_cur(ps_global->msgmap,
+ MIN(mn_get_cur(ps_global->msgmap),
+ mn_get_total(ps_global->msgmap)));
+
+ fs_give((void **)&old_folder);
+ fs_give((void **)&old_path);
+ }
+ else
+ rv = -1;
+
+ if(rv == -1){
+ q_status_message(SM_ORDER | SM_DING, 0, 4, _("No folder opened"));
+ mn_set_total(ps_global->msgmap, 0L);
+ mn_set_nmsgs(ps_global->msgmap, 0L);
+ mn_set_cur(ps_global->msgmap, -1L);
+ ps_global->cur_folder[0] = '\0';
+ }
+
+ if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
+ (*pith_opt_icon_text)(NULL, IT_MCLOSED);
+
+ if(ps_global->mail_stream && !(flags & DB_NOVISIT))
+ sp_set_recent_since_visited(ps_global->mail_stream, 0L);
+
+ if(ps_global->mail_stream)
+ sp_set_first_unseen(ps_global->mail_stream, 0L);
+
+ return(rv);
+ }
+ else{
+ if(old_folder != NULL){
+ fs_give((void **)&old_folder);
+ fs_give((void **)&old_path);
+ }
+ }
+
+ update_folder_unseen_by_stream(m, UFU_NONE);
+
+ /*----- success in opening the new folder ----*/
+ dprint((2, "Opened folder \"%s\" with %ld messages\n",
+ m->mailbox ? m->mailbox : "?", m->nmsgs));
+
+
+ /*--- A Little house keeping ---*/
+
+ ps_global->mail_stream = m;
+ if(!(flags & DB_NOVISIT))
+ sp_set_recent_since_visited(ps_global->mail_stream, 0L);
+
+ ps_global->msgmap = sp_msgmap(m);
+ if(!(rflags & SP_MATCH)){
+ sp_set_expunge_count(m, 0L);
+ sp_set_new_mail_count(m, 0L);
+ sp_set_dead_stream(m, 0);
+ sp_set_noticed_dead_stream(m, 0);
+ sp_set_mail_box_changed(m, 0);
+ reset_check_point(m);
+ }
+
+ if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
+ (*pith_opt_icon_text)(NULL, IT_MCLOSED);
+
+ ps_global->last_unambig_folder[0] = '\0';
+
+ /*
+ * remember old folder and context...
+ */
+ if(context_isambig(ps_global->cur_folder)){
+ ps_global->context_last = ps_global->context_current;
+ snprintf(ps_global->context_current->last_folder,
+ sizeof(ps_global->context_current->last_folder),
+ "%s", ps_global->cur_folder);
+ ps_global->last_unambig_folder[0] = '\0';
+ }
+ else{
+ ps_global->context_last = NULL;
+ snprintf(ps_global->last_unambig_folder,
+ sizeof(ps_global->last_unambig_folder),
+ "%s", ps_global->cur_folder);
+ }
+
+ /* folder in a subdir of context? */
+ if(ps_global->context_current->dir->prev)
+ snprintf(ps_global->cur_folder, sizeof(ps_global->cur_folder), "%s%s",
+ ps_global->context_current->dir->ref, newfolder);
+ else{
+ strncpy(ps_global->cur_folder,
+ (open_inbox) ? ps_global->inbox_name : newfolder,
+ sizeof(ps_global->cur_folder)-1);
+ ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
+ }
+
+ sp_set_fldr(ps_global->mail_stream, ps_global->cur_folder);
+
+ if(new_context){
+ ps_global->context_last = ps_global->context_current;
+ ps_global->context_current = new_context;
+
+ if(!open_inbox)
+ sp_set_context(ps_global->mail_stream, ps_global->context_current);
+ }
+
+ clear_index_cache(ps_global->mail_stream, 0);
+ reset_index_format();
+
+ /*
+ * Start news reading with messages the user's marked deleted
+ * hidden from view...
+ */
+ if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
+ msgno_exclude_deleted(ps_global->mail_stream, ps_global->msgmap);
+
+ if(we_cancel && F_OFF(F_QUELL_FILTER_MSGS, ps_global))
+ cancel_busy_cue(0);
+
+ /*
+ * If the stream we got from the open above was already opened earlier
+ * for some temporary use, then it wouldn't have been filtered. That's
+ * why we need this flag, so that we will filter if needed.
+ */
+ if(!sp_flagged(ps_global->mail_stream, SP_FILTERED))
+ process_filter_patterns(ps_global->mail_stream, ps_global->msgmap, 0L);
+
+ /*
+ * If no filtering messages wait until here to cancel the busy cue
+ * because the user will be waiting for that filtering with nothing
+ * showing the activity otherwise.
+ */
+ if(we_cancel && F_ON(F_QUELL_FILTER_MSGS, ps_global))
+ cancel_busy_cue(0);
+
+ if(!(rflags & SP_MATCH) || !(rflags & SP_LOCKED))
+ reset_sort_order(SRT_VRB);
+ else if(sp_new_mail_count(ps_global->mail_stream) > 0L
+ || sp_unsorted_newmail(ps_global->mail_stream)
+ || sp_need_to_rethread(ps_global->mail_stream))
+ refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
+
+ report = new_messages_string(ps_global->mail_stream);
+ fname = folder_name_decoded((unsigned char *)newfolder);
+ q_status_message7(SM_ORDER, 0, 4,
+ "%s \"%s\" opened with %s message%s%s%s%s",
+ IS_NEWS(ps_global->mail_stream)
+ ? "News group" : "Folder",
+ open_inbox ? pretty_fn(fname ? (char *) fname : newfolder)
+ : (fname ? (char *)fname : newfolder),
+ comatose(mn_get_total(ps_global->msgmap)),
+ plural(mn_get_total(ps_global->msgmap)),
+ (!open_inbox
+ && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED))
+ ? " (StayOpen)" : "",
+ READONLY_FOLDER(ps_global->mail_stream)
+ ? " READONLY" : "",
+ report ? report : "");
+
+ if(fname)
+ fs_give((void **)&fname);
+
+ if(report)
+ fs_give((void **)&report);
+
+#ifdef _WINDOWS
+ mswin_settitle(pretty_fn(newfolder));
+#endif
+ /*
+ * Set current message number when re-opening Stay-Open or
+ * cached folders.
+ */
+ if(rflags & SP_MATCH){
+ if(rflags & SP_LOCKED){
+ if(F_OFF(F_STARTUP_STAYOPEN, ps_global)
+ && (cur = get_msgno_by_msg_id(ps_global->mail_stream,
+ sp_saved_cur_msg_id(ps_global->mail_stream),
+ ps_global->msgmap)) >= 1L
+ && cur <= mn_get_total(ps_global->msgmap)){
+ cur_already_set++;
+ mn_set_cur(ps_global->msgmap, (MsgNo) cur);
+ if(flags & DB_FROMTAB){
+ /*
+ * When we TAB to a folder that is a StayOpen folder we try
+ * to increment the current message # by one instead of doing
+ * some search again. Some people probably won't like this
+ * behavior, especially if the new message that has arrived
+ * comes before where we are in the index. That's why we have
+ * the F_STARTUP_STAYOPEN feature above.
+ */
+ mn_inc_cur(m, ps_global->msgmap, MH_NONE);
+ }
+ /* else leave it where it is */
+
+ adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
+ }
+ }
+ else{
+ /*
+ * If we're reopening a cached open stream that wasn't explicitly
+ * kept open by the user, then the user expects it to act pretty
+ * much like we are re-opening the stream. A problem is that the
+ * recent messages are still recent because we haven't closed the
+ * stream, so we fake a quasi-recentness by remembering the last
+ * uid assigned on the stream when we pine_mail_close. Then when
+ * we come back messages with uids higher than that are recent.
+ *
+ * If uid_validity has changed, then we don't use any special
+ * treatment, but just do the regular search.
+ */
+ if(m->uid_validity == sp_saved_uid_validity(m)){
+ long i;
+
+ /*
+ * Because first_sorted_flagged uses sequence numbers, find the
+ * sequence number of the first message after the old last
+ * uid assigned. I.e., the first recent message.
+ */
+ for(i = m->nmsgs; i > 0L; i--)
+ if(mail_uid(m, i) <= sp_saved_uid_last(m))
+ break;
+
+ if(i > 0L && i < m->nmsgs)
+ pc = i+1L;
+ }
+ }
+ }
+
+
+ if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){
+
+ perfolder_startup_rule = reset_startup_rule(ps_global->mail_stream);
+
+ if(ps_global->start_entry > 0){
+ mn_set_cur(ps_global->msgmap, mn_get_revsort(ps_global->msgmap)
+ ? first_sorted_flagged(F_NONE, m,
+ ps_global->start_entry,
+ THREADING() ? 0 : FSF_SKIP_CHID)
+ : first_sorted_flagged(F_SRCHBACK, m,
+ ps_global->start_entry,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ ps_global->start_entry = 0;
+ }
+ else if(perfolder_startup_rule != IS_NOTSET ||
+ open_inbox ||
+ ps_global->context_current->use & CNTXT_INCMNG){
+ unsigned use_this_startup_rule;
+
+ if(perfolder_startup_rule != IS_NOTSET)
+ use_this_startup_rule = perfolder_startup_rule;
+ else
+ use_this_startup_rule = ps_global->inc_startup_rule;
+
+ switch(use_this_startup_rule){
+ /*
+ * For news in incoming collection we're doing the same thing
+ * for first-unseen and first-recent. In both those cases you
+ * get first-unseen if FAKE_NEW is off and first-recent if
+ * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the
+ * same as first recent because all recent msgs are unseen
+ * and all unrecent msgs are seen (see pine_mail_open).
+ */
+ case IS_FIRST_UNSEEN:
+first_unseen:
+ mn_set_cur(ps_global->msgmap,
+ (sp_first_unseen(m)
+ && mn_get_sort(ps_global->msgmap) == SortArrival
+ && !mn_get_revsort(ps_global->msgmap)
+ && !get_lflag(ps_global->mail_stream, NULL,
+ sp_first_unseen(m), MN_EXLD)
+ && (n = mn_raw2m(ps_global->msgmap,
+ sp_first_unseen(m))))
+ ? n
+ : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ break;
+
+ case IS_FIRST_RECENT:
+first_recent:
+ /*
+ * We could really use recent for news but this is the way
+ * it has always worked, so we'll leave it. That is, if
+ * the FAKE_NEW feature is on, recent and unseen are
+ * equivalent, so it doesn't matter. If the feature isn't
+ * on, all the undeleted messages are unseen and we start
+ * at the first one. User controls with the FAKE_NEW feature.
+ */
+ if(IS_NEWS(ps_global->mail_stream)){
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ }
+ else{
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_RECENT | F_UNSEEN
+ | F_UNDEL,
+ m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ }
+ break;
+
+ case IS_FIRST_IMPORTANT:
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ break;
+
+ case IS_FIRST_IMPORTANT_OR_UNSEEN:
+
+ if(IS_NEWS(ps_global->mail_stream))
+ goto first_unseen;
+
+ {
+ MsgNo flagged, first_unseen;
+
+ flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID);
+ first_unseen = (sp_first_unseen(m)
+ && mn_get_sort(ps_global->msgmap) == SortArrival
+ && !mn_get_revsort(ps_global->msgmap)
+ && !get_lflag(ps_global->mail_stream, NULL,
+ sp_first_unseen(m), MN_EXLD)
+ && (n = mn_raw2m(ps_global->msgmap,
+ sp_first_unseen(m))))
+ ? n
+ : first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID);
+ mn_set_cur(ps_global->msgmap,
+ (MsgNo) MIN((int) flagged, (int) first_unseen));
+
+ }
+
+ break;
+
+ case IS_FIRST_IMPORTANT_OR_RECENT:
+
+ if(IS_NEWS(ps_global->mail_stream))
+ goto first_recent;
+
+ {
+ MsgNo flagged, first_recent;
+
+ flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID);
+ first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN
+ | F_UNDEL,
+ m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID);
+ mn_set_cur(ps_global->msgmap,
+ (MsgNo) MIN((int) flagged, (int) first_recent));
+ }
+
+ break;
+
+ case IS_FIRST:
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ break;
+
+ case IS_LAST:
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_UNDEL, m, pc,
+ FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID)));
+ break;
+
+ default:
+ panic("Unexpected incoming startup case");
+ break;
+
+ }
+ }
+ else if(IS_NEWS(ps_global->mail_stream)){
+ /*
+ * This will go to two different places depending on the FAKE_NEW
+ * feature (see pine_mail_open).
+ */
+ mn_set_cur(ps_global->msgmap,
+ first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
+ THREADING() ? 0 : FSF_SKIP_CHID));
+ }
+ else{
+ mn_set_cur(ps_global->msgmap,
+ mn_get_revsort(ps_global->msgmap)
+ ? 1L
+ : mn_get_total(ps_global->msgmap));
+ }
+
+ adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
+ }
+ else if(!(rflags & SP_MATCH)){
+ mn_set_cur(ps_global->msgmap, -1L);
+ }
+
+ if(ps_global->mail_stream)
+ sp_set_first_unseen(ps_global->mail_stream, 0L);
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Expand a folder name, taking account of the folders_dir and
+ any home directory reference
+
+ Args: filename -- The name of the file that is the folder
+
+ Result: The folder name is expanded in place.
+ Returns 0 and queues status message if unsuccessful.
+ Input string is overwritten with expanded name.
+ Returns 1 if successful.
+
+ ----*/
+int
+expand_foldername(char *filename, size_t len)
+{
+ char temp_filename[MAXPATH+1];
+
+ dprint((5, "=== expand_foldername called (%s) ===\n",
+ filename ? filename : "?"));
+
+ /*
+ * We used to check for valid filename chars here if "filename"
+ * didn't refer to a remote mailbox. This has been rethought
+ */
+
+ strncpy(temp_filename, filename, sizeof(temp_filename)-1);
+ temp_filename[sizeof(temp_filename)-1] = '\0';
+ if(strucmp(temp_filename, "inbox") == 0) {
+ strncpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" :
+ ps_global->VAR_INBOX_PATH, len-1);
+ filename[len-1] = '\0';
+ } else if(temp_filename[0] == '{') {
+ strncpy(filename, temp_filename, len-1);
+ filename[len-1] = '\0';
+ } else if(ps_global->restricted && filename_is_restricted(temp_filename)){
+ q_status_message(SM_ORDER, 0, 3, "Can only open local folders");
+ return(0);
+ } else if(temp_filename[0] == '*') {
+ strncpy(filename, temp_filename, len-1);
+ filename[len-1] = '\0';
+ } else if(ps_global->VAR_OPER_DIR && filename_parent_ref(temp_filename)){
+ q_status_message(SM_ORDER, 0, 3,
+ "\"..\" not allowed in folder name");
+ return(0);
+ } else if (is_homedir_path(temp_filename)){
+ if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) {
+ q_status_message1(SM_ORDER, 3, 3, "Error expanding folder \"%s\"", temp_filename);
+ return(0);
+ }
+ strncpy(filename, temp_filename, len-1);
+ filename[len-1] = '\0';
+ } else if(F_ON(F_USE_CURRENT_DIR, ps_global) || is_absolute_path(temp_filename)){
+ strncpy(filename, temp_filename, len-1);
+ filename[len-1] = '\0';
+ } else if(ps_global->VAR_OPER_DIR){
+ build_path(filename, ps_global->VAR_OPER_DIR, temp_filename, len);
+ } else {
+ build_path(filename,
+#ifdef IS_WINDOWS
+ ps_global->folders_dir,
+#else /* UNIX */
+ ps_global->home_dir,
+#endif /* UNIX */
+ temp_filename, len);
+ }
+
+ dprint((5, "returning \"%s\"\n", filename ? filename : "?"));
+ return(1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Expunge (if confirmed) and close a mail stream
+
+ Args: stream -- The MAILSTREAM * to close
+ final_msg -- If non-null, this should be set to point to a
+ message to print out in the caller, it is allocated
+ here and freed by the caller.
+
+ Result: Mail box is expunged and closed. A message is displayed to
+ say what happened
+ ----*/
+void
+expunge_and_close(MAILSTREAM *stream, char **final_msg, long unsigned int flags)
+{
+ long i, delete_count, seen_not_del;
+ char buff1[MAX_SCREEN_COLS+1], *moved_msg = NULL,
+ buff2[MAX_SCREEN_COLS+1], *folder;
+ CONTEXT_S *context;
+ struct variable *vars = ps_global->vars;
+ int ret, expunge = FALSE, no_close = 0;
+ char ing[4];
+
+ no_close = (flags & EC_NO_CLOSE);
+
+ if(!(stream && sp_flagged(stream, SP_LOCKED)))
+ stream = NULL;
+
+ /* check for dead stream */
+ if(stream && sp_dead_stream(stream)){
+ pine_mail_actually_close(stream);
+ stream = NULL;
+ }
+
+ if(stream != NULL){
+ context = sp_context(stream);
+ folder = STREAMNAME(stream);
+
+ dprint((2, "expunge_and_close: \"%s\"%s\n",
+ folder, no_close ? " (NO_CLOSE bit set)" : ""));
+
+ update_folder_unseen_by_stream(stream, UFU_NONE);
+
+ if(final_msg)
+ strncpy(ing, "ed", sizeof(ing));
+ else
+ strncpy(ing, "ing", sizeof(ing));
+
+ ing[sizeof(ing)-1] = '\0';
+
+ buff1[0] = '\0';
+ buff2[0] = '\0';
+
+ if(!stream->rdonly){
+
+ if(pith_opt_begin_closing)
+ (*pith_opt_begin_closing)(flags, folder);
+
+ mail_expunge_prefilter(stream, MI_CLOSING);
+
+ /*
+ * Be sure to expunge any excluded (filtered) msgs
+ * Do it here so they're not copied into read/archived
+ * folders *AND* to be sure we don't refilter them
+ * next time the folder's opened.
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if(get_lflag(stream, NULL, i, MN_EXLD)){ /* if there are any */
+ delete_filtered_msgs(stream); /* delete them all */
+ expunge = TRUE;
+ break;
+ }
+
+ /* Save read messages? */
+ if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
+ && sp_flagged(stream, SP_INBOX)
+ && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){
+
+ if(F_ON(F_AUTO_READ_MSGS,ps_global)
+ || (pith_opt_read_msg_prompt
+ && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER)))
+ /* move inbox's read messages */
+ moved_msg = move_read_msgs(stream, VAR_READ_MESSAGE_FOLDER,
+ buff1, sizeof(buff1), -1L);
+ }
+ else if(VAR_ARCHIVED_FOLDERS)
+ moved_msg = move_read_incoming(stream, context, folder,
+ VAR_ARCHIVED_FOLDERS,
+ buff1, sizeof(buff1));
+
+ /*
+ * We need the count_flagged to be executed not only to set
+ * delete_count, but also to set the searched bits in all of
+ * the deleted messages. The searched bit is used in the monkey
+ * business section below which undeletes deleted messages
+ * before expunging. It determines which messages are deleted
+ * by examining the searched bit, which had better be set or not
+ * based on this count_flagged call rather than some random
+ * search that happened earlier.
+ */
+ delete_count = count_flagged(stream, F_DEL);
+ if(F_ON(F_EXPUNGE_MANUALLY,ps_global))
+ delete_count = 0L;
+
+ ret = 'n';
+ if(!ps_global->noexpunge_on_close && delete_count){
+
+ if(F_ON(F_FULL_AUTO_EXPUNGE,ps_global)
+ || (F_ON(F_AUTO_EXPUNGE, ps_global)
+ && ((!strucmp(folder,ps_global->inbox_name))
+ || (context && (context->use & CNTXT_INCMNG)))
+ && context_isambig(folder))){
+ ret = 'y';
+ }
+ else if(pith_opt_expunge_prompt)
+ ret = (*pith_opt_expunge_prompt)(stream, pretty_fn(folder), delete_count);
+
+ /* get this message back in queue */
+ if(moved_msg)
+ q_status_message(SM_ORDER,
+ F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
+
+ if(ret == 'y'){
+ long filtered;
+
+ filtered = any_lflagged(sp_msgmap(stream), MN_EXLD);
+
+ snprintf(buff2, sizeof(buff2),
+ "%s%s%s%.30s%s%s %s message%s and remov%s %s.",
+ no_close ? "" : "Clos",
+ no_close ? "" : ing,
+ no_close ? "" : " \"",
+ no_close ? "" : pretty_fn(folder),
+ no_close ? "" : "\". ",
+ final_msg ? "Kept" : "Keeping",
+ comatose(stream->nmsgs - filtered - delete_count),
+ plural(stream->nmsgs - filtered - delete_count),
+ ing,
+ long2string(delete_count));
+ if(final_msg)
+ *final_msg = cpystr(buff2);
+ else
+ q_status_message(SM_ORDER,
+ no_close ? 1 :
+ (F_ON(F_AUTO_EXPUNGE,ps_global)
+ || F_ON(F_FULL_AUTO_EXPUNGE,ps_global))
+ ? 0 : 3,
+ 5, buff2);
+
+ flush_status_messages(1);
+ ps_global->mm_log_error = 0;
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(stream);
+ ps_global->expunge_in_progress = 0;
+ if(ps_global->mm_log_error && final_msg && *final_msg){
+ fs_give((void **)final_msg);
+ *final_msg = NULL;
+ }
+ }
+ }
+
+ if(ret != 'y'){
+ if(!ps_global->noexpunge_on_close && expunge){
+ MESSAGECACHE *mc;
+ char *seq;
+ int expbits;
+
+ /*
+ * filtered message monkey business.
+ * The Plan:
+ * 1) light sequence bits for legit deleted msgs
+ * and store marker in local extension
+ * 2) clear their deleted flag
+ * 3) perform expunge to removed filtered msgs
+ * 4) restore deleted flags for legit msgs
+ * based on local extension bit
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if(!get_lflag(stream, NULL, i, MN_EXLD)
+ && (((mc = mail_elt(stream, i)) && mc->valid && mc->deleted)
+ || (mc && !mc->valid && mc->searched))){
+ mc->sequence = 1;
+ expbits = MSG_EX_DELETE;
+ msgno_exceptions(stream, i, "0", &expbits, TRUE);
+ }
+ else if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ if((seq = build_sequence(stream, NULL, NULL)) != NULL){
+ mail_flag(stream, seq, "\\DELETED", ST_SILENT);
+ fs_give((void **) &seq);
+ }
+
+ ps_global->mm_log_error = 0;
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(stream);
+ ps_global->expunge_in_progress = 0;
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence
+ = (msgno_exceptions(stream, i, "0", &expbits, FALSE)
+ && (expbits & MSG_EX_DELETE));
+
+ if((seq = build_sequence(stream, NULL, NULL)) != NULL){
+ mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
+ fs_give((void **) &seq);
+ }
+ }
+
+ if(!no_close){
+ if(stream->nmsgs){
+ snprintf(buff2, sizeof(buff2),
+ "Clos%s folder \"%.*s\". %s%s%s message%s.",
+ ing,
+ sizeof(buff2)-50, pretty_fn(folder),
+ final_msg ? "Kept" : "Keeping",
+ (stream->nmsgs == 1L) ? " single" : " all ",
+ (stream->nmsgs > 1L)
+ ? comatose(stream->nmsgs) : "",
+ plural(stream->nmsgs));
+ }
+ else{
+ snprintf(buff2, sizeof(buff2), "Clos%s empty folder \"%.*s\"",
+ ing, sizeof(buff2)-50, pretty_fn(folder));
+ }
+
+ if(final_msg)
+ *final_msg = cpystr(buff2);
+ else
+ q_status_message(SM_ORDER, 0, 3, buff2);
+ }
+ }
+ }
+ else{
+ if(IS_NEWS(stream)){
+ /*
+ * Mark the filtered messages deleted so they aren't
+ * filtered next time.
+ */
+ for(i = 1L; i <= stream->nmsgs; i++){
+ int exbits;
+ if(msgno_exceptions(stream, i, "0" , &exbits, FALSE)
+ && (exbits & MSG_EX_FILTERED)){
+ delete_filtered_msgs(stream);
+ break;
+ }
+ }
+ /* first, look to archive read messages */
+ if((moved_msg = move_read_incoming(stream, context, folder,
+ VAR_ARCHIVED_FOLDERS,
+ buff1, sizeof(buff1))) != NULL)
+ q_status_message(SM_ORDER,
+ F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
+
+ snprintf(buff2, sizeof(buff2), "Clos%s news group \"%.*s\"",
+ ing, sizeof(buff2)-50, pretty_fn(folder));
+
+ if(F_ON(F_NEWS_CATCHUP, ps_global)){
+ MESSAGECACHE *mc;
+
+ /* count visible messages */
+ (void) count_flagged(stream, F_DEL);
+ for(i = 1L, delete_count = 0L; i <= stream->nmsgs; i++)
+ if(!(get_lflag(stream, NULL, i, MN_EXLD)
+ || ((mc = mail_elt(stream, i)) && mc->valid
+ && mc->deleted)
+ || (mc && !mc->valid && mc->searched)))
+ delete_count++;
+
+ if(delete_count && pith_opt_expunge_prompt){
+ ret = (*pith_opt_expunge_prompt)(stream, pretty_fn(folder), delete_count);
+ if(ret == 'y'){
+ char seq[64];
+
+ snprintf(seq, sizeof(seq), "1:%ld", stream->nmsgs);
+ mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
+ }
+ }
+ }
+
+ if(F_ON(F_NEWS_CROSS_DELETE, ps_global))
+ cross_delete_crossposts(stream);
+ }
+ else
+ snprintf(buff2, sizeof(buff2),
+ "Clos%s read-only folder \"%.*s\". No changes to save",
+ ing, sizeof(buff2)-60, pretty_fn(folder));
+
+ if(final_msg)
+ *final_msg = cpystr(buff2);
+ else
+ q_status_message(SM_ORDER, 0, 2, buff2);
+ }
+
+ /*
+ * Make darn sure any mm_log fallout caused above get's seen...
+ */
+ if(!no_close){
+ flush_status_messages(1);
+ pine_mail_close(stream);
+ }
+ }
+}
+
+
+void
+agg_select_all(MAILSTREAM *stream, MSGNO_S *msgmap, long int *diff, int on)
+{
+ long i;
+ int hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++){
+ if(on){ /* mark 'em all */
+ set_lflag(stream, msgmap, i, MN_SLCT, 1);
+ }
+ else { /* unmark 'em all */
+ if(get_lflag(stream, msgmap, i, MN_SLCT)){
+ if(diff)
+ (*diff)++;
+
+ set_lflag(stream, msgmap, i, MN_SLCT, 0);
+ }
+ else if(hidden)
+ set_lflag(stream, msgmap, i, MN_HIDE, 0);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Move all read messages from srcfldr to dstfldr
+
+ Args: stream -- stream to usr
+ dstfldr -- folder to receive moved messages
+ buf -- place to write success message
+
+ Returns: success message or NULL for failure
+ ----*/
+char *
+move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long int searched)
+{
+ long i, raw;
+ int we_cancel = 0;
+ MSGNO_S *msgmap = NULL;
+ CONTEXT_S *save_context = NULL;
+ char *bufp = NULL;
+ MESSAGECACHE *mc;
+
+ if(!is_absolute_path(dstfldr)
+ && !(save_context = default_save_context(ps_global->context_list)))
+ save_context = ps_global->context_list;
+
+ /*
+ * Use the "searched" bit to select the set of messages
+ * we want to save. If searched is non-neg, the message
+ * cache already has the necessary "searched" bits set.
+ */
+ if(searched < 0L)
+ searched = count_flagged(stream, F_SEEN | F_UNDEL);
+
+ if(searched){
+ /*
+ * We're going to be messing with SLCT flags in order
+ * to do our work. If this stream is a StayOpen stream
+ * we want to restore those flags after we're done
+ * using them. So copy them into STMP so we can put them
+ * back below.
+ */
+ msgmap = sp_msgmap(stream);
+ if(sp_flagged(stream, SP_PERMLOCKED))
+ copy_lflags(stream, msgmap, MN_SLCT, MN_STMP);
+
+ set_lflags(stream, msgmap, MN_SLCT, 0);
+
+ /* select search results */
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ if((raw = mn_m2raw(msgmap, i)) > 0L && stream
+ && raw <= stream->nmsgs
+ && (mc = mail_elt(stream,raw))
+ && ((mc->valid && mc->seen && !mc->deleted)
+ || (!mc->valid && mc->searched)))
+ set_lflag(stream, msgmap, i, MN_SLCT, 1);
+
+ pseudo_selected(stream, msgmap);
+ snprintf(buf, buflen, "Moving %s read message%s to \"%s\"",
+ comatose(searched), plural(searched), dstfldr);
+ we_cancel = busy_cue(buf, NULL, 0);
+ if(save(ps_global, stream, save_context, dstfldr, msgmap,
+ SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched)
+ strncpy(bufp = buf + 1, "Moved", MIN(5,buflen)); /* change Moving to Moved */
+
+ buf[buflen-1] = '\0';
+ if(we_cancel)
+ cancel_busy_cue(bufp ? 0 : -1);
+
+ if(sp_flagged(stream, SP_PERMLOCKED)){
+ restore_selected(msgmap);
+ copy_lflags(stream, msgmap, MN_STMP, MN_SLCT);
+ }
+ }
+
+ return(bufp);
+}
+
+
+/*----------------------------------------------------------------------
+ Move read messages from folder if listed in archive
+
+ Args:
+
+ ----*/
+char *
+move_read_incoming(MAILSTREAM *stream, CONTEXT_S *context, char *folder,
+ char **archive, char *buf, size_t buflen)
+{
+ char *s, *d, *f = folder;
+ long seen_undel;
+
+ if(buf && buflen > 0)
+ buf[0] = '\0';
+
+ if(archive && !sp_flagged(stream, SP_INBOX)
+ && context && (context->use & CNTXT_INCMNG)
+ && ((context_isambig(folder)
+ && folder_is_nick(folder, FOLDERS(context), 0))
+ || folder_index(folder, context, FI_FOLDER) > 0)
+ && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){
+
+ for(; f && *archive; archive++){
+ char *p;
+
+ get_pair(*archive, &s, &d, 1, 0);
+ if(s && d
+ && (!strcmp(s, folder)
+ || (context_isambig(folder)
+ && (p = folder_is_nick(folder, FOLDERS(context), 0))
+ && !strcmp(s, p)))){
+ if(F_ON(F_AUTO_READ_MSGS,ps_global)
+ || (pith_opt_read_msg_prompt
+ && (*pith_opt_read_msg_prompt)(seen_undel, d)))
+ buf = move_read_msgs(stream, d, buf, buflen, seen_undel);
+
+ f = NULL; /* bust out after cleaning up */
+ }
+
+ fs_give((void **)&s);
+ fs_give((void **)&d);
+ }
+ }
+
+ return((buf && *buf) ? buf : NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Delete all references to a deleted news posting
+
+
+ ---*/
+void
+cross_delete_crossposts(MAILSTREAM *stream)
+{
+ if(count_flagged(stream, F_DEL)){
+ static char *fields[] = {"Xref", NULL};
+ MAILSTREAM *tstream;
+ CONTEXT_S *fake_context;
+ char *xref, *p, *group, *uidp,
+ *newgrp, newfolder[MAILTMPLEN];
+ long i, hostlatch = 0L;
+ imapuid_t uid;
+ int we_cancel = 0;
+ MESSAGECACHE *mc;
+
+ strncpy(newfolder, stream->mailbox, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ if(!(newgrp = strstr(newfolder, "#news.")))
+ return; /* weird mailbox */
+
+ newgrp += 6;
+
+ we_cancel = busy_cue("Busy deleting crosspostings", NULL, 1);
+
+ /* build subscribed list */
+ strncpy(newgrp, "[]", sizeof(newfolder)-(newgrp-newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ fake_context = new_context(newfolder, 0);
+ build_folder_list(NULL, fake_context, "*", NULL, BFL_LSUB);
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if(!get_lflag(stream, NULL, i, MN_EXLD)
+ && (mc = mail_elt(stream, i)) && mc->deleted){
+
+ if((xref = pine_fetchheader_lines(stream, i, NULL, fields)) != NULL){
+ if((p = strstr(xref, ": ")) != NULL){
+ p += 2;
+ hostlatch = 0L;
+ while(*p){
+ group = p;
+ uidp = NULL;
+
+ /* get server */
+ while(*++p && !isspace((unsigned char) *p))
+ if(*p == ':'){
+ *p = '\0';
+ uidp = p + 1;
+ }
+
+ /* tie off uid/host */
+ if(*p)
+ *p++ = '\0';
+
+ if(uidp){
+ /*
+ * For the nonce, we're only deleting valid
+ * uid's from outside the current newsgroup
+ * and inside only subscribed newsgroups
+ */
+ if(strcmp(group, stream->mailbox
+ + (newgrp - newfolder))
+ && folder_index(group, fake_context,
+ FI_FOLDER) >= 0){
+ if((uid = strtoul(uidp, NULL, 10)) != 0L){
+ strncpy(newgrp, group, sizeof(newfolder)-(newgrp-newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ if((tstream = pine_mail_open(NULL,
+ newfolder,
+ SP_USEPOOL,
+ NULL)) != NULL){
+ mail_flag(tstream, ulong2string(uid),
+ "\\DELETED",
+ ST_SET | ST_UID);
+ pine_mail_close(tstream);
+ }
+ }
+ else
+ break; /* bogus uid */
+ }
+ }
+ else if(!hostlatch++){
+ char *p, *q;
+
+ if(stream->mailbox[0] == '{'
+ && !((p = strpbrk(stream->mailbox+1, "}:/"))
+ && !struncmp(stream->mailbox + 1,
+ q = canonical_name(group),
+ p - (stream->mailbox + 1))
+ && q[p - (stream->mailbox + 1)] == '\0'))
+ break; /* different server? */
+ }
+ else
+ break; /* bogus field! */
+ }
+ }
+
+ fs_give((void **) &xref);
+ }
+ }
+
+ free_context(&fake_context);
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+ }
+}
+
+
+/*
+ * Original version from Eduardo Chappa.
+ *
+ * Returns a string describing the number of new/unseen messages
+ * for use in the status line. Can return NULL. Caller must free the memory.
+ */
+char *
+new_messages_string(MAILSTREAM *stream)
+{
+ char message[80] = {'\0'};
+ long new = 0L, uns = 0L;
+ int i, imapstatus = 0;
+
+ for (i = 0; ps_global->index_disp_format[i].ctype != iNothing
+ && ps_global->index_disp_format[i].ctype != iIStatus
+ && ps_global->index_disp_format[i].ctype != iSIStatus; i++)
+ ;
+
+ imapstatus = ps_global->index_disp_format[i].ctype == iIStatus
+ || ps_global->index_disp_format[i].ctype == iSIStatus;
+
+ get_new_message_count(stream, imapstatus, &new, &uns);
+
+ if(imapstatus)
+ snprintf(message, sizeof(message), " - %s%s%s%s%s%s%s",
+ uns != 0L ? comatose((long) new) : "",
+ uns != 0L ? " " : "",
+ uns != 0L ? _("recent") : "",
+ uns > 0L ? ", " : "",
+ uns != -1L ? comatose((long) uns) : "",
+ uns != -1L ? " " : "",
+ uns != -1L ? _("unseen") : "");
+ else if(!imapstatus && new > 0L)
+ snprintf(message, sizeof(message), " - %s %s",
+ comatose((long) new), _("new"));
+
+ return(*message ? cpystr(message) : NULL);
+}
+
+
+void
+get_new_message_count(MAILSTREAM *stream, int imapstatus,
+ long *new, long *unseen)
+{
+ if(new)
+ *new = 0L;
+
+ if(unseen)
+ *unseen = 0L;
+
+ if(imapstatus){
+ if(new)
+ *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
+
+ if(!IS_NEWS(stream)){
+ if(unseen)
+ *unseen = count_flagged(stream, F_UNSEEN | F_UNDEL);
+ }
+ else if(unseen)
+ *unseen = -1L;
+ }
+ else{
+ if(IS_NEWS(stream)){
+ if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
+ if(new)
+ *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
+ }
+ }
+ else{
+ if(new)
+ *new = count_flagged(stream, F_UNSEEN | F_UNDEL | F_UNANS);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ ZOOM the message index (set any and all necessary hidden flag bits)
+
+ Args: state -- usual pine state
+ msgmap -- usual message mapping
+ Returns: number of messages zoomed in on
+
+ ----*/
+long
+zoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int onflag)
+{
+ long i, count = 0L, first = 0L, msgno;
+ PINETHRD_S *thrd = NULL, *topthrd = NULL, *nthrd;
+
+ if(any_lflagged(msgmap, onflag)){
+
+ if(THREADING() && sp_viewing_a_thread(stream)){
+ /* get top of current thread */
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+ }
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++){
+ if(!get_lflag(stream, msgmap, i, onflag)){
+ set_lflag(stream, msgmap, i, MN_HIDE, 1);
+ }
+ else{
+
+ /*
+ * Because subject lines depend on whether or not
+ * other parts of the thread above us are visible or not.
+ */
+ if(THREADING() && !THRD_INDX()
+ && ps_global->thread_disp_style == THREAD_MUTTLIKE)
+ clear_index_cache_ent(stream, i, 0);
+
+ /*
+ * If a selected message is hidden beneath a collapsed
+ * thread (not beneath a thread index line, but a collapsed
+ * thread or subthread) then we make it visible. The user
+ * should be able to see the selected messages when they
+ * Zoom. We could get a bit fancier and re-collapse the
+ * thread when the user unzooms, but we don't do that
+ * for now.
+ */
+ if(THREADING() && !THRD_INDX()
+ && get_lflag(stream, msgmap, i, MN_CHID)){
+
+ /*
+ * What we need to do is to unhide this message and
+ * uncollapse any parent above us.
+ * Also, when we uncollapse a parent, we need to
+ * trace back down the tree and unhide until we get
+ * to a collapse point or the end. That's what
+ * set_thread_subtree does.
+ */
+
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
+
+ if(thrd && thrd->parent)
+ thrd = fetch_thread(stream, thrd->parent);
+ else
+ thrd = NULL;
+
+ /* unhide and uncollapse its parents */
+ while(thrd){
+ /* if this parent is collapsed */
+ if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
+ /* uncollapse this parent and unhide its subtree */
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+ if(msgno > 0L && msgno <= mn_get_total(msgmap)){
+ set_lflag(stream, msgmap, msgno,
+ MN_COLL | MN_CHID, 0);
+ if(thrd->next &&
+ (nthrd = fetch_thread(stream, thrd->next)))
+ set_thread_subtree(stream, nthrd, msgmap,
+ 0, MN_CHID);
+ }
+
+ /* collapse symbol will be wrong */
+ clear_index_cache_ent(stream, msgno, 0);
+ }
+
+ /*
+ * Continue up tree to next parent looking for
+ * more collapse points.
+ */
+ if(thrd->parent)
+ thrd = fetch_thread(stream, thrd->parent);
+ else
+ thrd = NULL;
+ }
+ }
+
+ count++;
+ if(!first){
+ if(THRD_INDX()){
+ /* find msgno of top of thread for msg i */
+ if((thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
+ && thrd->top)
+ first = mn_raw2m(msgmap, thrd->top);
+ }
+ else if(THREADING() && sp_viewing_a_thread(stream)){
+ /* want first selected message in this thread */
+ if(topthrd
+ && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
+ && thrd->top
+ && topthrd->rawno == thrd->top)
+ first = i;
+ }
+ else
+ first = i;
+ }
+ }
+ }
+
+ if(THRD_INDX()){
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ if(count_lflags_in_thread(stream, thrd, msgmap, onflag) == 0)
+ mn_set_cur(msgmap, first);
+ }
+ else if((THREADING() && sp_viewing_a_thread(stream))
+ || !get_lflag(stream, msgmap, mn_get_cur(msgmap), onflag)){
+ if(!first){
+ int flags = 0;
+
+ /*
+ * Nothing was selected in the thread we were in, so
+ * drop back to the Thread Index instead. Set the current
+ * thread to the first one that has a selection in it.
+ */
+
+ unview_thread(state, stream, msgmap);
+
+ i = next_sorted_flagged(F_UNDEL, stream, 1L, &flags);
+
+ if(flags & NSF_FLAG_MATCH
+ && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
+ && thrd->top)
+ first = mn_raw2m(msgmap, thrd->top);
+ else
+ first = 1L; /* can't happen */
+
+ mn_set_cur(msgmap, first);
+ }
+ else{
+ if(msgline_hidden(stream, msgmap, mn_get_cur(msgmap), 0))
+ mn_set_cur(msgmap, first);
+ }
+ }
+ }
+
+ return(count);
+}
+
+
+
+/*----------------------------------------------------------------------
+ UnZOOM the message index (clear any and all hidden flag bits)
+
+ Args: state -- usual pine state
+ msgmap -- usual message mapping
+ Returns: 1 if hidden bits to clear and they were, 0 if none to clear
+
+ ----*/
+int
+unzoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ register long i;
+
+ if(!any_lflagged(msgmap, MN_HIDE))
+ return(0);
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ set_lflag(stream, msgmap, i, MN_HIDE, 0);
+
+ return(1);
+}
+
+
+int
+agg_text_select(MAILSTREAM *stream, MSGNO_S *msgmap, char type, char *namehdr,
+ int not, int check_for_my_addresses,
+ char *sstring, char *charset, SEARCHSET **limitsrch)
+{
+ int old_imap, we_cancel;
+ int me_with_regex = 0;
+ long searchflags;
+ SEARCHPGM *srchpgm, *pgm, *secondpgm = NULL, *thirdpgm = NULL;
+ SEARCHPGM *mepgm = NULL;
+
+ if(!stream)
+ return(1);
+
+ old_imap = (is_imap_stream(stream) && !modern_imap_stream(stream));
+
+ /*
+ * Special case code for matching one of the user's addresses.
+ */
+ if(check_for_my_addresses){
+ char **t, *alt;
+
+ if(F_OFF(F_DISABLE_REGEX, ps_global)){
+ for(t = ps_global->VAR_ALT_ADDRS; !me_with_regex && t && t[0] && t[0][0]; t++){
+ alt = (*t);
+ if(contains_regex_special_chars(alt))
+ me_with_regex++;
+ }
+ }
+
+ /*
+ * In this case we can't use search because it doesn't support
+ * regex. So we have to manually do the whole thing ourselves.
+ * The searching is done in the subroutine and the searched bits
+ * will be set on return.
+ */
+ if(me_with_regex){
+ search_for_our_regex_addresses(stream, type, not, limitsrch ? *limitsrch : NULL);
+ return(0);
+ }
+ else{
+ PATGRP_S *patgrp = NULL;
+ PATTERN_S *p = NULL;
+ PATTERN_S *pattern = NULL, **nextp;
+ char buf[1000];
+
+ /*
+ * We're going to use the pattern matching machinery to generate
+ * a search program. We build a pattern whose only purpose is
+ * to generate the program.
+ */
+ nextp = &pattern;
+
+ /* add standard me addresses to list */
+ if(ps_global->VAR_USER_ID){
+ if(ps_global->userdomain && ps_global->userdomain[0]){
+ p = (PATTERN_S *) fs_get(sizeof(*p));
+ memset((void *) p, 0, sizeof(*p));
+ snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
+ ps_global->userdomain);
+ p->substring = cpystr(buf);
+ *nextp = p;
+ nextp = &p->next;
+ }
+
+ if(!ps_global->userdomain && ps_global->localdomain && ps_global->localdomain[0]){
+ p = (PATTERN_S *) fs_get(sizeof(*p));
+ memset((void *) p, 0, sizeof(*p));
+ snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
+ ps_global->localdomain);
+ p->substring = cpystr(buf);
+ *nextp = p;
+ nextp = &p->next;
+ }
+
+ if(!ps_global->userdomain && ps_global->hostname && ps_global->hostname[0]){
+ p = (PATTERN_S *) fs_get(sizeof(*p));
+ memset((void *) p, 0, sizeof(*p));
+ snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
+ ps_global->hostname);
+ p->substring = cpystr(buf);
+ *nextp = p;
+ nextp = &p->next;
+ }
+ }
+
+ /* add user's alternate addresses */
+ for(t = ps_global->VAR_ALT_ADDRS; t && t[0] && t[0][0]; t++){
+ alt = (*t);
+ if(alt && alt[0]){
+ p = (PATTERN_S *) fs_get(sizeof(*p));
+ memset((void *) p, 0, sizeof(*p));
+ p->substring = cpystr(alt);
+ *nextp = p;
+ nextp = &p->next;
+ }
+ }
+
+ patgrp = (PATGRP_S *) fs_get(sizeof(*patgrp));
+ memset((void *) patgrp, 0, sizeof(*patgrp));
+
+ switch(type){
+ case 'r' :
+ patgrp->recip = pattern;
+ break;
+ case 'p' :
+ patgrp->partic = pattern;
+ break;
+ case 'f' :
+ patgrp->from = pattern;
+ break;
+ case 'c' :
+ patgrp->cc = pattern;
+ break;
+ case 't' :
+ patgrp->to = pattern;
+ break;
+ default :
+ q_status_message(SM_ORDER, 3, 3, "Unhandled case in agg_text_select");
+ break;
+ }
+
+ mepgm = match_pattern_srchpgm(patgrp, stream, NULL);
+
+ free_patgrp(&patgrp);
+ }
+ }
+
+ if(mepgm){
+ if(not && !old_imap){
+ srchpgm = mail_newsearchpgm();
+ srchpgm->not = mail_newsearchpgmlist();
+ srchpgm->not->pgm = mepgm;
+ }
+ else{
+ srchpgm = mepgm;
+ }
+
+ }
+ else{
+ /* create a search program and fill it in */
+ srchpgm = pgm = mail_newsearchpgm();
+ if(not && !old_imap){
+ srchpgm->not = mail_newsearchpgmlist();
+ srchpgm->not->pgm = mail_newsearchpgm();
+ pgm = srchpgm->not->pgm;
+ }
+ }
+
+ if(!mepgm)
+ switch(type){
+ case 'h' : /* Any header */
+ pgm->header = mail_newsearchheader (namehdr, sstring);
+ break;
+
+ case 'r' : /* TO or CC */
+ if(old_imap){
+ /* No OR on old servers */
+ pgm->to = mail_newstringlist();
+ pgm->to->text.data = (unsigned char *) cpystr(sstring);
+ pgm->to->text.size = strlen(sstring);
+ secondpgm = mail_newsearchpgm();
+ secondpgm->cc = mail_newstringlist();
+ secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
+ secondpgm->cc->text.size = strlen(sstring);
+ }
+ else{
+ pgm->or = mail_newsearchor();
+ pgm->or->first->to = mail_newstringlist();
+ pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
+ pgm->or->first->to->text.size = strlen(sstring);
+ pgm->or->second->cc = mail_newstringlist();
+ pgm->or->second->cc->text.data = (unsigned char *) cpystr(sstring);
+ pgm->or->second->cc->text.size = strlen(sstring);
+ }
+
+ break;
+
+ case 'p' : /* TO or CC or FROM */
+ if(old_imap){
+ /* No OR on old servers */
+ pgm->to = mail_newstringlist();
+ pgm->to->text.data = (unsigned char *) cpystr(sstring);
+ pgm->to->text.size = strlen(sstring);
+ secondpgm = mail_newsearchpgm();
+ secondpgm->cc = mail_newstringlist();
+ secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
+ secondpgm->cc->text.size = strlen(sstring);
+ thirdpgm = mail_newsearchpgm();
+ thirdpgm->from = mail_newstringlist();
+ thirdpgm->from->text.data = (unsigned char *) cpystr(sstring);
+ thirdpgm->from->text.size = strlen(sstring);
+ }
+ else{
+ pgm->or = mail_newsearchor();
+ pgm->or->first->to = mail_newstringlist();
+ pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
+ pgm->or->first->to->text.size = strlen(sstring);
+
+ pgm->or->second->or = mail_newsearchor();
+ pgm->or->second->or->first->cc = mail_newstringlist();
+ pgm->or->second->or->first->cc->text.data =
+ (unsigned char *) cpystr(sstring);
+ pgm->or->second->or->first->cc->text.size = strlen(sstring);
+ pgm->or->second->or->second->from = mail_newstringlist();
+ pgm->or->second->or->second->from->text.data =
+ (unsigned char *) cpystr(sstring);
+ pgm->or->second->or->second->from->text.size = strlen(sstring);
+ }
+
+ break;
+
+ case 'f' : /* FROM */
+ pgm->from = mail_newstringlist();
+ pgm->from->text.data = (unsigned char *) cpystr(sstring);
+ pgm->from->text.size = strlen(sstring);
+ break;
+
+ case 'c' : /* CC */
+ pgm->cc = mail_newstringlist();
+ pgm->cc->text.data = (unsigned char *) cpystr(sstring);
+ pgm->cc->text.size = strlen(sstring);
+ break;
+
+ case 't' : /* TO */
+ pgm->to = mail_newstringlist();
+ pgm->to->text.data = (unsigned char *) cpystr(sstring);
+ pgm->to->text.size = strlen(sstring);
+ break;
+
+ case 's' : /* SUBJECT */
+ pgm->subject = mail_newstringlist();
+ pgm->subject->text.data = (unsigned char *) cpystr(sstring);
+ pgm->subject->text.size = strlen(sstring);
+ break;
+
+ case 'a' : /* ALL TEXT */
+ pgm->text = mail_newstringlist();
+ pgm->text->text.data = (unsigned char *) cpystr(sstring);
+ pgm->text->text.size = strlen(sstring);
+ break;
+
+ case 'b' : /* ALL BODY TEXT */
+ pgm->body = mail_newstringlist();
+ pgm->body->text.data = (unsigned char *) cpystr(sstring);
+ pgm->body->text.size = strlen(sstring);
+ break;
+
+ default :
+ dprint((1,"\n - BOTCH: select_text unrecognized type\n"));
+ return(1);
+ }
+
+ /*
+ * If we happen to have any messages excluded, make sure we
+ * don't waste time searching their text...
+ */
+ srchpgm->msgno = (limitsrch ? *limitsrch : NULL);
+
+ /* TRANSLATORS: warning to user that we're busy selecting messages */
+ we_cancel = busy_cue(_("Busy Selecting"), NULL, 1);
+
+ searchflags = SE_NOPREFETCH | (secondpgm ? 0 : SE_FREE);
+
+ pine_mail_search_full(stream, !old_imap ? charset : NULL, srchpgm,
+ searchflags);
+
+ /* search for To or Cc; or To or Cc or From on old imap server */
+ if(secondpgm){
+ if(srchpgm){
+ srchpgm->msgno = NULL;
+ mail_free_searchpgm(&srchpgm);
+ }
+
+ secondpgm->msgno = (limitsrch ? *limitsrch : NULL);
+ searchflags |= (SE_RETAIN | (thirdpgm ? 0 : SE_FREE));
+
+ pine_mail_search_full(stream, NULL, secondpgm, searchflags);
+
+ if(thirdpgm){
+ if(secondpgm){
+ secondpgm->msgno = NULL;
+ mail_free_searchpgm(&secondpgm);
+ }
+
+ thirdpgm->msgno = (limitsrch ? *limitsrch : NULL);
+ searchflags |= SE_FREE;
+ pine_mail_search_full(stream, NULL, thirdpgm, searchflags);
+ }
+ }
+
+ /* we know this was freed in mail_search, let caller know */
+ if(limitsrch)
+ *limitsrch = NULL;
+
+ if(old_imap && not){
+ MESSAGECACHE *mc;
+ long msgno;
+
+ /*
+ * Old imap server doesn't have a NOT, so we actually searched for
+ * the subject (or whatever) instead of !subject. Flip the searched
+ * bits.
+ */
+ for(msgno = 1L; msgno <= mn_get_total(msgmap); msgno++)
+ if(stream && msgno <= stream->nmsgs
+ && (mc=mail_elt(stream, msgno)) && mc->searched)
+ mc->searched = NIL;
+ else
+ mc->searched = T;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return(0);
+}
+
+
+void
+search_for_our_regex_addresses(MAILSTREAM *stream, char type, int not,
+ SEARCHSET *searchset)
+{
+ long rawno, count = 0L;
+ MESSAGECACHE *mc;
+ ADDRESS *addr1 = NULL, *addr2 = NULL, *addr3 = NULL;
+ ENVELOPE *env;
+ SEARCHSET *s, *ss = NULL;
+ extern MAILSTREAM *mm_search_stream;
+ extern long mm_search_count;
+
+ mm_search_count = 0L;
+ mm_search_stream = stream;
+
+ if(!stream)
+ return;
+
+ /* set searched bits to zero */
+ for(rawno = 1L; rawno <= stream->nmsgs; rawno++)
+ if((mc=mail_elt(stream, rawno)) != NULL)
+ mc->searched = NIL;
+
+ /* set sequence bits for envelopes we need */
+ for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
+ if((mc = mail_elt(stream, rawno)) != NULL){
+ if((!searchset || in_searchset(searchset, (unsigned long) rawno))
+ && !mc->private.msg.env){
+ mc->sequence = 1;
+ count++;
+ }
+ else
+ mc->sequence = 0;
+ }
+ }
+
+ /*
+ * Set up a searchset that will control the fetch ahead.
+ */
+ if(count){
+ ss = build_searchset(stream);
+ if(ss){
+ SEARCHSET **sset = NULL;
+
+ mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
+
+ /* this resets automatically after the first fetch */
+ sset = (SEARCHSET **) mail_parameters(stream,
+ GET_FETCHLOOKAHEAD,
+ (void *) stream);
+ if(sset)
+ *sset = ss;
+ }
+ }
+
+ for(s = searchset; s; s = s->next){
+ for(rawno = s->first; rawno <= s->last; rawno++){
+ env = pine_mail_fetchenvelope(stream, rawno);
+ addr1 = addr2 = addr3 = NULL;
+ switch(type){
+ case 'r' :
+ addr1 = env ? env->to : NULL;
+ addr2 = env ? env->cc : NULL;
+ break;
+ case 'p' :
+ addr1 = env ? env->to : NULL;
+ addr2 = env ? env->cc : NULL;
+ addr3 = env ? env->from : NULL;
+ break;
+ case 'f' :
+ addr1 = env ? env->from : NULL;
+ break;
+ case 'c' :
+ addr1 = env ? env->cc : NULL;
+ break;
+ break;
+ case 't' :
+ addr1 = env ? env->to : NULL;
+ break;
+ default :
+ q_status_message(SM_ORDER, 3, 3, "Unhandled case2 in agg_text_select");
+ break;
+ }
+
+ if(addr1 && address_is_us(addr1, ps_global)){
+ if((mc=mail_elt(stream, rawno)) != NULL)
+ mm_searched(stream, rawno);
+ }
+ else if(addr2 && address_is_us(addr2, ps_global)){
+ if((mc=mail_elt(stream, rawno)) != NULL)
+ mm_searched(stream, rawno);
+ }
+ else if(addr3 && address_is_us(addr3, ps_global)){
+ if((mc=mail_elt(stream, rawno)) != NULL)
+ mm_searched(stream, rawno);
+ }
+ }
+ }
+
+ if(ss)
+ mail_free_searchset(&ss);
+
+ if(not){
+ for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
+ if((mc=mail_elt(stream, rawno)) && mc->searched)
+ mc->searched = NIL;
+ else
+ mc->searched = T;
+ }
+ }
+}
+
+
+int
+agg_flag_select(MAILSTREAM *stream, int not, int crit, SEARCHSET **limitsrch)
+{
+ SEARCHPGM *pgm;
+
+ pgm = mail_newsearchpgm();
+ switch(crit){
+ case 'n' :
+ if(not){
+ SEARCHPGM *notpgm;
+
+ /* this is the same as seen or deleted or answered */
+ pgm->not = mail_newsearchpgmlist();
+ notpgm = pgm->not->pgm = mail_newsearchpgm();
+ notpgm->unseen = notpgm->undeleted = notpgm->unanswered = 1;
+ }
+ else
+ pgm->unseen = pgm->undeleted = pgm->unanswered = 1;
+
+ break;
+
+ case 'd' :
+ if(not)
+ pgm->undeleted = 1;
+ else
+ pgm->deleted = 1;
+
+ break;
+
+ case 'r' :
+ if(not)
+ pgm->old = 1;
+ else
+ pgm->recent = 1;
+
+ break;
+
+ case 'u' :
+ if(not)
+ pgm->seen = 1;
+ else
+ pgm->unseen = 1;
+
+ break;
+
+ case 'a':
+ /*
+ * Not a true "not", we are implicitly only interested in undeleted.
+ */
+ if(not)
+ pgm->unanswered = pgm->undeleted = 1;
+ else
+ pgm->answered = pgm->undeleted = 1;
+ break;
+
+ case 'f':
+ {
+ STRINGLIST **slpp;
+
+ for(slpp = (not) ? &pgm->unkeyword : &pgm->keyword;
+ *slpp;
+ slpp = &(*slpp)->next)
+ ;
+
+ *slpp = mail_newstringlist();
+ (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
+ (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
+ }
+
+ break;
+
+ case '*' :
+ if(not)
+ pgm->unflagged = 1;
+ else
+ pgm->flagged = 1;
+
+ break;
+
+ default :
+ return(1);
+ break;
+ }
+
+ pgm->msgno = (limitsrch ? *limitsrch : NULL);
+ pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
+ /* we know this was freed in mail_search, let caller know */
+ if(limitsrch)
+ *limitsrch = NULL;
+
+ return(0);
+}
+
+
+/*
+ * Get the user name from the mailbox portion of an address.
+ *
+ * Args: mailbox -- the mailbox portion of an address (lhs of address)
+ * target -- a buffer to put the result in
+ * len -- length of the target buffer
+ *
+ * Returns the left most portion up to the first '%', ':' or '@',
+ * and to the right of any '!' (as if c-client would give us such a mailbox).
+ * Returns NULL if it can't find a username to point to.
+ */
+char *
+get_uname(char *mailbox, char *target, int len)
+{
+ int i, start, end;
+
+ if(!mailbox || !*mailbox)
+ return(NULL);
+
+ end = strlen(mailbox) - 1;
+ for(start = end; start > -1 && mailbox[start] != '!'; start--)
+ if(strindex("%:@", mailbox[start]))
+ end = start - 1;
+
+ start++; /* compensate for either case above */
+
+ for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
+ target[i-start] = isupper((unsigned char)mailbox[i])
+ ? tolower((unsigned char)mailbox[i])
+ : mailbox[i];
+
+ target[i-start] = '\0'; /* tie it off */
+
+ return(*target ? target : NULL);
+}
diff --git a/pith/mailcmd.h b/pith/mailcmd.h
new file mode 100644
index 00000000..9e99c6f3
--- /dev/null
+++ b/pith/mailcmd.h
@@ -0,0 +1,77 @@
+/*
+ * $Id: mailcmd.h 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILCMD_INCLUDED
+#define PITH_MAILCMD_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/msgno.h"
+#include "../pith/context.h"
+#include "../pith/indxtype.h"
+
+
+#define EC_NONE 0x00 /* flags modifying expunge_and_close */
+#define EC_NO_CLOSE 0x01 /* don't close at end */
+
+
+/*
+ * mailcmd options
+ */
+#define MCMD_NONE 0
+#define MCMD_AGG 0x01
+#define MCMD_AGG_2 0x02
+#define MCMD_SILENT 0x04
+
+
+/* do_broach_folder flags */
+#define DB_NOVISIT 0x01 /* this is a preopen, not a real visit */
+#define DB_FROMTAB 0x02 /* opening because of TAB command */
+#define DB_INBOXWOCNTXT 0x04 /* interpret inbox as one true inbox */
+
+
+/*
+ * generic "is aggregate message command?" test
+ */
+#define MCMD_ISAGG(O) ((O) & (MCMD_AGG | MCMD_AGG_2))
+
+
+/* exported protoypes */
+int any_messages(MSGNO_S *, char *, char *);
+void bogus_utf8_command(char *, char *);
+int can_set_flag(struct pine *, char *, int);
+void cmd_cancelled(char *);
+void cmd_quota(struct pine *);
+int cmd_delete(struct pine *, MSGNO_S *, int, char *(*)(struct pine *, MSGNO_S *));
+int cmd_undelete(struct pine *, MSGNO_S *, int);
+int cmd_expunge_work(MAILSTREAM *, MSGNO_S *);
+CONTEXT_S *broach_get_folder(CONTEXT_S *, int *, char **);
+int do_broach_folder(char *, CONTEXT_S *, MAILSTREAM **, unsigned long);
+void expunge_and_close(MAILSTREAM *, char **, unsigned long);
+void agg_select_all(MAILSTREAM *, MSGNO_S *, long *, int);
+char *move_read_msgs(MAILSTREAM *, char *, char *, size_t, long);
+char *move_read_incoming(MAILSTREAM *, CONTEXT_S *, char *, char **, char *, size_t);
+void cross_delete_crossposts(MAILSTREAM *);
+long zoom_index(struct pine *, MAILSTREAM *, MSGNO_S *, int);
+int unzoom_index(struct pine *, MAILSTREAM *, MSGNO_S *);
+int agg_text_select(MAILSTREAM *, MSGNO_S *, char, char *, int, int, char *,
+ char *, SEARCHSET **);
+int agg_flag_select(MAILSTREAM *, int, int, SEARCHSET **);
+char *get_uname(char *, char *, int);
+int expand_foldername(char *, size_t);
+
+
+#endif /* PITH_MAILCMD_INCLUDED */
diff --git a/pith/mailindx.c b/pith/mailindx.c
new file mode 100644
index 00000000..f3b68bfb
--- /dev/null
+++ b/pith/mailindx.c
@@ -0,0 +1,6434 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailindx.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mailindx.h"
+#include "../pith/mailview.h"
+#include "../pith/flag.h"
+#include "../pith/icache.h"
+#include "../pith/msgno.h"
+#include "../pith/thread.h"
+#include "../pith/strlst.h"
+#include "../pith/status.h"
+#include "../pith/mailcmd.h"
+#include "../pith/search.h"
+#include "../pith/charset.h"
+#include "../pith/reply.h"
+#include "../pith/bldaddr.h"
+#include "../pith/addrstring.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/pattern.h"
+#include "../pith/sequence.h"
+#include "../pith/color.h"
+#include "../pith/stream.h"
+#include "../pith/string.h"
+#include "../pith/send.h"
+#include "../pith/options.h"
+#include "../pith/ablookup.h"
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+/*
+ * pointers to formatting functions
+ */
+ICE_S *(*format_index_line)(INDEXDATA_S *);
+void (*setup_header_widths)(MAILSTREAM *);
+
+/*
+ * pointer to optional load_overview functionality
+ */
+void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *);
+
+/*
+ * pointer to hook for saving index format state
+ */
+void (*pith_opt_save_index_state)(int);
+
+/*
+ * hook to allow caller to insert cue that indicates a condensed
+ * thread relationship cue
+ */
+int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
+int (*pith_opt_truncate_sfstr)(void);
+
+
+/*
+ * Internal prototypes
+ */
+void setup_for_thread_index_screen(void);
+ICE_S *format_index_index_line(INDEXDATA_S *);
+ICE_S *format_thread_index_line(INDEXDATA_S *);
+int set_index_addr(INDEXDATA_S *, char *, ADDRESS *, char *, int, char *);
+int ctype_is_fixed_length(IndexColType);
+void setup_index_header_widths(MAILSTREAM *);
+void setup_thread_header_widths(MAILSTREAM *);
+int parse_index_format(char *, INDEX_COL_S **);
+int index_in_overview(MAILSTREAM *);
+ADDRESS *fetch_from(INDEXDATA_S *);
+ADDRESS *fetch_sender(INDEXDATA_S *);
+char *fetch_newsgroups(INDEXDATA_S *);
+char *fetch_subject(INDEXDATA_S *);
+char *fetch_date(INDEXDATA_S *);
+long fetch_size(INDEXDATA_S *);
+BODY *fetch_body(INDEXDATA_S *);
+char *fetch_firsttext(INDEXDATA_S *idata, int);
+char *fetch_header(INDEXDATA_S *idata, char *hdrname);
+void subj_str(INDEXDATA_S *, char *, size_t, SubjKW, int, ICE_S *);
+void key_str(INDEXDATA_S *, SubjKW, ICE_S *);
+void header_str(INDEXDATA_S *, HEADER_TOK_S *, ICE_S *);
+void prio_str(INDEXDATA_S *, IndexColType, ICE_S *);
+void from_str(IndexColType, INDEXDATA_S *, char *, size_t, ICE_S *);
+int day_of_week(struct date *);
+int day_of_year(struct date *);
+unsigned long ice_hash(ICE_S *);
+char *left_adjust(int);
+char *right_adjust(int);
+char *format_str(int, int);
+char *copy_format_str(int, int, char *, int);
+void set_print_format(IELEM_S *, int, int);
+void set_ielem_widths_in_field(IFIELD_S *);
+
+
+#define BIGWIDTH 2047
+
+
+/*----------------------------------------------------------------------
+ Initialize the index_disp_format array in ps_global from this
+ format string.
+
+ Args: format -- the string containing the format tokens
+ answer -- put the answer here, free first if there was a previous
+ value here
+ ----*/
+void
+init_index_format(char *format, INDEX_COL_S **answer)
+{
+ char *p;
+ int i, w, monabb_width = 0, column = 0;
+
+ /*
+ * Record the fact that SCORE appears in some index format. This
+ * is a heavy-handed approach. It will stick at 1 if any format ever
+ * contains score during this session. This is ok since it will just
+ * cause recalculation if wrong and these things rarely change much.
+ */
+ if(!ps_global->a_format_contains_score && format
+ && strstr(format, "SCORE")){
+ ps_global->a_format_contains_score = 1;
+ /* recalculate need for scores */
+ scores_are_used(SCOREUSE_INVALID);
+ }
+
+ set_need_format_setup(ps_global->mail_stream);
+ /* if custom format is specified, try it, else go with default */
+ if(!(format && *format && parse_index_format(format, answer))){
+ static INDEX_COL_S answer_default[] = {
+ {iStatus, Fixed, 3},
+ {iMessNo, WeCalculate},
+ {iSDateTime24, WeCalculate},
+ {iFromTo, Percent, 33}, /* percent of rest */
+ {iSizeNarrow, WeCalculate},
+ {iSubjKey, Percent, 67},
+ {iNothing}
+ };
+
+ if(*answer)
+ free_index_format(answer);
+
+ *answer = (INDEX_COL_S *)fs_get(sizeof(answer_default));
+ memcpy(*answer, answer_default, sizeof(answer_default));
+ }
+
+ /*
+ * Test to see how long the month abbreviations are.
+ */
+ for(i = 1; i <= 12; i++){
+ p = month_abbrev_locale(i);
+ monabb_width = MAX(utf8_width(p), monabb_width);
+ }
+
+ monabb_width = MIN(MAX(2, monabb_width), 5);
+
+ /*
+ * Fill in req_width's for WeCalculate items.
+ */
+ for(column = 0; (*answer)[column].ctype != iNothing; column++){
+
+ /* don't use strftime if we're not trying to use the LC_TIME stuff */
+ if(F_ON(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ switch((*answer)[column].ctype){
+ case iSDate:
+ (*answer)[column].ctype = iS1Date;
+ break;
+ case iSDateTime:
+ (*answer)[column].ctype = iSDateTimeS1;
+ break;
+ case iSDateTime24:
+ (*answer)[column].ctype = iSDateTimeS124;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if((*answer)[column].wtype == WeCalculate){
+ switch((*answer)[column].ctype){
+ case iPrio:
+ case iPrioBang:
+ case iAtt:
+ (*answer)[column].req_width = 1;
+ break;
+ case iYear2Digit:
+ case iDay:
+ case iMon:
+ case iDay2Digit:
+ case iMon2Digit:
+ case iArrow:
+ case iKeyInit:
+ (*answer)[column].req_width = 2;
+ break;
+ case iStatus:
+ case iMessNo:
+ case iInit:
+ (*answer)[column].req_width = 3;
+ break;
+ case iYear:
+ case iDayOrdinal:
+ case iSIStatus:
+ (*answer)[column].req_width = 4;
+ break;
+ case iTime24:
+ case iTimezone:
+ case iSizeNarrow:
+ case iKey:
+ (*answer)[column].req_width = 5;
+ break;
+ case iFStatus:
+ case iIStatus:
+ case iScore:
+ (*answer)[column].req_width = 6;
+ break;
+ case iTime12:
+ case iSTime:
+ case iKSize:
+ case iSize:
+ case iPrioAlpha:
+ (*answer)[column].req_width = 7;
+ break;
+ case iS1Date:
+ case iS2Date:
+ case iS3Date:
+ case iS4Date:
+ case iDateIsoS:
+ case iSizeComma:
+ (*answer)[column].req_width = 8;
+ break;
+ case iMonAbb:
+ (*answer)[column].req_width = monabb_width;
+ (*answer)[column].monabb_width = monabb_width;
+ break;
+ case iDayOfWeekAbb:
+ {
+ w = 0;
+
+ /*
+ * Test to see how long it is.
+ */
+ for(i = 0; i < 7; i++){
+ p = day_abbrev_locale(i);
+ w = MAX(utf8_width(p), w);
+ }
+
+ (*answer)[column].req_width = MIN(MAX(2, w), 5);
+ }
+ break;
+ case iDate:
+ (*answer)[column].req_width = monabb_width + 3;
+ (*answer)[column].monabb_width = monabb_width;
+ break;
+ case iMonLong:
+ {
+ w = 0;
+
+ /*
+ * Test to see how long it is.
+ */
+ for(i = 1; i <= 12; i++){
+ p = month_name_locale(i);
+ w = MAX(utf8_width(p), w);
+ }
+
+ (*answer)[column].req_width = MIN(MAX(3, w), 12);
+ }
+ break;
+ case iDayOfWeek:
+ {
+ w = 0;
+
+ for(i = 0; i < 7; i++){
+ p = day_name_locale(i);
+ w = MAX(utf8_width(p), w);
+ }
+
+ (*answer)[column].req_width = MIN(MAX(3, w), 12);
+ }
+ break;
+ case iSDate:
+ case iSDateTime:
+ case iSDateTime24:
+ case iPrefDate:
+ case iPrefTime:
+ case iPrefDateTime:
+ {
+ /*
+ * Format a date to see how long it is.
+ * Make it as least as long as "Yesterday".
+ * We should really use the width of the longest
+ * of the translated yesterdays and friends but...
+ */
+ struct tm tm;
+ int len = 20;
+ char ss[100];
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 106;
+ tm.tm_mon = 11;
+ tm.tm_mday = 31;
+ tm.tm_hour = 3;
+ tm.tm_min = 23;
+ tm.tm_wday = 3;
+ switch((*answer)[column].ctype){
+ case iPrefTime:
+ our_strftime(ss, sizeof(ss), "%X", &tm);
+ break;
+ case iPrefDateTime:
+ our_strftime(ss, sizeof(ss), "%c", &tm);
+ len = 32;
+ break;
+ default:
+ our_strftime(ss, sizeof(ss), "%x", &tm);
+ break;
+ }
+ (*answer)[column].req_width = MIN(MAX(9, utf8_width(ss)), len);
+ }
+
+ (*answer)[column].monabb_width = monabb_width;
+ break;
+
+ case iDescripSize:
+ case iSDateIsoS:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTimeIsoS:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTimeIsoS24:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ /*
+ * These SDates are 8 wide but they need to be 9 for "Yesterday".
+ */
+ (*answer)[column].req_width = 9;
+ break;
+ case iDateIso:
+ case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
+ (*answer)[column].req_width = 10;
+ break;
+ case iLDate:
+ (*answer)[column].req_width = 12;
+ (*answer)[column].monabb_width = monabb_width;
+ break;
+ case iRDate:
+ (*answer)[column].req_width = 16;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ calc_extra_hdrs();
+ if(get_extra_hdrs())
+ (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
+ (void *) get_extra_hdrs());
+}
+
+
+void
+reset_index_format(void)
+{
+ long rflags = ROLE_DO_OTHER;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ int we_set_it = 0;
+
+ if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
+ NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
+ break;
+ }
+
+ if(pat && pat->action && !pat->action->bogus
+ && pat->action->index_format){
+ we_set_it++;
+ init_index_format(pat->action->index_format,
+ &ps_global->index_disp_format);
+ }
+ }
+
+ if(!we_set_it)
+ init_index_format(ps_global->VAR_INDEX_FORMAT,
+ &ps_global->index_disp_format);
+}
+
+
+void
+free_index_format(INDEX_COL_S **disp_format)
+{
+ INDEX_COL_S *cdesc = NULL;
+
+ if(disp_format && *disp_format){
+ for(cdesc = (*disp_format); cdesc->ctype != iNothing; cdesc++)
+ if(cdesc->hdrtok)
+ free_hdrtok(&cdesc->hdrtok);
+
+ fs_give((void **) disp_format);
+ }
+}
+
+
+HEADER_TOK_S *
+new_hdrtok(char *hdrname)
+{
+ HEADER_TOK_S *hdrtok;
+
+ hdrtok = (HEADER_TOK_S *) fs_get(sizeof(HEADER_TOK_S));
+ memset(hdrtok, 0, sizeof(HEADER_TOK_S));
+ hdrtok->hdrname = hdrname ? cpystr(hdrname) : NULL;
+ hdrtok->fieldnum = 0;
+ hdrtok->adjustment = Left;
+ hdrtok->fieldsepcnt = 1;
+ hdrtok->fieldseps = cpystr(" ");
+
+ return(hdrtok);
+}
+
+
+void
+free_hdrtok(HEADER_TOK_S **hdrtok)
+{
+ if(hdrtok && *hdrtok){
+ if((*hdrtok)->hdrname)
+ fs_give((void **) &(*hdrtok)->hdrname);
+
+ if((*hdrtok)->fieldseps)
+ fs_give((void **) &(*hdrtok)->fieldseps);
+
+ fs_give((void **) hdrtok);
+ }
+}
+
+
+/* popular ones first to make it slightly faster */
+static INDEX_PARSE_T itokens[] = {
+ {"STATUS", iStatus, FOR_INDEX},
+ {"MSGNO", iMessNo, FOR_INDEX},
+ {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"FROMORTO", iFromTo, FOR_INDEX},
+ {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX},
+ {"SIZE", iSize, FOR_INDEX},
+ {"SIZECOMMA", iSizeComma, FOR_INDEX},
+ {"SIZENARROW", iSizeNarrow, FOR_INDEX},
+ {"KSIZE", iKSize, FOR_INDEX},
+ {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"FULLSTATUS", iFStatus, FOR_INDEX},
+ {"IMAPSTATUS", iIStatus, FOR_INDEX},
+ {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX},
+ {"SUBJKEY", iSubjKey, FOR_INDEX},
+ {"SUBJKEYINIT", iSubjKeyInit, FOR_INDEX},
+ {"SUBJECTTEXT", iSubjectText, FOR_INDEX},
+ {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX},
+ {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX},
+ {"OPENINGTEXT", iOpeningText, FOR_INDEX},
+ {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX},
+ {"KEY", iKey, FOR_INDEX},
+ {"KEYINIT", iKeyInit, FOR_INDEX},
+ {"DESCRIPSIZE", iDescripSize, FOR_INDEX},
+ {"ATT", iAtt, FOR_INDEX},
+ {"SCORE", iScore, FOR_INDEX},
+ {"PRIORITY", iPrio, FOR_INDEX},
+ {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX},
+ {"PRIORITY!", iPrioBang, FOR_INDEX},
+ {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"NEWSANDTO", iNewsAndTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"RECIPSANDNEWS", iRecipsAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE},
+ {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb,
+ FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"CURPREFDATETIME", iCurPrefDateTime,
+ FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit,
+ FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT},
+ {"HEADER", iHeader, FOR_INDEX},
+ {"TEXT", iText, FOR_INDEX},
+ {"ARROW", iArrow, FOR_INDEX},
+ {"NEWLINE", iNewLine, FOR_REPLY_INTRO},
+ {"CURSORPOS", iCursorPos, FOR_TEMPLATE},
+ {NULL, iNothing, FOR_NOTHING}
+};
+
+INDEX_PARSE_T *
+itoken(int i)
+{
+ return((i < sizeof(itokens) && itokens[i].name) ? &itokens[i] : NULL);
+}
+
+
+/*
+ * Args txt -- The token being checked begins at the beginning
+ * of txt. The end of the token is delimited by a null, or
+ * white space, or an underscore if DELIM_USCORE is set,
+ * or a left paren if DELIM_PAREN is set.
+ * flags -- Flags contains the what_for value, and DELIM_ values.
+ *
+ * Returns A ptr to an INDEX_PARSE_T from itokens above, else NULL.
+ */
+INDEX_PARSE_T *
+itoktype(char *txt, int flags)
+{
+ INDEX_PARSE_T *pt;
+ char token[100 + 1];
+ char *v, *w;
+
+ /*
+ * Separate a copy of the possible token out of txt.
+ */
+ v = txt;
+ w = token;
+ while(w < token+sizeof(token)-1 &&
+ *v &&
+ !(!(*v & 0x80) && isspace((unsigned char)*v)) &&
+ !(flags & DELIM_USCORE && *v == '_') &&
+ !(flags & DELIM_PAREN && *v == '(') &&
+ !(flags & DELIM_COLON && *v == ':'))
+ *w++ = *v++;
+
+ *w = '\0';
+
+ for(pt = itokens; pt->name; pt++)
+ if(pt->what_for & flags && !strucmp(pt->name, token))
+ return(pt);
+
+ return(NULL);
+}
+
+
+int
+parse_index_format(char *format_str, INDEX_COL_S **answer)
+{
+ int i, column = 0;
+ char *p, *q;
+ INDEX_PARSE_T *pt;
+ INDEX_COL_S cdesc[200]; /* plenty of temp storage for answer */
+
+ memset((void *)cdesc, 0, sizeof(cdesc));
+
+ p = format_str;
+ while(p && *p && column < 200-1){
+ /* skip leading white space for next word */
+ p = skip_white_space(p);
+ pt = itoktype(p, FOR_INDEX | DELIM_PAREN | DELIM_COLON);
+
+ /* ignore unrecognized word */
+ if(!pt){
+ for(q = p; *p && !isspace((unsigned char)*p); p++)
+ ;
+
+ if(*p)
+ *p++ = '\0';
+
+ dprint((1,
+ "parse_index_format: unrecognized token: %s\n",
+ q ? q : "?"));
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ _("Unrecognized word in index-format: %s"), q);
+ continue;
+ }
+
+ cdesc[column].ctype = pt->ctype;
+
+ if(pt->ctype == iHeader || pt->ctype == iText){
+ /*
+ * iHeader field has special syntax.
+ *
+ * HEADER:hdrname(width,fieldnum,field_separators,L_or_R)
+ *
+ * where width is the regular width or percentage width or
+ * left out for default width, fieldnum defaults to 0 for
+ * whole thing, 1 for first field, ...
+ * and field_separators is a list of characters which separate
+ * the fields. The whole parenthesized part is optional. If used
+ * the arguments can be dropped from the right, so
+ *
+ * HEADER:hdrname or
+ * HEADER:hdrname(10) or
+ * HEADER:hdrname(10%) or
+ * HEADER:hdrname(10,2) or
+ * HEADER:hdrname(,2) or
+ * HEADER:hdrname(10,2, ) or
+ * HEADER:hdrname(10,2,\,:) or
+ * HEADER:hdrname(10,2,\,:,R)
+ *
+ * iText field uses the hdrtok field for convenience. It has syntax
+ *
+ * TEXT:text or
+ * TEXT:text(10) or
+ * TEXT:text(10%)
+ *
+ * and the literal text goes into the index line. It is also special
+ * because there is no 1 column space after this field.
+ */
+
+ /* skip over name */
+ p += strlen(pt->name);
+
+ /* look for header name */
+ if(*p == ':'){
+ char *w, hdrname[200];
+
+ hdrname[0] = '\0';
+ w = hdrname;
+ p++;
+ if(*p == '\"'){ /* quoted name */
+ p++;
+ while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
+ if(*p == '\\')
+ p++;
+
+ *w++ = *p++;
+ }
+
+ *w = '\0';
+ if(*p == '\"')
+ p++;
+ }
+ else{
+ while(w < hdrname + sizeof(hdrname)-1 &&
+ !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
+ *p != '(')
+ *w++ = *p++;
+
+ *w = '\0';
+ }
+
+ if(hdrname[0]){
+ cdesc[column].hdrtok = new_hdrtok(hdrname);
+ }
+ else{
+ if(pt->ctype == iHeader){
+ dprint((1, "parse_index_token: HEADER should be followed by :hdrname\n"));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
+ }
+ else{
+ dprint((1, "parse_index_token: TEXT should be followed by :text\n"));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
+ }
+ }
+ }
+ else{
+ if(pt->ctype == iHeader){
+ dprint((1, "parse_index_token: HEADER should be followed by :hdrname, not %s\n", p));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "index token HEADER should be followed by :hdrname");
+ }
+ else{
+ dprint((1, "parse_index_token: TEXT should be followed by :text, not %s\n", p));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "index token TEXT should be followed by :text");
+ }
+
+ /* skip over rest of bogus config */
+ while(!(!(*p & 0x80) && isspace((unsigned char)*p)) && *p != '(')
+ p++;
+ }
+ }
+ else{
+ /* skip over name and look for parens */
+ p += strlen(pt->name);
+ }
+
+ if(*p == '('){
+ p++;
+ q = p;
+ while(p && *p && isdigit((unsigned char) *p))
+ p++;
+
+ if(pt->ctype == iHeader){
+ /* first argument is width or width percentage, like for others */
+ if(p && *p && (*p == ')' || *p == ',')){
+ if(p > q){
+ cdesc[column].wtype = Fixed;
+ cdesc[column].req_width = atoi(q);
+ }
+ else{
+ cdesc[column].wtype = WeCalculate;
+ cdesc[column].req_width = 0;
+ }
+ }
+ else if(p && *p && *p == '%' && p > q){
+ cdesc[column].wtype = Percent;
+ cdesc[column].req_width = atoi(q);
+ p++;
+ }
+ else{
+ cdesc[column].wtype = WeCalculate;
+ cdesc[column].req_width = 0;
+ }
+
+ /* optional 2nd argument is field number, 0 whole thing, 1, 2, ... */
+ if(p && *p && *p == ','){
+ p++;
+ /* no space allowed between arguments */
+ if(*p && isdigit((unsigned char) *p)){
+ q = p;
+ while(*p && isdigit((unsigned char) *p))
+ p++;
+
+ cdesc[column].hdrtok->fieldnum = atoi(q);
+
+ /*
+ * Optional 3rd argument is field separators.
+ * Comma is \, and backslash is \\.
+ */
+ if(*p == ','){
+ int j;
+
+ p++;
+ /* don't use default */
+ if(*p && *p != ')' && *p != ',' && cdesc[column].hdrtok->fieldseps)
+ cdesc[column].hdrtok->fieldseps[0] = '\0';
+
+ j = 0;
+ if(*p == '\"' && strchr(p+1, '\"')){
+ p++;
+ while(*p && *p != ')' && *p != '\"' && *p != ','){
+ if(cdesc[column].hdrtok->fieldseps)
+ fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
+
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(cdesc[column].hdrtok->fieldseps){
+ cdesc[column].hdrtok->fieldseps[j++] = *p++;
+ cdesc[column].hdrtok->fieldseps[j] = '\0';
+ cdesc[column].hdrtok->fieldsepcnt = j;
+ }
+ }
+
+ if(*p == '\"')
+ p++;
+ }
+ else{
+ while(*p && *p != ')' && *p != ','){
+ if(cdesc[column].hdrtok->fieldseps)
+ fs_resize((void **) &cdesc[column].hdrtok->fieldseps, j+2);
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(cdesc[column].hdrtok->fieldseps){
+ cdesc[column].hdrtok->fieldseps[j++] = *p++;
+ cdesc[column].hdrtok->fieldseps[j] = '\0';
+ cdesc[column].hdrtok->fieldsepcnt = j;
+ }
+ }
+ }
+
+ /* optional 4th argument, left or right adjust */
+ if(*p == ','){
+ p++;
+ if(*p == 'L' || *p == 'l')
+ cdesc[column].hdrtok->adjustment = Left;
+ else if(*p == 'R' || *p == 'r')
+ cdesc[column].hdrtok->adjustment = Right;
+ else{
+ dprint((1, "parse_index_token: HEADER 4th argument should be L or R, not\n", *p ? p : "<null>"));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 4th argument should be L or R");
+ }
+ }
+ }
+ }
+ else{
+ dprint((1, "parse_index_token: HEADER 2nd argument should be field number, not\n", *p ? p : "<null>"));
+ q_status_message(SM_ORDER | SM_DING, 0, 3, "HEADER 2nd argument should be field number, a non-negative digit");
+ }
+ }
+ }
+ else{
+ if(p && *p && *p == ')' && p > q){
+ cdesc[column].wtype = Fixed;
+ cdesc[column].req_width = atoi(q);
+ }
+ else if(p && *p && *p == '%' && p > q){
+ cdesc[column].wtype = Percent;
+ cdesc[column].req_width = atoi(q);
+ }
+ else{
+ cdesc[column].wtype = WeCalculate;
+ cdesc[column].req_width = 0;
+ }
+ }
+ }
+ else{
+ /* if they left out width for iText we can figure it out */
+ if(pt->ctype == iText && cdesc[column].hdrtok && cdesc[column].hdrtok->hdrname){
+ cdesc[column].wtype = Fixed;
+ cdesc[column].req_width = utf8_width(cdesc[column].hdrtok->hdrname);
+ }
+ else{
+ cdesc[column].wtype = WeCalculate;
+ cdesc[column].req_width = 0;
+ }
+ }
+
+ column++;
+ /* skip text at end of word */
+ while(p && *p && !isspace((unsigned char)*p))
+ p++;
+ }
+
+ /* if, after all that, we didn't find anything recognizable, bitch */
+ if(!column){
+ dprint((1, "Completely unrecognizable index-format\n"));
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Configured \"index-format\" unrecognizable. Using default."));
+ return(0);
+ }
+
+ /* Finish with Nothing column */
+ cdesc[column].ctype = iNothing;
+
+ /* free up old answer */
+ if(*answer)
+ free_index_format(answer);
+
+ /* allocate space for new answer */
+ *answer = (INDEX_COL_S *)fs_get((column+1)*sizeof(INDEX_COL_S));
+ memset((void *)(*answer), 0, (column+1)*sizeof(INDEX_COL_S));
+ /* copy answer to real place */
+ for(i = 0; i <= column; i++)
+ (*answer)[i] = cdesc[i];
+
+ return(1);
+}
+
+
+/*
+ * These types are basically fixed in width.
+ * The order is slightly significant. The ones towards the front of the
+ * list get space allocated sooner than the ones at the end of the list.
+ */
+static IndexColType fixed_ctypes[] = {
+ iMessNo, iStatus, iFStatus, iIStatus, iSIStatus,
+ iDate, iSDate, iSDateTime, iSDateTime24,
+ iSTime, iLDate,
+ iS1Date, iS2Date, iS3Date, iS4Date, iDateIso, iDateIsoS,
+ iSDateIso, iSDateIsoS,
+ iSDateS1, iSDateS2, iSDateS3, iSDateS4,
+ iSDateTimeIso, iSDateTimeIsoS,
+ iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4,
+ iSDateTimeIso24, iSDateTimeIsoS24,
+ iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424,
+ iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize,
+ iPrio, iPrioBang, iPrioAlpha, iInit,
+ iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit,
+ iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek
+};
+
+
+int
+ctype_is_fixed_length(IndexColType ctype)
+{
+ int j;
+
+ for(j = 0; ; j++){
+ if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
+ break;
+
+ if(ctype == fixed_ctypes[j])
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------
+ Setup the widths of the various columns in the index display
+ ----*/
+void
+setup_index_header_widths(MAILSTREAM *stream)
+{
+ int colspace; /* for reserving space between columns */
+ int j, some_to_calculate;
+ int space_left, screen_width, fix;
+ int keep_going, tot_pct, was_sl;
+ long max_msgno;
+ WidthType wtype;
+ INDEX_COL_S *cdesc;
+
+ max_msgno = mn_get_total(ps_global->msgmap);
+
+ dprint((8, "=== setup_index_header_widths() ===\n"));
+
+ clear_icache_flags(stream);
+ screen_width = ps_global->ttyo->screen_cols;
+ space_left = screen_width;
+ some_to_calculate = 0;
+ colspace = -1;
+
+ /*
+ * Calculate how many fields there are so we know how many spaces
+ * between columns to reserve. Fill in Fixed widths now. Reserve
+ * special case WeCalculate with non-zero req_widths before doing
+ * Percent cases below.
+ */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++){
+
+ if(cdesc->wtype == Fixed){
+ cdesc->width = cdesc->req_width;
+ if(cdesc->width > 0)
+ colspace++;
+ }
+ else if(cdesc->wtype == Percent){
+ cdesc->width = 0; /* calculated later */
+ colspace++;
+ }
+ else{ /* WeCalculate */
+ cdesc->width = cdesc->req_width; /* reserve this for now */
+ some_to_calculate++;
+ colspace++;
+ }
+
+ /* no space after iText */
+ if(cdesc->ctype == iText)
+ colspace--;
+
+ space_left -= cdesc->width;
+ }
+
+ colspace = MAX(colspace, 0);
+
+ space_left -= colspace; /* space between columns */
+
+ ps_global->display_keywords_in_subject = 0;
+ ps_global->display_keywordinits_in_subject = 0;
+
+ /*
+ * Set the actual lengths for the fixed width fields and set up
+ * the left or right adjustment for everything.
+ * There should be a case setting actual_length for all of the types
+ * in fixed_ctypes.
+ */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++){
+
+ wtype = cdesc->wtype;
+
+ if(cdesc->ctype == iSubjKey || cdesc->ctype == iSubjKeyText)
+ ps_global->display_keywords_in_subject = 1;
+ else if(cdesc->ctype == iSubjKeyInit || cdesc->ctype == iSubjKeyInitText)
+ ps_global->display_keywordinits_in_subject = 1;
+
+ if(wtype == WeCalculate || wtype == Percent || cdesc->width != 0){
+
+ switch(cdesc->ctype){
+ case iSDate: case iSDateIso: case iSDateIsoS:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ case iSTime:
+ set_format_includes_smartdate(stream);
+ break;
+
+ default:
+ break;
+ }
+
+ if(ctype_is_fixed_length(cdesc->ctype)){
+ switch(cdesc->ctype){
+ case iPrio:
+ case iPrioBang:
+ case iAtt:
+ cdesc->actual_length = 1;
+ cdesc->adjustment = Left;
+ break;
+
+ case iYear2Digit:
+ case iDay2Digit:
+ case iMon2Digit:
+ cdesc->actual_length = 2;
+ cdesc->adjustment = Left;
+ break;
+
+ case iArrow:
+ cdesc->actual_length = 2;
+ cdesc->adjustment = Right;
+ break;
+
+ case iStatus:
+ case iInit:
+ cdesc->actual_length = 3;
+ cdesc->adjustment = Left;
+ break;
+
+ case iMessNo:
+ set_format_includes_msgno(stream);
+ if(max_msgno < 1000)
+ cdesc->actual_length = 3;
+ else if(max_msgno < 10000)
+ cdesc->actual_length = 4;
+ else if(max_msgno < 100000)
+ cdesc->actual_length = 5;
+ else
+ cdesc->actual_length = 6;
+
+ cdesc->adjustment = Right;
+ break;
+
+ case iYear:
+ case iSIStatus:
+ cdesc->actual_length = 4;
+ cdesc->adjustment = Left;
+ break;
+
+ case iTime24:
+ case iTimezone:
+ cdesc->actual_length = 5;
+ cdesc->adjustment = Left;
+ break;
+
+ case iSizeNarrow:
+ cdesc->actual_length = 5;
+ cdesc->adjustment = Right;
+ break;
+
+ case iFStatus:
+ case iIStatus:
+ cdesc->actual_length = 6;
+ cdesc->adjustment = Left;
+ break;
+
+ case iScore:
+ cdesc->actual_length = 6;
+ cdesc->adjustment = Right;
+ break;
+
+ case iTime12:
+ case iSize:
+ case iKSize:
+ cdesc->actual_length = 7;
+ cdesc->adjustment = Right;
+ break;
+
+ case iSTime:
+ cdesc->actual_length = 7;
+ cdesc->adjustment = Left;
+ break;
+
+ case iPrioAlpha:
+ cdesc->actual_length = 7;
+ cdesc->adjustment = Left;
+ break;
+
+
+ case iS1Date:
+ case iS2Date:
+ case iS3Date:
+ case iS4Date:
+ case iDateIsoS:
+ cdesc->actual_length = 8;
+ cdesc->adjustment = Left;
+ break;
+
+ case iSizeComma:
+ cdesc->actual_length = 8;
+ cdesc->adjustment = Right;
+ break;
+
+ case iSDate:
+ case iSDateTime:
+ case iSDateTime24:
+ case iMonAbb:
+ case iDayOfWeekAbb:
+ case iDayOfWeek:
+ case iDate:
+ case iMonLong:
+ cdesc->actual_length = cdesc->req_width;
+ cdesc->adjustment = Left;
+ break;
+
+ case iSDateIsoS:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTimeIsoS:
+ case iSDateTimeIsoS24:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
+ if(cdesc->ctype == iSDateIso
+ || cdesc->ctype == iSDateTimeIso
+ || cdesc->ctype == iSDateTimeIso24)
+ cdesc->actual_length = 10;
+ else
+ cdesc->actual_length = 9;
+
+ cdesc->adjustment = Left;
+ break;
+
+ case iDescripSize:
+ cdesc->actual_length = 9;
+ cdesc->adjustment = Right;
+ break;
+
+ case iDateIso:
+ cdesc->actual_length = 10;
+ cdesc->adjustment = Left;
+ break;
+
+ case iLDate:
+ cdesc->actual_length = 12;
+ cdesc->adjustment = Left;
+ break;
+
+ default:
+ panic("Unhandled fixed case in setup_index_header");
+ break;
+ }
+ }
+ else if(cdesc->ctype == iHeader)
+ cdesc->adjustment = cdesc->hdrtok ? cdesc->hdrtok->adjustment : Left;
+ else
+ cdesc->adjustment = Left;
+ }
+ }
+
+ if(ps_global->display_keywords_in_subject)
+ ps_global->display_keywordinits_in_subject = 0;
+
+ /* if have reserved unneeded space for size, give it back */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++)
+ if(cdesc->ctype == iSize || cdesc->ctype == iKSize ||
+ cdesc->ctype == iSizeNarrow ||
+ cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){
+ if(cdesc->actual_length == 0){
+ if((fix=cdesc->width) > 0){ /* had this reserved */
+ cdesc->width = 0;
+ space_left += fix;
+ }
+
+ space_left++; /* +1 for space between columns */
+ }
+ }
+
+ /*
+ * Calculate the field widths that are basically fixed in width.
+ * Do them in this order in case we don't have enough space to go around.
+ * The set of fixed_ctypes here is the same as the set where we
+ * set the actual_lengths above.
+ */
+ for(j = 0; space_left > 0 && some_to_calculate; j++){
+
+ if(j >= sizeof(fixed_ctypes)/sizeof(*fixed_ctypes))
+ break;
+
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0 && some_to_calculate;
+ cdesc++)
+ if(cdesc->ctype == fixed_ctypes[j] && cdesc->wtype == WeCalculate){
+ some_to_calculate--;
+ fix = MIN(cdesc->actual_length - cdesc->width, space_left);
+ cdesc->width += fix;
+ space_left -= fix;
+ }
+ }
+
+ /*
+ * Fill in widths for Percent cases. If there are no more to calculate,
+ * use the percentages as relative numbers and use the rest of the space,
+ * else treat them as absolute percentages of the original avail screen.
+ */
+ if(space_left > 0){
+ if(some_to_calculate){
+ int tot_requested = 0;
+
+ /*
+ * Requests are treated as percent of screen width. See if they
+ * will all fit. If not, trim them back proportionately.
+ */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++){
+ if(cdesc->wtype == Percent){
+ /* The 2, 200, and +100 are because we're rounding */
+ fix = ((2*cdesc->req_width *
+ (screen_width-colspace))+100) / 200;
+ tot_requested += fix;
+ }
+ }
+
+ if(tot_requested > space_left){
+ int multiplier = (100 * space_left) / tot_requested;
+
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0;
+ cdesc++){
+ if(cdesc->wtype == Percent){
+ /* The 2, 200, and +100 are because we're rounding */
+ fix = ((2*cdesc->req_width *
+ (screen_width-colspace))+100) / 200;
+ fix = (2 * fix * multiplier + 100) / 200;
+ fix = MIN(fix, space_left);
+ cdesc->width += fix;
+ space_left -= fix;
+ }
+ }
+ }
+ else{
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0;
+ cdesc++){
+ if(cdesc->wtype == Percent){
+ /* The 2, 200, and +100 are because we're rounding */
+ fix = ((2*cdesc->req_width *
+ (screen_width-colspace))+100) / 200;
+ fix = MIN(fix, space_left);
+ cdesc->width += fix;
+ space_left -= fix;
+ }
+ }
+ }
+ }
+ else{
+ tot_pct = 0;
+ was_sl = space_left;
+ /* add up total percentages requested */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++)
+ if(cdesc->wtype == Percent)
+ tot_pct += cdesc->req_width;
+
+ /* give relative weight to requests */
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0 && tot_pct > 0;
+ cdesc++){
+ if(cdesc->wtype == Percent){
+ fix = ((2*cdesc->req_width*was_sl)+tot_pct) / (2*tot_pct);
+ fix = MIN(fix, space_left);
+ cdesc->width += fix;
+ space_left -= fix;
+ }
+ }
+ }
+ }
+
+ /* split up rest, give twice as much to Subject */
+ keep_going = 1;
+ while(space_left > 0 && keep_going){
+ keep_going = 0;
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0;
+ cdesc++){
+ if(cdesc->wtype == WeCalculate && !ctype_is_fixed_length(cdesc->ctype)){
+ keep_going++;
+ cdesc->width++;
+ space_left--;
+ if(space_left > 0 && (cdesc->ctype == iSubject
+ || cdesc->ctype == iSubjectText
+ || cdesc->ctype == iSubjKey
+ || cdesc->ctype == iSubjKeyText
+ || cdesc->ctype == iSubjKeyInit
+ || cdesc->ctype == iSubjKeyInitText)){
+ cdesc->width++;
+ space_left--;
+ }
+ }
+ }
+ }
+
+ /* if still more, pad out percent's */
+ keep_going = 1;
+ while(space_left > 0 && keep_going){
+ keep_going = 0;
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left > 0;
+ cdesc++){
+ if(cdesc->wtype == Percent && !ctype_is_fixed_length(cdesc->ctype)){
+ keep_going++;
+ cdesc->width++;
+ space_left--;
+ }
+ }
+ }
+
+ /* if user made Fixed fields too big, give back space */
+ keep_going = 1;
+ while(space_left < 0 && keep_going){
+ keep_going = 0;
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && space_left < 0;
+ cdesc++){
+ if(cdesc->wtype == Fixed && cdesc->width > 0){
+ keep_going++;
+ cdesc->width--;
+ space_left++;
+ }
+ }
+ }
+
+ if(pith_opt_save_index_state)
+ (*pith_opt_save_index_state)(FALSE);
+}
+
+
+void
+setup_thread_header_widths(MAILSTREAM *stream)
+{
+ clear_icache_flags(stream);
+ if(pith_opt_save_index_state)
+ (*pith_opt_save_index_state)(TRUE);
+}
+
+
+/*
+ * load_overview - c-client call back to gather overview data
+ *
+ * Note: if we never get called, UID represents a hole
+ * if we're passed a zero UID, totally bogus overview data
+ * if we're passed a zero obuf, mostly bogus overview data
+ */
+void
+load_overview(MAILSTREAM *stream, imapuid_t uid, OVERVIEW *obuf, long unsigned int rawno)
+{
+ if(obuf && rawno >= 1L && stream && rawno <= stream->nmsgs){
+ INDEXDATA_S idata;
+ ICE_S *ice;
+
+ memset(&idata, 0, sizeof(INDEXDATA_S));
+ idata.no_fetch = 1;
+
+ /*
+ * Only really load the thing if we've got an NNTP stream
+ * otherwise we're just using mail_fetch_overview to load the
+ * IMAP envelope cache with the specific set of messages
+ * in a single RTT.
+ */
+ idata.stream = stream;
+ idata.rawno = rawno;
+ idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
+ idata.size = obuf->optional.octets;
+ idata.from = obuf->from;
+ idata.date = obuf->date;
+ idata.subject = obuf->subject;
+
+ ice = (*format_index_line)(&idata);
+ if(idata.bogus && ice){
+ if(THRD_INDX()){
+ if(ice->tice)
+ clear_ice(&ice->tice);
+ }
+ else
+ clear_ice(&ice);
+ }
+ else if(F_OFF(F_QUELL_NEWS_ENV_CB, ps_global)
+ && (!THRD_INDX() || (ice && ice->tice))
+ && !msgline_hidden(stream, sp_msgmap(stream), idata.msgno, 0)
+ && pith_opt_paint_index_hline){
+ (*pith_opt_paint_index_hline)(stream, idata.msgno, ice);
+ }
+ }
+}
+
+
+ICE_S *
+build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
+ long int msgno, long int top_msgno, int msgcount, int *fetched)
+{
+ ICE_S *ice, *ic;
+ MESSAGECACHE *mc;
+ long n, i, cnt, rawno, visible, limit = -1L;
+
+ rawno = mn_m2raw(msgmap, msgno);
+
+ /* cache hit? */
+ if(THRD_INDX()){
+ ice = fetch_ice(stream, rawno);
+ if(!ice)
+ return(NULL);
+
+ if(ice->tice && ice->tice->ifield
+ && ice->tice->color_lookup_done && ice->tice->widths_done){
+#ifdef DEBUG
+ char buf[MAX_SCREEN_COLS+1];
+ simple_index_line(buf, sizeof(buf), ice->tice, msgno);
+#endif
+ dprint((9, "Hitt: Returning %p -> <%s (%d)\n",
+ ice->tice,
+ buf[0] ? buf : "?",
+ buf[0] ? strlen(buf) : 0));
+ return(ice);
+ }
+ }
+ else{
+ ice = fetch_ice(stream, rawno);
+ if(!ice)
+ return(NULL);
+
+ if(ice->ifield && ice->color_lookup_done && ice->widths_done){
+#ifdef DEBUG
+ char buf[MAX_SCREEN_COLS+1];
+ simple_index_line(buf, sizeof(buf), ice, msgno);
+#endif
+ dprint((9, "Hit: Returning %p -> <%s (%d)\n",
+ ice,
+ buf[0] ? buf : "?",
+ buf[0] ? strlen(buf) : 0));
+ return(ice);
+ }
+ }
+
+ /*
+ * If we are in THRD_INDX() and the width changed we don't currently
+ * have a method of fixing just the widths and print_format strings.
+ * Instead, we clear the index cache entry and start over.
+ */
+ if(THRD_INDX() && ice && ice->tice && ice->tice->ifield
+ && !ice->tice->widths_done){
+ clear_ice(&ice->tice);
+ }
+
+ /*
+ * Fetch everything we need to start filling in the index line
+ * explicitly via mail_fetch_overview. On an nntp stream
+ * this has the effect of building the index lines in the
+ * load_overview callback. Under IMAP we're either getting
+ * the envelope data via the imap_envelope callback or
+ * preloading the cache. Either way, we're getting exactly
+ * what we want rather than relying on linear lookahead sort
+ * of prefetch...
+ */
+ if(!(fetched && *fetched) && index_in_overview(stream)
+ && ((THRD_INDX() && !(ice->tice && ice->tice->ifield))
+ || (!THRD_INDX() && !ice->ifield))){
+ char *seq;
+ int count;
+ MESSAGECACHE *mc;
+ PINETHRD_S *thrd;
+
+ if(fetched)
+ (*fetched)++;
+
+ /* clear sequence bits */
+ for(n = 1L; n <= stream->nmsgs; n++)
+ if((mc = mail_elt(stream, n)) != NULL)
+ mc->sequence = 0;
+
+ /*
+ * Light interesting bits
+ * NOTE: not set above because m2raw's cheaper
+ * than raw2m for every message
+ */
+
+ /*
+ * Unfortunately, it is expensive to calculate visible pages
+ * in thread index if we are zoomed, so we don't try.
+ */
+ if(THRD_INDX() && any_lflagged(msgmap, MN_HIDE))
+ visible = msgmap->visible_threads;
+ else if(THREADING() && sp_viewing_a_thread(stream)){
+ /*
+ * We know that all visible messages in the thread are marked
+ * with MN_CHID2.
+ */
+ for(visible = 0L, n = top_msgno;
+ visible < msgcount && n <= mn_get_total(msgmap);
+ n++){
+
+ if(!get_lflag(stream, msgmap, n, MN_CHID2))
+ break;
+
+ if(!msgline_hidden(stream, msgmap, n, 0))
+ visible++;
+ }
+
+ }
+ else
+ visible = mn_get_total(msgmap)
+ - any_lflagged(msgmap, MN_HIDE|MN_CHID);
+
+ limit = MIN(visible, msgcount);
+
+ if(THRD_INDX()){
+ count = i = 0;
+
+ /*
+ * First add the msgno we're asking for in case it
+ * isn't visible.
+ */
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, msgno));
+ if(msgno <= mn_get_total(msgmap)
+ && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
+ count += mark_msgs_in_thread(stream, thrd, msgmap);
+ }
+
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, top_msgno));
+
+ /*
+ * Loop through visible threads, marking them for fetching.
+ * Stop at end of screen or sooner if we run out of visible
+ * threads.
+ */
+ while(thrd){
+ n = mn_raw2m(msgmap, thrd->rawno);
+ if(n >= msgno
+ && n <= mn_get_total(msgmap)
+ && (!(ic=fetch_ice(stream,thrd->rawno)) || !(ic=ic->tice) || !ic->ifield)){
+ count += mark_msgs_in_thread(stream, thrd, msgmap);
+ }
+
+ if(++i >= limit)
+ break;
+
+ /* find next thread which is visible */
+ do{
+ if(mn_get_revsort(msgmap) && thrd->prevthd)
+ thrd = fetch_thread(stream, thrd->prevthd);
+ else if(!mn_get_revsort(msgmap) && thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ } while(thrd
+ && msgline_hidden(stream, msgmap,
+ mn_raw2m(msgmap, thrd->rawno), 0));
+ }
+ }
+ else{
+ count = i = 0;
+
+ /*
+ * First add the msgno we're asking for in case it
+ * isn't visible.
+ */
+ if(msgno > 0L && msgno <= mn_get_total(msgmap)
+ && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,msgno)))) || !ic->ifield)){
+ if((thrd = fetch_thread(stream, rawno)) != NULL){
+ /*
+ * If we're doing a MUTTLIKE display the index line
+ * may depend on the thread parent, and grandparent,
+ * and further back. So just fetch the whole thread
+ * in that case.
+ */
+ if(THREADING()
+ && ps_global->thread_disp_style == THREAD_MUTTLIKE
+ && thrd->top)
+ thrd = fetch_thread(stream, thrd->top);
+
+ count += mark_msgs_in_thread(stream, thrd, msgmap);
+ }
+ else if(rawno > 0L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream,rawno))
+ && !mc->private.msg.env){
+ mc->sequence = 1;
+ count++;
+ }
+ }
+
+ n = top_msgno;
+ while(1){
+ if(n >= msgno
+ && n <= mn_get_total(msgmap)
+ && (!(ic=fetch_ice(stream, (rawno=mn_m2raw(msgmap,n)))) || !ic->ifield)){
+ if((thrd = fetch_thread(stream, rawno)) != NULL){
+ /*
+ * If we're doing a MUTTLIKE display the index line
+ * may depend on the thread parent, and grandparent,
+ * and further back. So just fetch the whole thread
+ * in that case.
+ */
+ if(THREADING()
+ && ps_global->thread_disp_style == THREAD_MUTTLIKE
+ && thrd->top)
+ thrd = fetch_thread(stream, thrd->top);
+
+ count += mark_msgs_in_thread(stream, thrd, msgmap);
+ }
+ else if(rawno > 0L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream,rawno))
+ && !mc->private.msg.env){
+ mc->sequence = 1;
+ count++;
+ }
+ }
+
+ if(++i >= limit)
+ break;
+
+ /* find next n which is visible */
+ while(++n <= mn_get_total(msgmap)
+ && msgline_hidden(stream, msgmap, n, 0))
+ ;
+ }
+ }
+
+ if(count){
+ seq = build_sequence(stream, NULL, NULL);
+ if(seq){
+ ps_global->dont_count_flagchanges = 1;
+ mail_fetch_overview_sequence(stream, seq,
+ (stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap"))
+ ? NULL : load_overview);
+ ps_global->dont_count_flagchanges = 0;
+ fs_give((void **) &seq);
+ }
+ }
+
+ /*
+ * reassign ice from the cache as it may've been built
+ * within the overview callback or it may have become stale
+ * in the prior sequence bit setting loop ...
+ */
+ rawno = mn_m2raw(msgmap, msgno);
+ ice = fetch_ice(stream, rawno);
+ if(!ice)
+ return(NULL);
+ }
+
+ if((THRD_INDX() && !(ice->tice && ice->tice->ifield))
+ || (!THRD_INDX() && !ice->ifield)){
+ INDEXDATA_S idata;
+
+ /*
+ * With pre-fetching/callback-formatting done and no success,
+ * fall into formatting the requested line...
+ */
+ memset(&idata, 0, sizeof(INDEXDATA_S));
+ idata.stream = stream;
+ idata.msgno = msgno;
+ idata.rawno = mn_m2raw(msgmap, msgno);
+ if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, idata.rawno))){
+ idata.size = mc->rfc822_size;
+ index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
+ }
+ else
+ idata.bogus = 2;
+
+ ice = (*format_index_line)(&idata);
+ if(!ice)
+ return(NULL);
+ }
+
+ /*
+ * If needed, reset the print_format strings so that they add up to
+ * the right total width. The reset width functionality isn't implemented
+ * for THRD_INDX() so we are just doing a complete rebuild in that
+ * case. This is driven by the clear_ice() call in clear_index_cache_ent()
+ * so it should never be the case that THRD_INDX() is true and only
+ * widths_done needs to be fixed.
+ */
+ if((!THRD_INDX() && ice->ifield && !ice->widths_done)){
+ ICE_S *working_ice;
+ IFIELD_S *ifield;
+ INDEX_COL_S *cdesc;
+
+ if(need_format_setup(stream))
+ setup_header_widths(stream);
+
+ if(THRD_INDX())
+ working_ice = ice ? ice->tice : NULL;
+ else
+ working_ice = ice;
+
+ if(working_ice){
+ /*
+ * First fix the ifield widths. The cdescs with nonzero widths
+ * should correspond to the ifields that are defined.
+ */
+ ifield = working_ice->ifield;
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing && ifield; cdesc++){
+ if(cdesc->width){
+ if(cdesc->ctype != ifield->ctype){
+ dprint((1, "build_header_work(%ld): cdesc->ctype=%d != ifield->ctype=%d NOT SUPPOSED TO HAPPEN!\n", msgno, (int) cdesc->ctype, (int) ifield->ctype));
+ assert(0);
+ }
+
+ ifield->width = cdesc->width;
+ ifield = ifield->next;
+ }
+ }
+
+ /* fix the print_format strings and widths */
+ for(ifield = working_ice->ifield; ifield; ifield = ifield->next)
+ set_ielem_widths_in_field(ifield);
+
+ working_ice->widths_done = 1;
+ }
+ }
+
+ if(THRD_INDX() && ice->tice)
+ ice->tice->color_lookup_done = 1;
+
+ /*
+ * Look for a color for this line (and other lines in the current
+ * view). This does a SEARCH for each role which has a color until
+ * it finds a match. This will be satisfied by the c-client
+ * cache created by the mail_fetch_overview above if it is a header
+ * search.
+ */
+ if(!THRD_INDX() && !ice->color_lookup_done){
+ COLOR_PAIR *linecolor;
+ SEARCHSET *ss, *s;
+ ICE_S *ic;
+ PAT_STATE *pstate = NULL;
+
+ if(pico_usingcolor()){
+ if(limit < 0L){
+ if(THREADING() && sp_viewing_a_thread(stream)){
+ for(visible = 0L, n = top_msgno;
+ visible < msgcount && n <= mn_get_total(msgmap);
+ n++){
+
+ if(!get_lflag(stream, msgmap, n, MN_CHID2))
+ break;
+
+ if(!msgline_hidden(stream, msgmap, n, 0))
+ visible++;
+ }
+
+ }
+ else
+ visible = mn_get_total(msgmap)
+ - any_lflagged(msgmap, MN_HIDE|MN_CHID);
+
+ limit = MIN(visible, msgcount);
+ }
+ /* clear sequence bits */
+ for(n = 1L; n <= stream->nmsgs; n++)
+ if((mc = mail_elt(stream, n)) != NULL)
+ mc->sequence = 0;
+
+ cnt = i = 0;
+ n = top_msgno;
+ while(1){
+ if(n >= msgno
+ && n <= mn_get_total(msgmap)
+ && (!(ic=fetch_ice(stream,(rawno = mn_m2raw(msgmap, n)))) || !ic->color_lookup_done)){
+
+ if(rawno >= 1L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))){
+ mc->sequence = 1;
+ cnt++;
+ }
+ }
+
+ if(++i >= limit)
+ break;
+
+ /* find next n which is visible */
+ while(++n <= mn_get_total(msgmap)
+ && msgline_hidden(stream, msgmap, n, 0))
+ ;
+ }
+
+ /*
+ * Why is there a loop here? The first call to get_index_line_color
+ * will return a set of messages which match one of the roles.
+ * Then, we eliminate those messages from the search set and try
+ * again. This time we'd get past that role and into a different
+ * role. Because of that, we hang onto the state and don't reset
+ * to the first_pattern on the second and subsequent times
+ * through the loop, avoiding fruitless match_pattern calls in
+ * get_index_line_color.
+ * Before the first call, pstate should be set to NULL.
+ */
+ while(cnt > 0L){
+ ss = build_searchset(stream);
+ if(ss){
+ int colormatch;
+
+ linecolor = NULL;
+ colormatch = get_index_line_color(stream, ss, &pstate,
+ &linecolor);
+
+ /*
+ * Assign this color to all matched msgno's and
+ * turn off the sequence bit so we won't check
+ * for them again.
+ */
+ if(colormatch){
+ for(s = ss; s; s = s->next){
+ for(n = s->first; n <= s->last; n++){
+ if(n >= 1L && n <= stream->nmsgs
+ && (mc = mail_elt(stream, n))
+ && mc->searched){
+ cnt--;
+ mc->sequence = 0;
+ ic = fetch_ice(stream, n);
+ if(ic){
+ ic->color_lookup_done = 1;
+ if(linecolor)
+ ic->linecolor = new_color_pair(linecolor->fg,
+ linecolor->bg);
+ }
+ }
+ }
+ }
+
+ if(linecolor)
+ free_color_pair(&linecolor);
+ }
+ else{
+ /* have to mark the rest of the lookups done */
+ for(s = ss; s && cnt > 0; s = s->next){
+ for(n = s->first; n <= s->last && cnt > 0; n++){
+ if(n >= 1L && n <= stream->nmsgs
+ && (mc = mail_elt(stream, n))
+ && mc->sequence){
+ cnt--;
+ ic = fetch_ice(stream, n);
+ if(ic)
+ ic->color_lookup_done = 1;
+ }
+ }
+ }
+
+ /* just making sure */
+ cnt = 0L;
+ }
+
+ mail_free_searchset(&ss);
+ }
+ else
+ cnt = 0L;
+ }
+
+ ice = fetch_ice(stream, mn_m2raw(msgmap, msgno));
+ }
+ else
+ ice->color_lookup_done = 1;
+ }
+
+ return(ice); /* Return formatted index data */
+}
+
+
+int
+day_of_week(struct date *d)
+{
+ int m, y;
+
+ m = d->month;
+ y = d->year;
+ if(m <= 2){
+ m += 9;
+ y--;
+ }
+ else
+ m -= 3; /* March is month 0 */
+
+ return((d->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100))%7);
+}
+
+
+static int daytab[2][13] = {
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
+
+int
+day_of_year(struct date *d)
+{
+ int i, leap, doy;
+
+ if(d->year <= 0 || d->month < 1 || d->month > 12)
+ return(-1);
+
+ doy = d->day;
+ leap = (d->year%4 == 0 && d->year%100 != 0) || d->year%400 == 0;
+ for(i = 1; i < d->month; i++)
+ doy += daytab[leap][i];
+
+ return(doy);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Format a string summarizing the message header for index on screen
+
+ Args: buffer -- buffer to place formatted line
+ idata -- snot it takes to format the line
+
+ Result: returns pointer given buffer IF entry formatted
+ else NULL if there was a problem (but the buffer is
+ still suitable for display)
+ ----*/
+ICE_S *
+format_index_index_line(INDEXDATA_S *idata)
+{
+ char str[BIGWIDTH+1], to_us, status, *field,
+ *p, *newsgroups;
+ int i, collapsed = 0, start, fromfield;
+ long l, score;
+ BODY *body = NULL;
+ MESSAGECACHE *mc;
+ ADDRESS *addr, *toaddr, *ccaddr, *last_to;
+ PINETHRD_S *thrd = NULL;
+ INDEX_COL_S *cdesc = NULL;
+ ICE_S *ice, **icep;
+ IFIELD_S *ifield;
+ IELEM_S *ielem;
+ struct variable *vars = ps_global->vars;
+
+ dprint((8, "=== format_index_line(msgno=%ld,rawno=%ld) ===\n",
+ idata ? idata->msgno : -1, idata ? idata->rawno : -1));
+
+
+ ice = fetch_ice(idata->stream, idata->rawno);
+ if(!ice)
+ return(NULL);
+
+ free_ifield(&ice->ifield);
+
+ /*
+ * Operate on a temporary copy of ice. The reason for this
+ * is that we may end up causing a pine_mail_fetchenvelope() call
+ * (e.g., in to_us_symbol_for_thread()) that causes an mm_flags()
+ * and mm_flags may do a clear_ice(), freeing the ice we are working
+ * on out from under us. We try to fetch everything we need in
+ * build_header_work() but c-client will short-circuit our request
+ * if we already got the raw header for some reason. One possible
+ * reason is a categorizer command in a filter. In that case
+ * we still need a fetch fast to get the rest of the envelope data.
+ */
+ ice = copy_ice(ice);
+
+ /* is this a collapsed thread index line? */
+ if(!idata->bogus && THREADING()){
+ thrd = fetch_thread(idata->stream, idata->rawno);
+ collapsed = thrd && thrd->next
+ && get_lflag(idata->stream, NULL,
+ idata->rawno, MN_COLL);
+ }
+
+ /* calculate contents of the required fields */
+ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++)
+ if(cdesc->width){
+ memset(str, 0, sizeof(str));
+ ifield = new_ifield(&ice->ifield);
+ ifield->ctype = cdesc->ctype;
+ ifield->width = cdesc->width;
+ fromfield = 0;
+
+ if(idata->bogus){
+ if(cdesc->ctype == iMessNo)
+ snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
+ else if(idata->bogus < 2 && (cdesc->ctype == iSubject
+ || cdesc->ctype == iSubjectText
+ || cdesc->ctype == iSubjKey
+ || cdesc->ctype == iSubjKeyText
+ || cdesc->ctype == iSubjKeyInit
+ || cdesc->ctype == iSubjKeyInitText))
+ snprintf(str, sizeof(str), "%s", _("[ No Message Text Available ]"));
+ }
+ else
+ switch(cdesc->ctype){
+ case iStatus:
+ to_us = status = ' ';
+ if(collapsed){
+ thrd = fetch_thread(idata->stream, idata->rawno);
+ to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
+ status = status_symbol_for_thread(idata->stream, thrd,
+ cdesc->ctype);
+ }
+ else{
+ if((mc=mail_elt(idata->stream,idata->rawno)) && mc->flagged)
+ to_us = '*'; /* simple */
+ else if(!IS_NEWS(idata->stream)){
+ for(addr = fetch_to(idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ ice->to_us = 1;
+ if(to_us == ' ')
+ to_us = '+';
+
+ break;
+ }
+
+ if(to_us != '+' && resent_to_us(idata)){
+ ice->to_us = 1;
+ if(to_us == ' ')
+ to_us = '+';
+ }
+
+ if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
+ for(addr = fetch_cc(idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ ice->cc_us = 1;
+ to_us = '-';
+ break;
+ }
+ }
+
+ status = (!idata->stream || !IS_NEWS(idata->stream)
+ || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
+ ? 'N' : ' ';
+
+ if(mc->seen)
+ status = ' ';
+
+ if(user_flag_is_set(idata->stream, idata->rawno, FORWARDED_FLAG))
+ status = 'F';
+
+ if(mc->answered)
+ status = 'A';
+
+ if(mc->deleted)
+ status = 'D';
+ }
+
+ snprintf(str, sizeof(str), "%c %c", to_us, status);
+
+ ifield->leftadj = 1;
+ for(i = 0; i < 3; i++){
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = (char *) fs_get(2 * sizeof(char));
+ ielem->data[0] = str[i];
+ ielem->data[1] = '\0';
+ ielem->datalen = 1;
+ set_print_format(ielem, 1, ifield->leftadj);
+ }
+
+ if(pico_usingcolor()){
+
+ if(str[0] == '*'){
+ if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
+ ielem = ifield->ielem;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
+ }
+ }
+ else if(str[0] == '+' || str[0] == '-'){
+ if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
+ ielem = ifield->ielem;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
+ }
+ }
+
+ if(str[2] == 'D'){
+ if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
+ }
+ }
+ else if(str[2] == 'A'){
+ if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
+ }
+ }
+ else if(str[2] == 'F'){
+ if(VAR_IND_FWD_FORE_COLOR && VAR_IND_FWD_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_FWD_FORE_COLOR, VAR_IND_FWD_BACK_COLOR);
+ }
+ }
+ else if(str[2] == 'N'){
+ if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
+ }
+ }
+ }
+
+ break;
+
+ case iFStatus:
+ case iIStatus:
+ case iSIStatus:
+ {
+ char new, answered, deleted, flagged;
+
+ if(collapsed){
+ thrd = fetch_thread(idata->stream, idata->rawno);
+ to_us = to_us_symbol_for_thread(idata->stream, thrd, 0);
+ }
+ else{
+ to_us = ' ';
+ if(!IS_NEWS(idata->stream)){
+ for(addr = fetch_to(idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ to_us = '+';
+ break;
+ }
+
+ if(to_us == ' ' && resent_to_us(idata))
+ to_us = '+';
+
+ if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
+ for(addr = fetch_cc(idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ to_us = '-';
+ break;
+ }
+ }
+ }
+
+ new = answered = deleted = flagged = ' ';
+
+ if(collapsed){
+ unsigned long save_branch, cnt, tot_in_thrd;
+
+ /*
+ * Branch is a sibling, not part of the thread, so
+ * don't consider it when displaying this line.
+ */
+ save_branch = thrd->branch;
+ thrd->branch = 0L;
+
+ tot_in_thrd = count_flags_in_thread(idata->stream, thrd,
+ F_NONE);
+
+ cnt = count_flags_in_thread(idata->stream, thrd, F_DEL);
+ if(cnt)
+ deleted = (cnt == tot_in_thrd) ? 'D' : 'd';
+
+ cnt = count_flags_in_thread(idata->stream, thrd, F_ANS);
+ if(cnt)
+ answered = (cnt == tot_in_thrd) ? 'A' : 'a';
+
+ /* no lower case *, same thing for some or all */
+ if(count_flags_in_thread(idata->stream, thrd, F_FLAG))
+ flagged = '*';
+
+ new = status_symbol_for_thread(idata->stream, thrd,
+ cdesc->ctype);
+
+ thrd->branch = save_branch;
+ }
+ else{
+ mc = (idata->rawno > 0L && idata->stream
+ && idata->rawno <= idata->stream->nmsgs)
+ ? mail_elt(idata->stream, idata->rawno) : NULL;
+ if(mc && mc->valid){
+ if(cdesc->ctype == iIStatus || cdesc->ctype == iSIStatus){
+ if(mc->recent)
+ new = mc->seen ? 'R' : 'N';
+ else if (!mc->seen)
+ new = 'U';
+ }
+ else if(!mc->seen
+ && (!IS_NEWS(idata->stream)
+ || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
+ new = 'N';
+
+ if(mc->answered)
+ answered = 'A';
+
+ if(mc->deleted)
+ deleted = 'D';
+
+ if(mc->flagged)
+ flagged = '*';
+ }
+ }
+
+ snprintf(str, sizeof(str), "%c %c%c%c%c", to_us, flagged, new,
+ answered, deleted);
+
+ if(cdesc->ctype == iSIStatus)
+ start = 2;
+ else
+ start = 0;
+
+ ifield->leftadj = 1;
+ for(i = start; i < 6; i++){
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = (char *) fs_get(2 * sizeof(char));
+ ielem->data[0] = str[i];
+ ielem->data[1] = '\0';
+ ielem->datalen = 1;
+ set_print_format(ielem, 1, ifield->leftadj);
+ }
+
+ if(pico_usingcolor()){
+
+ if(str[0] == '+' || str[0] == '-'){
+ if(start == 0
+ && VAR_IND_PLUS_FORE_COLOR
+ && VAR_IND_PLUS_BACK_COLOR){
+ ielem = ifield->ielem;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR, VAR_IND_PLUS_BACK_COLOR);
+ }
+ }
+
+ if(str[2] == '*'){
+ if(VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem;
+ else
+ ielem = ifield->ielem->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR);
+ }
+ }
+
+ if(str[3] == 'N' || str[3] == 'n'){
+ if(VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem->next;
+ else
+ ielem = ifield->ielem->next->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR, VAR_IND_NEW_BACK_COLOR);
+ }
+ }
+ else if(str[3] == 'R' || str[3] == 'r'){
+ if(VAR_IND_REC_FORE_COLOR && VAR_IND_REC_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem->next;
+ else
+ ielem = ifield->ielem->next->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_REC_FORE_COLOR, VAR_IND_REC_BACK_COLOR);
+ }
+ }
+ else if(str[3] == 'U' || str[3] == 'u'){
+ if(VAR_IND_UNS_FORE_COLOR && VAR_IND_UNS_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem->next;
+ else
+ ielem = ifield->ielem->next->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_UNS_FORE_COLOR, VAR_IND_UNS_BACK_COLOR);
+ }
+ }
+
+ if(str[4] == 'A' || str[4] == 'a'){
+ if(VAR_IND_ANS_FORE_COLOR && VAR_IND_ANS_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem->next->next;
+ else
+ ielem = ifield->ielem->next->next->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_ANS_FORE_COLOR, VAR_IND_ANS_BACK_COLOR);
+ }
+ }
+
+ if(str[5] == 'D' || str[5] == 'd'){
+ if(VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
+ if(start == 2)
+ ielem = ifield->ielem->next->next->next;
+ else
+ ielem = ifield->ielem->next->next->next->next->next;
+
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR, VAR_IND_DEL_BACK_COLOR);
+ }
+ }
+ }
+ }
+
+ break;
+
+ case iMessNo:
+ /*
+ * This is a special case. The message number is
+ * generated on the fly in the painting routine.
+ * But the data array is allocated here in case it
+ * is useful for the paint routine.
+ */
+ snprintf(str, sizeof(str), "%*.*s", ifield->width, ifield->width, " ");
+ break;
+
+ case iArrow:
+ snprintf(str, sizeof(str), "%-*.*s", ifield->width, ifield->width, " ");
+ if(VAR_IND_ARR_FORE_COLOR && VAR_IND_ARR_BACK_COLOR){
+ ifield->leftadj = 1;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(str);
+ ielem->datalen = strlen(str);
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_ARR_FORE_COLOR,
+ VAR_IND_ARR_BACK_COLOR);
+ }
+
+ break;
+
+ case iScore:
+ score = get_msg_score(idata->stream, idata->rawno);
+ if(score == SCORE_UNDEF){
+ SEARCHSET *ss = NULL;
+
+ ss = mail_newsearchset();
+ ss->first = ss->last = (unsigned long) idata->rawno;
+ if(ss){
+ /*
+ * This looks like it might be expensive to get the
+ * score for each message when needed but it shouldn't
+ * be too bad because we know we have the envelope
+ * data cached. We can't calculate all of the scores
+ * we need for the visible messages right here in
+ * one fell swoop because we don't have the other
+ * envelopes yet. And we can't get the other
+ * envelopes at this point because we may be in
+ * the middle of a c-client callback (pine_imap_env).
+ * (Actually we could, because we know whether or
+ * not we're in the callback because of the no_fetch
+ * parameter.)
+ * We have another problem if the score rules depend
+ * on something other than envelope data. I guess they
+ * only do that if they have an alltext (search the
+ * text of the message) definition. So, we're going
+ * to pass no_fetch to calculate_scores so that it
+ * can return an error if we need the text data but
+ * can't get it because of no_fetch. Setting bogus
+ * will cause us to do the scores calculation later
+ * when we are no longer in the callback.
+ */
+ idata->bogus =
+ (calculate_some_scores(idata->stream,
+ ss, idata->no_fetch) == 0)
+ ? 1 : 0;
+ score = get_msg_score(idata->stream, idata->rawno);
+ mail_free_searchset(&ss);
+ }
+ }
+
+ snprintf(str, sizeof(str), "%ld", score != SCORE_UNDEF ? score : 0L);
+ break;
+
+ case iDate: case iMonAbb: case iLDate:
+ case iSDate: case iSTime:
+ case iS1Date: case iS2Date: case iS3Date: case iS4Date:
+ case iDateIso: case iDateIsoS: case iTime24: case iTime12:
+ case iSDateIsoS: case iSDateIso:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTime:
+ case iSDateTimeIsoS: case iSDateTimeIso:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTime24:
+ case iSDateTimeIsoS24: case iSDateTimeIso24:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ case iTimezone: case iYear: case iYear2Digit:
+ case iRDate: case iDay: case iDay2Digit: case iMon2Digit:
+ case iDayOrdinal: case iMon: case iMonLong:
+ case iDayOfWeekAbb: case iDayOfWeek:
+ case iPrefDate: case iPrefTime: case iPrefDateTime:
+ date_str(fetch_date(idata), cdesc->ctype, 0, str, sizeof(str), cdesc->monabb_width);
+ break;
+
+ case iFromTo:
+ case iFromToNotNews:
+ case iFrom:
+ case iAddress:
+ case iMailbox:
+ fromfield++;
+ from_str(cdesc->ctype, idata, str, sizeof(str), ice);
+ break;
+
+ case iTo:
+ if(((field = ((addr = fetch_to(idata))
+ ? "To"
+ : (addr = fetch_cc(idata))
+ ? "Cc"
+ : NULL))
+ && !set_index_addr(idata, field, addr, NULL, BIGWIDTH, str))
+ || !field)
+ if((newsgroups = fetch_newsgroups(idata)) != NULL)
+ snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, newsgroups);
+
+ break;
+
+ case iCc:
+ set_index_addr(idata, "Cc", fetch_cc(idata), NULL, BIGWIDTH, str);
+ break;
+
+ case iRecips:
+ toaddr = fetch_to(idata);
+ ccaddr = fetch_cc(idata);
+ for(last_to = toaddr;
+ last_to && last_to->next;
+ last_to = last_to->next)
+ ;
+
+ /* point end of to list temporarily at cc list */
+ if(last_to)
+ last_to->next = ccaddr;
+
+ set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
+
+ if(last_to)
+ last_to->next = NULL;
+
+ break;
+
+ case iSender:
+ fromfield++;
+ if((addr = fetch_sender(idata)) != NULL)
+ set_index_addr(idata, "Sender", addr, NULL, BIGWIDTH, str);
+
+ break;
+
+ case iInit:
+ {ADDRESS *addr;
+
+ if((addr = fetch_from(idata)) && addr->personal){
+ char *name, *initials = NULL;
+
+ name = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, addr->personal);
+ if(name == addr->personal){
+ strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
+ tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
+ name = (char *) tmp_20k_buf;
+ }
+
+ if(name && *name){
+ initials = reply_quote_initials(name);
+ snprintf(str, sizeof(str), "%-.*s", BIGWIDTH, initials);
+ }
+ }
+ }
+
+ break;
+
+ case iSize:
+ /* 0 ... 9999 */
+ if((l = fetch_size(idata)) < 10*1000L)
+ snprintf(str, sizeof(str), "(%lu)", l);
+ /* 10K ... 999K */
+ else if(l < 1000L*1000L - 1000L/2){
+ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%luK)", l);
+ }
+ /* 1.0M ... 99.9M */
+ else if(l < 1000L*100L*1000L - 100L*1000L/2){
+ l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
+ }
+ /* 100M ... 2000M */
+ else if(l <= 2*1000L*1000L*1000L){
+ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%luM)", l);
+ }
+ else
+ snprintf(str, sizeof(str), "(HUGE!)");
+
+ break;
+
+ case iSizeComma:
+ /* 0 ... 99,999 */
+ if((l = fetch_size(idata)) < 100*1000L)
+ snprintf(str, sizeof(str), "(%s)", comatose(l));
+ /* 100K ... 9,999K */
+ else if(l < 10L*1000L*1000L - 1000L/2){
+ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%sK)", comatose(l));
+ }
+ /* 10.0M ... 999.9M */
+ else if(l < 1000L*1000L*1000L - 100L*1000L/2){
+ l = l/(100L*1000L) + (l%(100L*1000L) >= (100*1000L/2)
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%lu.%luM)", l/10L, l % 10L);
+ }
+ /* 1,000M ... 2,000M */
+ else if(l <= 2*1000L*1000L*1000L){
+ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%sM)", comatose(l));
+ }
+ else
+ snprintf(str, sizeof(str), "(HUGE!)");
+
+ break;
+
+ case iSizeNarrow:
+ /* 0 ... 999 */
+ if((l = fetch_size(idata)) < 1000L)
+ snprintf(str, sizeof(str), "(%lu)", l);
+ /* 1K ... 99K */
+ else if(l < 100L*1000L - 1000L/2){
+ l = l/1000L + (l%1000L >= 1000L/2 ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%luK)", l);
+ }
+ /* .1M ... .9M */
+ else if(l < 1000L*1000L - 100L*1000L/2){
+ l = l/(100L*1000L) + (l%(100L*1000L) >= 100L*1000L/2
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(.%luM)", l);
+ }
+ /* 1M ... 99M */
+ else if(l < 1000L*100L*1000L - 1000L*1000L/2){
+ l = l/(1000L*1000L) + (l%(1000L*1000L) >= (1000L*1000L/2)
+ ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%luM)", l);
+ }
+ /* .1G ... .9G */
+ else if(l < 1000L*1000L*1000L - 100L*1000L*1000L/2){
+ l = l/(100L*1000L*1000L) + (l%(100L*1000L*1000L) >=
+ (100L*1000L*1000L/2) ? 1L : 0L);
+ snprintf(str, sizeof(str), "(.%luG)", l);
+ }
+ /* 1G ... 2G */
+ else if(l <= 2*1000L*1000L*1000L){
+ l = l/(1000L*1000L*1000L) + (l%(1000L*1000L*1000L) >=
+ (1000L*1000L*1000L/2) ? 1L : 0L);
+ snprintf(str, sizeof(str), "(%luG)", l);
+ }
+ else
+ snprintf(str, sizeof(str), "(HUGE!)");
+
+ break;
+
+ /* From Carl Jacobsen <carl@ucsd.edu> */
+ case iKSize:
+ l = fetch_size(idata);
+ l = (l / 1024L) + (l % 1024L != 0 ? 1 : 0);
+
+ if(l < 1024L) { /* 0k .. 1023k */
+ snprintf(str, sizeof(str), "(%luk)", l);
+
+ } else if (l < 100L * 1024L){ /* 1.0M .. 99.9M */
+ snprintf(str, sizeof(str), "(%lu.M)", (l * 10L) / 1024L);
+ if ((p = strchr(str, '.')) != NULL) {
+ p--; p[1] = p[0]; p[0] = '.'; /* swap last digit & . */
+ }
+ } else if (l <= 2L * 1024L * 1024L) { /* 100M .. 2048 */
+ snprintf(str, sizeof(str), "(%luM)", l / 1024L);
+ } else {
+ snprintf(str, sizeof(str), "(HUGE!)");
+ }
+
+ break;
+
+ case iDescripSize:
+ if((body = fetch_body(idata)) != NULL)
+ switch(body->type){
+ case TYPETEXT:
+ {
+ mc = (idata->rawno > 0L && idata->stream
+ && idata->rawno <= idata->stream->nmsgs)
+ ? mail_elt(idata->stream, idata->rawno) : NULL;
+ if(mc && mc->rfc822_size < 6000)
+ snprintf(str, sizeof(str), "(short )");
+ else if(mc && mc->rfc822_size < 25000)
+ snprintf(str, sizeof(str), "(medium )");
+ else if(mc && mc->rfc822_size < 100000)
+ snprintf(str, sizeof(str), "(long )");
+ else
+ snprintf(str, sizeof(str), "(huge )");
+ }
+
+ break;
+
+ case TYPEMULTIPART:
+ if(strucmp(body->subtype, "MIXED") == 0){
+ int x;
+
+ x = body->nested.part
+ ? body->nested.part->body.type
+ : TYPETEXT + 1000;
+ switch(x){
+ case TYPETEXT:
+ if(body->nested.part->body.size.bytes < 6000)
+ snprintf(str, sizeof(str), "(short+ )");
+ else if(body->nested.part->body.size.bytes
+ < 25000)
+ snprintf(str, sizeof(str), "(medium+)");
+ else if(body->nested.part->body.size.bytes
+ < 100000)
+ snprintf(str, sizeof(str), "(long+ )");
+ else
+ snprintf(str, sizeof(str), "(huge+ )");
+ break;
+
+ default:
+ snprintf(str, sizeof(str), "(multi )");
+ break;
+ }
+ }
+ else if(strucmp(body->subtype, "DIGEST") == 0)
+ snprintf(str, sizeof(str), "(digest )");
+ else if(strucmp(body->subtype, "ALTERNATIVE") == 0)
+ snprintf(str, sizeof(str), "(mul/alt)");
+ else if(strucmp(body->subtype, "PARALLEL") == 0)
+ snprintf(str, sizeof(str), "(mul/par)");
+ else
+ snprintf(str, sizeof(str), "(multi )");
+
+ break;
+
+ case TYPEMESSAGE:
+ snprintf(str, sizeof(str), "(message)");
+ break;
+
+ case TYPEAPPLICATION:
+ snprintf(str, sizeof(str), "(applica)");
+ break;
+
+ case TYPEAUDIO:
+ snprintf(str, sizeof(str), "(audio )");
+ break;
+
+ case TYPEIMAGE:
+ snprintf(str, sizeof(str), "(image )");
+ break;
+
+ case TYPEVIDEO:
+ snprintf(str, sizeof(str), "(video )");
+ break;
+
+ default:
+ snprintf(str, sizeof(str), "(other )");
+ break;
+ }
+
+ break;
+
+ case iAtt:
+ str[0] = SPACE;
+ str[1] = '\0';
+ if((body = fetch_body(idata)) &&
+ body->type == TYPEMULTIPART &&
+ strucmp(body->subtype, "ALTERNATIVE") != 0){
+ PART *part;
+ int atts = 0;
+
+ part = body->nested.part; /* 1st part, don't count */
+ while(part && part->next && atts < 10){
+ atts++;
+ part = part->next;
+ }
+
+ if(atts > 9)
+ str[0] = '*';
+ else if(atts > 0)
+ str[0] = '0' + atts;
+ }
+
+ break;
+
+ case iSubject:
+ subj_str(idata, str, sizeof(str), NoKW, 0, ice);
+ break;
+
+ case iSubjectText:
+ subj_str(idata, str, sizeof(str), NoKW, 1, ice);
+ break;
+
+ case iSubjKey:
+ subj_str(idata, str, sizeof(str), KW, 0, ice);
+ break;
+
+ case iSubjKeyText:
+ subj_str(idata, str, sizeof(str), KW, 1, ice);
+ break;
+
+ case iSubjKeyInit:
+ subj_str(idata, str, sizeof(str), KWInit, 0, ice);
+ break;
+
+ case iSubjKeyInitText:
+ subj_str(idata, str, sizeof(str), KWInit, 1, ice);
+ break;
+
+ case iOpeningText:
+ case iOpeningTextNQ:
+ if(idata->no_fetch)
+ idata->bogus = 1;
+ else{
+ char *first_text;
+
+ first_text = fetch_firsttext(idata, cdesc->ctype == iOpeningTextNQ);
+
+ if(first_text){
+ strncpy(str, first_text, BIGWIDTH);
+ str[BIGWIDTH] = '\0';
+ fs_give((void **) &first_text);
+ }
+ }
+
+ break;
+
+ case iKey:
+ key_str(idata, KW, ice);
+ break;
+
+ case iKeyInit:
+ key_str(idata, KWInit, ice);
+ break;
+
+ case iNews:
+ if((newsgroups = fetch_newsgroups(idata)) != NULL){
+ strncpy(str, newsgroups, BIGWIDTH);
+ str[BIGWIDTH] = '\0';
+ }
+
+ break;
+
+ case iNewsAndTo:
+ if((newsgroups = fetch_newsgroups(idata)) != NULL)
+ strncpy(str, newsgroups, sizeof(str));
+
+ if((l = strlen(str)) < sizeof(str)){
+ if(sizeof(str) - l < 6)
+ strncpy(str+l, "...", sizeof(str)-l);
+ else{
+ if(l > 0){
+ strncpy(str+l, " and ", sizeof(str)-l);
+ set_index_addr(idata, "To", fetch_to(idata),
+ NULL, BIGWIDTH-l-5, str+l+5);
+ if(!str[l+5])
+ str[l] = '\0';
+ }
+ else
+ set_index_addr(idata, "To", fetch_to(idata),
+ NULL, BIGWIDTH, str);
+ }
+ }
+
+ break;
+
+ case iToAndNews:
+ set_index_addr(idata, "To", fetch_to(idata),
+ NULL, BIGWIDTH, str);
+ if((l = strlen(str)) < sizeof(str) &&
+ (newsgroups = fetch_newsgroups(idata))){
+ if(sizeof(str) - l < 6)
+ strncpy(str+l, "...", sizeof(str)-l);
+ else{
+ if(l > 0)
+ strncpy(str+l, " and ", sizeof(str)-l);
+
+ if(l > 0)
+ strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
+ else
+ strncpy(str, newsgroups, BIGWIDTH);
+ }
+ }
+
+ break;
+
+ case iNewsAndRecips:
+ if((newsgroups = fetch_newsgroups(idata)) != NULL)
+ strncpy(str, newsgroups, BIGWIDTH);
+
+ if((l = strlen(str)) < BIGWIDTH){
+ if(BIGWIDTH - l < 6)
+ strncpy(str+l, "...", BIGWIDTH-l);
+ else{
+ toaddr = fetch_to(idata);
+ ccaddr = fetch_cc(idata);
+ for(last_to = toaddr;
+ last_to && last_to->next;
+ last_to = last_to->next)
+ ;
+
+ /* point end of to list temporarily at cc list */
+ if(last_to)
+ last_to->next = ccaddr;
+
+ if(l > 0){
+ strncpy(str+l, " and ", sizeof(str)-l);
+ set_index_addr(idata, "To", toaddr,
+ NULL, BIGWIDTH-l-5, str+l+5);
+ if(!str[l+5])
+ str[l] = '\0';
+ }
+ else
+ set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
+
+ if(last_to)
+ last_to->next = NULL;
+ }
+ }
+
+ break;
+
+ case iRecipsAndNews:
+ toaddr = fetch_to(idata);
+ ccaddr = fetch_cc(idata);
+ for(last_to = toaddr;
+ last_to && last_to->next;
+ last_to = last_to->next)
+ ;
+
+ /* point end of to list temporarily at cc list */
+ if(last_to)
+ last_to->next = ccaddr;
+
+ set_index_addr(idata, "To", toaddr, NULL, BIGWIDTH, str);
+
+ if(last_to)
+ last_to->next = NULL;
+
+ if((l = strlen(str)) < BIGWIDTH &&
+ (newsgroups = fetch_newsgroups(idata))){
+ if(BIGWIDTH - l < 6)
+ strncpy(str+l, "...", BIGWIDTH-l);
+ else{
+ if(l > 0)
+ strncpy(str+l, " and ", sizeof(str)-l);
+
+ if(l > 0)
+ strncpy(str+l+5, newsgroups, BIGWIDTH-l-5);
+ else
+ strncpy(str, newsgroups, BIGWIDTH);
+ }
+ }
+
+ break;
+
+ case iPrio:
+ case iPrioAlpha:
+ case iPrioBang:
+ prio_str(idata, cdesc->ctype, ice);
+ break;
+
+ case iHeader:
+ header_str(idata, cdesc->hdrtok, ice);
+ break;
+
+ case iText:
+ strncpy(str, (cdesc->hdrtok && cdesc->hdrtok->hdrname) ? cdesc->hdrtok->hdrname : "", sizeof(str));
+ str[sizeof(str)-1] = '\0';
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * If the element wasn't already filled in above, do it here.
+ */
+ if(!ifield->ielem){
+ ielem = new_ielem(&ifield->ielem);
+
+ ielem->freedata = 1;
+ ielem->data = cpystr(str);
+ ielem->datalen = strlen(str);
+
+ if(fromfield && pico_usingcolor()
+ && ps_global->VAR_IND_FROM_FORE_COLOR
+ && ps_global->VAR_IND_FROM_BACK_COLOR){
+ ielem->type = eTypeCol;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_FROM_FORE_COLOR,
+ ps_global->VAR_IND_FROM_BACK_COLOR);
+ /*
+ * This space is here so that if the text does
+ * not extend all the way to the end of the field then
+ * we'll switch the color back and paint the rest of the
+ * field in the Normal color or the index line color.
+ */
+ ielem = new_ielem(&ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+ else if((cdesc->ctype == iOpeningText || cdesc->ctype == iOpeningTextNQ)
+ && pico_usingcolor()
+ && ps_global->VAR_IND_OP_FORE_COLOR
+ && ps_global->VAR_IND_OP_BACK_COLOR){
+ ielem->type = eTypeCol;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR,
+ ps_global->VAR_IND_OP_BACK_COLOR);
+ /*
+ * This space is here so that if the text does
+ * not extend all the way to the end of the field then
+ * we'll switch the color back and paint the rest of the
+ * field in the Normal color or the index line color.
+ */
+ ielem = new_ielem(&ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+
+ ifield->leftadj = (cdesc->adjustment == Left) ? 1 : 0;
+ set_ielem_widths_in_field(ifield);
+ }
+ }
+
+ ice->widths_done = 1;
+ ice->id = ice_hash(ice);
+
+ /*
+ * Now we have to put the temporary copy of ice back as the
+ * real thing.
+ */
+ icep = fetch_ice_ptr(idata->stream, idata->rawno);
+ if(icep){
+ free_ice(icep); /* free what is already there */
+ *icep = ice;
+ }
+
+ return(ice);
+}
+
+
+ICE_S *
+format_thread_index_line(INDEXDATA_S *idata)
+{
+ char *p, buffer[BIGWIDTH+1];
+ int thdlen, space_left, i;
+ PINETHRD_S *thrd = NULL;
+ ICE_S *ice, *tice = NULL, **ticep = NULL;
+ IFIELD_S *ifield;
+ IELEM_S *ielem;
+ int (*save_sfstr_func)(void);
+ struct variable *vars = ps_global->vars;
+
+ dprint((8, "=== format_thread_index_line(%ld,%ld) ===\n",
+ idata ? idata->msgno : -1, idata ? idata->rawno : -1));
+
+ space_left = ps_global->ttyo->screen_cols;
+
+ if(ps_global->msgmap->max_thrdno < 1000)
+ thdlen = 3;
+ else if(ps_global->msgmap->max_thrdno < 10000)
+ thdlen = 4;
+ else if(ps_global->msgmap->max_thrdno < 100000)
+ thdlen = 5;
+ else
+ thdlen = 6;
+
+ ice = fetch_ice(idata->stream, idata->rawno);
+
+ thrd = fetch_thread(idata->stream, idata->rawno);
+
+ if(!thrd || !ice) /* can't happen? */
+ return(ice);
+
+ if(!ice->tice){
+ tice = (ICE_S *) fs_get(sizeof(*tice));
+ memset(tice, 0, sizeof(*tice));
+ ice->tice = tice;
+ }
+
+ tice = ice->tice;
+
+ if(!tice)
+ return(ice);
+
+ free_ifield(&tice->ifield);
+
+ ticep = &ice->tice;
+ tice = copy_ice(tice);
+
+ if(space_left >= 3){
+ char to_us, status;
+
+ p = buffer;
+ to_us = to_us_symbol_for_thread(idata->stream, thrd, 1);
+ status = status_symbol_for_thread(idata->stream, thrd, iStatus);
+
+ if((p-buffer)+3 < sizeof(buffer)){
+ p[0] = to_us;
+ p[1] = ' ';
+ p[2] = status;
+ p[3] = '\0';;
+ }
+
+ space_left -= 3;
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->ctype = iStatus;
+ ifield->width = 3;
+ ifield->leftadj = 1;
+ for(i = 0; i < 3; i++){
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = (char *) fs_get(2 * sizeof(char));
+ ielem->data[0] = p[i];
+ ielem->data[1] = '\0';
+ ielem->datalen = 1;
+ set_print_format(ielem, 1, ifield->leftadj);
+ }
+
+ if(pico_usingcolor()){
+ if(to_us == '*'
+ && VAR_IND_IMP_FORE_COLOR && VAR_IND_IMP_BACK_COLOR){
+ ielem = ifield->ielem;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR,
+ VAR_IND_IMP_BACK_COLOR);
+ if(F_ON(F_COLOR_LINE_IMPORTANT, ps_global))
+ tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR,
+ VAR_IND_IMP_BACK_COLOR);
+ }
+ else if((to_us == '+' || to_us == '-')
+ && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){
+ ielem = ifield->ielem;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_PLUS_FORE_COLOR,
+ VAR_IND_PLUS_BACK_COLOR);
+ }
+
+ if(status == 'D'
+ && VAR_IND_DEL_FORE_COLOR && VAR_IND_DEL_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_DEL_FORE_COLOR,
+ VAR_IND_DEL_BACK_COLOR);
+ }
+ else if(status == 'N'
+ && VAR_IND_NEW_FORE_COLOR && VAR_IND_NEW_BACK_COLOR){
+ ielem = ifield->ielem->next->next;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_NEW_FORE_COLOR,
+ VAR_IND_NEW_BACK_COLOR);
+ }
+ }
+ }
+
+ if(space_left >= thdlen+1){
+ p = buffer;
+ space_left--;
+
+ snprintf(p, sizeof(buffer), "%*.*s", thdlen, thdlen, "");
+ space_left -= thdlen;
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->ctype = iMessNo;
+ ifield->width = thdlen;
+ ifield->leftadj = 0;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(p);
+ ielem->datalen = strlen(p);
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ }
+
+ if(space_left >= 7){
+
+ p = buffer;
+ space_left--;
+
+ date_str(fetch_date(idata), iDate, 0, p, sizeof(buffer), 0);
+ if(sizeof(buffer) > 6)
+ p[6] = '\0';
+
+ if(strlen(p) < 6 && (sizeof(buffer)) > 6){
+ char *q;
+
+ for(q = p + strlen(p); q < p + 6; q++)
+ *q = ' ';
+ }
+
+ space_left -= 6;
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->ctype = iDate;
+ ifield->width = 6;
+ ifield->leftadj = 1;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(p);
+ ielem->datalen = ifield->width;
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ }
+
+
+ if(space_left > 3){
+ int from_width, subj_width, bigthread_adjust;
+ long in_thread;
+ char from[BIGWIDTH+1];
+ char tcnt[50];
+
+ space_left--;
+
+ in_thread = count_lflags_in_thread(idata->stream, thrd,
+ ps_global->msgmap, MN_NONE);
+
+ p = buffer;
+ if(in_thread == 1 && THRD_AUTO_VIEW())
+ snprintf(tcnt, sizeof(tcnt), " ");
+ else
+ snprintf(tcnt, sizeof(tcnt), "(%ld)", in_thread);
+
+ bigthread_adjust = MAX(0, strlen(tcnt) - 3);
+
+ /* third of the rest */
+ from_width = MAX((space_left-1)/3 - bigthread_adjust, 1);
+
+ /* the rest */
+ subj_width = space_left - from_width - 1;
+
+ if(strlen(tcnt) > subj_width)
+ tcnt[subj_width] = '\0';
+
+ from[0] = '\0';
+ save_sfstr_func = pith_opt_truncate_sfstr;
+ pith_opt_truncate_sfstr = NULL;
+ from_str(iFromTo, idata, from, sizeof(from), tice);
+ pith_opt_truncate_sfstr = save_sfstr_func;
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->leftadj = 1;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->type = eTypeCol;
+ ielem->data = cpystr(from);
+ ielem->datalen = strlen(from);
+ ifield->width = from_width;
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ ifield->ctype = iFrom;
+ if(from_width > 0 && pico_usingcolor()
+ && VAR_IND_FROM_FORE_COLOR && VAR_IND_FROM_BACK_COLOR){
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_FROM_FORE_COLOR,
+ VAR_IND_FROM_BACK_COLOR);
+ }
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->leftadj = 0;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(tcnt);
+ ielem->datalen = strlen(tcnt);
+ ifield->width = ielem->datalen;
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ ifield->ctype = iAtt; /* not used, except that it isn't special */
+
+ subj_width -= strlen(tcnt);
+
+ if(subj_width > 0)
+ subj_width--;
+
+ if(subj_width > 0){
+ if(idata->bogus){
+ if(idata->bogus < 2)
+ snprintf(buffer, sizeof(buffer), "%-.*s", BIGWIDTH,
+ _("[ No Message Text Available ]"));
+ }
+ else{
+ buffer[0] = '\0';
+ save_sfstr_func = pith_opt_truncate_sfstr;
+ pith_opt_truncate_sfstr = NULL;
+ subj_str(idata, buffer, sizeof(buffer), NoKW, 0, NULL);
+ pith_opt_truncate_sfstr = save_sfstr_func;
+ }
+
+ ifield = new_ifield(&tice->ifield);
+ ifield->leftadj = 1;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->type = eTypeCol;
+ ielem->data = cpystr(buffer);
+ ielem->datalen = strlen(buffer);
+ ifield->width = subj_width;
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ ifield->ctype = iSubject;
+ if(pico_usingcolor() && VAR_IND_SUBJ_FORE_COLOR && VAR_IND_SUBJ_BACK_COLOR){
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(VAR_IND_SUBJ_FORE_COLOR,
+ VAR_IND_SUBJ_BACK_COLOR);
+ }
+ }
+ }
+ else if(space_left > 1){
+ snprintf(p, sizeof(buffer)-(p-buffer), "%-.*s", space_left-1, " ");
+ ifield = new_ifield(&tice->ifield);
+ ifield->leftadj = 1;
+ ielem = new_ielem(&ifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(p);
+ ielem->datalen = strlen(p);
+ ifield->width = space_left-1;
+ set_print_format(ielem, ifield->width, ifield->leftadj);
+ ifield->ctype = iSubject;
+ }
+
+ tice->widths_done = 1;
+ tice->id = ice_hash(tice);
+
+ if(ticep){
+ free_ice(ticep); /* free what is already there */
+ *ticep = tice;
+ }
+
+ return(ice);
+}
+
+
+/*
+ * Print the fields of ice in buf with a single space between fields.
+ *
+ * Args buf -- place to put the line
+ * ice -- the data for the line
+ * msgno -- this is the msgno to be used, blanks if <= 0
+ *
+ * Returns a pointer to buf.
+ */
+char *
+simple_index_line(char *buf, size_t buflen, ICE_S *ice, long int msgno)
+{
+ char *p;
+ IFIELD_S *ifield, *previfield = NULL;
+ IELEM_S *ielem;
+
+ if(!buf)
+ panic("NULL buf in simple_index_line()");
+
+ if(buflen > 0)
+ buf[0] = '\0';
+
+ p = buf;
+
+ if(ice){
+
+ for(ifield = ice->ifield; ifield && p-buf < buflen; ifield = ifield->next){
+
+ /* space between fields */
+ if(ifield != ice->ifield && !(previfield && previfield->ctype == iText))
+ *p++ = ' ';
+
+ /* message number string is generated on the fly */
+ if(ifield->ctype == iMessNo){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width){
+ if(msgno > 0L)
+ snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
+ else
+ snprintf(ielem->data, ielem->datalen+1, "%*.*s", ifield->width, ifield->width, "");
+ }
+ }
+
+ for(ielem = ifield->ielem;
+ ielem && ielem->print_format && p-buf < buflen;
+ ielem = ielem->next){
+ char *src;
+ size_t bytes_added;
+
+ src = ielem->data;
+ bytes_added = utf8_pad_to_width(p, src,
+ buflen-(p-buf) * sizeof(char),
+ ielem->wid, ifield->leftadj);
+ p += bytes_added;
+ }
+
+ previfield = ifield;
+ }
+
+ if(p-buf < buflen)
+ *p = '\0';
+ }
+
+ buf[buflen-1] = '\0';
+
+ return(buf);
+}
+
+
+/*
+ * Look in current mail_stream for matches for messages in the searchset
+ * which match a color rule pattern. Return the color.
+ * The searched bit will be set for all of the messages which match the
+ * first pattern which has a match.
+ *
+ * Args stream -- the mail stream
+ * searchset -- restrict attention to this set of messages
+ * pstate -- The pattern state. On the first call it will be Null.
+ * Null means start over with a new first_pattern.
+ * After that it will be pointing to our local PAT_STATE
+ * so that next_pattern goes to the next one after the
+ * ones we've already checked.
+ *
+ * Returns 0 if no match, 1 if a match.
+ * The color that goes with the matched rule in returned_color.
+ * It may be NULL, which indicates default.
+ */
+int
+get_index_line_color(MAILSTREAM *stream, SEARCHSET *searchset,
+ PAT_STATE **pstate, COLOR_PAIR **returned_color)
+{
+ PAT_S *pat = NULL;
+ long rflags = ROLE_INCOL;
+ COLOR_PAIR *color = NULL;
+ int match = 0;
+ static PAT_STATE localpstate;
+
+ dprint((7, "get_index_line_color\n"));
+
+ if(returned_color)
+ *returned_color = NULL;
+
+ if(*pstate)
+ pat = next_pattern(*pstate);
+ else{
+ *pstate = &localpstate;
+ if(!nonempty_patterns(rflags, *pstate))
+ *pstate = NULL;
+
+ if(*pstate)
+ pat = first_pattern(*pstate);
+ }
+
+ if(*pstate){
+
+ /* Go through the possible roles one at a time until we get a match. */
+ while(!match && pat){
+ if(match_pattern(pat->patgrp, stream, searchset, NULL,
+ get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
+ if(!pat->action || pat->action->bogus)
+ break;
+
+ match++;
+ if(pat->action && pat->action->incol)
+ color = new_color_pair(pat->action->incol->fg,
+ pat->action->incol->bg);
+ }
+ else
+ pat = next_pattern(*pstate);
+ }
+ }
+
+ if(match && returned_color)
+ *returned_color = color;
+
+ return(match);
+}
+
+
+/*
+ *
+ */
+int
+index_in_overview(MAILSTREAM *stream)
+{
+ INDEX_COL_S *cdesc = NULL;
+
+ if(!(stream->mailbox && IS_REMOTE(stream->mailbox)))
+ return(FALSE); /* no point! */
+
+ if(stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "nntp")){
+
+ if(THRD_INDX())
+ return(TRUE);
+
+ for(cdesc = ps_global->index_disp_format;
+ cdesc->ctype != iNothing;
+ cdesc++)
+ switch(cdesc->ctype){
+ case iTo: /* can't be satisfied by XOVER */
+ case iSender: /* ... or specifically handled */
+ case iDescripSize: /* ... in news case */
+ case iAtt:
+ return(FALSE);
+
+ default :
+ break;
+ }
+ }
+
+ return(TRUE);
+}
+
+
+
+/*
+ * fetch_from - called to get a the index entry's "From:" field
+ */
+int
+resent_to_us(INDEXDATA_S *idata)
+{
+ if(!idata->valid_resent_to){
+ static char *fields[] = {"Resent-To", NULL};
+ char *h;
+
+ if(idata->no_fetch){
+ idata->bogus = 1; /* don't do this */
+ return(FALSE);
+ }
+
+ if((h = pine_fetchheader_lines(idata->stream,idata->rawno,NULL,fields)) != NULL){
+ idata->resent_to_us = parsed_resent_to_us(h);
+ fs_give((void **) &h);
+ }
+
+ idata->valid_resent_to = 1;
+ }
+
+ return(idata->resent_to_us);
+}
+
+
+int
+parsed_resent_to_us(char *h)
+{
+ char *p, *q;
+ ADDRESS *addr = NULL;
+ int rv = FALSE;
+
+ if((p = strindex(h, ':')) != NULL){
+ for(q = ++p; (q = strpbrk(q, "\015\012")) != NULL; q++)
+ *q = ' '; /* quash junk */
+
+ rfc822_parse_adrlist(&addr, p, ps_global->maildomain);
+ if(addr){
+ rv = address_is_us(addr, ps_global);
+ mail_free_address(&addr);
+ }
+ }
+
+ return(rv);
+}
+
+
+
+/*
+ * fetch_from - called to get a the index entry's "From:" field
+ */
+ADDRESS *
+fetch_from(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch) /* implies from is valid */
+ return(idata->from);
+ else if(idata->bogus)
+ idata->bogus = 2;
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->from);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_to - called to get a the index entry's "To:" field
+ */
+ADDRESS *
+fetch_to(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch){ /* check for specific validity */
+ if(idata->valid_to)
+ return(idata->to);
+ else
+ idata->bogus = 1; /* can't give 'em what they want */
+ }
+ else if(idata->bogus){
+ idata->bogus = 2; /* elevate bogosity */
+ }
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->to);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_cc - called to get a the index entry's "Cc:" field
+ */
+ADDRESS *
+fetch_cc(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch){ /* check for specific validity */
+ if(idata->valid_cc)
+ return(idata->cc);
+ else
+ idata->bogus = 1; /* can't give 'em what they want */
+ }
+ else if(idata->bogus){
+ idata->bogus = 2; /* elevate bogosity */
+ }
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->cc);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+
+/*
+ * fetch_sender - called to get a the index entry's "Sender:" field
+ */
+ADDRESS *
+fetch_sender(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch){ /* check for specific validity */
+ if(idata->valid_sender)
+ return(idata->sender);
+ else
+ idata->bogus = 1; /* can't give 'em what they want */
+ }
+ else if(idata->bogus){
+ idata->bogus = 2; /* elevate bogosity */
+ }
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->sender);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_newsgroups - called to get a the index entry's "Newsgroups:" field
+ */
+char *
+fetch_newsgroups(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch){ /* check for specific validity */
+ if(idata->valid_news)
+ return(idata->newsgroups);
+ else
+ idata->bogus = 1; /* can't give 'em what they want */
+ }
+ else if(idata->bogus){
+ idata->bogus = 2; /* elevate bogosity */
+ }
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->newsgroups);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_subject - called to get at the index entry's "Subject:" field
+ */
+char *
+fetch_subject(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch) /* implies subject is valid */
+ return(idata->subject);
+ else if(idata->bogus)
+ idata->bogus = 2;
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return(env->subject);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * Return an allocated copy of the first few characters from the body
+ * of the message for possible use in the index screen.
+ *
+ * Maybe we could figure out some way to do aggregate calls to get
+ * this info for all the lines in view instead of all the one at a
+ * time calls we're doing now.
+ */
+char *
+fetch_firsttext(INDEXDATA_S *idata, int delete_quotes)
+{
+ ENVELOPE *env;
+ BODY *body = NULL;
+ char *firsttext = NULL;
+ STORE_S *so;
+ gf_io_t pc;
+ long partial_fetch_len = 0L;
+ SEARCHSET *ss, **sset;
+
+try_again:
+
+ /*
+ * Prevent wild prefetch, just get the one we're after.
+ * Can we get this somehow in the overview call in build_header_work?
+ */
+ ss = mail_newsearchset();
+ ss->first = idata->rawno;
+ sset = (SEARCHSET **) mail_parameters(idata->stream,
+ GET_FETCHLOOKAHEAD,
+ (void *) idata->stream);
+ if(sset)
+ *sset = ss;
+
+ if((env = pine_mail_fetchstructure(idata->stream, idata->rawno, &body)) != NULL){
+ if(body){
+ char *subtype = NULL;
+ char *partno;
+
+ if((body->type == TYPETEXT
+ && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
+ ||
+ (body->type == TYPEMULTIPART && body->nested.part
+ && body->nested.part->body.type == TYPETEXT
+ && (subtype=body->nested.part->body.subtype)
+ && ALLOWED_SUBTYPE(subtype))
+ ||
+ (body->type == TYPEMULTIPART && body->nested.part
+ && body->nested.part->body.type == TYPEMULTIPART
+ && body->nested.part->body.nested.part
+ && body->nested.part->body.nested.part->body.type == TYPETEXT
+ && (subtype=body->nested.part->body.nested.part->body.subtype)
+ && ALLOWED_SUBTYPE(subtype))){
+
+ if((so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ char buf[1025], *p;
+ unsigned char c;
+ int success;
+ int one_space_done = 0;
+
+ if(partial_fetch_len == 0L){
+ if(subtype && !strucmp(subtype, "html"))
+ partial_fetch_len = 1024L;
+ else if(subtype && !strucmp(subtype, "plain"))
+ partial_fetch_len = delete_quotes ? 128L : 64L;
+ else
+ partial_fetch_len = 256L;
+ }
+
+ if((body->type == TYPETEXT
+ && (subtype=body->subtype) && ALLOWED_SUBTYPE(subtype))
+ ||
+ (body->type == TYPEMULTIPART && body->nested.part
+ && body->nested.part->body.type == TYPETEXT
+ && (subtype=body->nested.part->body.subtype)
+ && ALLOWED_SUBTYPE(subtype)))
+ partno = "1";
+ else
+ partno = "1.1";
+
+ gf_set_so_writec(&pc, so);
+ success = get_body_part_text(idata->stream, body, idata->rawno,
+ partno, partial_fetch_len, pc,
+ NULL, NULL,
+ GBPT_NOINTR | GBPT_PEEK |
+ (delete_quotes ? GBPT_DELQUOTES : 0));
+ gf_clear_so_writec(so);
+
+ if(success){
+ so_seek(so, 0L, 0);
+ p = buf;
+ while(p-buf < sizeof(buf)-1 && so_readc(&c, so)){
+ /* delete leading whitespace */
+ if(p == buf && isspace(c))
+ ;
+ /* and include just one space per run of whitespace */
+ else if(isspace(c)){
+ if(!one_space_done){
+ *p++ = SPACE;
+ one_space_done++;
+ }
+ }
+ else{
+ one_space_done = 0;
+ *p++ = c;
+ }
+ }
+
+ *p = '\0';
+
+ if(p > buf){
+ size_t l;
+
+ l = strlen(buf);
+ l += 100;
+ firsttext = fs_get((l+1) * sizeof(char));
+ firsttext[0] = '\0';
+ iutf8ncpy(firsttext, buf, l);
+ firsttext[l] = '\0';
+ removing_trailing_white_space(firsttext);
+ }
+ }
+
+ so_give(&so);
+
+ /* first if means we didn't fetch all of the data */
+ if(!(success > 1 && success < partial_fetch_len)){
+ if(partial_fetch_len < 4096L
+ && (!firsttext || utf8_width(firsttext) < 50)){
+ if(firsttext)
+ fs_give((void **) &firsttext);
+
+ partial_fetch_len = 4096L;
+ goto try_again;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(ss)
+ mail_free_searchset(&ss);
+
+ return(firsttext);
+}
+
+
+/*
+ * fetch_date - called to get at the index entry's "Date:" field
+ */
+char *
+fetch_date(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch) /* implies date is valid */
+ return(idata->date);
+ else if(idata->bogus)
+ idata->bogus = 2;
+ else{
+ ENVELOPE *env;
+
+ /* c-client call's just cache access at this point */
+ if((env = pine_mail_fetchenvelope(idata->stream, idata->rawno)) != NULL)
+ return((char *) env->date);
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_header - called to get at the index entry's "Hdrname:" field
+ */
+char *
+fetch_header(INDEXDATA_S *idata, char *hdrname)
+{
+ if(idata->no_fetch)
+ idata->bogus = 1;
+ else if(idata->bogus)
+ idata->bogus = 2;
+ else{
+ char *h, *p, *q, *decoded, *fields[2];
+ size_t retsize, decsize;
+ char *ret = NULL;
+ unsigned char *decode_buf = NULL;
+
+ fields[0] = hdrname;
+ fields[1] = NULL;
+ if(hdrname && hdrname[0]
+ && (h = pine_fetchheader_lines(idata->stream, idata->rawno,
+ NULL, fields))){
+
+ if(strlen(h) < strlen(hdrname) + 1){
+ fs_give((void **) &h);
+ return(cpystr(""));
+ }
+
+ /* skip "hdrname:" */
+ for(p = h + strlen(hdrname) + 1;
+ *p && isspace((unsigned char)*p); p++)
+ ;
+
+ decsize = (4 * strlen(p)) + 1;
+ decode_buf = (unsigned char *) fs_get(decsize * sizeof(unsigned char));
+ decoded = (char *) rfc1522_decode_to_utf8(decode_buf, decsize, p);
+ p = decoded;
+
+ retsize = strlen(decoded);
+ q = ret = (char *) fs_get((retsize+1) * sizeof(char));
+
+ *q = '\0';
+ while(q-ret < retsize && *p){
+ if(*p == '\015' || *p == '\012')
+ p++;
+ else if(*p == '\t'){
+ *q++ = SPACE;
+ p++;
+ }
+ else
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+
+ fs_give((void **) &h);
+ if(decode_buf)
+ fs_give((void **) &decode_buf);
+
+ return(ret);
+ }
+
+ idata->bogus = 1;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * fetch_size - called to get at the index entry's "size" field
+ */
+long
+fetch_size(INDEXDATA_S *idata)
+{
+ if(idata->no_fetch) /* implies size is valid */
+ return(idata->size);
+ else if(idata->bogus)
+ idata->bogus = 2;
+ else{
+ MESSAGECACHE *mc;
+
+ if(idata->stream && idata->rawno > 0L
+ && idata->rawno <= idata->stream->nmsgs
+ && (mc = mail_elt(idata->stream, idata->rawno)))
+ return(mc->rfc822_size);
+
+ idata->bogus = 1;
+ }
+
+ return(0L);
+}
+
+
+/*
+ * fetch_body - called to get a the index entry's body structure
+ */
+BODY *
+fetch_body(INDEXDATA_S *idata)
+{
+ BODY *body;
+
+ if(idata->bogus || idata->no_fetch){
+ idata->bogus = 2;
+ return(NULL);
+ }
+
+ if(pine_mail_fetchstructure(idata->stream, idata->rawno, &body))
+ return(body);
+
+ idata->bogus = 1;
+ return(NULL);
+}
+
+
+/*
+ * s is at least size width+1
+ */
+int
+set_index_addr(INDEXDATA_S *idata,
+ char *field,
+ struct mail_address *addr,
+ char *prefix,
+ int width,
+ char *s)
+{
+ ADDRESS *atmp;
+ char *p, *stmp = NULL, *sptr;
+ char *save_personal = NULL;
+ int orig_width;
+
+ s[0] = '\0';
+
+ for(atmp = addr; idata->stream && atmp; atmp = atmp->next)
+ if(atmp->host && atmp->host[0] == '.'){
+ char *pref, *h, *fields[2];
+
+ if(idata->no_fetch){
+ idata->bogus = 1;
+ return(TRUE);
+ }
+
+ fields[0] = field;
+ fields[1] = NULL;
+ if((h = pine_fetchheader_lines(idata->stream, idata->rawno,
+ NULL, fields)) != NULL){
+ if(strlen(h) < strlen(field) + 1){
+ p = h + strlen(h);
+ }
+ else{
+ /* skip "field:" */
+ for(p = h + strlen(field) + 1;
+ *p && isspace((unsigned char)*p); p++)
+ ;
+ }
+
+ orig_width = width;
+ sptr = stmp = (char *) fs_get((orig_width+1) * sizeof(char));
+
+ /* add prefix */
+ for(pref = prefix; pref && *pref; pref++)
+ if(width){
+ *sptr++ = *pref;
+ width--;
+ }
+ else
+ break;
+
+ while(width--)
+ if(*p == '\015' || *p == '\012')
+ p++; /* skip CR LF */
+ else if(!*p)
+ *sptr++ = ' ';
+ else if(*p == '\t'){
+ *sptr++ = ' ';
+ p++;
+ }
+ else
+ *sptr++ = *p++;
+
+ *sptr = '\0'; /* tie off return string */
+
+ if(stmp){
+ iutf8ncpy(s, stmp, orig_width+1);
+ s[orig_width] = '\0';
+ fs_give((void **) &stmp);
+ }
+
+ fs_give((void **) &h);
+ return(TRUE);
+ }
+ /* else fall thru and display what c-client gave us */
+ }
+
+ if(addr && !addr->next /* only one address */
+ && addr->host /* not group syntax */
+ && addr->personal && addr->personal[0]){ /* there is a personal name */
+ char buftmp[MAILTMPLEN];
+ int l;
+
+ if((l = prefix ? strlen(prefix) : 0) != 0)
+ strncpy(s, prefix, width+1);
+
+ snprintf(buftmp, sizeof(buftmp), "%s", addr->personal);
+ p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp);
+ removing_leading_and_trailing_white_space(p);
+
+ iutf8ncpy(s + l, p, width - l);
+
+ s[width] = '\0';
+
+ if(*(s+l))
+ return(TRUE);
+ else{
+ save_personal = addr->personal;
+ addr->personal = NULL;
+ }
+ }
+
+ if(addr){
+ char *a_string;
+ int l;
+
+ a_string = addr_list_string(addr, NULL, 0);
+ if(save_personal)
+ addr->personal = save_personal;
+
+ if((l = prefix ? strlen(prefix) : 0) != 0)
+ strncpy(s, prefix, width+1);
+
+ iutf8ncpy(s + l, a_string, width - l);
+ s[width] = '\0';
+
+ fs_give((void **)&a_string);
+
+ return(TRUE);
+ }
+
+ if(save_personal)
+ addr->personal = save_personal;
+
+ return(FALSE);
+}
+
+
+void
+index_data_env(INDEXDATA_S *idata, ENVELOPE *env)
+{
+ if(!env){
+ idata->bogus = 2;
+ return;
+ }
+
+ idata->from = env->from;
+ idata->to = env->to;
+ idata->cc = env->cc;
+ idata->sender = env->sender;
+ idata->subject = env->subject;
+ idata->date = (char *) env->date;
+ idata->newsgroups = env->newsgroups;
+
+ idata->valid_to = 1; /* signal that everythings here */
+ idata->valid_cc = 1;
+ idata->valid_sender = 1;
+ idata->valid_news = 1;
+}
+
+
+/*
+ * Put a string representing the date into str. The source date is
+ * in the string datesrc. The format to be used is in type.
+ * Notice that type is an IndexColType, but really only a subset of
+ * IndexColType types are allowed.
+ *
+ * Args datesrc -- The source date string
+ * type -- What type of output we want
+ * v -- If set, variable width output is ok. (Oct 9 not Oct 9)
+ * str -- Put the answer here.
+ * str_len -- Length of str
+ * monabb_width -- This is a hack to get dates to line up right. For
+ * example, in French (but without accents here)
+ * dec. 21
+ * fevr. 23
+ * mars 7
+ * For this monabb_width would be 5.
+ */
+void
+date_str(char *datesrc, IndexColType type, int v, char *str, size_t str_len,
+ int monabb_width)
+{
+ char year4[5], /* 4 digit year */
+ yearzero[3], /* zero padded, 2-digit year */
+ monzero[3], /* zero padded, 2-digit month */
+ mon[3], /* 1 or 2-digit month, no pad */
+ dayzero[3], /* zero padded, 2-digit day */
+ day[3], /* 1 or 2-digit day, no pad */
+ dayord[3], /* 2-letter ordinal label */
+ monabb[10], /* 3-letter month abbrev */
+ /* actually maybe not 3 if localized */
+ hour24[3], /* 2-digit, 24 hour clock hour */
+ hour12[3], /* 12 hour clock hour, no pad */
+ minzero[3], /* zero padded, 2-digit minutes */
+ timezone[6]; /* timezone, like -0800 or +... */
+ int hr12;
+ int curtype, lastmonthtype, lastyeartype, preftype;
+ int sdatetimetype, sdatetime24type;
+ struct date d;
+#define TODAYSTR N_("Today")
+
+ curtype = (type == iCurDate ||
+ type == iCurDateIso ||
+ type == iCurDateIsoS ||
+ type == iCurPrefDate ||
+ type == iCurPrefDateTime ||
+ type == iCurPrefTime ||
+ type == iCurTime24 ||
+ type == iCurTime12 ||
+ type == iCurDay ||
+ type == iCurDay2Digit ||
+ type == iCurDayOfWeek ||
+ type == iCurDayOfWeekAbb ||
+ type == iCurMon ||
+ type == iCurMon2Digit ||
+ type == iCurMonLong ||
+ type == iCurMonAbb ||
+ type == iCurYear ||
+ type == iCurYear2Digit);
+ lastmonthtype = (type == iLstMon ||
+ type == iLstMon2Digit ||
+ type == iLstMonLong ||
+ type == iLstMonAbb ||
+ type == iLstMonYear ||
+ type == iLstMonYear2Digit);
+ lastyeartype = (type == iLstYear ||
+ type == iLstYear2Digit);
+ sdatetimetype = (type == iSDateTime ||
+ type == iSDateTimeIso ||
+ type == iSDateTimeIsoS ||
+ type == iSDateTimeS1 ||
+ type == iSDateTimeS2 ||
+ type == iSDateTimeS3 ||
+ type == iSDateTimeS4 ||
+ type == iSDateTime24 ||
+ type == iSDateTimeIso24 ||
+ type == iSDateTimeIsoS24 ||
+ type == iSDateTimeS124 ||
+ type == iSDateTimeS224 ||
+ type == iSDateTimeS324 ||
+ type == iSDateTimeS424);
+ sdatetime24type = (type == iSDateTime24 ||
+ type == iSDateTimeIso24 ||
+ type == iSDateTimeIsoS24 ||
+ type == iSDateTimeS124 ||
+ type == iSDateTimeS224 ||
+ type == iSDateTimeS324 ||
+ type == iSDateTimeS424);
+ preftype = (type == iPrefDate ||
+ type == iPrefDateTime ||
+ type == iPrefTime ||
+ type == iCurPrefDate ||
+ type == iCurPrefDateTime ||
+ type == iCurPrefTime);
+ if(str_len > 0)
+ str[0] = '\0';
+
+ if(!(datesrc && datesrc[0]) && !(curtype || lastmonthtype || lastyeartype))
+ return;
+
+ if(curtype || lastmonthtype || lastyeartype){
+ char dbuf[200];
+
+ rfc822_date(dbuf);
+ parse_date(dbuf, &d);
+
+ if(lastyeartype)
+ d.year--;
+ else if(lastmonthtype){
+ d.month--;
+ if(d.month <= 0){
+ d.month = 12;
+ d.year--;
+ }
+ }
+ }
+ else{
+ parse_date(F_ON(F_DATES_TO_LOCAL,ps_global)
+ ? convert_date_to_local(datesrc) : datesrc, &d);
+ if(d.year == -1 || d.month == -1 || d.day == -1){
+ sdatetimetype = 0;
+ sdatetime24type = 0;
+ preftype = 0;
+ switch(type){
+ case iSDate: case iSDateTime: case iSDateTime24:
+ type = iS1Date;
+ break;
+
+ case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
+ case iPrefDate: case iPrefTime: case iPrefDateTime:
+ type = iDateIso;
+ break;
+
+ case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
+ type = iDateIsoS;
+ break;
+
+ case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
+ type = iS1Date;
+ break;
+
+ case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
+ type = iS1Date;
+ break;
+
+ case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
+ type = iS1Date;
+ break;
+
+ case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
+ type = iS1Date;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /* some special ones to start with */
+ if(preftype){
+ struct tm tm, *tmptr = NULL;
+ time_t now;
+
+ /*
+ * Make sure we get the right one if we're using current time.
+ */
+ if(curtype){
+ now = time((time_t *) 0);
+ if(now != (time_t) -1)
+ tmptr = localtime(&now);
+ }
+
+ if(!tmptr){
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
+ tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
+ tm.tm_mday = MIN(MAX(d.day, 1), 31);
+ tm.tm_hour = MIN(MAX(d.hour, 0), 23);
+ tm.tm_min = MIN(MAX(d.minute, 0), 59);
+ tm.tm_wday = MIN(MAX(d.wkday, 0), 6);
+ tmptr = &tm;
+ }
+
+ switch(type){
+ case iPrefDate:
+ case iCurPrefDate:
+ our_strftime(str, str_len, "%x", tmptr);
+ break;
+ case iPrefTime:
+ case iCurPrefTime:
+ our_strftime(str, str_len, "%X", tmptr);
+ break;
+ case iPrefDateTime:
+ case iCurPrefDateTime:
+ our_strftime(str, str_len, "%c", tmptr);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ return;
+ }
+
+ strncpy(monabb, (d.month > 0 && d.month < 13)
+ ? month_abbrev_locale(d.month) : "", sizeof(monabb));
+ monabb[sizeof(monabb)-1] = '\0';
+
+ strncpy(mon, (d.month > 0 && d.month < 13)
+ ? int2string(d.month) : "", sizeof(mon));
+ mon[sizeof(mon)-1] = '\0';
+
+ strncpy(day, (d.day > 0 && d.day < 32)
+ ? int2string(d.day) : "", sizeof(day));
+ day[sizeof(day)-1] = '\0';
+
+ strncpy(dayord,
+ (d.day <= 0 || d.day > 31) ? "" :
+ (d.day == 1 || d.day == 21 || d.day == 31) ? "st" :
+ (d.day == 2 || d.day == 22 ) ? "nd" :
+ (d.day == 3 || d.day == 23 ) ? "rd" : "th", sizeof(dayord));
+
+ dayord[sizeof(dayord)-1] = '\0';
+
+ strncpy(year4, (d.year >= 1000 && d.year < 10000)
+ ? int2string(d.year) : "????", sizeof(year4));
+ year4[sizeof(year4)-1] = '\0';
+
+ if(d.year >= 0){
+ if((d.year % 100) < 10){
+ yearzero[0] = '0';
+ strncpy(yearzero+1, int2string(d.year % 100), sizeof(yearzero)-1);
+ }
+ else
+ strncpy(yearzero, int2string(d.year % 100), sizeof(yearzero));
+ }
+ else
+ strncpy(yearzero, "??", sizeof(yearzero));
+
+ yearzero[sizeof(yearzero)-1] = '\0';
+
+ if(d.month > 0 && d.month < 10){
+ monzero[0] = '0';
+ strncpy(monzero+1, int2string(d.month), sizeof(monzero)-1);
+ }
+ else if(d.month >= 10 && d.month <= 12)
+ strncpy(monzero, int2string(d.month), sizeof(monzero));
+ else
+ strncpy(monzero, "??", sizeof(monzero));
+
+ monzero[sizeof(monzero)-1] = '\0';
+
+ if(d.day > 0 && d.day < 10){
+ dayzero[0] = '0';
+ strncpy(dayzero+1, int2string(d.day), sizeof(dayzero)-1);
+ }
+ else if(d.day >= 10 && d.day <= 31)
+ strncpy(dayzero, int2string(d.day), sizeof(dayzero));
+ else
+ strncpy(dayzero, "??", sizeof(dayzero));
+
+ dayzero[sizeof(dayzero)-1] = '\0';
+
+ hr12 = (d.hour == 0) ? 12 :
+ (d.hour > 12) ? (d.hour - 12) : d.hour;
+ hour12[0] = '\0';
+ if(hr12 > 0 && hr12 <= 12)
+ strncpy(hour12, int2string(hr12), sizeof(hour12));
+
+ hour12[sizeof(hour12)-1] = '\0';
+
+ hour24[0] = '\0';
+ if(d.hour >= 0 && d.hour < 10){
+ hour24[0] = '0';
+ strncpy(hour24+1, int2string(d.hour), sizeof(hour24)-1);
+ }
+ else if(d.hour >= 10 && d.hour < 24)
+ strncpy(hour24, int2string(d.hour), sizeof(hour24));
+
+ hour24[sizeof(hour24)-1] = '\0';
+
+ minzero[0] = '\0';
+ if(d.minute >= 0 && d.minute < 10){
+ minzero[0] = '0';
+ strncpy(minzero+1, int2string(d.minute), sizeof(minzero)-1);
+ }
+ else if(d.minute >= 10 && d.minute <= 60)
+ strncpy(minzero, int2string(d.minute), sizeof(minzero));
+
+ minzero[sizeof(minzero)-1] = '\0';
+
+ if(sizeof(timezone) > 5){
+ if(d.hours_off_gmt <= 0){
+ timezone[0] = '-';
+ d.hours_off_gmt *= -1;
+ d.min_off_gmt *= -1;
+ }
+ else
+ timezone[0] = '+';
+
+ timezone[1] = '\0';
+ if(d.hours_off_gmt >= 0 && d.hours_off_gmt < 10){
+ timezone[1] = '0';
+ strncpy(timezone+2, int2string(d.hours_off_gmt), sizeof(timezone)-2);
+ }
+ else if(d.hours_off_gmt >= 10 && d.hours_off_gmt < 24)
+ strncpy(timezone+1, int2string(d.hours_off_gmt), sizeof(timezone)-1);
+ else{
+ timezone[1] = '0';
+ timezone[2] = '0';
+ }
+
+ timezone[3] = '\0';
+ if(d.min_off_gmt >= 0 && d.min_off_gmt < 10){
+ timezone[3] = '0';
+ strncpy(timezone+4, int2string(d.min_off_gmt), sizeof(timezone)-4);
+ }
+ else if(d.min_off_gmt >= 10 && d.min_off_gmt <= 60)
+ strncpy(timezone+3, int2string(d.min_off_gmt), sizeof(timezone)-3);
+ else{
+ timezone[3] = '0';
+ timezone[4] = '0';
+ }
+
+ timezone[5] = '\0';
+ timezone[sizeof(timezone)-1] = '\0';
+ }
+
+ switch(type){
+ case iRDate:
+ /* this one is not locale-specific */
+ snprintf(str, str_len, "%s%s%s %s %s",
+ (d.wkday != -1) ? day_abbrev(d.wkday) : "",
+ (d.wkday != -1) ? ", " : "",
+ day,
+ (d.month > 0 && d.month < 13) ? month_abbrev(d.month) : "",
+ year4);
+ break;
+ case iDayOfWeekAbb:
+ case iCurDayOfWeekAbb:
+ strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_abbrev_locale(d.wkday) : "", str_len);
+ str[str_len-1] = '\0';
+ break;
+ case iDayOfWeek:
+ case iCurDayOfWeek:
+ strncpy(str, (d.wkday >= 0 && d.wkday <= 6) ? day_name_locale(d.wkday) : "", str_len);
+ str[str_len-1] = '\0';
+ break;
+ case iYear:
+ case iCurYear:
+ case iLstYear:
+ case iLstMonYear:
+ strncpy(str, year4, str_len);
+ break;
+ case iDay2Digit:
+ case iCurDay2Digit:
+ strncpy(str, dayzero, str_len);
+ break;
+ case iMon2Digit:
+ case iCurMon2Digit:
+ case iLstMon2Digit:
+ strncpy(str, monzero, str_len);
+ break;
+ case iYear2Digit:
+ case iCurYear2Digit:
+ case iLstYear2Digit:
+ case iLstMonYear2Digit:
+ strncpy(str, yearzero, str_len);
+ break;
+ case iTimezone:
+ strncpy(str, timezone, str_len);
+ break;
+ case iDay:
+ case iCurDay:
+ strncpy(str, day, str_len);
+ break;
+ case iDayOrdinal:
+ snprintf(str, str_len, "%s%s", day, dayord);
+ break;
+ case iMon:
+ case iCurMon:
+ case iLstMon:
+ if(d.month > 0 && d.month <= 12)
+ strncpy(str, int2string(d.month), str_len);
+
+ break;
+ case iMonAbb:
+ case iCurMonAbb:
+ case iLstMonAbb:
+ strncpy(str, monabb, str_len);
+ break;
+ case iMonLong:
+ case iCurMonLong:
+ case iLstMonLong:
+ strncpy(str, (d.month > 0 && d.month < 13)
+ ? month_name_locale(d.month) : "", str_len);
+ break;
+ case iDate:
+ case iCurDate:
+ if(v)
+ snprintf(str, str_len, "%s%s%s", monabb, (monabb[0] && day[0]) ? " " : "", day);
+ else{
+ if(monabb_width > 0)
+ utf8_snprintf(str, str_len, "%-*.*w %2s",
+ monabb_width, monabb_width, monabb, day);
+ else
+ snprintf(str, str_len, "%s %2s", monabb, day);
+ }
+
+ break;
+ case iLDate:
+ if(v)
+ snprintf(str, str_len, "%s%s%s%s%s", monabb,
+ (monabb[0] && day[0]) ? " " : "", day,
+ ((monabb[0] || day[0]) && year4[0]) ? ", " : "",
+ year4);
+ else{
+ if(monabb_width > 0)
+ utf8_snprintf(str, str_len, "%-*.*w %2s%c %4s",
+ monabb_width, monabb_width,
+ monabb, day,
+ (monabb[0] && day[0] && year4[0]) ? ',' : ' ', year4);
+ else
+ snprintf(str, str_len, "%s %2s%c %4s", monabb, day,
+ (monabb[0] && day[0] && year4[0]) ? ',' : ' ',
+ year4);
+ }
+
+ break;
+ case iS1Date:
+ case iS2Date:
+ case iS3Date:
+ case iS4Date:
+ case iDateIso:
+ case iDateIsoS:
+ case iCurDateIso:
+ case iCurDateIsoS:
+ if(monzero[0] == '?' && dayzero[0] == '?' &&
+ yearzero[0] == '?')
+ snprintf(str, str_len, "%8s", "");
+ else{
+ switch(type){
+ case iS1Date:
+ snprintf(str, str_len, "%2s/%2s/%2s",
+ monzero, dayzero, yearzero);
+ break;
+ case iS2Date:
+ snprintf(str, str_len, "%2s/%2s/%2s",
+ dayzero, monzero, yearzero);
+ break;
+ case iS3Date:
+ snprintf(str, str_len, "%2s.%2s.%2s",
+ dayzero, monzero, yearzero);
+ break;
+ case iS4Date:
+ snprintf(str, str_len, "%2s.%2s.%2s",
+ yearzero, monzero, dayzero);
+ break;
+ case iDateIsoS:
+ case iCurDateIsoS:
+ snprintf(str, str_len, "%2s-%2s-%2s",
+ yearzero, monzero, dayzero);
+ break;
+ case iDateIso:
+ case iCurDateIso:
+ snprintf(str, str_len, "%4s-%2s-%2s",
+ year4, monzero, dayzero);
+ break;
+ default:
+ break;
+ }
+ }
+
+ break;
+ case iTime24:
+ case iCurTime24:
+ snprintf(str, str_len, "%2s%c%2s",
+ (hour24[0] && minzero[0]) ? hour24 : "",
+ (hour24[0] && minzero[0]) ? ':' : ' ',
+ (hour24[0] && minzero[0]) ? minzero : "");
+ break;
+ case iTime12:
+ case iCurTime12:
+ snprintf(str, str_len, "%s%c%2s%s",
+ (hour12[0] && minzero[0]) ? hour12 : "",
+ (hour12[0] && minzero[0]) ? ':' : ' ',
+ (hour12[0] && minzero[0]) ? minzero : "",
+ (hour12[0] && minzero[0] && d.hour < 12) ? "am" :
+ (hour12[0] && minzero[0] && d.hour >= 12) ? "pm" :
+ " ");
+ break;
+ case iSDate: case iSDateIso: case iSDateIsoS:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ { struct date now, last_day;
+ char dbuf[200];
+ int msg_day_of_year, now_day_of_year, today;
+ int diff, ydiff, last_day_of_year;
+
+ rfc822_date(dbuf);
+ parse_date(dbuf, &now);
+ today = day_of_week(&now) + 7;
+
+ if(today >= 0+7 && today <= 6+7){
+ now_day_of_year = day_of_year(&now);
+ msg_day_of_year = day_of_year(&d);
+ ydiff = now.year - d.year;
+
+ if(msg_day_of_year == -1)
+ diff = -100;
+ else if(ydiff == 0)
+ diff = now_day_of_year - msg_day_of_year;
+ else if(ydiff == 1){
+ last_day = d;
+ last_day.month = 12;
+ last_day.day = 31;
+ last_day_of_year = day_of_year(&last_day);
+
+ diff = now_day_of_year +
+ (last_day_of_year - msg_day_of_year);
+ }
+ else if(ydiff == -1){
+ last_day = now;
+ last_day.month = 12;
+ last_day.day = 31;
+ last_day_of_year = day_of_year(&last_day);
+
+ diff = -1 * (msg_day_of_year +
+ (last_day_of_year - now_day_of_year));
+ }
+ else if(ydiff > 1)
+ diff = 100;
+ else
+ diff = -100;
+
+ if(diff == 0)
+ strncpy(str, _(TODAYSTR), str_len);
+ else if(diff == 1)
+ strncpy(str, _("Yesterday"), str_len);
+ else if(diff > 1 && diff < 7)
+ snprintf(str, str_len, "%s", day_name_locale((today - diff) % 7));
+ else if(diff == -1)
+ strncpy(str, _("Tomorrow"), str_len);
+ else if(diff < -1 && diff > -7)
+ snprintf(str, str_len, _("Next %.3s!"),
+ day_name_locale((today - diff) % 7));
+ else if(diff > 0
+ && (ydiff == 0
+ || (ydiff == 1 && 12 + now.month - d.month < 6))){
+ if(v)
+ snprintf(str, str_len, "%s%s%s", monabb,
+ (monabb[0] && day[0]) ? " " : "", day);
+ else{
+ if(monabb_width > 0)
+ utf8_snprintf(str, str_len, "%-*.*w %2s",
+ monabb_width, monabb_width, monabb, day);
+ else
+ snprintf(str, str_len, "%s %2s", monabb, day);
+ }
+ }
+ else{
+ if(msg_day_of_year == -1 && (type == iSDate || type == iSDateTime))
+ type = iSDateTimeIsoS;
+
+ switch(type){
+ case iSDate: case iSDateTime: case iSDateTime24:
+ {
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = MIN(MAX(d.year-1900, 0), 2000);
+ tm.tm_mon = MIN(MAX(d.month-1, 0), 11);
+ tm.tm_mday = MIN(MAX(d.day, 1), 31);
+ tm.tm_hour = MIN(MAX(d.hour, 0), 23);
+ tm.tm_min = MIN(MAX(d.minute, 0), 59);
+ our_strftime(str, str_len, "%x", &tm);
+ }
+
+ break;
+ case iSDateS1: case iSDateTimeS1: case iSDateTimeS124:
+ if(v)
+ snprintf(str, str_len, "%s/%s/%s%s", mon, day, yearzero,
+ diff < 0 ? "!" : "");
+ else
+ snprintf(str, str_len, "%s%s/%s/%s%s",
+ (mon[0] && mon[1]) ? "" : " ",
+ mon, dayzero, yearzero,
+ diff < 0 ? "!" : "");
+ break;
+ case iSDateS2: case iSDateTimeS2: case iSDateTimeS224:
+ if(v)
+ snprintf(str, str_len, "%s/%s/%s%s", day, mon, yearzero,
+ diff < 0 ? "!" : "");
+ else
+ snprintf(str, str_len, "%s%s/%s/%s%s",
+ (day[0] && day[1]) ? "" : " ",
+ day, monzero, yearzero,
+ diff < 0 ? "!" : "");
+ break;
+ case iSDateS3: case iSDateTimeS3: case iSDateTimeS324:
+ if(v)
+ snprintf(str, str_len, "%s.%s.%s%s", day, mon, yearzero,
+ diff < 0 ? "!" : "");
+ else
+ snprintf(str, str_len, "%s%s.%s.%s%s",
+ (day[0] && day[1]) ? "" : " ",
+ day, monzero, yearzero,
+ diff < 0 ? "!" : "");
+ break;
+ case iSDateS4: case iSDateTimeS4: case iSDateTimeS424:
+ if(v)
+ snprintf(str, str_len, "%s.%s.%s%s",
+ yearzero, monzero, dayzero,
+ diff < 0 ? "!" : "");
+ else
+ snprintf(str, str_len, "%s.%s.%s%s",
+ yearzero, monzero, dayzero,
+ diff < 0 ? "!" : "");
+ break;
+ case iSDateIsoS: case iSDateTimeIsoS: case iSDateTimeIsoS24:
+ snprintf(str, str_len, "%2s-%2s-%2s",
+ yearzero, monzero, dayzero);
+ break;
+ case iSDateIso: case iSDateTimeIso: case iSDateTimeIso24:
+ snprintf(str, str_len, "%4s-%2s-%2s",
+ year4, monzero, dayzero);
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+ else{
+ if(v)
+ snprintf(str, str_len, "%s%s%s", monabb,
+ (monabb[0] && day[0]) ? " " : "", day);
+ else{
+ if(monabb_width > 0)
+ utf8_snprintf(str, str_len, "%-*.*w %2s",
+ monabb_width, monabb_width, monabb, day);
+ else
+ snprintf(str, str_len, "%s %2s", monabb, day);
+ }
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ str[str_len-1] = '\0';
+
+ if(type == iSTime ||
+ (sdatetimetype && !strcmp(str, _(TODAYSTR)))){
+ struct date now, last_day;
+ char dbuf[200], *Ddd, *ampm;
+ int daydiff;
+
+ str[0] = '\0';
+ rfc822_date(dbuf);
+ parse_date(dbuf, &now);
+
+ /* Figure out if message date lands in the past week */
+
+ /* (if message dated this month or last month...) */
+ if((d.year == now.year && d.month >= now.month - 1) ||
+ (d.year == now.year - 1 && d.month == 12 && now.month == 1)){
+
+ daydiff = day_of_year(&now) - day_of_year(&d);
+
+ /*
+ * If msg in end of last year (and we're in first bit of "this"
+ * year), diff will be backwards; fix up by adding number of days
+ * in last year (usually 365, but occasionally 366)...
+ */
+ if(d.year == now.year - 1){
+ last_day = d;
+ last_day.month = 12;
+ last_day.day = 31;
+
+ daydiff += day_of_year(&last_day);
+ }
+ }
+ else
+ daydiff = -100; /* comfortably out of range (of past week) */
+
+ /* Build 2-digit hour and am/pm indicator, used below */
+
+ if(d.hour >= 0 && d.hour < 24){
+ snprintf(hour12, sizeof(hour12), "%02d", (d.hour % 12 == 0) ? 12 : d.hour % 12);
+ ampm = (d.hour < 12) ? "am" : "pm";
+ snprintf(hour24, sizeof(hour24), "%02d", d.hour);
+ }
+ else{
+ strncpy(hour12, "??", sizeof(hour12));
+ hour12[sizeof(hour12)-1] = '\0';
+ ampm = "__";
+ strncpy(hour24, "??", sizeof(hour24));
+ hour24[sizeof(hour24)-1] = '\0';
+ }
+
+ /* Build date/time in str, in format similar to that used by w(1) */
+
+ if(daydiff == 0){ /* If date is today, "HH:MMap" */
+ if(d.minute >= 0 && d.minute < 60)
+ snprintf(minzero, sizeof(minzero), "%02d", d.minute);
+ else{
+ strncpy(minzero, "??", sizeof(minzero));
+ minzero[sizeof(minzero)-1] = '\0';
+ }
+
+ snprintf(str, str_len, "%s:%s%s", sdatetime24type ? hour24 : hour12,
+ minzero, sdatetime24type ? "" : ampm);
+ }
+ else if(daydiff >= 1 && daydiff < 6){ /* If <1wk ago, "DddHHap" */
+
+ if(d.month >= 1 && d.day >= 1 && d.year >= 0 &&
+ d.month <= 12 && d.day <= 31 && d.year <= 9999)
+ Ddd = day_abbrev_locale(day_of_week(&d));
+ else
+ Ddd = "???";
+
+ snprintf(str, str_len, "%s%s%s", Ddd, hour12, ampm);
+ }
+ else{ /* date is old or future, "ddMmmyy" */
+ strncpy(monabb, (d.month >= 1 && d.month <= 12)
+ ? month_abbrev_locale(d.month) : "???", sizeof(monabb));
+ monabb[sizeof(monabb)-1] = '\0';
+
+ if(d.day >= 1 && d.day <= 31)
+ snprintf(dayzero, sizeof(dayzero), "%02d", d.day);
+ else{
+ strncpy(dayzero, "??", sizeof(dayzero));
+ dayzero[sizeof(dayzero)-1] = '\0';
+ }
+
+ if(d.year >= 0 && d.year <= 9999)
+ snprintf(yearzero, sizeof(yearzero), "%02d", d.year % 100);
+ else{
+ strncpy(yearzero, "??", sizeof(yearzero));
+ yearzero[sizeof(yearzero)-1] = '\0';
+ }
+
+ snprintf(str, str_len, "%s%s%s", dayzero, monabb, yearzero);
+ }
+
+ if(str[0] == '0'){ /* leading 0 (date|hour) elided or blanked */
+ if(v)
+ memmove(str, str + 1, strlen(str));
+ else
+ str[0] = ' ';
+ }
+ }
+}
+
+
+/*
+ * Format a string representing the keywords into ice.
+ *
+ * This needs to be done in UTF-8, which may be tricky since it isn't labelled.
+ *
+ * Args idata -- which message?
+ * kwtype -- keywords or kw initials
+ * ice -- index cache entry for message
+ */
+void
+key_str(INDEXDATA_S *idata, SubjKW kwtype, ICE_S *ice)
+{
+ int firstone = 1;
+ KEYWORD_S *kw;
+ char *word;
+ COLOR_PAIR *color = NULL;
+ SPEC_COLOR_S *sc = ps_global->kw_colors;
+ IELEM_S *ielem = NULL;
+ IFIELD_S *ourifield = NULL;
+
+ if(ice && ice->ifield){
+ /* move to last ifield, the one we're working */
+ for(ourifield = ice->ifield;
+ ourifield && ourifield->next;
+ ourifield = ourifield->next)
+ ;
+ }
+
+ if(!ourifield)
+ return;
+
+ if(kwtype == KWInit){
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
+ word = (kw->nick && kw->nick[0]) ? kw->nick :
+ (kw->kw && kw->kw[0]) ? kw->kw : "";
+
+ /*
+ * Pick off the first initial. Since word is UTF-8 it may
+ * take more than one byte for the first initial.
+ */
+
+ if(word && word[0]){
+ UCS ucs;
+ unsigned long remaining_octets;
+ unsigned char *inputp;
+
+ remaining_octets = strlen(word);
+ inputp = (unsigned char *) word;
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+ ielem->datalen = (unsigned) (inputp - (unsigned char *) word);
+ ielem->data = (char *) fs_get((ielem->datalen + 1) * sizeof(char));
+ strncpy(ielem->data, word, ielem->datalen);
+ ielem->data[ielem->datalen] = '\0';
+
+ if(pico_usingcolor()
+ && ((kw->nick && kw->nick[0]
+ && (color=hdr_color(kw->nick,NULL,sc)))
+ || (kw->kw && kw->kw[0]
+ && (color=hdr_color(kw->kw,NULL,sc))))){
+ ielem->color = color;
+ color = NULL;
+ }
+ }
+ }
+
+ if(color)
+ free_color_pair(&color);
+ }
+ }
+ }
+ else if(kwtype == KW){
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(user_flag_is_set(idata->stream, idata->rawno, kw->kw)){
+
+ if(!firstone){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+
+ firstone = 0;
+
+ word = (kw->nick && kw->nick[0]) ? kw->nick :
+ (kw->kw && kw->kw[0]) ? kw->kw : "";
+
+ if(word[0]){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(word);
+ ielem->datalen = strlen(word);
+
+ if(pico_usingcolor()
+ && ((kw->nick && kw->nick[0]
+ && (color=hdr_color(kw->nick,NULL,sc)))
+ || (kw->kw && kw->kw[0]
+ && (color=hdr_color(kw->kw,NULL,sc))))){
+ ielem->color = color;
+ color = NULL;
+ }
+ }
+
+ if(color)
+ free_color_pair(&color);
+ }
+ }
+ }
+
+ /*
+ * If we're coloring some of the fields then add a dummy field
+ * at the end that can soak up the rest of the space after the last
+ * colored keyword. Otherwise, the last one's color will extend to
+ * the end of the field.
+ */
+ if(pico_usingcolor()){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+
+ ourifield->leftadj = 1;
+ set_ielem_widths_in_field(ourifield);
+}
+
+
+void
+prio_str(INDEXDATA_S *idata, IndexColType ctype, ICE_S *ice)
+{
+ IFIELD_S *ourifield = NULL;
+ IELEM_S *ielem = NULL;
+ char *hdrval;
+ PRIORITY_S *p;
+ int v;
+
+ if(ice && ice->ifield){
+ /* move to last ifield, the one we're working */
+ for(ourifield = ice->ifield;
+ ourifield && ourifield->next;
+ ourifield = ourifield->next)
+ ;
+ }
+
+ if(!ourifield)
+ return;
+
+ hdrval = fetch_header(idata, PRIORITYNAME);
+
+ if(hdrval && hdrval[0] && isdigit(hdrval[0])){
+ v = atoi(hdrval);
+ if(v >= 1 && v <= 5 && v != 3){
+
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+
+ switch(ctype){
+ case iPrio:
+ ielem->data = (char *) fs_get(2 * sizeof(char));
+ ielem->data[0] = hdrval[0];
+ ielem->data[1] = '\0';
+ break;
+
+ case iPrioAlpha:
+ for(p = priorities; p && p->desc; p++)
+ if(p->val == v)
+ break;
+
+ if(p && p->desc)
+ ielem->data = cpystr(p->desc);
+
+ break;
+
+ case iPrioBang:
+ ielem->data = (char *) fs_get(2 * sizeof(char));
+ ielem->data[0] = '\0';
+ switch(v){
+ case 1: case 2:
+ ielem->data[0] = '!';
+ break;
+
+ case 4: case 5:
+ /*
+ * We could put a Unicode downarrow in here but
+ * we have no way of knowing if the user's font
+ * will have it (I think).
+ */
+ ielem->data[0] = 'v';
+ break;
+ }
+
+ ielem->data[1] = '\0';
+
+ break;
+
+ default:
+ panic("Unhandled case in prio_str");
+ break;
+ }
+
+ if(!ielem->data)
+ ielem->data = cpystr("");
+
+ if(ielem && ielem->data)
+ ielem->datalen = strlen(ielem->data);
+
+ if((v == 1 || v == 2) && pico_usingcolor()
+ && ps_global->VAR_IND_HIPRI_FORE_COLOR
+ && ps_global->VAR_IND_HIPRI_BACK_COLOR){
+ ielem->type = eTypeCol;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_HIPRI_FORE_COLOR, ps_global->VAR_IND_HIPRI_BACK_COLOR);
+ }
+ else if((v == 4 || v == 5) && pico_usingcolor()
+ && ps_global->VAR_IND_LOPRI_FORE_COLOR
+ && ps_global->VAR_IND_LOPRI_BACK_COLOR){
+ ielem->type = eTypeCol;
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_LOPRI_FORE_COLOR, ps_global->VAR_IND_LOPRI_BACK_COLOR);
+ }
+
+ ourifield->leftadj = 1;
+ set_ielem_widths_in_field(ourifield);
+ }
+
+ fs_give((void **) &hdrval);
+ }
+}
+
+
+void
+header_str(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok, ICE_S *ice)
+{
+ IFIELD_S *ourifield = NULL;
+ IELEM_S *ielem = NULL;
+ char *fieldval = NULL;
+
+ if(ice && ice->ifield){
+ /* move to last ifield, the one we're working */
+ for(ourifield = ice->ifield;
+ ourifield && ourifield->next;
+ ourifield = ourifield->next)
+ ;
+ }
+
+ if(!ourifield)
+ return;
+
+ fieldval = get_fieldval(idata, hdrtok);
+
+ if(fieldval){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->freedata = 1;
+ ielem->data = fieldval;
+ ielem->datalen = strlen(fieldval);
+ fieldval = NULL;
+ ourifield->leftadj = (hdrtok->adjustment == Left) ? 1 : 0;
+ }
+
+ set_ielem_widths_in_field(ourifield);
+}
+
+
+char *
+get_fieldval(INDEXDATA_S *idata, HEADER_TOK_S *hdrtok)
+{
+ int sep, fieldnum;
+ char *hdrval = NULL, *testval;
+ char *fieldval = NULL, *firstval;
+ char *retfieldval = NULL;
+
+ if(!hdrtok)
+ return(retfieldval);
+
+ if(hdrtok && hdrtok->hdrname && hdrtok->hdrname[0])
+ hdrval = fetch_header(idata, hdrtok ? hdrtok->hdrname : "");
+
+ /* find start of fieldnum'th field */
+ fieldval = hdrval;
+ for(fieldnum = MAX(hdrtok->fieldnum-1, 0);
+ fieldnum > 0 && fieldval && *fieldval; fieldnum--){
+
+ firstval = NULL;
+ for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
+ testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
+ if(testval && (!firstval || testval < firstval))
+ firstval = testval;
+ }
+
+ fieldval = firstval;
+ if(fieldval && *fieldval)
+ fieldval++;
+ }
+
+ /* tie off end of field */
+ if(fieldval && *fieldval && hdrtok->fieldnum > 0){
+ firstval = NULL;
+ for(sep = 0; sep < hdrtok->fieldsepcnt; sep++){
+ testval = hdrtok->fieldseps ? strchr(fieldval, hdrtok->fieldseps[sep]) : NULL;
+ if(testval && (!firstval || testval < firstval))
+ firstval = testval;
+ }
+
+ if(firstval)
+ *firstval = '\0';
+ }
+
+ if(!fieldval)
+ fieldval = "";
+
+ retfieldval = cpystr(fieldval);
+
+ if(hdrval)
+ fs_give((void **) &hdrval);
+
+ return(retfieldval);
+}
+
+
+long
+scorevalfrommsg(MAILSTREAM *stream, MsgNo rawno, HEADER_TOK_S *hdrtok, int no_fetch)
+{
+ INDEXDATA_S idata;
+ MESSAGECACHE *mc;
+ char *fieldval = NULL;
+ long retval = 0L;
+
+ memset(&idata, 0, sizeof(INDEXDATA_S));
+ idata.stream = stream;
+ idata.no_fetch = no_fetch;
+ idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
+ idata.rawno = rawno;
+ if(stream && idata.rawno > 0L && idata.rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, idata.rawno))){
+ idata.size = mc->rfc822_size;
+ index_data_env(&idata, pine_mail_fetchenvelope(stream,idata.rawno));
+ }
+ else
+ idata.bogus = 2;
+
+ fieldval = get_fieldval(&idata, hdrtok);
+
+ if(fieldval){
+ retval = atol(fieldval);
+ fs_give((void **) &fieldval);
+ }
+
+ return(retval);
+}
+
+
+/*
+ * Put a string representing the subject into str. Idata tells us which
+ * message we are referring to.
+ *
+ * This means we should ensure that all data ends up being UTF-8 data.
+ * That covers the data in ice ielems and str.
+ *
+ * Args idata -- which message?
+ * str -- destination buffer
+ * strsize -- size of str buffer
+ * kwtype -- prepend keywords or kw initials before the subject
+ * opening -- add first text from body of message if there's room
+ * ice -- index cache entry for message
+ */
+void
+subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int opening, ICE_S *ice)
+{
+ char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL;
+ char *p, *border, *q = NULL, *free_subj = NULL;
+ char *sp;
+ size_t len;
+ int width = -1;
+ int depth = 0, mult = 2;
+ int save;
+ int do_subj = 0, truncated_tree = 0;
+ PINETHRD_S *thd, *thdorig;
+ IELEM_S *ielem = NULL, *subjielem = NULL;
+ IFIELD_S *ourifield = NULL;
+
+ if(strsize <= 0)
+ return;
+
+ /*
+ * If we need the data at the start of the message and we're in
+ * a c-client callback, defer the data lookup until later.
+ */
+ if(opening && idata->no_fetch){
+ idata->bogus = 1;
+ return;
+ }
+
+ if(ice && ice->ifield){
+ /* move to last ifield, the one we're working on */
+ for(ourifield = ice->ifield;
+ ourifield && ourifield->next;
+ ourifield = ourifield->next)
+ ;
+ }
+
+ str[0] = str[strsize-1] = '\0';
+ origstr = str;
+ rawsubj = fetch_subject(idata);
+ if(!rawsubj)
+ rawsubj = "";
+
+ /*
+ * Before we do anything else, decode the character set in the subject and
+ * work with the result.
+ */
+ sp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, rawsubj);
+
+ len = strlen(sp);
+ len += 100; /* for possible charset, escaped characters */
+ origsubj = fs_get((len+1) * sizeof(unsigned char));
+ origsubj[0] = '\0';
+
+ iutf8ncpy(origsubj, sp, len);
+
+ origsubj[len] = '\0';
+ removing_trailing_white_space(origsubj);
+
+ /*
+ * origsubj is the original subject but it has been decoded. We need
+ * to free it at the end of this routine.
+ */
+
+
+ /*
+ * prepend_keyword will put the keyword stuff before the subject
+ * and split the subject up into its colored parts in subjielem.
+ * Subjielem is a local ielem which will have to be fit into the
+ * real ifield->ielem later. The print_format strings in subjielem will
+ * not be filled in by prepend_keyword because of the fact that we
+ * may have to adjust things for threading below.
+ * We use subjielem in case we want to insert some threading information
+ * at the front of the subject.
+ */
+ if(kwtype == KW || kwtype == KWInit){
+ subject = prepend_keyword_subject(idata->stream, idata->rawno,
+ origsubj, kwtype,
+ ourifield ? &subjielem : NULL,
+ ps_global->VAR_KW_BRACES);
+ free_subj = subject;
+ }
+ else{
+ subject = origsubj;
+ if(ourifield){
+ subjielem = new_ielem(&subjielem);
+ subjielem->type = eTypeCol;
+ subjielem->freedata = 1;
+ subjielem->data = cpystr(subject);
+ subjielem->datalen = strlen(subject);
+ if(pico_usingcolor()
+ && ps_global->VAR_IND_SUBJ_FORE_COLOR
+ && ps_global->VAR_IND_SUBJ_BACK_COLOR){
+ subjielem->freecolor = 1;
+ subjielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
+ }
+ }
+ }
+
+ /*
+ * This space is here so that if the subject does
+ * not extend all the way to the end of the field then
+ * we'll switch the color back and paint the rest of the
+ * field in the Normal color or the index line color.
+ */
+ if(!opening){
+ ielem = new_ielem(&subjielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+
+ if(!subject)
+ subject = "";
+
+ if(THREADING()
+ && (ps_global->thread_disp_style == THREAD_STRUCT
+ || ps_global->thread_disp_style == THREAD_MUTTLIKE
+ || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
+ || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
+
+ /*
+ * Why do we want to truncate the subject and from strs?
+ * It's so we can put the [5] thread count things in below when
+ * we are threading and the thread structure runs off the right
+ * hand edge of the screen. This routine doesn't know that it
+ * is running off the edge unless it knows the actual width
+ * that we have to draw in.
+ */
+ if(pith_opt_truncate_sfstr
+ && (*pith_opt_truncate_sfstr)()
+ && ourifield
+ && ourifield->width > 0)
+ width = ourifield->width;
+
+ if(width < 0)
+ width = strsize-1;
+
+ width = MIN(width, strsize-1);
+
+ /*
+ * We're counting on the fact that this initial part of the
+ * string is ascii and we have one octet per character and
+ * characters are width 1 on the screen.
+ */
+ border = str + width;
+
+ thdorig = thd = fetch_thread(idata->stream, idata->rawno);
+
+ if(pith_opt_condense_thread_cue)
+ width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
+ thd && thd->next
+ && get_lflag(idata->stream,
+ NULL,idata->rawno,
+ MN_COLL));
+
+ /*
+ * width is < available strsize and
+ * border points to something less than or equal
+ * to the end of the buffer.
+ */
+
+ sptr = str;
+
+ if(thd)
+ while(thd->parent &&
+ (thd = fetch_thread(idata->stream, thd->parent)))
+ depth++;
+
+ if(depth > 0){
+ if(ps_global->thread_disp_style == THREAD_INDENT_SUBJ1)
+ mult = 1;
+
+ sptr += (mult*depth);
+ for(thd = thdorig, p = str + mult*depth - mult;
+ thd && thd->parent && p >= str;
+ thd = fetch_thread(idata->stream, thd->parent), p -= mult){
+ if(p + mult >= border && !q){
+ if(width >= 4 && depth < 100){
+ snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
+ q = str + width-4;
+ }
+ else if(width >= 5 && depth < 1000){
+ snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
+ q = str + width-5;
+ }
+ else{
+ snprintf(str, width+1, "%s", repeat_char(width, '.'));
+ q = str;
+ }
+
+ border = q;
+ truncated_tree++;
+ }
+
+ if(p < border){
+ p[0] = ' ';
+ if(p + 1 < border)
+ p[1] = ' ';
+
+ if(ps_global->thread_disp_style == THREAD_STRUCT
+ || ps_global->thread_disp_style == THREAD_MUTTLIKE){
+ /*
+ * WARNING!
+ * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
+ * is ascii.
+ */
+ if(thd == thdorig && !thd->branch)
+ p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
+ else if(thd == thdorig || thd->branch)
+ p[0] = '|';
+
+ if(p + 1 < border && thd == thdorig)
+ p[1] = '-';
+ }
+ }
+ }
+ }
+
+ if(sptr && !truncated_tree){
+ /*
+ * Look to see if the subject is the same as the previous
+ * message in the thread, if any. If it is the same, don't
+ * reprint the subject.
+ *
+ * Note that when we're prepending keywords to the subject,
+ * and the user changes a keyword, we do invalidate
+ * the index cache for that message but we don't go to the
+ * trouble of invalidating the index cache for the the child
+ * of that node in the thread, so the MUTT subject line
+ * display for the child may be wrong. That is, it may show
+ * it is the same as this subject even though it no longer
+ * is, or vice versa.
+ */
+ if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
+ if(depth == 0)
+ do_subj++;
+ else{
+ if(thdorig->parent &&
+ (thd = fetch_thread(idata->stream, thdorig->parent))
+ && thd->rawno){
+ char *this_orig = NULL,
+ *prev_orig = NULL,
+ *free_prev_orig = NULL,
+ *this_prep = NULL, /* includes prepend */
+ *prev_prep = NULL;
+ ENVELOPE *env;
+ mailcache_t mc;
+ SORTCACHE *sc = NULL;
+
+ /* get the stripped subject of previous message */
+ mc = (mailcache_t) mail_parameters(NIL, GET_CACHE, NIL);
+ if(mc)
+ sc = (*mc)(idata->stream, thd->rawno, CH_SORTCACHE);
+
+ if(sc && sc->subject)
+ prev_orig = sc->subject;
+ else{
+ char *stripthis;
+
+ env = pine_mail_fetchenvelope(idata->stream,
+ thd->rawno);
+ stripthis = (env && env->subject)
+ ? env->subject : "";
+
+ mail_strip_subject(stripthis, &prev_orig);
+
+ free_prev_orig = prev_orig;
+ }
+
+ mail_strip_subject(rawsubj, &this_orig);
+
+ if(kwtype == KW || kwtype == KWInit){
+ prev_prep = prepend_keyword_subject(idata->stream,
+ thd->rawno,
+ prev_orig,
+ kwtype, NULL,
+ ps_global->VAR_KW_BRACES);
+
+ this_prep = prepend_keyword_subject(idata->stream,
+ idata->rawno,
+ this_orig,
+ kwtype, NULL,
+ ps_global->VAR_KW_BRACES);
+
+ if((this_prep || prev_prep)
+ && ((this_prep && !prev_prep)
+ || (prev_prep && !this_prep)
+ || strucmp(this_prep, prev_prep)))
+ do_subj++;
+ }
+ else{
+ if((this_orig || prev_orig)
+ && ((this_orig && !prev_orig)
+ || (prev_orig && !this_orig)
+ || strucmp(this_orig, prev_orig)))
+ do_subj++;
+ }
+
+ /*
+ * If some of the thread is zoomed out of view, we
+ * want to display the subject of the first one that
+ * is in view. If any of the parents or grandparents
+ * etc of this message are visible, then we don't
+ * need to worry about it. If all of the parents have
+ * been zoomed away, then this is the first one.
+ *
+ * When you're looking at a particular case where
+ * some of the messages of a thread are selected it
+ * seems like we should look at not only our
+ * direct parents, but the siblings of the parent
+ * too. But that's not really correct, because those
+ * siblings are basically the starts of different
+ * branches, separate from our branch. They could
+ * have their own subjects, for example. This will
+ * give us cases where it looks like we are showing
+ * the subject too much, but it will be correct!
+ *
+ * In zoom_index() we clear_index_cache_ent for
+ * some lines which have subjects which might become
+ * visible when we zoom, and also in set_lflags
+ * where we might change subjects by unselecting
+ * something when zoomed.
+ */
+ if(!do_subj){
+ while(thd){
+ if(!msgline_hidden(idata->stream,
+ sp_msgmap(idata->stream),
+ mn_raw2m(sp_msgmap(idata->stream),
+ (long) thd->rawno),
+ 0)){
+ break; /* found a visible parent */
+ }
+
+ if(thd && thd->parent)
+ thd = fetch_thread(idata->stream,thd->parent);
+ else
+ thd = NULL;
+ }
+
+ if(!thd) /* none were visible */
+ do_subj++;
+ }
+
+ if(this_orig)
+ fs_give((void **) &this_orig);
+
+ if(this_prep)
+ fs_give((void **) &this_prep);
+
+ if(free_prev_orig)
+ fs_give((void **) &free_prev_orig);
+
+ if(prev_prep)
+ fs_give((void **) &prev_prep);
+ }
+ else
+ do_subj++;
+ }
+ }
+ else
+ do_subj++;
+
+ if(do_subj){
+ /*
+ * We don't need to worry about truncating to width
+ * here. If we go over the right hand edge it will be
+ * truncated.
+ */
+ strsize -= (sptr - str);
+
+ strncpy(sptr, subject, strsize-1);
+ sptr[strsize-1] = '\0';
+ }
+ else if(ps_global->thread_disp_style == THREAD_MUTTLIKE){
+ strsize -= (sptr - str);
+
+ if(strsize > 0){
+ sptr[0] = '>';
+ sptr++;
+ }
+
+ /*
+ * We decided we don't need the subject so we'd better
+ * eliminate subjielem.
+ */
+ free_ielem(&subjielem);
+ }
+ }
+ else
+ free_ielem(&subjielem); /* no room for actual subject */
+
+ if(ourifield && sptr && sptr > origstr){
+ ielem = new_ielem(&ourifield->ielem);
+ ielem->type = eThreadInfo;
+ ielem->freedata = 1;
+ save = *sptr;
+ *sptr = '\0';
+ ielem->data = cpystr(origstr);
+ ielem->datalen = strlen(origstr);
+ *sptr = save;
+ }
+ }
+ else{
+ /*
+ * Not much to do for the non-threading case. Just copy the
+ * subject we have so far into str and truncate it.
+ */
+ strncpy(str, subject, strsize-1);
+ str[strsize-1] = '\0';
+ }
+
+ if(ourifield){
+ /*
+ * We need to add subjielem to the end of the ourifield->ielem list.
+ */
+ if(subjielem){
+ if(ourifield->ielem){
+ for(ielem = ourifield->ielem;
+ ielem && ielem->next; ielem = ielem->next)
+ ;
+
+ ielem->next = subjielem;
+ }
+ else
+ ourifield->ielem = subjielem;
+ }
+
+ ourifield->leftadj = 1;
+ }
+
+ if(opening && ourifield){
+ IELEM_S *ftielem = NULL;
+ size_t len;
+ char *first_text;
+
+ first_text = fetch_firsttext(idata, 0);
+
+ if(first_text){
+ char sep[200];
+ int seplen;
+
+ strncpy(sep, ps_global->VAR_OPENING_SEP ? ps_global->VAR_OPENING_SEP : " - ",
+ sizeof(sep));
+ sep[sizeof(sep)-1] = '\0';
+ removing_double_quotes(sep);
+ seplen = strlen(sep);
+
+ ftielem = new_ielem(&ftielem);
+ ftielem->type = eTypeCol;
+ ftielem->freedata = 1;
+ len = strlen(first_text) + seplen;
+ ftielem->data = (char *) fs_get((len + 1) * sizeof(char));
+
+ strncpy(ftielem->data, sep, seplen);
+ strncpy(ftielem->data+seplen, first_text, len+1-seplen);
+ ftielem->data[len] = '\0';
+
+ ftielem->datalen = strlen(ftielem->data);
+ if(first_text)
+ fs_give((void **) &first_text);
+
+ if(ftielem){
+ if(pico_usingcolor()
+ && ps_global->VAR_IND_OP_FORE_COLOR
+ && ps_global->VAR_IND_OP_BACK_COLOR){
+ ftielem->freecolor = 1;
+ ftielem->color = new_color_pair(ps_global->VAR_IND_OP_FORE_COLOR, ps_global->VAR_IND_OP_BACK_COLOR);
+
+ /*
+ * This space is here so that if the opening text does
+ * not extend all the way to the end of the field then
+ * we'll switch the color back and paint the rest of the
+ * field in the Normal color or the index line color.
+ */
+ ielem = new_ielem(&ftielem);
+ ielem->freedata = 1;
+ ielem->data = cpystr(" ");
+ ielem->datalen = 1;
+ }
+
+ if(ourifield->ielem){
+ for(ielem = ourifield->ielem;
+ ielem && ielem->next; ielem = ielem->next)
+ ;
+
+ ielem->next = ftielem;
+ }
+ else
+ ourifield->ielem = ftielem;
+ }
+
+ ourifield->leftadj = 1;
+ }
+ }
+
+ if(ourifield)
+ set_ielem_widths_in_field(ourifield);
+
+ if(origsubj)
+ fs_give((void **) &origsubj);
+
+ if(free_subj)
+ fs_give((void **) &free_subj);
+}
+
+
+/*
+ * Returns an allocated string which is the passed in subject with a
+ * list of keywords prepended.
+ *
+ * If kwtype == KW you will end up with
+ *
+ * {keyword1 keyword2} subject
+ *
+ * (actually, keyword nicknames will be used instead of the actual keywords
+ * in the case that the user defined nicknames)
+ *
+ * If kwtype == KWInit you get
+ *
+ * {AB} subject
+ *
+ * where A is the first letter of the first keyword and B is the first letter
+ * of the second defined keyword. No space between them. There could be more
+ * than two.
+ *
+ * If an ielemp is passed in it will be filled out with the data and colors
+ * of the pieces of the subject but the print_format strings will not
+ * be set.
+ */
+char *
+prepend_keyword_subject(MAILSTREAM *stream, long int rawno, char *subject,
+ SubjKW kwtype, IELEM_S **ielemp, char *braces)
+{
+ char *p, *next_piece, *retsubj = NULL, *str;
+ char *left_brace = NULL, *right_brace = NULL;
+ size_t len;
+ int some_set = 0, save;
+ IELEM_S *ielem;
+ KEYWORD_S *kw;
+ COLOR_PAIR *color = NULL;
+ SPEC_COLOR_S *sc = ps_global->kw_colors;
+
+ if(!subject)
+ subject = "";
+
+ if(braces && *braces)
+ get_pair(braces, &left_brace, &right_brace, 1, 0);
+
+ len = (left_brace ? strlen(left_brace) : 0) +
+ (right_brace ? strlen(right_brace) : 0);
+
+ if(stream && rawno >= 0L && rawno <= stream->nmsgs){
+ for(kw = ps_global->keywords; kw; kw = kw->next)
+ if(user_flag_is_set(stream, rawno, kw->kw)){
+ if(kwtype == KW){
+ if(some_set)
+ len++; /* space between keywords */
+
+ str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
+ len += strlen(str);
+ }
+ else if(kwtype == KWInit){
+ str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
+ /* interested in only the first UTF-8 initial */
+ if(str && str[0]){
+ UCS ucs;
+ unsigned long remaining_octets;
+ unsigned char *inputp;
+
+ remaining_octets = strlen(str);
+ inputp = (unsigned char *) str;
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ len += (unsigned) (inputp - (unsigned char *) str);
+ }
+ }
+ }
+
+ some_set++;
+ }
+ }
+
+ if((kwtype == KW || kwtype == KWInit) && some_set){
+ len += strlen(subject); /* subject is already UTF-8 if needed */
+ retsubj = (char *) fs_get((len + 1) * sizeof(*retsubj));
+ memset(retsubj, 0, (len + 1) * sizeof(*retsubj));
+ next_piece = p = retsubj;
+
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(user_flag_is_set(stream, rawno, kw->kw)){
+ if(p == retsubj){
+ if(left_brace && len > 0)
+ sstrncpy(&p, left_brace, len);
+ }
+ else if(kwtype == KW)
+ *p++ = ' ';
+
+ if(ielemp && p > next_piece){
+ save = *p;
+ *p = '\0';
+ ielem = new_ielem(ielemp);
+ ielem->freedata = 1;
+ ielem->data = cpystr(next_piece);
+ ielem->datalen = strlen(next_piece);
+ *p = save;
+ next_piece = p;
+ }
+
+ str = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
+
+ if(kwtype == KWInit){
+ if(str && str[0]){
+ UCS ucs;
+ unsigned long remaining_octets;
+ unsigned char *inputp;
+
+ remaining_octets = strlen(str);
+ inputp = (unsigned char *) str;
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ if(len-(p-retsubj) > 0){
+ sstrncpy(&p, str, MIN(inputp - (unsigned char *) str,len-(p-retsubj)));
+ if(p > next_piece && ielemp && pico_usingcolor()
+ && ((kw->nick && kw->nick[0]
+ && (color=hdr_color(kw->nick,NULL,sc)))
+ || (kw->kw && kw->kw[0]
+ && (color=hdr_color(kw->kw,NULL,sc))))){
+ ielem = new_ielem(ielemp);
+ ielem->freedata = 1;
+ save = *p;
+ *p = '\0';
+ ielem->data = cpystr(next_piece);
+ ielem->datalen = strlen(next_piece);
+ ielem->color = color;
+ color = NULL;
+ *p = save;
+ next_piece = p;
+ }
+ }
+ }
+
+ if(color)
+ free_color_pair(&color);
+ }
+ }
+ else{
+ if(len-(p-retsubj) > 0)
+ sstrncpy(&p, str, len-(p-retsubj));
+
+ if(p > next_piece && ielemp && pico_usingcolor()
+ && ((kw->nick && kw->nick[0]
+ && (color=hdr_color(kw->nick,NULL,sc)))
+ || (kw->kw && kw->kw[0]
+ && (color=hdr_color(kw->kw,NULL,sc))))){
+ ielem = new_ielem(ielemp);
+ ielem->freedata = 1;
+ save = *p;
+ *p = '\0';
+ ielem->data = cpystr(next_piece);
+ ielem->datalen = strlen(next_piece);
+ ielem->color = color;
+ color = NULL;
+ *p = save;
+ next_piece = p;
+ }
+
+ if(color)
+ free_color_pair(&color);
+ }
+ }
+ }
+
+ if(len-(p-retsubj) > 0 && right_brace)
+ sstrncpy(&p, right_brace, len-(p-retsubj));
+
+ if(ielemp && p > next_piece){
+ save = *p;
+ *p = '\0';
+ ielem = new_ielem(ielemp);
+ ielem->freedata = 1;
+ ielem->data = cpystr(next_piece);
+ ielem->datalen = strlen(next_piece);
+ *p = save;
+ next_piece = p;
+ }
+
+ if(len-(p-retsubj) > 0 && subject)
+ sstrncpy(&p, subject, len-(p-retsubj));
+
+ if(ielemp && p > next_piece){
+ save = *p;
+ *p = '\0';
+ ielem = new_ielem(ielemp);
+ ielem->type = eTypeCol;
+ ielem->freedata = 1;
+ ielem->data = cpystr(next_piece);
+ ielem->datalen = strlen(next_piece);
+ *p = save;
+ next_piece = p;
+ if(pico_usingcolor()
+ && ps_global->VAR_IND_SUBJ_FORE_COLOR
+ && ps_global->VAR_IND_SUBJ_BACK_COLOR){
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
+ }
+ }
+
+ retsubj[len] = '\0'; /* just making sure */
+ }
+ else{
+ if(ielemp){
+ ielem = new_ielem(ielemp);
+ ielem->type = eTypeCol;
+ ielem->freedata = 1;
+ ielem->data = cpystr(subject);
+ ielem->datalen = strlen(subject);
+ if(pico_usingcolor()
+ && ps_global->VAR_IND_SUBJ_FORE_COLOR
+ && ps_global->VAR_IND_SUBJ_BACK_COLOR){
+ ielem->freecolor = 1;
+ ielem->color = new_color_pair(ps_global->VAR_IND_SUBJ_FORE_COLOR, ps_global->VAR_IND_SUBJ_BACK_COLOR);
+ }
+ }
+
+ retsubj = cpystr(subject);
+ }
+
+ if(braces){
+ if(left_brace)
+ fs_give((void **) &left_brace);
+
+ if(right_brace)
+ fs_give((void **) &right_brace);
+ }
+
+ return(retsubj);
+}
+
+
+/*
+ * This means we should ensure that all data ends up being UTF-8 data.
+ * That covers the data in ice ielems and str.
+ */
+void
+from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_S *ice)
+{
+ char *field, *newsgroups, *border, *p, *fptr = NULL, *q = NULL;
+ ADDRESS *addr;
+ int width = -1;
+ int depth = 0, mult = 2;
+ PINETHRD_S *thd, *thdorig;
+
+ if(THREADING()
+ && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
+ || ps_global->thread_disp_style == THREAD_INDENT_FROM2
+ || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
+
+ if(pith_opt_truncate_sfstr && (*pith_opt_truncate_sfstr)()){
+ IFIELD_S *ourifield = NULL;
+
+ if(ice && ice->ifield){
+ /* move to last ifield, the one we're working on */
+ for(ourifield = ice->ifield;
+ ourifield && ourifield->next;
+ ourifield = ourifield->next)
+ ;
+ }
+
+ if(ourifield && ourifield->width > 0)
+ width = ourifield->width;
+ }
+
+ if(width < 0)
+ width = strsize-1;
+
+ width = MIN(width, strsize-1);
+
+ thdorig = thd = fetch_thread(idata->stream, idata->rawno);
+ border = str + width;
+ if(pith_opt_condense_thread_cue)
+ width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width,
+ thd && thd->next
+ && get_lflag(idata->stream,
+ NULL,idata->rawno,
+ MN_COLL));
+
+ fptr = str;
+
+ if(thd)
+ while(thd->parent && (thd = fetch_thread(idata->stream, thd->parent)))
+ depth++;
+
+ if(depth > 0){
+ if(ps_global->thread_disp_style == THREAD_INDENT_FROM1)
+ mult = 1;
+
+ fptr += (mult*depth);
+ for(thd = thdorig, p = str + mult*depth - mult;
+ thd && thd->parent && p >= str;
+ thd = fetch_thread(idata->stream, thd->parent), p -= mult){
+ if(p + mult >= border && !q){
+ if(width >= 4 && depth < 100){
+ snprintf(str, width+1, "%*s[%2d]", width-4, "", depth);
+ q = str + width-4;
+ }
+ else if(width >= 5 && depth < 1000){
+ snprintf(str, width+1, "%*s[%3d]", width-5, "", depth);
+ q = str + width-5;
+ }
+ else{
+ snprintf(str, width+1, "%s", repeat_char(width, '.'));
+ q = str;
+ }
+
+ border = q;
+ fptr = NULL;
+ }
+
+ if(p + 1 < border){
+ p[0] = p[1] = ' ';
+ if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
+ /*
+ * WARNING!
+ * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
+ * is ascii.
+ */
+ if(thd == thdorig && !thd->branch)
+ p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
+ else if(thd == thdorig || thd->branch)
+ p[0] = '|';
+
+ if(thd == thdorig)
+ p[1] = '-';
+ }
+ }
+ else if(p < border){
+ p[0] = ' ';
+ if(ps_global->thread_disp_style == THREAD_STRUCT_FROM){
+ /*
+ * WARNING!
+ * There is an unwarranted assumption here that VAR_THREAD_LASTREPLY_CHAR[0]
+ * is ascii.
+ */
+ if(thd == thdorig && !thd->branch)
+ p[0] = ps_global->VAR_THREAD_LASTREPLY_CHAR[0];
+ else if(thd == thdorig || thd->branch)
+ p[0] = '|';
+ }
+ }
+ }
+ }
+ }
+ else
+ fptr = str;
+
+ if(fptr){
+ strsize -= (fptr - str);
+ switch(ctype){
+ case iFromTo:
+ case iFromToNotNews:
+ if(!(addr = fetch_from(idata)) || address_is_us(addr, ps_global)){
+ if(strsize-1 <= 4){
+ strncpy(fptr, "To: ", strsize-1);
+ fptr[strsize-1] = '\0';
+ break;
+ }
+ else{
+ if((field = ((addr = fetch_to(idata))
+ ? "To"
+ : (addr = fetch_cc(idata))
+ ? "Cc"
+ : NULL))
+ && set_index_addr(idata, field, addr, "To: ",
+ strsize-1, fptr))
+ break;
+
+ if(ctype == iFromTo &&
+ (newsgroups = fetch_newsgroups(idata)) &&
+ *newsgroups){
+ snprintf(fptr, strsize, "To: %-*.*s", strsize-1-4, strsize-1-4,
+ newsgroups);
+ break;
+ }
+
+ /* else fall thru to From: */
+ }
+ }
+ /* else fall thru to From: */
+
+ if(idata->bogus)
+ break;
+
+ case iFrom:
+ set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr);
+ break;
+
+ case iAddress:
+ case iMailbox:
+ if((addr = fetch_from(idata)) && addr->mailbox && addr->mailbox[0]){
+ char *mb = NULL, *hst = NULL, *at = NULL;
+ size_t len;
+
+ mb = addr->mailbox;
+ if(ctype == iAddress && addr->host && addr->host[0]
+ && addr->host[0] != '.'){
+ at = "@";
+ hst = addr->host;
+ }
+
+ len = strlen(mb);
+ if(!at || strsize-1 <= len)
+ snprintf(fptr, strsize, "%-*.*s", strsize-1, strsize-1, mb);
+ else
+ snprintf(fptr, strsize, "%s@%-*.*s", mb, strsize-1-len-1, strsize-1-len-1, hst);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/*
+ * Set up the elements contained in field so that they take up the
+ * whole field width. Data is assumed to be UTF-8.
+ */
+void
+set_ielem_widths_in_field(IFIELD_S *ifield)
+{
+ IELEM_S *ielem = NULL;
+ int datawidth, fmtwidth;
+
+ if(!ifield)
+ return;
+
+ fmtwidth = ifield->width;
+
+ for(ielem = ifield->ielem; ielem && fmtwidth > 0; ielem = ielem->next){
+ if(!ifield->leftadj && ielem->next){
+ dprint((1, "set_ielem_widths_in_field(%d): right adjust with multiple elements, NOT SUPPOSED TO HAPPEN!\n", (int) ifield->ctype));
+ assert(0);
+ }
+
+ datawidth = (int) utf8_width(ielem->data);
+ if(datawidth >= fmtwidth || !ielem->next){
+ set_print_format(ielem, fmtwidth, ifield->leftadj);
+ fmtwidth = 0;
+ }
+ else{
+ set_print_format(ielem, datawidth, ifield->leftadj);
+ fmtwidth -= datawidth;
+ }
+ }
+}
+
+
+/*
+ * Simple hash function from K&R 2nd edition, p. 144.
+ *
+ * This one is modified to never return 0 so we can use that as a special
+ * value. Also, LINE_HASH_N fits in an unsigned long, so it too can be used
+ * as a special value that can't be returned by line_hash.
+ */
+unsigned long
+line_hash(char *s)
+{
+ unsigned long hashval;
+
+ for(hashval = 0; *s != '\0'; s++)
+ hashval = *s + 31 * hashval;
+
+ hashval = hashval % LINE_HASH_N;
+
+ if(!hashval)
+ hashval++;
+
+ return(hashval);
+}
+
+
+/*
+ * Returns nonzero if considered hidden, 0 if not considered hidden.
+ */
+int
+msgline_hidden(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags)
+{
+ int ret;
+
+ if(flags & MH_ANYTHD){
+ ret = ((any_lflagged(msgmap, MN_HIDE) > 0)
+ && get_lflag(stream, msgmap, msgno, MN_HIDE));
+ }
+ else if(flags & MH_THISTHD && THREADING() && sp_viewing_a_thread(stream)){
+ ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
+ || !get_lflag(stream, msgmap, msgno, MN_CHID2));
+ }
+ else{
+ if(THREADING() && sp_viewing_a_thread(stream)){
+ ret = (get_lflag(stream, msgmap, msgno, MN_HIDE)
+ || !get_lflag(stream, msgmap, msgno, MN_CHID2)
+ || get_lflag(stream, msgmap, msgno, MN_CHID));
+ }
+ else if(THRD_INDX()){
+ /*
+ * If this message is in the collapsed part of a thread,
+ * it's hidden. It must be a top-level of a thread to be
+ * considered visible. Even if it is top-level, it is only
+ * visible if some message in the thread is not hidden.
+ */
+ if(get_lflag(stream, msgmap, msgno, MN_CHID)) /* not top */
+ ret = 1;
+ else{
+ unsigned long rawno;
+ PINETHRD_S *thrd = NULL;
+
+ rawno = mn_m2raw(msgmap, msgno);
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ ret = !thread_has_some_visible(stream, thrd);
+ }
+ }
+ else{
+ ret = ((any_lflagged(msgmap, MN_HIDE | MN_CHID) > 0)
+ && get_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID));
+ }
+ }
+
+ dprint((10,
+ "msgline_hidden(%ld): %s\n", msgno, ret ? "HID" : "VIS"));
+
+ return(ret);
+}
+
+
+void
+adjust_cur_to_visible(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long n, cur;
+ int dir;
+
+ cur = mn_get_cur(msgmap);
+
+ /* if current is hidden, adjust */
+ if(cur >= 1L && cur <= mn_get_total(msgmap)
+ && msgline_hidden(stream, msgmap, cur, 0)){
+
+ dir = mn_get_revsort(msgmap) ? -1 : 1;
+
+ for(n = cur;
+ ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
+ && (n >= 1L && n <= mn_get_total(msgmap))
+ && msgline_hidden(stream, msgmap, n, 0);
+ n -= dir)
+ ;
+
+ if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
+ && (n >= 1L && n <= mn_get_total(msgmap)))
+ mn_reset_cur(msgmap, n);
+ else{ /* no visible in that direction */
+ for(n = cur;
+ ((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
+ && (n >= 1L && n <= mn_get_total(msgmap))
+ && msgline_hidden(stream, msgmap, n, 0);
+ n += dir)
+ ;
+
+ if(((dir == 1 && n >= 1L) || (dir == -1 && n <= mn_get_total(msgmap)))
+ && (n >= 1L && n <= mn_get_total(msgmap)))
+ mn_reset_cur(msgmap, n);
+ /* else trouble! */
+ }
+ }
+}
+
+
+void
+setup_for_index_index_screen(void)
+{
+ format_index_line = format_index_index_line;
+ setup_header_widths = setup_index_header_widths;
+}
+
+
+void
+setup_for_thread_index_screen(void)
+{
+ format_index_line = format_thread_index_line;
+ setup_header_widths = setup_thread_header_widths;
+}
+
+
+unsigned long
+ice_hash(ICE_S *ice)
+{
+ char buf[MAX_SCREEN_COLS+1];
+
+ buf[0] = '\0';
+
+ if(ice)
+ simple_index_line(buf, sizeof(buf), ice, 0L);
+
+ buf[sizeof(buf) - 1] = '\0';
+
+ return(line_hash(buf));
+}
+
+
+char *
+left_adjust(int width)
+{
+ return(format_str(width, 1));
+}
+
+
+char *
+right_adjust(int width)
+{
+ return(format_str(width, 0));
+}
+
+
+/*
+ * Returns allocated and filled in format string.
+ */
+char *
+format_str(int width, int left)
+{
+ char *format;
+ size_t len;
+
+ len = PRINT_FORMAT_LEN(width,left) * sizeof(char);
+ format = (char *) fs_get(len + 1);
+ copy_format_str(width, left, format, len);
+ format[len] = '\0';
+
+ return(format);
+}
+
+
+/*
+ * Put the left or right adjusted format string of width width into
+ * dest. Dest is of size n+1.
+ */
+char *
+copy_format_str(int width, int left, char *dest, int n)
+{
+ char *p;
+
+ p = int2string(width);
+
+ snprintf(dest, n+1, "%%%s%s.%ss", left ? "-" : "", p, p);
+
+ dest[n] = '\0';
+
+ return(dest);
+}
+
+
+/*
+ * Sets up the print_format string to be width wide with left or right
+ * adjust. Takes care of memory freeing and allocation.
+ */
+void
+set_print_format(IELEM_S *ielem, int width, int leftadj)
+{
+ if(ielem){
+ ielem->wid = width;
+
+ if(ielem->print_format){
+ /* is there enough room? */
+ if(ielem->freeprintf < PRINT_FORMAT_LEN(width,leftadj)+1){
+ fs_resize((void **) &ielem->print_format,
+ (PRINT_FORMAT_LEN(width,leftadj)+1) * sizeof(char));
+ ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
+ }
+
+ copy_format_str(width, leftadj, ielem->print_format,
+ PRINT_FORMAT_LEN(width,leftadj));
+ }
+ else{
+ ielem->print_format = leftadj ? left_adjust(width)
+ : right_adjust(width);
+ ielem->freeprintf = (PRINT_FORMAT_LEN(width,leftadj) + 1) * sizeof(char);
+ }
+ }
+}
diff --git a/pith/mailindx.h b/pith/mailindx.h
new file mode 100644
index 00000000..4c45268c
--- /dev/null
+++ b/pith/mailindx.h
@@ -0,0 +1,60 @@
+/*
+ * $Id: mailindx.h 925 2008-02-06 02:03:01Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILINDX_INCLUDED
+#define PITH_MAILINDX_INCLUDED
+
+
+#include "../pith/indxtype.h"
+#include "../pith/msgno.h"
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/flag.h"
+
+
+extern ICE_S *(*format_index_line)(INDEXDATA_S *);
+extern void (*setup_header_widths)(MAILSTREAM *);
+
+
+/* exported prototypes */
+int msgline_hidden(MAILSTREAM *, MSGNO_S *, long, int);
+void adjust_cur_to_visible(MAILSTREAM *, MSGNO_S *);
+unsigned long line_hash(char *);
+void init_index_format(char *, INDEX_COL_S **);
+void free_index_format(INDEX_COL_S **);
+void reset_index_format(void);
+INDEX_PARSE_T *itoktype(char *, int);
+char *prepend_keyword_subject(MAILSTREAM *, long, char *, SubjKW, IELEM_S **, char *);
+int get_index_line_color(MAILSTREAM *, SEARCHSET *, PAT_STATE **, COLOR_PAIR **);
+void setup_for_index_index_screen(void);
+void setup_for_thread_index_screen(void);
+INDEX_PARSE_T *itoken(int);
+void load_overview(MAILSTREAM *, unsigned long, OVERVIEW *, imapuid_t);
+ICE_S *build_header_work(struct pine *, MAILSTREAM *, MSGNO_S *, long, long, int, int *);
+char *simple_index_line(char *, size_t, ICE_S *, long);
+int resent_to_us(INDEXDATA_S *);
+int parsed_resent_to_us(char *);
+ADDRESS *fetch_cc(INDEXDATA_S *);
+void index_data_env(INDEXDATA_S *, ENVELOPE *);
+void date_str(char *, IndexColType, int, char *, size_t, int);
+ADDRESS *fetch_to(INDEXDATA_S *);
+char *get_fieldval(INDEXDATA_S *, HEADER_TOK_S *);
+long scorevalfrommsg(MAILSTREAM *, MsgNo, HEADER_TOK_S *, int);
+HEADER_TOK_S *new_hdrtok(char *);
+void free_hdrtok(HEADER_TOK_S **);
+
+
+#endif /* PITH_MAILINDX_INCLUDED */
diff --git a/pith/maillist.c b/pith/maillist.c
new file mode 100644
index 00000000..5b51ceaf
--- /dev/null
+++ b/pith/maillist.c
@@ -0,0 +1,210 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: maillist.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * * * * * * * * * RFC 2369 support routines * * * * * * * *
+ */
+
+
+#include "../pith/headers.h"
+#include "../pith/maillist.h"
+#include "../pith/state.h"
+#include "../pith/url.h"
+
+
+/*
+ * Internal prototypes
+ */
+int rfc2369_parse(char *, RFC2369_S *);
+
+/*
+ * * NOTE * These have to remain in sync with the MLCMD_* macros
+ * in maillist.h. Sorry.
+ */
+static RFC2369FIELD_S rfc2369_fields[] = {
+ {"List-Help",
+ "get information about the list and instructions on how to join",
+ "seek help"},
+ {"List-Unsubscribe",
+ "remove yourself from the list (Unsubscribe)",
+ "UNsubscribe"},
+ {"List-Subscribe",
+ "add yourself to the list (Subscribe)",
+ "Subscribe"},
+ {"List-Post",
+ "send a message to the entire list (Post)",
+ "post a message"},
+ {"List-Owner",
+ "send a message to the list owner",
+ "contact the list owner"},
+ {"List-Archive",
+ "view archive of messages sent to the list",
+ "view the archive"}
+};
+
+
+char **
+rfc2369_hdrs(char **hdrs)
+{
+ int i;
+
+ for(i = 0; i < MLCMD_COUNT; i++)
+ hdrs[i] = rfc2369_fields[i].name;
+
+ hdrs[i] = NULL;
+ return(hdrs);
+}
+
+
+int
+rfc2369_parse_fields(char *h, RFC2369_S *data)
+{
+ char *ep, *nhp, *tp;
+ int i, rv = FALSE;
+
+ for(i = 0; i < MLCMD_COUNT; i++)
+ data[i].field = rfc2369_fields[i];
+
+ for(nhp = h; h; h = nhp){
+ /* coerce h to start of field */
+ for(ep = h;;)
+ if((tp = strpbrk(ep, "\015\012")) != NULL){
+ if(strindex(" \t", *((ep = tp) + 2))){
+ *ep++ = ' '; /* flatten continuation */
+ *ep++ = ' ';
+ for(; *ep; ep++) /* advance past whitespace */
+ if(*ep == '\t')
+ *ep = ' ';
+ else if(*ep != ' ')
+ break;
+ }
+ else{
+ *ep = '\0'; /* tie off header data */
+ nhp = ep + 2; /* start of next header */
+ break;
+ }
+ }
+ else{
+ while(*ep) /* find the end of this line */
+ ep++;
+
+ nhp = NULL; /* no more header fields */
+ break;
+ }
+
+ /* if length is within reason, see if we're interested */
+ if(ep - h < MLCMD_REASON && rfc2369_parse(h, data))
+ rv = TRUE;
+ }
+
+ return(rv);
+}
+
+
+int
+rfc2369_parse(char *h, RFC2369_S *data)
+{
+ int l, ifield, idata = 0;
+ char *p, *p1, *url, *comment;
+
+ /* look for interesting name's */
+ for(ifield = 0; ifield < MLCMD_COUNT; ifield++)
+ if(!struncmp(h, rfc2369_fields[ifield].name,
+ l = strlen(rfc2369_fields[ifield].name))
+ && *(h += l) == ':'){
+ /* unwrap any transport encodings */
+ if((p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, ++h)) == tmp_20k_buf)
+ strcpy(h, p); /* assumption #383: decoding shrinks */
+
+ url = comment = NULL;
+ while(*h){
+ while(*h == ' ')
+ h++;
+
+ switch(*h){
+ case '<' : /* URL */
+ if((p = strindex(h, '>')) != NULL){
+ url = ++h; /* remember where it starts */
+ *p = '\0'; /* tie it off */
+ h = p + 1; /* advance h */
+ for(p = p1 = url; (*p1 = *p) != '\0'; p++)
+ if(*p1 != ' ')
+ p1++; /* remove whitespace ala RFC */
+ }
+ else
+ *h = '\0'; /* tie off junk */
+
+ break;
+
+ case '(' : /* Comment */
+ comment = rfc822_skip_comment(&h, LONGT);
+ break;
+
+ case 'N' : /* special case? */
+ case 'n' :
+ if(ifield == MLCMD_POST
+ && (*(h+1) == 'O' || *(h+1) == 'o')
+ && (!*(h+2) || *(h+2) == ' ')){
+ ; /* yup! */
+
+ url = h;
+ *(h + 2) = '\0';
+ h += 3;
+ break;
+ }
+
+ default :
+ removing_trailing_white_space(h);
+ if(!url
+ && (url = rfc1738_scan(h, &l))
+ && url == h && l == strlen(h)){
+ removing_trailing_white_space(h);
+ data[ifield].data[idata].value = url;
+ }
+ else
+ data[ifield].data[idata].error = h;
+
+ return(1); /* return junk */
+ }
+
+ while(*h == ' ')
+ h++;
+
+ switch(*h){
+ case ',' :
+ h++;
+
+ case '\0':
+ if(url || (comment && *comment)){
+ data[ifield].data[idata].value = url;
+ data[ifield].data[idata].comment = comment;
+ url = comment = NULL;
+ }
+
+ if(++idata == MLCMD_MAXDATA)
+ *h = '\0';
+
+ default :
+ break;
+ }
+ }
+ }
+
+ return(idata);
+}
diff --git a/pith/maillist.h b/pith/maillist.h
new file mode 100644
index 00000000..bc9b02b5
--- /dev/null
+++ b/pith/maillist.h
@@ -0,0 +1,57 @@
+/*
+ * $Id: maillist.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILLIST_INCLUDED
+#define PITH_MAILLIST_INCLUDED
+
+
+/*
+ * Constants and structs to aid RFC 2369 support
+ */
+#define MLCMD_HELP 0
+#define MLCMD_UNSUB 1
+#define MLCMD_SUB 2
+#define MLCMD_POST 3
+#define MLCMD_OWNER 4
+#define MLCMD_ARCHIVE 5
+#define MLCMD_COUNT 6
+#define MLCMD_MAXDATA 3
+#define MLCMD_REASON 8192
+
+
+typedef struct rfc2369_field_s {
+ char *name,
+ *description,
+ *action;
+} RFC2369FIELD_S;
+
+typedef struct rfc2369_data_s {
+ char *value,
+ *comment,
+ *error;
+} RFC2369DATA_S;
+
+typedef struct rfc2369_s {
+ RFC2369FIELD_S field;
+ RFC2369DATA_S data[MLCMD_MAXDATA];
+} RFC2369_S;
+
+
+/* exported protoypes */
+char **rfc2369_hdrs(char **);
+int rfc2369_parse_fields(char *, RFC2369_S *);
+
+
+#endif /* PITH_MAILLIST_INCLUDED */
diff --git a/pith/mailpart.h b/pith/mailpart.h
new file mode 100644
index 00000000..e7038086
--- /dev/null
+++ b/pith/mailpart.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: mailpart.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILPART_INCLUDED
+#define PITH_MAILPART_INCLUDED
+
+
+#include "../pith/atttype.h"
+#include "../pith/state.h"
+
+
+#define MIME_MSG(t,s) ((t) == TYPEMESSAGE && (s) && !strucmp((s),"rfc822"))
+
+#define MIME_DGST(t,s) ((t) == TYPEMULTIPART && (s) && !strucmp((s),"digest"))
+
+/* Is this a message attachment? */
+#define MIME_VCARD(t,s) ((((t) == TYPETEXT || (t) == TYPEAPPLICATION) \
+ && (s) && !strucmp((s),"DIRECTORY")) \
+ || ((t) == TYPETEXT \
+ && (s) && !strucmp((s),"X-VCARD")))
+
+/* Is this a multipart signed? */
+#define MIME_MULT_SIGNED(t,s) ((t) == TYPEMULTIPART && (s) && !strucmp((s),"signed"))
+
+
+#define MIME_MSG_A(a) MIME_MSG((a)->body->type, (a)->body->subtype)
+
+/* Is this a digest attachment? */
+#define MIME_DGST_A(a) MIME_DGST((a)->body->type, (a)->body->subtype)
+
+/* Is this a vCard attachment? */
+#define MIME_VCARD_A(a) MIME_VCARD((a)->body->type, (a)->body->subtype)
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_MAILPART_INCLUDED */
diff --git a/pith/mailview.c b/pith/mailview.c
new file mode 100644
index 00000000..40728aab
--- /dev/null
+++ b/pith/mailview.c
@@ -0,0 +1,3271 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ mailview.c
+ Implements message data gathering and formatting
+
+ ====*/
+
+
+#include "headers.h"
+#include "../pith/mailview.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/editorial.h"
+#include "../pith/mimedesc.h"
+#include "../pith/margin.h"
+#include "../pith/color.h"
+#include "../pith/strlst.h"
+#include "../pith/charset.h"
+#include "../pith/status.h"
+#include "../pith/maillist.h"
+#include "../pith/mailcmd.h"
+#include "../pith/mailindx.h"
+#include "../pith/imap.h"
+#include "../pith/detach.h"
+#include "../pith/text.h"
+#include "../pith/url.h"
+#include "../pith/rfc2231.h"
+#include "../pith/list.h"
+#include "../pith/stream.h"
+#include "../pith/send.h"
+#include "../pith/filter.h"
+#include "../pith/string.h"
+#include "../pith/ablookup.h"
+#include "../pith/escapes.h"
+#include "../pith/keyword.h"
+#include "../pith/smime.h"
+
+
+#define FBUF_LEN (50)
+
+#define ISRFCEOL(S) (*(S) == '\015' && *((S)+1) == '\012')
+
+
+/*
+ * This is a list of header fields that are represented canonically
+ * by the c-client's ENVELOPE structure. The list is used by the
+ * two functions below to decide if a given field is included in this
+ * set.
+ */
+static struct envelope_s {
+ char *name;
+ long val;
+} envelope_hdrs[] = {
+ {"from", FE_FROM},
+ {"sender", FE_SENDER},
+ {"date", FE_DATE},
+ {"to", FE_TO},
+ {"cc", FE_CC},
+ {"bcc", FE_BCC},
+ {"newsgroups", FE_NEWSGROUPS},
+ {"subject", FE_SUBJECT},
+ {"message-id", FE_MESSAGEID},
+ {"reply-to", FE_REPLYTO},
+ {"followup-to", FE_FOLLOWUPTO},
+ {"in-reply-to", FE_INREPLYTO},
+/* {"return-path", FE_RETURNPATH}, not usually filled in */
+ {"references", FE_REFERENCES},
+ {NULL, 0}
+};
+
+
+/*
+ * Hook for optional display of rfc2369 content
+ */
+int (*pith_opt_rfc2369_editorial)(long, HANDLE_S **, int, int, gf_io_t);
+
+
+
+
+/*
+ * Internal prototypes
+ */
+int format_blip_seen(long);
+int is_an_env_hdr(char *);
+int is_an_addr_hdr(char *);
+void format_env_hdr(MAILSTREAM *, long, char *, ENVELOPE *,
+ fmt_env_t, gf_io_t, char *, char *, int);
+int delineate_this_header(char *, char *, char **, char **);
+char *url_embed(int);
+int color_headers(long, char *, LT_INS_S **, void *);
+int url_hilite_hdr(long, char *, LT_INS_S **, void *);
+int pad_to_right_edge(long, char *, LT_INS_S **, void *);
+int url_bogus_imap(char **, char *, char *);
+int format_raw_header(MAILSTREAM *, long, char *, gf_io_t);
+void format_envelope(MAILSTREAM *, long, char *, ENVELOPE *,
+ gf_io_t, long, char *, int);
+int any_hdr_color(char *);
+void format_addr_string(MAILSTREAM *, long, char *, char *,
+ ADDRESS *, int, char *, gf_io_t);
+void pine_rfc822_write_address_noquote(ADDRESS *, gf_io_t, int *);
+void format_newsgroup_string(char *, char *, int, gf_io_t);
+int format_raw_hdr_string(char *, char *, gf_io_t, char *, int);
+int format_env_puts(char *, gf_io_t);
+int find_field(char **, char *, size_t);
+int embed_color(COLOR_PAIR *, gf_io_t);
+COLOR_PAIR *get_cur_embedded_color(void);
+void clear_cur_embedded_color(void);
+
+
+
+
+/*----------------------------------------------------------------------
+ Format a message message for viewing
+
+ Args: msgno -- The number of the message to view
+ env -- pointer to the message's envelope
+ body -- pointer to the message's body
+ handlesp -- address of pointer to the message's handles
+ flgs -- possible flags listed in pith/mailview.h with
+ prefix FM_
+ pc -- write to this function
+
+Result: Returns true if no problems encountered, else false.
+
+First the envelope is formatted; next a list of all attachments is
+formatted if there is more than one. Then all the body parts are
+formatted, fetching them as needed. This includes headers of included
+message. Richtext is also formatted. An entry is made in the text for
+parts that are not displayed or can't be displayed.
+
+ ----*/
+int
+format_message(long int msgno, ENVELOPE *env, struct mail_bodystruct *body,
+ HANDLE_S **handlesp, int flgs, gf_io_t pc)
+{
+ char *decode_err = NULL;
+ HEADER_S h;
+ int width;
+
+ clear_cur_embedded_color();
+
+ if(!(flgs & FM_DISPLAY))
+ flgs |= FM_NOINDENT;
+
+ width = (flgs & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
+
+ /*---- format and copy envelope ----*/
+ if(!(flgs & FM_NOEDITORIAL)){
+ if(ps_global->full_header == 1)
+ /* TRANSLATORS: User is viewing a message and all the quoted text is
+ being shown. */
+ q_status_message(SM_INFO, 0, 3, _("All quoted text being included"));
+ else if(ps_global->full_header == 2)
+ q_status_message(SM_INFO, 0, 3,
+ /* TRANSLATORS: User is viewing a message and all of
+ the header text is being shown. */
+ _("Full header mode ON. All header text being included"));
+ }
+
+ HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except, FE_DEFAULT);
+ switch(format_header(ps_global->mail_stream, msgno, NULL,
+ env, &h, NULL, handlesp, flgs, NULL, pc)){
+
+ case -1 : /* write error */
+ goto write_error;
+
+ case 1 : /* fetch error */
+ if(!(gf_puts("[ Error fetching header ]", pc)
+ && !gf_puts(NEWLINE, pc)))
+ goto write_error;
+
+ break;
+ }
+
+ if(!(body == NULL
+ || (ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))))
+ format_attachment_list(msgno, body, handlesp, flgs, width, pc);
+
+ /* write delimiter and body */
+ if(gf_puts(NEWLINE, pc)
+ && (decode_err = format_body(msgno, body, handlesp, &h, flgs, width, pc)) == NULL)
+ return(1);
+
+
+ write_error:
+
+ if(!(flgs & FM_DISPLAY))
+ q_status_message1(SM_ORDER, 3, 4, _("Error writing message: %s"),
+ decode_err ? decode_err : error_description(errno));
+
+ return(0);
+}
+
+
+char *
+format_body(long int msgno, BODY *body, HANDLE_S **handlesp, HEADER_S *hp, int flgs, int width, gf_io_t pc)
+{
+ int filt_only_c0 = 0, wrapflags, error_found = 0;
+ int is_in_sig = OUT_SIG_BLOCK;
+ char *charset, *decode_err = NULL, *tmp1, *description;
+ ATTACH_S *a;
+ URL_HILITE_S uh;
+ gf_io_t gc;
+
+ if(body == NULL
+ || (ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))) {
+
+ /*--- Server is not an IMAP2bis, It can't parse MIME
+ so we just show the text here. Hopefully the
+ message isn't a MIME message
+ ---*/
+ void *text2;
+
+ if((text2 = (void *)pine_mail_fetch_text(ps_global->mail_stream,
+ msgno, NULL, NULL, NIL)) != NULL){
+
+ if(!gf_puts(NEWLINE, pc)) /* write delimiter */
+ return("Write Error");
+
+ gf_set_readc(&gc, text2, (unsigned long)strlen(text2), CharStar, 0);
+ gf_filter_init();
+
+ /*
+ * We need to translate the message
+ * into UTF-8, but that's trouble in the full header case
+ * because we don't know what to translate from. We'll just
+ * take a guess by looking for the first text part and
+ * using its charset.
+ */
+ if(body && body->type == TYPETEXT)
+ charset = parameter_val(body->parameter, "charset");
+ else if(body && body->type == TYPEMULTIPART && body->nested.part
+ && body->nested.part->body.type == TYPETEXT)
+ charset = parameter_val(body->nested.part->body.parameter, "charset");
+ else
+ charset = ps_global->display_charmap;
+
+ if(strucmp(charset, "us-ascii") && strucmp(charset, "utf-8")){
+ /* transliterate message text to UTF-8 */
+ gf_link_filter(gf_utf8, gf_utf8_opt(charset));
+ }
+
+ /* link in filters, similar to what is done in decode_text() */
+ if(!ps_global->pass_ctrl_chars){
+ gf_link_filter(gf_escape_filter, NULL);
+ filt_only_c0 = 1;
+ gf_link_filter(gf_control_filter,
+ gf_control_filter_opt(&filt_only_c0));
+ }
+
+ gf_link_filter(gf_tag_filter, NULL);
+
+ if((F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ && handlesp){
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite,
+ gf_url_hilite_opt(&uh,handlesp,0)));
+ }
+
+ if((flgs & FM_DISPLAY)
+ && !(flgs & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ps_global->VAR_SIGNATURE_FORE_COLOR
+ && ps_global->VAR_SIGNATURE_BACK_COLOR){
+ gf_link_filter(gf_line_test, gf_line_test_opt(color_signature, &is_in_sig));
+ }
+
+ if((flgs & FM_DISPLAY)
+ && !(flgs & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ps_global->VAR_QUOTE1_FORE_COLOR
+ && ps_global->VAR_QUOTE1_BACK_COLOR){
+ gf_link_filter(gf_line_test, gf_line_test_opt(color_a_quote, NULL));
+ }
+
+ if(!(flgs & FM_NOWRAP)){
+ wrapflags = (flgs & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN) : GFW_NONE;
+ if(flgs & FM_DISPLAY
+ && !(flgs & FM_NOCOLOR)
+ && pico_usingcolor())
+ wrapflags |= GFW_USECOLOR;
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(width, width,
+ (flgs & FM_NOINDENT)
+ ? NULL : format_view_margin(),
+ 0,
+ wrapflags));
+ }
+
+ gf_link_filter(gf_nvtnl_local, NULL);
+ if((decode_err = gf_pipe(gc, pc)) != NULL){
+ /* TRANSLATORS: There was an error putting together a message for
+ viewing. The arg is the description of the error. */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Formatting error: %s"), decode_err);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if(gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)
+ && !format_editorial(tmp_20k_buf, width, flgs, handlesp, pc)
+ && gf_puts(NEWLINE, pc))
+ decode_err = NULL;
+ else
+ return(decode_err);
+ }
+ }
+
+ if(!text2){
+ if(!gf_puts(NEWLINE, pc)
+ || !gf_puts(_(" [ERROR fetching text of message]"), pc)
+ || !gf_puts(NEWLINE, pc)
+ || !gf_puts(NEWLINE, pc))
+ return("Write Error");
+ }
+ }
+ else{
+ int show_parts = 0;
+
+ /*======== Now loop through formatting all the parts =======*/
+ for(a = ps_global->atmts; a->description != NULL; a++) {
+
+ if(a->body->type == TYPEMULTIPART){
+#ifdef SMIME
+ if(strucmp(a->body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)==0){
+ if(a->description){
+ if(!(!format_editorial(a->description, width, flgs, handlesp, pc)
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ return("Write Error");
+ }
+ }
+#endif /* SMIME */
+ continue;
+ }
+
+ if(!a->shown) {
+ if(a->suppress_editorial)
+ continue;
+
+ if(!(flgs & FM_NOEDITORIAL)
+ && (!gf_puts(NEWLINE, pc)
+ || (decode_err = part_desc(a->number, a->body,
+ (flgs & FM_DISPLAY)
+ ? (a->can_display != MCD_NONE)
+ ? 1 : 2
+ : 3, width, flgs, pc))))
+ return("Write Error");
+
+ continue;
+ }
+
+ switch(a->body->type){
+
+ case TYPETEXT:
+ /*
+ * If a message is multipart *and* the first part of it
+ * is text *and that text is empty, there is a good chance that
+ * there was actually something there that c-client was
+ * unable to parse. Here we report the empty message body
+ * and insert the raw RFC822.TEXT (if full-headers are
+ * on).
+ */
+ if(body->type == TYPEMULTIPART
+ && a == ps_global->atmts
+ && a->body->size.bytes == 0
+ && F_ON(F_ENABLE_FULL_HDR, ps_global)){
+ char *err = NULL;
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Empty or malformed message%s.",
+ ps_global->full_header == 2
+ ? ". Displaying raw text"
+ : ". Use \"H\" to see raw text");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if(!(gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)
+ && !format_editorial(tmp_20k_buf, width, flgs, handlesp, pc)
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ return("Write Error");
+
+ if(ps_global->full_header == 2
+ && (err = detach_raw(ps_global->mail_stream, msgno,
+ a->number, pc, flgs))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s%s [ Formatting error: %s ]%s%s",
+ NEWLINE, NEWLINE, err, NEWLINE, NEWLINE);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if(!gf_puts(tmp_20k_buf, pc))
+ return("Write Error");
+ }
+
+ break;
+ }
+
+ /*
+ * Don't write our delimiter if this text part is
+ * the first part of a message/rfc822 segment...
+ */
+ if(show_parts && a != ps_global->atmts
+ && !((a[-1].body && a[-1].body->type == TYPEMESSAGE)
+#ifdef SMIME
+ || (a[-1].body->type == TYPEMULTIPART
+ && a[-1].body->subtype
+ && (strucmp(a[-1].body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)==0)
+ && &a[-1] != ps_global->atmts
+ && a[-2].body && a[-2].body->type == TYPEMESSAGE)
+#endif /* SMIME */
+ )
+ && !(flgs & FM_NOEDITORIAL)){
+ tmp1 = a->body->description ? a->body->description
+ : "Attached Text";
+ description = iutf8ncpy((char *)(tmp_20k_buf+10000),
+ (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+15000), 5000, tmp1), 5000);
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Part %s: \"%.1024s\"", a->number,
+ description);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if(!(gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)
+ && !format_editorial(tmp_20k_buf, width, flgs, handlesp, pc)
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ return("Write Error");
+ }
+
+ error_found += decode_text(a, msgno, pc, handlesp,
+ (flgs & FM_DISPLAY) ? InLine : QStatus,
+ flgs);
+ break;
+
+ case TYPEMESSAGE:
+ tmp1 = a->body->description ? a->body->description
+ : (strucmp(a->body->subtype, "delivery-status") == 0)
+ ? "Delivery Status"
+ : "Included Message";
+ description = iutf8ncpy((char *)(tmp_20k_buf+10000),
+ (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+15000), 5000, tmp1), 5000);
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Part %s: \"%.1024s\"", a->number,
+ description);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if(!(gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)
+ && !format_editorial(tmp_20k_buf, width, flgs, handlesp, pc)
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ return("Write Error");
+
+ if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
+ /* imapenvonly, we may not have all the headers we need */
+ if(a->body->nested.msg->env->imapenvonly)
+ mail_fetch_header(ps_global->mail_stream, msgno,
+ a->number, NULL, NULL, FT_PEEK);
+ switch(format_header(ps_global->mail_stream, msgno, a->number,
+ a->body->nested.msg->env, hp,
+ NULL, handlesp, flgs, NULL, pc)){
+ case -1 : /* write error */
+ return("Write Error");
+
+ case 1 : /* fetch error */
+ if(!(gf_puts("[ Error fetching header ]", pc)
+ && !gf_puts(NEWLINE, pc)))
+ return("Write Error");
+
+ break;
+ }
+ }
+ else if(a->body->subtype && strucmp(a->body->subtype, "external-body") == 0){
+ int *margin, avail, m1, m2;
+
+ avail = width;
+ margin = (flgs & FM_NOINDENT) ? NULL : format_view_margin();
+
+ m1 = MAX(MIN(margin ? margin[0] : 0, avail), 0);
+ avail -= m1;
+
+ m2 = MAX(MIN(margin ? margin[1] : 0, avail), 0);
+ avail -= m2;
+
+ if(format_editorial("This part is not included and can be fetched as follows:", avail, flgs, handlesp, pc)
+ || !gf_puts(NEWLINE, pc)
+ || format_editorial(display_parameters(a->body->parameter), avail, flgs, handlesp, pc))
+ return("Write Error");
+ }
+ else
+ error_found += decode_text(a, msgno, pc, handlesp,
+ (flgs&FM_DISPLAY) ? InLine : QStatus,
+ flgs);
+
+ if(!gf_puts(NEWLINE, pc))
+ return("Write Error");
+
+ break;
+
+ default:
+ if((decode_err = part_desc(a->number, a->body,
+ (flgs & FM_DISPLAY) ? 1 : 3,
+ width, flgs, pc)) != NULL)
+ return("Write Error");
+ }
+
+ show_parts++;
+ }
+
+ if(!(!error_found
+ && (pith_opt_rfc2369_editorial ? (*pith_opt_rfc2369_editorial)(msgno, handlesp, flgs, width, pc) : 1)
+ && format_blip_seen(msgno)))
+ return("Cannot format body.");
+ }
+
+ return(NULL);
+}
+
+
+int
+format_attachment_list(long int msgno, BODY *body, HANDLE_S **handlesp, int flgs, int width, gf_io_t pc)
+{
+ ATTACH_S *a;
+
+ if(flgs & FM_NEW_MESS) {
+ zero_atmts(ps_global->atmts);
+ describe_mime(body, "", 1, 1, 0, flgs);
+ }
+
+ /*----- First do the list of parts/attachments if needed ----*/
+ if((flgs & FM_DISPLAY)
+ && (ps_global->atmts[1].description
+ || (ps_global->atmts[0].body
+ && ps_global->atmts[0].body->type != TYPETEXT))){
+ char tmp[6*MAX_SCREEN_COLS + 1], *tmpp;
+ int i, n, maxnumwid = 0, maxsizewid = 0, *margin;
+ int avail, m1, m2, hwid, s1, s2, s3, s4, s5, dwid, shownwid;
+ int sizewid, descwid, dashwid, partwid, padwid;
+ COLOR_PAIR *hdrcolor = NULL;
+
+ if((flgs & FM_DISPLAY)
+ && !(flgs & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ps_global->VAR_HEADER_GENERAL_FORE_COLOR
+ && ps_global->VAR_HEADER_GENERAL_BACK_COLOR
+ && ps_global->VAR_NORM_FORE_COLOR
+ && ps_global->VAR_NORM_BACK_COLOR
+ && (colorcmp(ps_global->VAR_HEADER_GENERAL_FORE_COLOR,
+ ps_global->VAR_NORM_FORE_COLOR)
+ || colorcmp(ps_global->VAR_HEADER_GENERAL_BACK_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR))){
+
+ if((hdrcolor = new_color_pair(ps_global->VAR_HEADER_GENERAL_FORE_COLOR,
+ ps_global->VAR_HEADER_GENERAL_BACK_COLOR)) != NULL){
+ if(!pico_is_good_colorpair(hdrcolor))
+ free_color_pair(&hdrcolor);
+ }
+ }
+
+ margin = (flgs & FM_NOINDENT) ? NULL : format_view_margin();
+
+ /*
+ * Attachment list header
+ */
+
+ avail = width;
+
+ m1 = MAX(MIN(margin ? margin[0] : 0, avail), 0);
+ avail -= m1;
+
+ m2 = MAX(MIN(margin ? margin[1] : 0, avail), 0);
+ avail -= m2;
+
+ hwid = MAX(avail, 0);
+
+ i = utf8_width(_("Parts/Attachments:"));
+ partwid = MIN(i, hwid);
+ padwid = hdrcolor ? (hwid-partwid) : 0;
+
+ if(m1 > 0){
+ snprintf(tmp, sizeof(tmp), "%*.*s", m1, m1, "");
+ if(!gf_puts(tmp, pc))
+ return(0);
+ }
+
+ utf8_snprintf(tmp, sizeof(tmp),
+ "%-*.*w%*.*s",
+ /* TRANSLATORS: A label */
+ partwid, partwid, _("Parts/Attachments:"),
+ padwid, padwid, "");
+
+ if(!((!hdrcolor || embed_color(hdrcolor, pc)) && gf_puts(tmp, pc) && gf_puts(NEWLINE, pc)))
+ return(0);
+
+
+ /*----- Figure max display widths -----*/
+ for(a = ps_global->atmts; a->description != NULL; a++){
+ if((n = utf8_width(a->number)) > maxnumwid)
+ maxnumwid = n;
+
+ if((n = utf8_width(a->size)) > maxsizewid)
+ maxsizewid = n;
+ }
+
+ /*
+ * ----- adjust max lengths for nice display -----
+ *
+ * marg _ D _ number _ Shown _ _ _ size _ _ description marg
+ *
+ */
+
+ avail = width - m1 - m2;
+
+ s1 = MAX(MIN(1, avail), 0);
+ avail -= s1;
+
+ dwid = MAX(MIN(1, avail), 0);
+ avail -= dwid;
+
+ s2 = MAX(MIN(1, avail), 0);
+ avail -= s2;
+
+ maxnumwid = MIN(maxnumwid, width/3);
+ maxnumwid = MAX(MIN(maxnumwid, avail), 0);
+ avail -= maxnumwid;
+
+ s3 = MAX(MIN(1, avail), 0);
+ avail -= s3;
+
+ shownwid = MAX(MIN(5, avail), 0);
+ avail -= shownwid;
+
+ s4 = MAX(MIN(3, avail), 0);
+ avail -= s4;
+
+ sizewid = MAX(MIN(maxsizewid, avail), 0);
+ avail -= sizewid;
+
+ s5 = MAX(MIN(2, avail), 0);
+ avail -= s5;
+
+ descwid = MAX(0, avail);
+
+ /*----- Format the list of attachments -----*/
+ for(a = ps_global->atmts; a->description != NULL; a++){
+ COLOR_PAIR *lastc = NULL;
+ char numbuf[50];
+ int thisdescwid, padwid;
+
+#ifdef SMIME
+ if(a->body->type == TYPEMULTIPART
+ && (strucmp(a->body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)==0))
+ continue;
+#endif /* SMIME */
+
+ i = utf8_width((descwid > 2 && a->description) ? a->description : "");
+ thisdescwid = MIN(i, descwid);
+ padwid = hdrcolor ? (descwid-thisdescwid) : 0;
+
+ if(m1 > 0){
+ snprintf(tmp, sizeof(tmp), "%*.*s", m1, m1, "");
+ if(!gf_puts(tmp, pc))
+ return(0);
+ }
+
+ utf8_snprintf(tmp, sizeof(tmp),
+ "%*.*s%*.*w%*.*s%-*.*w%*.*s%*.*w%*.*s%*.*w%*.*s%-*.*w",
+ s1, s1, "",
+ dwid, dwid,
+ msgno_part_deleted(ps_global->mail_stream, msgno, a->number) ? "D" : "",
+ s2, s2, "",
+ maxnumwid, maxnumwid,
+ a->number
+ ? short_str(a->number, numbuf, sizeof(numbuf), maxnumwid, FrontDots)
+ : "",
+ s3, s3, "",
+ shownwid, shownwid,
+ a->shown ? "Shown" :
+ (a->can_display != MCD_NONE && !(a->can_display & MCD_EXT_PROMPT))
+ ? "OK " : "",
+ s4, s4, "",
+ sizewid, sizewid,
+ a->size ? a->size : "",
+ s5, s5, "",
+ thisdescwid, thisdescwid,
+ (descwid > 2 && a->description) ? a->description : "");
+
+ if(!(!hdrcolor || embed_color(hdrcolor, pc)))
+ return(0);
+
+ if(F_ON(F_VIEW_SEL_ATTACH, ps_global) && handlesp){
+ char buf[16], color[64];
+ int l;
+ HANDLE_S *h;
+
+ for(tmpp = tmp; *tmpp && *tmpp == ' '; tmpp++)
+ if(!(*pc)(' '))
+ return(0);
+
+ h = new_handle(handlesp);
+ h->type = Attach;
+ h->h.attach = a;
+
+ snprintf(buf, sizeof(buf), "%d", h->key);
+ buf[sizeof(buf)-1] = '\0';
+
+ if(!(flgs & FM_NOCOLOR)
+ && handle_start_color(color, sizeof(color), &l, 1)){
+ lastc = get_cur_embedded_color();
+ if(!gf_nputs(color, (long) l, pc))
+ return(0);
+ }
+ else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)
+ && (!((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDON))))
+ return(0);
+
+ if(!((*pc)(TAG_EMBED) && (*pc)(TAG_HANDLE)
+ && (*pc)(strlen(buf)) && gf_puts(buf, pc)))
+ return(0);
+ }
+ else
+ tmpp = tmp;
+
+ if(!format_env_puts(tmpp, pc))
+ return(0);
+
+ if(F_ON(F_VIEW_SEL_ATTACH, ps_global) && handlesp){
+ if(lastc){
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ if(!((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDOFF)))
+ return(0);
+ }
+
+ if(!embed_color(lastc, pc))
+ return(0);
+
+ free_color_pair(&lastc);
+ }
+ else if(!((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDOFF)))
+ return(0);
+
+ if(!((*pc)(TAG_EMBED) && (*pc)(TAG_INVOFF)))
+ return(0);
+ }
+
+ if(padwid > 0){
+ snprintf(tmp, sizeof(tmp), "%*.*s", padwid, padwid, "");
+ if(!gf_puts(tmp, pc))
+ return(0);
+ }
+
+ if(!gf_puts(NEWLINE, pc))
+ return(0);
+ }
+
+ /*
+ * Dashed line after list
+ */
+
+ if(hdrcolor){
+ avail = width - m1 - m2;
+ hwid = MAX(avail, 0);
+
+ dashwid = MAX(MIN(40, hwid-2), 0);
+ padwid = hwid - dashwid;
+ if(m1 > 0){
+ snprintf(tmp, sizeof(tmp), "%*.*s", m1, m1, "");
+ if(!gf_puts(tmp, pc))
+ return(0);
+ }
+
+ snprintf(tmp, sizeof(tmp),
+ "%s%*.*s",
+ repeat_char(dashwid, '-'),
+ padwid, padwid, "");
+ }
+ else{
+ avail = width - m1 -2;
+
+ dashwid = MAX(MIN(40, avail), 0);
+ avail -= dashwid;
+
+ snprintf(tmp, sizeof(tmp),
+ "%*.*s%s",
+ m1, m1, "",
+ repeat_char(dashwid, '-'));
+ }
+
+ if(!((!hdrcolor || embed_color(hdrcolor, pc)) && gf_puts(tmp, pc) && gf_puts(NEWLINE, pc)))
+ return(0);
+
+ if(hdrcolor)
+ free_color_pair(&hdrcolor);
+ }
+
+ return(1);
+}
+
+
+
+/*
+ * format_blip_seen - if seen bit (which is usually cleared as a side-effect
+ * of body part fetches as we're formatting) for the
+ * given message isn't set (likely because there
+ * weren't any parts suitable for display), then make
+ * sure to set it here.
+ */
+int
+format_blip_seen(long int msgno)
+{
+ MESSAGECACHE *mc;
+
+ if(msgno > 0L && ps_global->mail_stream
+ && msgno <= ps_global->mail_stream->nmsgs
+ && (mc = mail_elt(ps_global->mail_stream, msgno))
+ && !mc->seen
+ && !ps_global->mail_stream->rdonly)
+ mail_flag(ps_global->mail_stream, long2string(msgno), "\\SEEN", ST_SET);
+
+ return(1);
+}
+
+
+/*
+ * is_an_env_hdr - is this name a header in the envelope structure?
+ *
+ * name - the header name to check
+ */
+int
+is_an_env_hdr(char *name)
+{
+ register int i;
+
+ for(i = 0; envelope_hdrs[i].name; i++)
+ if(!strucmp(name, envelope_hdrs[i].name))
+ return(1);
+
+ return(0);
+}
+
+
+
+
+/*
+ * is_an_addr_hdr - is this an address header?
+ *
+ * name - the header name to check
+ */
+int
+is_an_addr_hdr(char *fieldname)
+{
+ char fbuf[FBUF_LEN+1];
+ char *colon, *fname;
+ static char *addr_headers[] = {
+ "from",
+ "reply-to",
+ "to",
+ "cc",
+ "bcc",
+ "return-path",
+ "sender",
+ "x-sender",
+ "x-x-sender",
+ "resent-from",
+ "resent-to",
+ "resent-cc",
+ NULL
+ };
+
+ /* so it is pointing to NULL */
+ char **p = addr_headers + sizeof(addr_headers)/sizeof(*addr_headers) - 1;
+
+ if((colon = strindex(fieldname, ':')) != NULL){
+ strncpy(fbuf, fieldname, MIN(colon-fieldname,sizeof(fbuf)));
+ fbuf[MIN(colon-fieldname,sizeof(fbuf)-1)] = '\0';
+ fname = fbuf;
+ }
+ else
+ fname = fieldname;
+
+ if(fname && *fname){
+ for(p = addr_headers; *p; p++)
+ if(!strucmp(fname, *p))
+ break;
+ }
+
+ return((*p) ? 1 : 0);
+}
+
+
+/*
+ * Format a single field from the envelope
+ */
+void
+format_env_hdr(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env,
+ fmt_env_t fmt_env, gf_io_t pc, char *field, char *oacs, int flags)
+{
+ register int i;
+
+ if(!fmt_env)
+ fmt_env = format_envelope;
+
+ for(i = 0; envelope_hdrs[i].name; i++)
+ if(!strucmp(field, envelope_hdrs[i].name)){
+ (*fmt_env)(stream, msgno, section, env, pc, envelope_hdrs[i].val, oacs, flags);
+ return;
+ }
+}
+
+
+/*
+ * Look through header string beginning with "begin", for the next
+ * occurrence of header "field". Set "start" to that. Set "end" to point one
+ * position past all of the continuation lines that go with "field".
+ * That is, if "end" is converted to a null
+ * character then the string "start" will be the next occurence of header
+ * "field" including all of its continuation lines. Assume we
+ * have CRLF's as end of lines.
+ *
+ * If "field" is NULL, then we just leave "start" pointing to "begin" and
+ * make "end" the end of that header.
+ *
+ * Returns 1 if found, 0 if not.
+ */
+int
+delineate_this_header(char *field, char *begin, char **start, char **end)
+{
+ char tmpfield[MAILTMPLEN+2]; /* copy of field with colon appended */
+ char *p;
+ char *begin_srch;
+
+ if(field == NULL){
+ if(!begin || !*begin || isspace((unsigned char)*begin))
+ return 0;
+ else
+ *start = begin;
+ }
+ else{
+ strncpy(tmpfield, field, sizeof(tmpfield)-2);
+ tmpfield[sizeof(tmpfield)-2] = '\0';
+ strncat(tmpfield, ":", sizeof(tmpfield)-strlen(tmpfield)-1);
+ tmpfield[sizeof(tmpfield)-1] = '\0';
+
+ /*
+ * We require that start is at the beginning of a line, so
+ * either it equals begin (which we assume is the beginning of a
+ * line) or it is preceded by a CRLF.
+ */
+ begin_srch = begin;
+ *start = srchstr(begin_srch, tmpfield);
+ while(*start && *start != begin
+ && !(*start - 2 >= begin && ISRFCEOL(*start - 2))){
+ begin_srch = *start + 1;
+ *start = srchstr(begin_srch, tmpfield);
+ }
+
+ if(!*start)
+ return 0;
+ }
+
+ for(p = *start; *p; p++){
+ if(ISRFCEOL(p)
+ && (!isspace((unsigned char)*(p+2)) || *(p+2) == '\015')){
+ /*
+ * The final 015 in the test above is to test for the end
+ * of the headers.
+ */
+ *end = p+2;
+ break;
+ }
+ }
+
+ if(!*p)
+ *end = p;
+
+ return 1;
+}
+
+
+
+int
+handle_start_color(char *colorstring, size_t buflen, int *len, int use_hdr_color)
+{
+ *len = 0;
+
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *s;
+ char *basefg = NULL, *basebg = NULL;
+
+ basefg = use_hdr_color ? ps_global->VAR_HEADER_GENERAL_FORE_COLOR
+ : ps_global->VAR_NORM_FORE_COLOR;
+ basebg = use_hdr_color ? ps_global->VAR_HEADER_GENERAL_BACK_COLOR
+ : ps_global->VAR_NORM_BACK_COLOR;
+
+ if(ps_global->VAR_SLCTBL_FORE_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR, basefg))
+ fg = ps_global->VAR_SLCTBL_FORE_COLOR;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR, basebg))
+ bg = ps_global->VAR_SLCTBL_BACK_COLOR;
+
+ if(bg || fg){
+ COLOR_PAIR *tmp;
+
+ /*
+ * The blacks are just known good colors for
+ * testing whether the other color is good.
+ */
+ if((tmp = new_color_pair(fg ? fg : colorx(COL_BLACK),
+ bg ? bg : colorx(COL_BLACK))) != NULL){
+ if(pico_is_good_colorpair(tmp))
+ for(s = color_embed(fg, bg);
+ (*len) < buflen && (colorstring[*len] = *s);
+ s++, (*len)++)
+ ;
+
+ free_color_pair(&tmp);
+ }
+ }
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ strncpy(colorstring + (*len), url_embed(TAG_BOLDON), MIN(3,buflen-(*len)));
+ *len += 2;
+ }
+ }
+
+ colorstring[buflen-1] = '\0';
+
+ return(*len != 0);
+}
+
+
+int
+handle_end_color(char *colorstring, size_t buflen, int *len)
+{
+ *len = 0;
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *s;
+
+ /*
+ * We need to change the fg and bg colors back even if they
+ * are the same as the Normal Colors so that color_a_quote
+ * will have a chance to pick up those colors as the ones to
+ * switch to. We don't do this before the handle above so that
+ * the quote color will flow into the selectable item when
+ * the selectable item color is partly the same as the
+ * normal color. That is, suppose the normal color was black on
+ * cyan and the selectable color was blue on cyan, only a fg color
+ * change. We preserve the only-a-fg-color-change in a quote by
+ * letting the quote background color flow into the selectable text.
+ */
+ if(ps_global->VAR_SLCTBL_FORE_COLOR)
+ fg = ps_global->VAR_NORM_FORE_COLOR;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR)
+ bg = ps_global->VAR_NORM_BACK_COLOR;
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ strncpy(colorstring, url_embed(TAG_BOLDOFF), MIN(3,buflen));
+ *len = 2;
+ }
+
+ if(fg || bg)
+ for(s = color_embed(fg, bg); (*len) < buflen && (colorstring[*len] = *s); s++, (*len)++)
+ ;
+ }
+
+ colorstring[buflen-1] = '\0';
+
+ return(*len != 0);
+}
+
+
+char *
+url_embed(int embed)
+{
+ static char buf[3] = {TAG_EMBED};
+ buf[1] = embed;
+ buf[2] = '\0';
+ return(buf);
+}
+
+
+/*
+ * Paint the signature.
+ */
+int
+color_signature(long int linenum, char *line, LT_INS_S **ins, void *is_in_sig)
+{
+ struct variable *vars = ps_global->vars;
+ int *in_sig_block;
+ COLOR_PAIR *col = NULL;
+
+ if(is_in_sig == NULL)
+ return 0;
+
+ in_sig_block = (int *) is_in_sig;
+
+ if(!strcmp(line, SIGDASHES))
+ *in_sig_block = START_SIG_BLOCK;
+ else if(*line == '\0')
+ /*
+ * Suggested by Eduardo: allow for a blank line right after
+ * the sigdashes.
+ */
+ *in_sig_block = (*in_sig_block == START_SIG_BLOCK)
+ ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
+ else
+ *in_sig_block = (*in_sig_block != OUT_SIG_BLOCK)
+ ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
+
+ if(*in_sig_block != OUT_SIG_BLOCK
+ && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR
+ && (col = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
+ VAR_SIGNATURE_BACK_COLOR))){
+ if(!pico_is_good_colorpair(col))
+ free_color_pair(&col);
+ }
+
+ if(col){
+ char *p, fg[RGBLEN + 1], bg[RGBLEN + 1], rgbbuf[RGBLEN + 1];
+
+ ins = gf_line_test_new_ins(ins, line,
+ color_embed(col->fg, col->bg),
+ (2 * RGBLEN) + 4);
+
+ strncpy(fg, color_to_asciirgb(VAR_NORM_FORE_COLOR), sizeof(fg));
+ fg[sizeof(fg)-1] = '\0';
+ strncpy(bg, color_to_asciirgb(VAR_NORM_BACK_COLOR), sizeof(bg));
+ bg[sizeof(bg)-1] = '\0';
+
+ /*
+ * Loop watching colors, and override with
+ * signature color whenever the normal foreground and background
+ * colors are in force.
+ */
+
+ for(p = line; *p; )
+ if(*p++ == TAG_EMBED){
+
+ switch(*p++){
+ case TAG_HANDLE :
+ p += *p + 1; /* skip handle key */
+ break;
+
+ case TAG_FGCOLOR :
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_NORM_FORE_COLOR)
+ && !colorcmp(bg, VAR_NORM_BACK_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(col->fg,NULL),
+ RGBLEN + 2);
+ break;
+
+ case TAG_BGCOLOR :
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_NORM_BACK_COLOR)
+ && !colorcmp(fg, VAR_NORM_FORE_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(NULL,col->bg),
+ RGBLEN + 2);
+
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_NORM_FORE_COLOR,
+ VAR_NORM_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ free_color_pair(&col);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Line filter to add color to displayed headers.
+ */
+int
+color_headers(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ static char field[FBUF_LEN + 1];
+ char fg[RGBLEN + 1], bg[RGBLEN + 1], rgbbuf[RGBLEN + 1];
+ char *p, *q, *value, *beg;
+ COLOR_PAIR *color;
+ int in_quote = 0, in_comment = 0, did_color = 0;
+ struct variable *vars = ps_global->vars;
+
+ field[FBUF_LEN] = '\0';
+
+ if(isspace((unsigned char)*line)) /* continuation line */
+ value = line;
+ else{
+ if(!(value = strindex(line, ':')))
+ return(0);
+
+ memset(field, 0, sizeof(field));
+ strncpy(field, line, MIN(value-line, sizeof(field)-1));
+ }
+
+ for(value++; isspace((unsigned char)*value); value++)
+ ;
+
+ strncpy(fg, color_to_asciirgb(VAR_HEADER_GENERAL_FORE_COLOR), sizeof(fg));
+ fg[sizeof(fg)-1] = '\0';
+ strncpy(bg, color_to_asciirgb(VAR_HEADER_GENERAL_BACK_COLOR), sizeof(bg));
+ bg[sizeof(bg)-1] = '\0';
+
+ /*
+ * Split into two cases depending on whether this is a header which
+ * contains addresses or not. We may color addresses separately.
+ */
+ if(is_an_addr_hdr(field)){
+
+ /*
+ * If none of the patterns are for this header, don't bother parsing
+ * and checking each address.
+ */
+ if(!any_hdr_color(field))
+ return(0);
+
+ /*
+ * First check for patternless patterns which color whole line.
+ */
+ if((color = hdr_color(field, NULL, ps_global->hdr_colors)) != NULL){
+ if(pico_is_good_colorpair(color)){
+ ins = gf_line_test_new_ins(ins, value,
+ color_embed(color->fg, color->bg),
+ (2 * RGBLEN) + 4);
+ strncpy(fg, color_to_asciirgb(color->fg), sizeof(fg));
+ fg[sizeof(fg)-1] = '\0';
+ strncpy(bg, color_to_asciirgb(color->bg), sizeof(bg));
+ bg[sizeof(bg)-1] = '\0';
+ did_color++;
+ }
+ else
+ free_color_pair(&color);
+ }
+
+ /*
+ * Then go through checking address by address.
+ * Keep track of quotes and watch for color changes, and override
+ * with most recent header color whenever the normal foreground
+ * and background colors are in force.
+ */
+ beg = p = value;
+ while(*p){
+ switch(*p){
+ case '\\':
+ /* skip next character */
+ if(*(p+1) && (in_comment || in_quote))
+ p += 2;
+ else
+ p++;
+
+ break;
+
+ case '"':
+ if(!in_comment)
+ in_quote = 1 - in_quote;
+
+ p++;
+ break;
+
+ case '(':
+ in_comment++;
+ p++;
+ break;
+
+ case ')':
+ if(in_comment > 0)
+ in_comment--;
+
+ p++;
+ break;
+
+ case ',':
+ if(!(in_quote || in_comment)){
+ /* we reached the end of this address */
+ *p = '\0';
+ if(color)
+ free_color_pair(&color);
+
+ if((color = hdr_color(field, beg,
+ ps_global->hdr_colors)) != NULL){
+ if(pico_is_good_colorpair(color)){
+ did_color++;
+ ins = gf_line_test_new_ins(ins, beg,
+ color_embed(color->fg,
+ color->bg),
+ (2 * RGBLEN) + 4);
+ *p = ',';
+ for(q = p; q > beg &&
+ isspace((unsigned char)*(q-1)); q--)
+ ;
+
+ ins = gf_line_test_new_ins(ins, q,
+ color_embed(fg, bg),
+ (2 * RGBLEN) + 4);
+ }
+ else
+ free_color_pair(&color);
+ }
+ else
+ *p = ',';
+
+ for(p++; isspace((unsigned char)*p); p++)
+ ;
+
+ beg = p;
+ }
+ else
+ p++;
+
+ break;
+
+ case TAG_EMBED:
+ switch(*(++p)){
+ case TAG_HANDLE:
+ p++;
+ p += *p + 1; /* skip handle key */
+ break;
+
+ case TAG_FGCOLOR:
+ p++;
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_HEADER_GENERAL_FORE_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(color->fg,NULL),
+ RGBLEN + 2);
+ break;
+
+ case TAG_BGCOLOR:
+ p++;
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_HEADER_GENERAL_BACK_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(NULL,color->bg),
+ RGBLEN + 2);
+
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ default:
+ p++;
+ break;
+ }
+ }
+
+ for(q = beg; *q && isspace((unsigned char)*q); q++)
+ ;
+
+ if(*q && !(in_quote || in_comment)){
+ /* we reached the end of this address */
+ if(color)
+ free_color_pair(&color);
+
+ if((color = hdr_color(field, beg, ps_global->hdr_colors)) != NULL){
+ if(pico_is_good_colorpair(color)){
+ did_color++;
+ ins = gf_line_test_new_ins(ins, beg,
+ color_embed(color->fg,
+ color->bg),
+ (2 * RGBLEN) + 4);
+ for(q = p; q > beg && isspace((unsigned char)*(q-1)); q--)
+ ;
+
+ ins = gf_line_test_new_ins(ins, q,
+ color_embed(fg, bg),
+ (2 * RGBLEN) + 4);
+ }
+ else
+ free_color_pair(&color);
+ }
+ }
+
+ if(color)
+ free_color_pair(&color);
+
+ if(did_color)
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_HEADER_GENERAL_FORE_COLOR,
+ VAR_HEADER_GENERAL_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ }
+ else{
+
+ color = hdr_color(field, value, ps_global->hdr_colors);
+
+ if(color){
+ if(pico_is_good_colorpair(color)){
+ ins = gf_line_test_new_ins(ins, value,
+ color_embed(color->fg, color->bg),
+ (2 * RGBLEN) + 4);
+
+ /*
+ * Loop watching colors, and override with header
+ * color whenever the normal foreground and background
+ * colors are in force.
+ */
+ p = value;
+ while(*p)
+ if(*p++ == TAG_EMBED){
+
+ switch(*p++){
+ case TAG_HANDLE:
+ p += *p + 1; /* skip handle key */
+ break;
+
+ case TAG_FGCOLOR:
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_HEADER_GENERAL_FORE_COLOR)
+ && !colorcmp(bg, VAR_HEADER_GENERAL_BACK_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(color->fg,NULL),
+ RGBLEN + 2);
+ break;
+
+ case TAG_BGCOLOR:
+ snprintf(rgbbuf, sizeof(rgbbuf), "%.*s", RGBLEN, p);
+ rgbbuf[sizeof(rgbbuf)-1] = '\0';
+ p += RGBLEN; /* advance past color value */
+
+ if(!colorcmp(rgbbuf, VAR_HEADER_GENERAL_BACK_COLOR)
+ && !colorcmp(fg, VAR_HEADER_GENERAL_FORE_COLOR))
+ ins = gf_line_test_new_ins(ins, p,
+ color_embed(NULL,color->bg),
+ RGBLEN + 2);
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_HEADER_GENERAL_FORE_COLOR,
+ VAR_HEADER_GENERAL_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ }
+
+ free_color_pair(&color);
+ }
+ }
+
+ return(0);
+}
+
+
+int
+url_hilite(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ register char *lp, *up = NULL, *urlp = NULL,
+ *weburlp = NULL, *mailurlp = NULL;
+ int n, n1, n2, n3, l;
+ char buf[256], color[256];
+ HANDLE_S *h;
+ URL_HILITE_S *uh;
+
+ for(lp = line; ; lp = up + n){
+ /* scan for all of them so we can choose the first */
+ if(F_ON(F_VIEW_SEL_URL,ps_global))
+ urlp = rfc1738_scan(lp, &n1);
+ if(F_ON(F_VIEW_SEL_URL_HOST,ps_global))
+ weburlp = web_host_scan(lp, &n2);
+ if(F_ON(F_SCAN_ADDR,ps_global))
+ mailurlp = mail_addr_scan(lp, &n3);
+
+ if(urlp || weburlp || mailurlp){
+ up = urlp ? urlp :
+ weburlp ? weburlp : mailurlp;
+ if(up == urlp && weburlp && weburlp < up)
+ up = weburlp;
+ if(mailurlp && mailurlp < up)
+ up = mailurlp;
+
+ if(up == urlp){
+ n = n1;
+ weburlp = mailurlp = NULL;
+ }
+ else if(up == weburlp){
+ n = n2;
+ mailurlp = NULL;
+ }
+ else{
+ n = n3;
+ weburlp = NULL;
+ }
+ }
+ else
+ break;
+
+ uh = (URL_HILITE_S *) local;
+
+ h = new_handle(uh->handlesp);
+ h->type = URL;
+ h->h.url.path = (char *) fs_get((n + 10) * sizeof(char));
+ snprintf(h->h.url.path, n+10, "%s%.*s",
+ weburlp ? "http://" : (mailurlp ? "mailto:" : ""), n, up);
+ h->h.url.path[n+10-1] = '\0';
+
+ if(handle_start_color(color, sizeof(color), &l, uh->hdr_color))
+ ins = gf_line_test_new_ins(ins, up, color, l);
+ else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global))
+ ins = gf_line_test_new_ins(ins, up, url_embed(TAG_BOLDON), 2);
+
+ buf[0] = TAG_EMBED;
+ buf[1] = TAG_HANDLE;
+ snprintf(&buf[3], sizeof(buf)-3, "%d", h->key);
+ buf[sizeof(buf)-1] = '\0';
+ buf[2] = strlen(&buf[3]);
+ ins = gf_line_test_new_ins(ins, up, buf, (int) buf[2] + 3);
+
+ /* in case it was the current selection */
+ ins = gf_line_test_new_ins(ins, up + n, url_embed(TAG_INVOFF), 2);
+
+ if(scroll_handle_end_color(color, sizeof(color), &l, uh->hdr_color))
+ ins = gf_line_test_new_ins(ins, up + n, color, l);
+ else
+ ins = gf_line_test_new_ins(ins, up + n, url_embed(TAG_BOLDOFF), 2);
+
+ urlp = weburlp = mailurlp = NULL;
+ }
+
+ return(0);
+}
+
+
+int
+url_hilite_hdr(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ static int check_for_urls = 0;
+ register char *lp;
+
+ if(isspace((unsigned char)*line)) /* continuation, check or not
+ depending on last line */
+ lp = line;
+ else{
+ check_for_urls = 0;
+ if((lp = strchr(line, ':')) != NULL){ /* there ought to always be a colon */
+ FieldType ft;
+
+ *lp = '\0';
+
+ if(((ft = pine_header_standard(line)) == FreeText
+ || ft == Subject
+ || ft == TypeUnknown)
+ && strucmp(line, "message-id")
+ && strucmp(line, "newsgroups")
+ && strucmp(line, "references")
+ && strucmp(line, "in-reply-to")
+ && strucmp(line, "received")
+ && strucmp(line, "date")){
+ check_for_urls = 1;
+ }
+
+ *lp = ':';
+ }
+ }
+
+ if(check_for_urls)
+ (void) url_hilite(linenum, lp + 1, ins, local);
+
+ return(0);
+}
+
+
+int
+pad_to_right_edge(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char *p;
+ int wid = 0;
+ int total_wid;
+ struct variable *vars = ps_global->vars;
+
+ if(!line[0])
+ return 0;
+
+ total_wid = *((int *) local);
+
+ /* calculate width of line */
+ p = line;
+ while(*p){
+
+ switch(*p){
+ case TAG_EMBED:
+ p++;
+ switch(*p){
+ case TAG_HANDLE:
+ p++;
+ p += *p + 1; /* skip handle key */
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ p += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
+ break;
+
+ case TAG_INVON:
+ case TAG_INVOFF:
+ case TAG_BOLDON:
+ case TAG_BOLDOFF:
+ case TAG_ULINEON:
+ case TAG_ULINEOFF:
+ p++;
+ break;
+
+ default: /* literal embed char */
+ break;
+ }
+
+ break;
+
+ case TAB:
+ p++;
+ while(((++wid) & 0x07) != 0) /* add tab's spaces */
+ ;
+
+ break;
+
+ default:
+ wid += width_at_this_position((unsigned char *) p, strlen(p));
+ p++;
+ break;
+ }
+ }
+
+ if(total_wid > wid){
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_HEADER_GENERAL_FORE_COLOR,
+ VAR_HEADER_GENERAL_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ ins = gf_line_test_new_ins(ins, line+strlen(line),
+ repeat_char(total_wid-wid, ' '), total_wid-wid);
+ ins = gf_line_test_new_ins(ins, line + strlen(line),
+ color_embed(VAR_NORM_FORE_COLOR,
+ VAR_NORM_BACK_COLOR),
+ (2 * RGBLEN) + 4);
+ }
+
+ return(0);
+}
+
+
+
+#define UES_LEN 12
+#define UES_MAX 32
+int
+url_external_specific_handler(char *url, int len)
+{
+ static char list[UES_LEN * UES_MAX];
+
+ if(url){
+ char *p;
+ int i;
+
+ for(i = 0; i < UES_MAX && *(p = &list[i * UES_LEN]); i++)
+ if(strlen(p) == len && !struncmp(p, url, len))
+ return(1);
+ }
+ else{ /* initialize! */
+ char **l, *test, *cmd, *p, *p2;
+ int i = 0, n;
+
+ memset(list, 0, sizeof(list));
+ for(l = ps_global->VAR_BROWSER ; l && *l; l++){
+ get_pair(*l, &test, &cmd, 1, 1);
+
+ if((p = srchstr(test, "_scheme(")) && (p2 = strstr(p+8, ")_"))){
+ *p2 = '\0';
+
+ for(p += 8; *p && i < UES_MAX; p += n)
+ if((p2 = strchr(p, ',')) != NULL){
+ if((n = p2 - p) < UES_LEN){
+ strncpy(&list[i * UES_LEN], p, MIN(n, sizeof(list)-(i * UES_LEN)));
+ i++;
+ }
+ else
+ dprint((1,
+ "* * * HANLDER TOO LONG: %.*s\n", n,
+ p ? p : "?"));
+
+ n++;
+ }
+ else{
+ if(strlen(p) <= UES_LEN){
+ strncpy(&list[i * UES_LEN], p, sizeof(list)-(i * UES_LEN));
+ i++;
+ }
+
+ break;
+ }
+ }
+
+ if(test)
+ fs_give((void **) &test);
+
+ if(cmd)
+ fs_give((void **) &cmd);
+ }
+ }
+
+ return(0);
+}
+
+
+int
+url_imap_folder(char *true_url, char **folder, imapuid_t *uid_val,
+ imapuid_t *uid, char **search, int silent)
+{
+ char *url, *scheme, *p, *cmd, *server = NULL,
+ *user = NULL, *auth = NULL, *mailbox = NULL,
+ *section = NULL;
+ size_t l;
+ int rv = URL_IMAP_ERROR;
+
+ /*
+ * Since we're planting nulls, operate on a temporary copy...
+ */
+ scheme = silent ? NULL : "IMAP";
+ url = cpystr(true_url + 7);
+
+ /* Try to pick apart the "iserver" portion */
+ if((cmd = strchr(url, '/')) != NULL){ /* iserver "/" [mailbox] ? */
+ *cmd++ = '\0';
+ }
+ else{
+ dprint((2, "-- URL IMAP FOLDER: missing: %s\n",
+ url ? url : "?"));
+ cmd = &url[strlen(url)-1]; /* assume only iserver */
+ }
+
+ if((p = strchr(url, '@')) != NULL){ /* user | auth | pass? */
+ *p++ = '\0';
+ server = rfc1738_str(p);
+
+ /* only ";auth=*" supported (and also ";auth=anonymous") */
+ if((p = srchstr(url, ";auth=")) != NULL){
+ *p = '\0';
+ auth = rfc1738_str(p + 6);
+ }
+
+ if(*url)
+ user = rfc1738_str(url);
+ }
+ else
+ server = rfc1738_str(url);
+
+ if(!*server)
+ return(url_bogus_imap(&url, scheme, "No server specified"));
+
+ /*
+ * "iserver" in hand, pick apart the "icommand"...
+ */
+ p = NULL;
+ if(!*cmd || (p = srchstr(cmd, ";type="))){
+ char *criteria;
+
+ /*
+ * No "icommand" (all top-level folders) or "imailboxlist"...
+ */
+ if(p){
+ *p = '\0'; /* tie off criteria */
+ criteria = rfc1738_str(cmd); /* get "enc_list_mailbox" */
+ if(!strucmp(p = rfc1738_str(p+6), "lsub"))
+ rv |= URL_IMAP_IMBXLSTLSUB;
+ else if(strucmp(p, "list"))
+ return(url_bogus_imap(&url, scheme,
+ "Invalid list type specified"));
+ }
+ else{
+ rv |= URL_IMAP_ISERVERONLY;
+ criteria = "";
+ }
+
+ /* build folder list from specified server/criteria/list-method */
+ l = strlen(server) + strlen(criteria) + 10 + (user ? (strlen(user)+2) : 9);
+ *folder = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(*folder, l+1, "{%s/%s%s%s}%s%s%s", server,
+ user ? "user=\"" : "Anonymous",
+ user ? user : "",
+ user ? "\"" : "",
+ *criteria ? "[" : "", criteria, *criteria ? "[" : "");
+ (*folder)[l] = '\0';
+ rv |= URL_IMAP_IMAILBOXLIST;
+ }
+ else{
+ if((p = srchstr(cmd, "/;uid=")) != NULL){ /* "imessagepart" */
+ *p = '\0'; /* tie off mailbox [uidvalidity] */
+ if((section = srchstr(p += 6, "/;section=")) != NULL){
+ *section = '\0'; /* tie off UID */
+ section = rfc1738_str(section + 10);
+/* BUG: verify valid section spec ala rfc 2060 */
+ dprint((2,
+ "-- URL IMAP FOLDER: section not used: %s\n",
+ section ? section : "?"));
+ }
+
+ if(!(*uid = rfc1738_num(&p)) || *p) /* decode UID */
+ return(url_bogus_imap(&url, scheme, "Invalid data in UID"));
+
+ /* optional "uidvalidity"? */
+ if((p = srchstr(cmd, ";uidvalidity=")) != NULL){
+ *p = '\0';
+ p += 13;
+ if(!(*uid_val = rfc1738_num(&p)) || *p)
+ return(url_bogus_imap(&url, scheme,
+ "Invalid UIDVALIDITY"));
+ }
+
+ mailbox = rfc1738_str(cmd);
+ rv = URL_IMAP_IMESSAGEPART;
+ }
+ else{ /* "imessagelist" */
+ /* optional "uidvalidity"? */
+ if((p = srchstr(cmd, ";uidvalidity=")) != NULL){
+ *p = '\0';
+ p += 13;
+ if(!(*uid_val = rfc1738_num(&p)) || *p)
+ return(url_bogus_imap(&url, scheme,
+ "Invalid UIDVALIDITY"));
+ }
+
+ /* optional "enc_search"? */
+ if((p = strchr(cmd, '?')) != NULL){
+ *p = '\0';
+ if(search)
+ *search = cpystr(rfc1738_str(p + 1));
+/* BUG: verify valid search spec ala rfc 2060 */
+ }
+
+ mailbox = rfc1738_str(cmd);
+ rv = URL_IMAP_IMESSAGELIST;
+ }
+
+ if(auth && *auth != '*' && strucmp(auth, "anonymous"))
+ q_status_message(SM_ORDER, 3, 3,
+ "Unsupported authentication method. Using standard login.");
+
+ /*
+ * At this point our structure should contain the
+ * digested url. Now put it together for c-client...
+ */
+ l = strlen(server) + 8 + (mailbox ? strlen(mailbox) : 0)
+ + (user ? (strlen(user)+2) : 9);
+ *folder = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(*folder, l+1, "{%s%s%s%s%s}%s", server,
+ (user || !(auth && strucmp(auth, "anonymous"))) ? "/" : "",
+ user ? "user=\"" : ((auth && strucmp(auth, "anonymous")) ? "" : "Anonymous"),
+ user ? user : "",
+ user ? "\"" : "",
+ mailbox);
+ (*folder)[l] = '\0';
+ }
+
+ fs_give((void **) &url);
+ return(rv);
+}
+
+
+int
+url_bogus_imap(char **freeme, char *url, char *problem)
+{
+ fs_give((void **) freeme);
+ (void) url_bogus(url, problem);
+ return(URL_IMAP_ERROR);
+}
+
+
+/*
+ * url_bogus - report url syntax errors and such
+ */
+int
+url_bogus(char *url, char *reason)
+{
+ dprint((2, "-- bogus url \"%s\": %s\n",
+ url ? url : "<NULL URL>", reason ? reason : "?"));
+ if(url)
+ q_status_message3(SM_ORDER|SM_DING, 2, 3,
+ "Malformed \"%.*s\" URL: %.200s",
+ (void *) (strchr(url, ':') - url), url, reason);
+
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Format header text suitable for display
+
+ Args: stream -- mail stream for various header text fetches
+ msgno -- sequence number in stream of message we're interested in
+ section -- which section of message
+ env -- pointer to msg's envelope
+ hdrs -- struct containing what's to get formatted
+ prefix -- prefix to append to each output line
+ handlesp -- address of pointer to the message's handles
+ flags -- FM_ flags, see pith/mailview.h
+ final_pc -- function to write header text with
+
+ Result: 0 if all's well, -1 if write error, 1 if fetch error
+
+ NOTE: Blank-line delimiter is NOT written here. Newlines are written
+ in the local convention.
+
+ ----*/
+#define FHT_OK 0
+#define FHT_WRTERR -1
+#define FHT_FTCHERR 1
+int
+format_header(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env,
+ HEADER_S *hdrs, char *prefix, HANDLE_S **handlesp, int flags,
+ fmt_env_t fmt_env, gf_io_t final_pc)
+{
+ int rv = FHT_OK;
+ int nfields, i;
+ char *h = NULL, **fields = NULL, **v, *q, *start,
+ *finish, *current;
+ STORE_S *tmp_store;
+ URL_HILITE_S uh;
+ gf_io_t tmp_pc, tmp_gc;
+ struct variable *vars = ps_global->vars;
+
+ if((tmp_store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL)
+ gf_set_so_writec(&tmp_pc, tmp_store);
+ else
+ return(FHT_WRTERR);
+
+ if(!fmt_env)
+ fmt_env = format_envelope;
+
+ if(ps_global->full_header == 2){
+ rv = format_raw_header(stream, msgno, section, tmp_pc);
+ }
+ else{
+ /*
+ * First, calculate how big a fields array we need.
+ */
+
+ /* Custom header viewing list specified */
+ if(hdrs->type == HD_LIST){
+ /* view all these headers */
+ if(!hdrs->except){
+ for(nfields = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
+ if(!is_an_env_hdr(q))
+ nfields++;
+ }
+ /* view all except these headers */
+ else{
+ for(nfields = 0, v = hdrs->h.l; *v != NULL; v++)
+ nfields++;
+
+ if(nfields > 1)
+ nfields--; /* subtract one for ALL_EXCEPT field */
+ }
+ }
+ else
+ nfields = 6; /* default view */
+
+ /* allocate pointer space */
+ if(nfields){
+ fields = (char **)fs_get((size_t)(nfields+1) * sizeof(char *));
+ memset(fields, 0, (size_t)(nfields+1) * sizeof(char *));
+ }
+
+ if(hdrs->type == HD_LIST){
+ /* view all these headers */
+ if(!hdrs->except){
+ /* put the non-envelope headers in fields */
+ if(nfields)
+ for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
+ if(!is_an_env_hdr(q))
+ fields[i++] = q;
+ }
+ /* view all except these headers */
+ else{
+ /* put the list of headers not to view in fields */
+ if(nfields)
+ for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
+ if(strucmp(ALL_EXCEPT, q))
+ fields[i++] = q;
+ }
+
+ v = hdrs->h.l;
+ }
+ else{
+ if(nfields){
+ fields[i = 0] = "Resent-Date";
+ fields[++i] = "Resent-From";
+ fields[++i] = "Resent-To";
+ fields[++i] = "Resent-cc";
+ fields[++i] = "Resent-Subject";
+ }
+
+ v = fields;
+ }
+
+ /* custom view with exception list */
+ if(hdrs->type == HD_LIST && hdrs->except){
+ /*
+ * Go through each header in h and print it.
+ * First we check to see if it is an envelope header so we
+ * can print our envelope version of it instead of the raw version.
+ */
+
+ /* fetch all the other headers */
+ if(nfields)
+ h = pine_fetchheader_lines_not(stream, msgno, section, fields);
+
+ for(current = h;
+ h && delineate_this_header(NULL, current, &start, &finish);
+ current = finish){
+ char tmp[MAILTMPLEN+1];
+ char *colon_loc;
+
+ colon_loc = strindex(start, ':');
+ if(colon_loc && colon_loc < finish){
+ strncpy(tmp, start, MIN(colon_loc-start, sizeof(tmp)-1));
+ tmp[MIN(colon_loc-start, sizeof(tmp)-1)] = '\0';
+ }
+ else
+ colon_loc = NULL;
+
+ if(colon_loc && is_an_env_hdr(tmp)){
+ char *dummystart, *dummyfinish;
+
+ /*
+ * Pretty format for env hdrs.
+ * If the same header appears more than once, only
+ * print the last to avoid duplicates.
+ * They should have been combined in the env when parsed.
+ */
+ if(!delineate_this_header(tmp, current+1, &dummystart,
+ &dummyfinish))
+ format_env_hdr(stream, msgno, section, env,
+ fmt_env, tmp_pc, tmp, hdrs->charset, flags);
+ }
+ else{
+ if((rv = format_raw_hdr_string(start, finish, tmp_pc,
+ hdrs->charset, flags)) != 0)
+ goto write_error;
+ else
+ start = finish;
+ }
+ }
+ }
+ /* custom view or default */
+ else{
+ /* fetch the non-envelope headers */
+ if(nfields)
+ h = pine_fetchheader_lines(stream, msgno, section, fields);
+
+ /* default envelope for default view */
+ if(hdrs->type == HD_BFIELD)
+ (*fmt_env)(stream, msgno, section, env, tmp_pc, hdrs->h.b, hdrs->charset, flags);
+
+ /* go through each header in list, v initialized above */
+ for(; (q = *v) != NULL; v++){
+ if(is_an_env_hdr(q)){
+ /* pretty format for env hdrs */
+ format_env_hdr(stream, msgno, section, env,
+ fmt_env, tmp_pc, q, hdrs->charset, flags);
+ }
+ else{
+ /*
+ * Go through h finding all occurences of this header
+ * and all continuation lines, and output.
+ */
+ for(current = h;
+ h && delineate_this_header(q,current,&start,&finish);
+ current = finish){
+ if((rv = format_raw_hdr_string(start, finish, tmp_pc,
+ hdrs->charset, flags)) != 0)
+ goto write_error;
+ else
+ start = finish;
+ }
+ }
+ }
+ }
+ }
+
+
+ write_error:
+
+ gf_clear_so_writec(tmp_store);
+
+ if(!rv){ /* valid data? Do wrapping and filtering... */
+ int column;
+ char *errstr, *display_filter = NULL, trigger[MAILTMPLEN];
+ STORE_S *df_store = NULL;
+
+ so_seek(tmp_store, 0L, 0);
+
+ column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
+
+ /*
+ * Test for and act on any display filter
+ * This barely makes sense. The display filter is going
+ * to be getting UTF-8'ized headers here. In pre-alpine
+ * pine the display filter was being fed already decoded
+ * headers in whatever character set they were in.
+ * The good news is that that didn't make much
+ * sense either, so this shouldn't break anything.
+ * It seems unlikely that anybody is doing anything useful
+ * with the header part of display filters.
+ */
+ if(ps_global->tools.display_filter
+ && ps_global->tools.display_filter_trigger
+ && (display_filter = (*ps_global->tools.display_filter_trigger)(NULL, trigger, sizeof(trigger)))){
+ if((df_store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+
+ gf_set_so_writec(&tmp_pc, df_store);
+ gf_set_so_readc(&tmp_gc, df_store);
+ if((errstr = (*ps_global->tools.display_filter)(display_filter, tmp_store, tmp_pc, NULL)) != NULL){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Formatting error: %s"), errstr);
+ rv = FHT_WRTERR;
+ }
+ else
+ so_seek(df_store, 0L, 0);
+
+ gf_clear_so_writec(df_store);
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "No space for filtered text.");
+ rv = FHT_WRTERR;
+ }
+ }
+ else{
+ so_seek(tmp_store, 0L, 0);
+ gf_set_so_readc(&tmp_gc, tmp_store);
+ }
+
+ if(!rv){
+ int *margin, wrapflags = GFW_ONCOMMA;
+
+ gf_filter_init();
+ gf_link_filter(gf_local_nvtnl, NULL);
+
+ if((F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ && handlesp){
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite_hdr,
+ gf_url_hilite_opt(&uh,handlesp,1)));
+ wrapflags |= GFW_HANDLES;
+ }
+
+ if((flags & FM_DISPLAY)
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ((VAR_NORM_FORE_COLOR
+ && VAR_HEADER_GENERAL_FORE_COLOR
+ && colorcmp(VAR_NORM_FORE_COLOR, VAR_HEADER_GENERAL_FORE_COLOR))
+ ||
+ (VAR_NORM_BACK_COLOR
+ && VAR_HEADER_GENERAL_BACK_COLOR
+ && colorcmp(VAR_NORM_BACK_COLOR, VAR_HEADER_GENERAL_BACK_COLOR))))
+ wrapflags |= GFW_HDRCOLOR;
+
+ if((flags & FM_DISPLAY)
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ps_global->hdr_colors){
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(color_headers, NULL));
+ wrapflags |= (GFW_HANDLES | GFW_HDRCOLOR);
+ }
+
+ if(prefix && *prefix)
+ column = MAX(column-strlen(prefix), 50);
+
+ margin = format_view_margin();
+
+ if(!(flags & FM_NOWRAP))
+ gf_link_filter(gf_wrap,
+ gf_wrap_filter_opt(column, column,
+ (flags & FM_NOINDENT) ? NULL : margin,
+ 4, wrapflags));
+
+ if(prefix && *prefix)
+ gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
+
+ if((flags & FM_DISPLAY)
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor()
+ && ((VAR_NORM_FORE_COLOR
+ && VAR_HEADER_GENERAL_FORE_COLOR
+ && colorcmp(VAR_NORM_FORE_COLOR, VAR_HEADER_GENERAL_FORE_COLOR))
+ ||
+ (VAR_NORM_BACK_COLOR
+ && VAR_HEADER_GENERAL_BACK_COLOR
+ && colorcmp(VAR_NORM_BACK_COLOR, VAR_HEADER_GENERAL_BACK_COLOR)))){
+ int right_margin;
+ int total_wid;
+
+ right_margin = margin ? margin[1] : 0;
+ total_wid = column - right_margin;
+
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(pad_to_right_edge, (void *) &total_wid));
+ wrapflags |= GFW_HANDLES;
+ }
+
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ if((errstr = gf_pipe(tmp_gc, final_pc)) != NULL){
+ rv = FHT_WRTERR;
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Can't build header : %.200s", errstr);
+ }
+ }
+
+ if(df_store){
+ gf_clear_so_readc(df_store);
+ so_give(&df_store);
+ }
+ else
+ gf_clear_so_readc(tmp_store);
+ }
+
+ so_give(&tmp_store);
+
+ if(h)
+ fs_give((void **)&h);
+
+ if(fields)
+ fs_give((void **)&fields);
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Format RAW header text for display
+
+ Args: stream -- mail stream for various header text fetches
+ rawno -- sequence number in stream of message we're interested in
+ section -- which section of message
+ pc -- function to write header text with
+
+ Result: 0 if all's well, -1 if write error, 1 if fetch error
+
+ NOTE: Blank-line delimiter is NOT written here. Newlines are written
+ in the local convention.
+
+ ----*/
+int
+format_raw_header(MAILSTREAM *stream, long int msgno, char *section, gf_io_t pc)
+{
+ char *h = mail_fetch_header(stream, msgno, section, NULL, NULL, FT_PEEK);
+ unsigned char c;
+
+ if(h){
+ while(*h){
+ if(ISRFCEOL(h)){
+ h += 2;
+ if(!gf_puts(NEWLINE, pc))
+ return(FHT_WRTERR);
+
+ if(ISRFCEOL(h)) /* all done! */
+ return(FHT_OK);
+ }
+ else if((unsigned char)(*h) < 0x80 && FILTER_THIS(*h) &&
+ !(*(h+1) && *h == ESCAPE && match_escapes(h+1))){
+ c = (unsigned char) *h++;
+ if(!((*pc)(c >= 0x80 ? '~' : '^')
+ && (*pc)((c == 0x7f) ? '?' : (c & 0x1f) + '@')))
+ return(FHT_WRTERR);
+ }
+ else if(!(*pc)(*h++))
+ return(FHT_WRTERR);
+ }
+ }
+ else
+ return(FHT_FTCHERR);
+
+ return(FHT_OK);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Format c-client envelope data suitable for display
+
+ Args: s -- mail stream for various header text fetches
+ n -- raw sequence number in stream of message we're interested in
+ sect -- which section of message
+ e -- pointer to msg's envelope
+ pc -- function to write header text with
+ which -- which header lines to write
+ oacs --
+ flags -- FM_ flags, see pith/mailview.h
+
+ Result: 0 if all's well, -1 if write error, 1 if fetch error
+
+ NOTE: Blank-line delimiter is NOT written here. Newlines are written
+ in the local convention.
+
+ ----*/
+void
+format_envelope(MAILSTREAM *s, long int n, char *sect, ENVELOPE *e, gf_io_t pc,
+ long int which, char *oacs, int flags)
+{
+ char *q, *p2, buftmp[MAILTMPLEN];
+
+ if(!e)
+ return;
+
+ if((which & FE_DATE) && e->date) {
+ q = "Date: ";
+ snprintf(buftmp, sizeof(buftmp), "%s",
+ F_ON(F_DATES_TO_LOCAL,ps_global)
+ ? convert_date_to_local((char *) e->date) : (char *) e->date);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ p2 = (char *)rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp);
+ gf_puts(q, pc);
+ format_env_puts(p2, pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ if((which & FE_FROM) && e->from)
+ format_addr_string(s, n, sect, "From: ", e->from, flags, oacs, pc);
+
+ if((which & FE_REPLYTO) && e->reply_to
+ && (!e->from || !address_is_same(e->reply_to, e->from)))
+ format_addr_string(s, n, sect, "Reply-To: ", e->reply_to, flags, oacs, pc);
+
+ if((which & FE_TO) && e->to)
+ format_addr_string(s, n, sect, "To: ", e->to, flags, oacs, pc);
+
+ if((which & FE_CC) && e->cc)
+ format_addr_string(s, n, sect, "Cc: ", e->cc, flags, oacs, pc);
+
+ if((which & FE_BCC) && e->bcc)
+ format_addr_string(s, n, sect, "Bcc: ", e->bcc, flags, oacs, pc);
+
+ if((which & FE_RETURNPATH) && e->return_path)
+ format_addr_string(s, n, sect, "Return-Path: ", e->return_path,
+ flags, oacs, pc);
+
+ if((which & FE_NEWSGROUPS) && e->newsgroups){
+ int bogus = NIL;
+ format_newsgroup_string("Newsgroups: ", e->newsgroups, flags, pc);
+ if (!e->ngpathexists && e->message_id &&
+ strncmp (e->message_id,"<alpine.",8) &&
+ strncmp (e->message_id,"<Pine.",6) &&
+ strncmp (e->message_id,"<MS-C.",6) &&
+ strncmp (e->message_id,"<MailManager.",13) &&
+ strncmp (e->message_id,"<EasyMail.",11) &&
+ strncmp (e->message_id,"<ML-",4)) bogus = T;
+
+ if(bogus)
+ q_status_message(SM_ORDER, 0, 3,
+ "Unverified Newsgroup header -- Message MAY or MAY NOT have been posted");
+ }
+
+ if((which & FE_FOLLOWUPTO) && e->followup_to)
+ format_newsgroup_string("Followup-To: ", e->followup_to, flags, pc);
+
+ if((which & FE_SUBJECT) && e->subject && e->subject[0]){
+ char *freeme = NULL;
+
+ q = "Subject: ";
+ gf_puts(q, pc);
+
+ p2 = iutf8ncpy((char *)(tmp_20k_buf+10000), (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, e->subject), SIZEOF_20KBUF-10000);
+
+ if(flags & FM_DISPLAY
+ && (ps_global->display_keywords_in_subject
+ || ps_global->display_keywordinits_in_subject)){
+
+ /* don't bother if no keywords are defined */
+ if(some_user_flags_defined(s))
+ p2 = freeme = prepend_keyword_subject(s, n, p2,
+ ps_global->display_keywords_in_subject ? KW : KWInit,
+ NULL, ps_global->VAR_KW_BRACES);
+ }
+
+ format_env_puts(p2, pc);
+
+ if(freeme)
+ fs_give((void **) &freeme);
+
+ gf_puts(NEWLINE, pc);
+ }
+
+ if((which & FE_SENDER) && e->sender
+ && (!e->from || !address_is_same(e->sender, e->from)))
+ format_addr_string(s, n, sect, "Sender: ", e->sender, flags, oacs, pc);
+
+ if((which & FE_MESSAGEID) && e->message_id){
+ q = "Message-ID: ";
+ gf_puts(q, pc);
+ p2 = iutf8ncpy((char *)(tmp_20k_buf+10000), (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, e->message_id), SIZEOF_20KBUF-10000);
+ format_env_puts(p2, pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ if((which & FE_INREPLYTO) && e->in_reply_to){
+ q = "In-Reply-To: ";
+ gf_puts(q, pc);
+ p2 = iutf8ncpy((char *)(tmp_20k_buf+10000), (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, e->in_reply_to), SIZEOF_20KBUF-10000);
+ format_env_puts(p2, pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ if((which & FE_REFERENCES) && e->references) {
+ q = "References: ";
+ gf_puts(q, pc);
+ p2 = iutf8ncpy((char *)(tmp_20k_buf+10000), (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, e->references), SIZEOF_20KBUF-10000);
+ format_env_puts(p2, pc);
+ gf_puts(NEWLINE, pc);
+ }
+}
+
+
+
+
+/*
+ * The argument fieldname is something like "Subject:..." or "Subject".
+ * Look through the specs in speccolor for a match of the fieldname,
+ * and return the color that goes with any match, or NULL.
+ * Caller should free the color.
+ */
+COLOR_PAIR *
+hdr_color(char *fieldname, char *value, SPEC_COLOR_S *speccolor)
+{
+ SPEC_COLOR_S *hc = NULL;
+ COLOR_PAIR *color_pair = NULL;
+ char *colon, *fname;
+ char fbuf[FBUF_LEN+1];
+ int gotit;
+ PATTERN_S *pat;
+
+ colon = strindex(fieldname, ':');
+ if(colon){
+ strncpy(fbuf, fieldname, MIN(colon-fieldname,FBUF_LEN));
+ fbuf[MIN(colon-fieldname,FBUF_LEN)] = '\0';
+ fname = fbuf;
+ }
+ else
+ fname = fieldname;
+
+ if(fname && *fname)
+ for(hc = speccolor; hc; hc = hc->next)
+ if(hc->spec && !strucmp(fname, hc->spec)){
+ if(!hc->val)
+ break;
+
+ gotit = 0;
+ for(pat = hc->val; !gotit && pat; pat = pat->next)
+ if(srchstr(value, pat->substring))
+ gotit++;
+
+ if(gotit)
+ break;
+ }
+
+ if(hc && hc->fg && hc->fg[0] && hc->bg && hc->bg[0])
+ color_pair = new_color_pair(hc->fg, hc->bg);
+
+ return(color_pair);
+}
+
+
+/*
+ * The argument fieldname is something like "Subject:..." or "Subject".
+ * Look through the specs in hdr_colors for a match of the fieldname,
+ * and return 1 if that fieldname is in one of the patterns, 0 otherwise.
+ */
+int
+any_hdr_color(char *fieldname)
+{
+ SPEC_COLOR_S *hc = NULL;
+ char *colon, *fname;
+ char fbuf[FBUF_LEN+1];
+
+ colon = strindex(fieldname, ':');
+ if(colon){
+ strncpy(fbuf, fieldname, MIN(colon-fieldname,FBUF_LEN));
+ fbuf[MIN(colon-fieldname,FBUF_LEN)] = '\0';
+ fname = fbuf;
+ }
+ else
+ fname = fieldname;
+
+ if(fname && *fname)
+ for(hc = ps_global->hdr_colors; hc; hc = hc->next)
+ if(hc->spec && !strucmp(fname, hc->spec))
+ break;
+
+ return(hc ? 1 : 0);
+}
+
+
+/*----------------------------------------------------------------------
+ Format an address field, wrapping lines nicely at commas
+
+ Args: field_name -- The name of the field we're formatting ("TO: ", ...)
+ addr -- ADDRESS structure to format
+
+ Result: A formatted, malloced string is returned.
+ ----------------------------------------------------------------------*/
+void
+format_addr_string(MAILSTREAM *stream, long int msgno, char *section, char *field_name,
+ struct mail_address *addr, int flags, char *oacs, gf_io_t pc)
+{
+ char *ptmp, *mtmp;
+ int trailing = 0, group = 0;
+ ADDRESS *atmp;
+
+ if(!addr)
+ return;
+
+ /*
+ * quickly run down address list to make sure none are patently bogus.
+ * If so, just blat raw field out.
+ */
+ for(atmp = addr; stream && atmp; atmp = atmp->next)
+ if(atmp->host && atmp->host[0] == '.'){
+ char *field, *fields[2];
+
+ fields[1] = NULL;
+ fields[0] = cpystr(field_name);
+ if((ptmp = strchr(fields[0], ':')) != NULL)
+ *ptmp = '\0';
+
+ if((field = pine_fetchheader_lines(stream, msgno, section, fields)) != NULL){
+ char *h, *t;
+
+ for(t = h = field; *h ; t++)
+ if(*t == '\015' && *(t+1) == '\012'){
+ *t = '\0'; /* tie off line */
+ format_env_puts(h, pc);
+ if(*(h = (++t) + 1)) /* set new h and skip CRLF */
+ gf_puts(NEWLINE, pc); /* more to write */
+ else
+ break;
+ }
+ else if(!*t){ /* shouldn't happen much */
+ if(h != t)
+ format_env_puts(h, pc);
+
+ break;
+ }
+
+ fs_give((void **)&field);
+ }
+
+ fs_give((void **)&fields[0]);
+ gf_puts(NEWLINE, pc);
+ dprint((2, "Error in \"%s\" field address\n",
+ field_name ? field_name : "?"));
+ return;
+ }
+
+ gf_puts(field_name, pc);
+
+ while(addr){
+ atmp = addr->next; /* remember what's next */
+ addr->next = NULL;
+ if(!addr->host && addr->mailbox){
+ mtmp = addr->mailbox;
+ addr->mailbox = cpystr((char *)rfc1522_decode_to_utf8(
+ (unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, addr->mailbox));
+ }
+
+ ptmp = addr->personal; /* RFC 1522 personal name? */
+ addr->personal = iutf8ncpy((char *)tmp_20k_buf, (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000), SIZEOF_20KBUF-10000, addr->personal), 10000);
+ tmp_20k_buf[10000-1] = '\0';
+
+ if(!trailing) /* 1st pass, just address */
+ trailing++;
+ else{ /* else comma, unless */
+ if(!((group == 1 && addr->host) /* 1st addr in group, */
+ || (!addr->host && !addr->mailbox))){ /* or end of group */
+ gf_puts(",", pc);
+#if 0
+ gf_puts(NEWLINE, pc); /* ONE address/line please */
+ gf_puts(" ", pc);
+#endif
+ }
+
+ gf_puts(" ", pc);
+ }
+
+ pine_rfc822_write_address_noquote(addr, pc, &group);
+
+ addr->personal = ptmp; /* restore old personal ptr */
+ if(!addr->host && addr->mailbox){
+ fs_give((void **)&addr->mailbox);
+ addr->mailbox = mtmp;
+ }
+
+ addr->next = atmp;
+ addr = atmp;
+ }
+
+ gf_puts(NEWLINE, pc);
+}
+
+
+
+
+const char *rspecials_minus_quote_and_dot = "()<>@,;:\\[]";
+ /* RFC822 continuation, must start with CRLF */
+#define RFC822CONT "\015\012 "
+
+/* Write RFC822 address with some quoting turned off.
+ * Accepts:
+ * address to interpret
+ *
+ * (This is a copy of c-client's rfc822_write_address except
+ * we don't quote double quote and dot in personal names. It writes
+ * to a gf_io_t instead of to a buffer so that we don't have to worry
+ * about fixed sized buffer overflowing. It's also special cased to deal
+ * with only a single address.)
+ *
+ * The idea is that there are some places where we'd just like to display
+ * the personal name as is before applying confusing quoting. However,
+ * we do want to be careful not to break things that should be quoted so
+ * we'll only use this where we are sure. Quoting may look ugly but it
+ * doesn't usually break anything.
+ */
+void
+pine_rfc822_write_address_noquote(struct mail_address *adr, gf_io_t pc, int *group)
+{
+ extern const char *rspecials;
+
+ if (adr->host) { /* ordinary address? */
+ if (!(adr->personal || adr->adl)) pine_rfc822_address (adr, pc);
+ else { /* no, must use phrase <route-addr> form */
+ if (adr->personal)
+ pine_rfc822_cat (adr->personal, rspecials_minus_quote_and_dot, pc);
+
+ gf_puts(" <", pc); /* write address delimiter */
+ pine_rfc822_address(adr, pc);
+ gf_puts (">", pc); /* closing delimiter */
+ }
+
+ if(*group)
+ (*group)++;
+ }
+ else if (adr->mailbox) { /* start of group? */
+ /* yes, write group name */
+ pine_rfc822_cat (adr->mailbox, rspecials, pc);
+
+ gf_puts (": ", pc); /* write group identifier */
+ *group = 1; /* in a group */
+ }
+ else if (*group) { /* must be end of group (but be paranoid) */
+ gf_puts (";", pc);
+ *group = 0; /* no longer in that group */
+ }
+}
+
+
+/* Write RFC822 route-address to string
+ * Accepts:
+ * address to interpret
+ */
+
+void
+pine_rfc822_address(struct mail_address *adr, gf_io_t pc)
+{
+ extern char *wspecials;
+
+ if (adr && adr->host) { /* no-op if no address */
+ if (adr->adl) { /* have an A-D-L? */
+ gf_puts (adr->adl, pc);
+ gf_puts (":", pc);
+ }
+ /* write mailbox name */
+ pine_rfc822_cat (adr->mailbox, wspecials, pc);
+ if (*adr->host != '@') { /* unless null host (HIGHLY discouraged!) */
+ gf_puts ("@", pc); /* host delimiter */
+ gf_puts (adr->host, pc); /* write host name */
+ }
+ }
+}
+
+
+/* Concatenate RFC822 string
+ * Accepts:
+ * pointer to string to concatenate
+ * list of special characters
+ */
+
+void
+pine_rfc822_cat(char *src, const char *specials, gf_io_t pc)
+{
+ char *s;
+
+ if (strpbrk (src,specials)) { /* any specials present? */
+ gf_puts ("\"", pc); /* opening quote */
+ /* truly bizarre characters in there? */
+ while ((s = strpbrk (src,"\\\"")) != NULL) {
+ char save[2];
+
+ /* turn it into a null-terminated piece */
+ save[0] = *s;
+ save[1] = '\0';
+ *s = '\0';
+ gf_puts (src, pc); /* yes, output leader */
+ *s = save[0];
+ gf_puts ("\\", pc); /* quoting */
+ gf_puts (save, pc); /* output the bizarre character */
+ src = ++s; /* continue after the bizarre character */
+ }
+ if (*src) gf_puts (src, pc);/* output non-bizarre string */
+ gf_puts ("\"", pc); /* closing quote */
+ }
+ else gf_puts (src, pc); /* otherwise it's the easy case */
+}
+
+
+/*----------------------------------------------------------------------
+ Format an address field, wrapping lines nicely at commas
+
+ Args: field_name -- The name of the field we're formatting ("TO:", Cc:...)
+ newsgrps -- ADDRESS structure to format
+
+ Result: A formatted, malloced string is returned.
+
+The resuling lines formatted are 80 columns wide.
+ ----------------------------------------------------------------------*/
+void
+format_newsgroup_string(char *field_name, char *newsgrps, int flags, gf_io_t pc)
+{
+ char buf[MAILTMPLEN];
+ int trailing = 0, llen, alen;
+ char *next_ng;
+
+ if(!newsgrps || !*newsgrps)
+ return;
+
+ gf_puts(field_name, pc);
+
+ llen = strlen(field_name);
+ while(*newsgrps){
+ for(next_ng = newsgrps; *next_ng && *next_ng != ','; next_ng++);
+ strncpy(buf, newsgrps, MIN(next_ng - newsgrps, sizeof(buf)-1));
+ buf[MIN(next_ng - newsgrps, sizeof(buf)-1)] = '\0';
+ newsgrps = next_ng;
+ if(*newsgrps)
+ newsgrps++;
+ alen = strlen(buf);
+ if(!trailing){ /* first time thru, just address */
+ llen += alen;
+ trailing++;
+ }
+ else{ /* else preceding comma */
+ gf_puts(",", pc);
+ llen++;
+
+ if(alen + llen + 1 > 76){
+ gf_puts(NEWLINE, pc);
+ gf_puts(" ", pc);
+ llen = alen + 5;
+ }
+ else{
+ gf_puts(" ", pc);
+ llen += alen + 1;
+ }
+ }
+
+ if(alen && llen > 76){ /* handle long addresses */
+ register char *q, *p = &buf[alen-1];
+
+ while(p > buf){
+ if(isspace((unsigned char)*p)
+ && (llen - (alen - (int)(p - buf))) < 76){
+ for(q = buf; q < p; q++)
+ (*pc)(*q); /* write character */
+
+ gf_puts(NEWLINE, pc);
+ gf_puts(" ", pc);
+ gf_puts(p, pc);
+ break;
+ }
+ else
+ p--;
+ }
+
+ if(p == buf) /* no reasonable break point */
+ gf_puts(buf, pc);
+ }
+ else
+ gf_puts(buf, pc);
+ }
+
+ gf_puts(NEWLINE, pc);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Format a text field that's part of some raw (non-envelope) message header
+
+ Args: start --
+ finish --
+ pc --
+
+ Result: Semi-digested text (RFC 1522 decoded, anyway) written with "pc"
+
+ ----------------------------------------------------------------------*/
+int
+format_raw_hdr_string(char *start, char *finish, gf_io_t pc, char *oacs, int flags)
+{
+ register char *current;
+ unsigned char *p, *tmp = NULL, c;
+ size_t n, len;
+ char ch;
+ int rv = FHT_OK;
+
+ ch = *finish;
+ *finish = '\0';
+
+ if((n = 4*(finish-start)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = tmp = (unsigned char *) fs_get(len * sizeof(unsigned char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *) tmp_20k_buf;
+ }
+
+ if(islower((unsigned char)(*start)))
+ *start = toupper((unsigned char)(*start));
+
+ current = (char *) rfc1522_decode_to_utf8(p, len, start);
+
+ /* output from start to finish */
+ while(*current && rv == FHT_OK)
+ if(ISRFCEOL(current)){
+ if(!gf_puts(NEWLINE, pc))
+ rv = FHT_WRTERR;
+
+ current += 2;
+ }
+ else if((unsigned char)(*current) < 0x80 && FILTER_THIS(*current) &&
+ !(*(current+1) && *current == ESCAPE && match_escapes(current+1))){
+ c = (unsigned char) *current++;
+ if(!((*pc)(c >= 0x80 ? '~' : '^')
+ && (*pc)((c == 0x7f) ? '?' : (c & 0x1f) + '@')))
+ rv = FHT_WRTERR;
+ }
+ else if(!(*pc)(*current++))
+ rv = FHT_WRTERR;
+
+ if(tmp)
+ fs_give((void **) &tmp);
+
+ *finish = ch;
+
+ return(rv);
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Format a text field that's part of some raw (non-envelope) message header
+
+ Args: s --
+ pc --
+
+ Result: Output
+
+ ----------------------------------------------------------------------*/
+int
+format_env_puts(char *s, gf_io_t pc)
+{
+ if(ps_global->pass_ctrl_chars)
+ return(gf_puts(s, pc));
+
+ for(; *s; s++)
+ if((unsigned char)(*s) < 0x80 && FILTER_THIS(*s) && !(*(s+1) && *s == ESCAPE && match_escapes(s+1))){
+ if(!((*pc)((unsigned char) (*s) >= 0x80 ? '~' : '^')
+ && (*pc)((*s == 0x7f) ? '?' : (*s & 0x1f) + '@')))
+ return(0);
+ }
+ else if(!(*pc)(*s))
+ return(0);
+
+ return(1);
+}
+
+
+char *
+display_parameters(PARAMETER *params)
+{
+ int n, longest = 0;
+ char *d, *printme;
+ PARAMETER *p;
+ PARMLIST_S *parmlist;
+
+ for(p = params; p; p = p->next) /* ok if we include *'s */
+ if(p->attribute && (n = strlen(p->attribute)) > longest)
+ longest = MIN(32, n); /* shouldn't be any bigger than 32 */
+
+ d = tmp_20k_buf;
+ tmp_20k_buf[0] = '\0';
+ if((parmlist = rfc2231_newparmlist(params)) != NULL){
+ n = 0; /* n overloaded */
+ while(rfc2231_list_params(parmlist) && d < tmp_20k_buf + 10000){
+ if(n++){
+ snprintf(d, 10000-(d-tmp_20k_buf), "\n");
+ tmp_20k_buf[10000-1] = '\0';
+ d += strlen(d);
+ }
+
+ if(parmlist->value){
+ if(parmlist->attrib && strucmp(parmlist->attrib, "url") == 0){
+ snprintf(printme = tmp_20k_buf + 11000, 1000, "%s", parmlist->value);
+ sqzspaces(printme);
+ }
+ else
+ printme = strsquish(tmp_20k_buf + 11000, 1000, parmlist->value, 100);
+ }
+ else
+ printme = "";
+
+ snprintf(d, 10000-(d-tmp_20k_buf), "%-*s: %s", longest,
+ parmlist->attrib ? parmlist->attrib : "", printme);
+
+ tmp_20k_buf[10000-1] = '\0';
+ d += strlen(d);
+ }
+
+ rfc2231_free_parmlist(&parmlist);
+ }
+
+ return(tmp_20k_buf);
+}
+
+
+/*----------------------------------------------------------------------
+ Fetch the requested header fields from the msgno specified
+
+ Args: stream -- mail stream of open folder
+ msgno -- number of message to get header lines from
+ fields -- array of pointers to desired fields
+
+ Returns: allocated string containing matched header lines,
+ NULL on error.
+ ----*/
+char *
+pine_fetch_header(MAILSTREAM *stream, long int msgno, char *section, char **fields, long int flags)
+{
+ STRINGLIST *sl;
+ char *p, *m, *h = NULL, *match = NULL, *free_this, tmp[MAILTMPLEN];
+ char **pflds = NULL, **pp = NULL, **qq;
+
+ /*
+ * If the user misconfigures it is possible to have one of the fields
+ * set to the empty string instead of a header name. We want to catch
+ * that here instead of asking the server the nonsensical question.
+ */
+ for(pp = fields ? &fields[0] : NULL; pp && *pp; pp++)
+ if(!**pp)
+ break;
+
+ if(pp && *pp){ /* found an empty header field, fix it */
+ pflds = copy_list_array(fields);
+ for(pp = pflds; pp && *pp; pp++){
+ if(!**pp){ /* scoot rest of the lines up */
+ free_this = *pp;
+ for(qq = pp; *qq; qq++)
+ *qq = *(qq+1);
+
+ if(free_this)
+ fs_give((void **) &free_this);
+ }
+ }
+
+ /* no headers to look for, return NULL */
+ if(pflds && !*pflds && !(flags & FT_NOT)){
+ free_list_array(&pflds);
+ return(NULL);
+ }
+ }
+ else
+ pflds = fields;
+
+ sl = (pflds && *pflds) ? new_strlst(pflds) : NULL; /* package up fields */
+ h = mail_fetch_header(stream, msgno, section, sl, NULL, flags | FT_PEEK);
+ if (sl)
+ free_strlst(&sl);
+
+ if(!h){
+ if(pflds && pflds != fields)
+ free_list_array(&pflds);
+
+ return(NULL);
+ }
+
+ while(find_field(&h, tmp, sizeof(tmp))){
+ for(pp = &pflds[0]; *pp && strucmp(tmp, *pp); pp++)
+ ;
+
+ /* interesting field? */
+ if((p = (flags & FT_NOT) ? ((*pp) ? NULL : tmp) : *pp) != NULL){
+ /*
+ * Hold off allocating space for matching fields until
+ * we at least find one to copy...
+ */
+ if(!match)
+ match = m = fs_get(strlen(h) + strlen(p) + 1);
+
+ while(*p) /* copy field name */
+ *m++ = *p++;
+
+ while(*h && (*m++ = *h++)) /* header includes colon */
+ if(*(m-1) == '\n' && (*h == '\r' || !isspace((unsigned char)*h)))
+ break;
+
+ *m = '\0'; /* tie off match string */
+ }
+ else{ /* no match, pass this field */
+ while(*h && !(*h++ == '\n'
+ && (*h == '\r' || !isspace((unsigned char)*h))))
+ ;
+ }
+ }
+
+ if(pflds && pflds != fields)
+ free_list_array(&pflds);
+
+ return(match ? match : cpystr(""));
+}
+
+
+int
+find_field(char **h, char *tmp, size_t ntmp)
+{
+ char *otmp = tmp;
+
+ if(!h || !*h || !**h || isspace((unsigned char)**h))
+ return(0);
+
+ while(tmp-otmp<ntmp-1 && **h && **h != ':' && !isspace((unsigned char)**h))
+ *tmp++ = *(*h)++;
+
+ *tmp = '\0';
+ return(1);
+}
+
+
+static char *_last_embedded_fg_color, *_last_embedded_bg_color;
+
+
+int
+embed_color(COLOR_PAIR *cp, gf_io_t pc)
+{
+ if(cp && cp->fg){
+ if(_last_embedded_fg_color)
+ fs_give((void **)&_last_embedded_fg_color);
+
+ _last_embedded_fg_color = cpystr(cp->fg);
+
+ if(!(pc && (*pc)(TAG_EMBED) && (*pc)(TAG_FGCOLOR) &&
+ gf_puts(color_to_asciirgb(cp->fg), pc)))
+ return 0;
+ }
+
+ if(cp && cp->bg){
+ if(_last_embedded_bg_color)
+ fs_give((void **)&_last_embedded_bg_color);
+
+ _last_embedded_bg_color = cpystr(cp->bg);
+
+ if(!(pc && (*pc)(TAG_EMBED) && (*pc)(TAG_BGCOLOR) &&
+ gf_puts(color_to_asciirgb(cp->bg), pc)))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+COLOR_PAIR *
+get_cur_embedded_color(void)
+{
+ COLOR_PAIR *ret;
+
+ if(_last_embedded_fg_color && _last_embedded_bg_color)
+ ret = new_color_pair(_last_embedded_fg_color, _last_embedded_bg_color);
+ else
+ ret = pico_get_cur_color();
+
+ return(ret);
+}
+
+
+void
+clear_cur_embedded_color(void)
+{
+ if(_last_embedded_fg_color)
+ fs_give((void **)&_last_embedded_fg_color);
+
+ if(_last_embedded_bg_color)
+ fs_give((void **)&_last_embedded_bg_color);
+}
+
+
+int
+scroll_handle_start_color(char *colorstring, size_t buflen, int *len)
+{
+ *len = 0;
+
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *s;
+
+ if(ps_global->VAR_SLCTBL_FORE_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR,
+ ps_global->VAR_NORM_FORE_COLOR))
+ fg = ps_global->VAR_SLCTBL_FORE_COLOR;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR
+ && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR))
+ bg = ps_global->VAR_SLCTBL_BACK_COLOR;
+
+ if(bg || fg){
+ COLOR_PAIR *tmp;
+
+ /*
+ * The blacks are just known good colors for
+ * testing whether the other color is good.
+ */
+ if((tmp = new_color_pair(fg ? fg : colorx(COL_BLACK),
+ bg ? bg : colorx(COL_BLACK))) != NULL){
+ if(pico_is_good_colorpair(tmp))
+ for(s = color_embed(fg, bg);
+ (*len) < buflen && (colorstring[*len] = *s);
+ s++, (*len)++)
+ ;
+
+ free_color_pair(&tmp);
+ }
+ }
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ strncpy(colorstring + (*len), url_embed(TAG_BOLDON), MIN(3,buflen-(*len)));
+ *len += 2;
+ }
+ }
+
+ colorstring[buflen-1] = '\0';
+
+ return(*len != 0);
+}
+
+
+int
+scroll_handle_end_color(char *colorstring, size_t buflen, int *len, int use_hdr_color)
+{
+ *len = 0;
+ if(pico_usingcolor()){
+ char *fg = NULL, *bg = NULL, *s;
+ char *basefg = NULL, *basebg = NULL;
+
+ basefg = use_hdr_color ? ps_global->VAR_HEADER_GENERAL_FORE_COLOR
+ : ps_global->VAR_NORM_FORE_COLOR;
+ basebg = use_hdr_color ? ps_global->VAR_HEADER_GENERAL_BACK_COLOR
+ : ps_global->VAR_NORM_BACK_COLOR;
+
+ /*
+ * We need to change the fg and bg colors back even if they
+ * are the same as the Normal Colors so that color_a_quote
+ * will have a chance to pick up those colors as the ones to
+ * switch to. We don't do this before the handle above so that
+ * the quote color will flow into the selectable item when
+ * the selectable item color is partly the same as the
+ * normal color. That is, suppose the normal color was black on
+ * cyan and the selectable color was blue on cyan, only a fg color
+ * change. We preserve the only-a-fg-color-change in a quote by
+ * letting the quote background color flow into the selectable text.
+ */
+ if(ps_global->VAR_SLCTBL_FORE_COLOR)
+ fg = basefg;
+
+ if(ps_global->VAR_SLCTBL_BACK_COLOR)
+ bg = basebg;
+
+ if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ strncpy(colorstring, url_embed(TAG_BOLDOFF), MIN(3,buflen));
+ *len = 2;
+ }
+
+ if(fg || bg)
+ for(s = color_embed(fg, bg); (*len) < buflen && (colorstring[*len] = *s); s++, (*len)++)
+ ;
+ }
+
+ colorstring[buflen-1] = '\0';
+
+ return(*len != 0);
+}
+
+
+/*
+ * Helper routine that is of limited use.
+ * We need to tally up the screen width of
+ * a UTF-8 string as we go through the string.
+ * We just want the width of the character starting
+ * at str (and no longer than remaining_octets).
+ * If we're plopped into the middle of a UTF-8
+ * character we just want to return width zero.
+ */
+int
+width_at_this_position(unsigned char *str, unsigned long n)
+{
+ unsigned char *inputp = str;
+ unsigned long remaining_octets = n;
+ UCS ucs;
+ int width = 0;
+
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ if(!(ucs & U8G_ERROR || ucs == UBOGON)){
+ width = wcellwidth(ucs);
+ /* Writechar will print a '?' */
+ if(width < 0)
+ width = 1;
+ }
+
+ return(width);
+}
diff --git a/pith/mailview.h b/pith/mailview.h
new file mode 100644
index 00000000..a89f3f95
--- /dev/null
+++ b/pith/mailview.h
@@ -0,0 +1,155 @@
+/*
+ * $Id: mailview.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MAILVIEW_INCLUDED
+#define PITH_MAILVIEW_INCLUDED
+
+
+#include "../pith/store.h"
+#include "../pith/handle.h"
+#include "../pith/bitmap.h"
+#include "../pith/helptext.h"
+#include "../pith/msgno.h"
+#include "../pith/filttype.h"
+#include "../pith/pattern.h"
+#include "../pith/state.h"
+#include "../pith/charset.h"
+#include "../pith/color.h"
+
+
+/* format_message flags */
+#define FM_DISPLAY 0x0001 /* result is headed for display */
+#define FM_NEW_MESS 0x0002 /* a new message so zero out attachment descrip */
+#define FM_NOWRAP 0x0008 /* no wrapping done */
+#define FM_NOCOLOR 0x0010 /* no color added */
+#define FM_NOINDENT 0x0020 /* no indents, but only works has effect if wrapping */
+#define FM_NOEDITORIAL 0x0040 /* no editorial comments */
+#define FM_NOHTMLREL 0x0200 /* no relative links */
+#define FM_HTMLRELATED 0x0400 /* allow multi/related */
+#define FM_FORCEPREFPLN 0x0800 /* force prefer-plain this time */
+#define FM_FORCENOPREFPLN 0x1000 /* force not prefer-plain this time */
+#define FM_HIDESERVER 0x2000 /* HIDE servername after active HTML links */
+#define FM_HTML 0x4000 /* filter/preserve HTML markup */
+#define FM_HTMLIMAGES 0x8000 /* filter/preserve HTML IMG tags */
+
+
+#define SIGDASHES "-- "
+#define START_SIG_BLOCK 2
+#define IN_SIG_BLOCK 1
+#define OUT_SIG_BLOCK 0
+
+
+/*
+ * Which header fields should format_envelope output?
+ */
+#define FE_FROM 0x0001
+#define FE_SENDER 0x0002
+#define FE_DATE 0x0004
+#define FE_TO 0x0008
+#define FE_CC 0x0010
+#define FE_BCC 0x0020
+#define FE_NEWSGROUPS 0x0040
+#define FE_SUBJECT 0x0080
+#define FE_MESSAGEID 0x0100
+#define FE_REPLYTO 0x0200
+#define FE_FOLLOWUPTO 0x0400
+#define FE_INREPLYTO 0x0800
+#define FE_RETURNPATH 0x1000
+#define FE_REFERENCES 0x2000
+#define FE_DEFAULT (FE_FROM | FE_DATE | FE_TO | FE_CC | FE_BCC \
+ | FE_NEWSGROUPS | FE_SUBJECT | FE_REPLYTO \
+ | FE_FOLLOWUPTO)
+
+
+/*
+ * Function to format
+ */
+typedef void (*fmt_env_t)(MAILSTREAM *, long int, char *, ENVELOPE *, gf_io_t, long int, char *, int);
+
+/*
+ * Structure and macros to help control format_header_text
+ */
+typedef struct header_s {
+ unsigned type:4;
+ unsigned except:1;
+ union {
+ char **l; /* list of char *'s */
+ long b; /* bit field of header fields (FE_* above) */
+ } h;
+ char charset[CSET_MAX];
+} HEADER_S;
+
+
+/*
+ * Macro's to help sort out how we display MIME types
+ */
+#define MCD_NONE 0x00
+#define MCD_INTERNAL 0x01
+#define MCD_EXTERNAL 0x02
+#define MCD_EXT_PROMPT 0x04
+
+
+#define HD_LIST 1
+#define HD_BFIELD 2
+#define HD_INIT(H, L, E, B) { \
+ if((L) && (L)[0]){ \
+ (H)->type = HD_LIST; \
+ (H)->except = (E); \
+ (H)->h.l = (L); \
+ } \
+ else{ \
+ (H)->type = HD_BFIELD; \
+ (H)->h.b = (B); \
+ (H)->except = 0; \
+ } \
+ (H)->charset[0] = '\0'; \
+ }
+
+
+/* exported protoypes */
+int format_message(long, ENVELOPE *, BODY *, HANDLE_S **, int, gf_io_t);
+int format_attachment_list(long int, BODY *, HANDLE_S **, int, int, gf_io_t);
+char *format_body(long int, BODY *, HANDLE_S **, HEADER_S *, int, int, gf_io_t);
+int url_hilite(long, char *, LT_INS_S **, void *);
+int handle_start_color(char *, size_t, int *, int);
+int handle_end_color(char *, size_t, int *);
+
+/*
+ * BUG: BELOW IS UNIX/PC ONLY since config'd browser means nothing to webpine
+ */
+
+int url_external_specific_handler(char *, int);
+int url_imap_folder(char *, char **, imapuid_t *, imapuid_t *, char **, int);
+int url_bogus(char *, char *);
+void pine_rfc822_address(ADDRESS *, gf_io_t);
+void pine_rfc822_cat(char *, const char *, gf_io_t);
+int format_header(MAILSTREAM *, long, char *, ENVELOPE *, HEADER_S *,
+ char *, HANDLE_S **, int, fmt_env_t, gf_io_t);
+COLOR_PAIR *hdr_color(char *, char *, SPEC_COLOR_S *);
+char *display_parameters(PARAMETER *);
+char *pine_fetch_header(MAILSTREAM *, long, char *, char **, long);
+int color_signature(long, char *, LT_INS_S **, void *);
+int scroll_handle_start_color(char *, size_t, int *);
+int scroll_handle_end_color(char *, size_t, int *, int);
+int width_at_this_position(unsigned char *, unsigned long);
+
+/* currently mandatory to implement stubs */
+
+/* this is used in rfc2369_editorial() in format_message() */
+void rfc2369_display(MAILSTREAM *, MSGNO_S *, long);
+
+
+#endif /* PITH_MAILVIEW_INCLUDED */
diff --git a/pith/makefile.wnt b/pith/makefile.wnt
new file mode 100644
index 00000000..d9316dba
--- /dev/null
+++ b/pith/makefile.wnt
@@ -0,0 +1,88 @@
+# $Id: makefile.wnt 14098 2005-10-03 18:54:13Z jpf@u.washington.edu $
+#
+# ========================================================================
+# Copyright 2006-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+#
+#
+# Makefile for WIN NT version of the libpith.lib
+#
+#
+CC=cl
+RM=del
+CP=copy
+RC=rc
+
+#includes symbol info for debugging
+CDEBUG= #-Zi -Od
+LDEBUG= /DEBUG /DEBUGTYPE:CV
+
+STDCFLAGS= -I..\include -I..\regex -nologo -MT -DWIN32 -DDOS -D_WINDOWS -DMSC_MALLOC
+
+CFLAGS= $(CDEBUG) $(STDCFLAGS) $(NET) $(EXTRACFLAGS)
+
+LFLAGS= $(LDEBUG) $(EXTRALDFLAGS)
+
+RCFLAGS =
+
+LIBER=lib
+LIBARGS=/nologo /verbose
+
+HFILES= ../include/system.h ../include/general.h \
+ abdlc.h ablookup.h addrbook.h addrstring.h adjtime.h adrbklib.h atttype.h \
+ bitmap.h bldaddr.h busy.h charset.h color.h conf.h conftype.h context.h \
+ copyaddr.h debug.h detach.h detoken.h editorial.h escapes.h filter.h filttype.h \
+ flag.h folder.h foldertype.h handle.h headers.h help.h helptext.h hist.h icache.h imap.h indxtype.h \
+ init.h keyword.h ldap.h list.h mailcap.h mailcmd.h mailindx.h maillist.h \
+ mailpart.h mailview.h margin.h mimedesc.h mimetype.h msgno.h newmail.h news.h \
+ options.h pattern.h pineelt.h pipe.h readfile.h remote.h remtype.h repltype.h reply.h \
+ rfc2231.h save.h savetype.h search.h send.h sequence.h signal.h sort.h sorttype.h \
+ state.h status.h store.h stream.h string.h strlst.h takeaddr.h tempfile.h text.h \
+ thread.h url.h user.h util.h
+
+OFILES= ablookup.obj abdlc.obj addrbook.obj addrstring.obj adrbklib.obj bldaddr.obj charset.obj \
+ color.obj conf.obj context.obj copyaddr.obj detoken.obj detach.obj editorial.obj escapes.obj \
+ filter.obj flag.obj folder.obj handle.obj help.obj helptext.obj hist.obj icache.obj imap.obj init.obj \
+ keyword.obj ldap.obj list.obj mailcap.obj mailcmd.obj mailindx.obj maillist.obj mailview.obj \
+ margin.obj mimedesc.obj mimetype.obj msgno.obj newmail.obj news.obj pattern.obj pipe.obj \
+ readfile.obj remote.obj reply.obj rfc2231.obj save.obj search.obj sequence.obj send.obj sort.obj state.obj \
+ status.obj store.obj stream.obj string.obj strlst.obj takeaddr.obj tempfile.obj text.obj \
+ thread.obj adjtime.obj url.obj util.obj
+
+all: libpith.lib
+
+helptext.h: help_h_gen.exe pine.hlp
+ help_h_gen.exe < pine.hlp > helptext.h
+
+helptext.c: help_c_gen.exe pine.hlp
+ help_c_gen.exe < pine.hlp > helptext.c
+
+help_h_gen.exe:
+ $(CC) /c $(CFLAGS) help_h_gen.c
+ link /subsystem:console /out:help_h_gen.exe $(LFLAGS) help_h_gen.obj
+
+help_c_gen.exe:
+ $(CC) /c $(CFLAGS) help_c_gen.c
+ link /subsystem:console /out:help_c_gen.exe $(LFLAGS) help_c_gen.obj
+
+.c.obj:
+ $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c
+
+$(OFILES): $(HFILES)
+
+libpith.lib: $(OFILES)
+ $(RM) libpith.lib || rem
+ $(LIBER) /out:libpith.lib $(OFILES)
+
+clean:
+ $(RM) *.lib
+ $(RM) *.obj
+ $(RM) helptext.h helptext.c help_h_gen.exe help_c_gen.exe
diff --git a/pith/margin.c b/pith/margin.c
new file mode 100644
index 00000000..90fdd209
--- /dev/null
+++ b/pith/margin.c
@@ -0,0 +1,138 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: margin.c 1032 2008-04-11 00:30:04Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ margin.c
+ Implements message data gathering and formatting
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/string.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * format_view_margin - return sane value for vertical margins
+ */
+int *
+format_view_margin(void)
+{
+ static int margin[2];
+ char tmp[100], e[200], *err, lastchar = 0;
+ int left = 0, right = 0, leftm = 0, rightm = 0;
+ size_t l;
+
+ memset(margin, 0, sizeof(margin));
+
+ /*
+ * We initially tried to make sure that the user didn't shoot themselves
+ * in the foot by setting this too small. People seem to want to do some
+ * strange stuff, so we're going to relax the wild shot detection and
+ * let people set what they want, until we get to the point of totally
+ * absurd. We've also added the possibility of appending the letter c
+ * onto the width. That means treat the value as an absolute column
+ * instead of a width. That is, a right margin of 76c means wrap at
+ * column 76, whereas right margin of 4 means to wrap at column
+ * screen width - 4.
+ */
+ if(ps_global->VAR_VIEW_MARGIN_LEFT){
+ strncpy(tmp, ps_global->VAR_VIEW_MARGIN_LEFT, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ removing_leading_and_trailing_white_space(tmp);
+ if(tmp[0]){
+ l = strlen(tmp);
+ if(l > 0)
+ lastchar = tmp[l-1];
+
+ if(lastchar == 'c')
+ tmp[l-1] = '\0';
+
+ if((err = strtoval(tmp, &left, 0, 0, 0, e, sizeof(e), "Viewer-margin-left")) != NULL){
+ leftm = 0;
+ dprint((2, "%s\n", err));
+ }
+ else{
+ if(lastchar == 'c')
+ leftm = left-1;
+ else
+ leftm = left;
+
+ leftm = MIN(MAX(0, leftm), ps_global->ttyo->screen_cols);
+ }
+ }
+ }
+
+ if(ps_global->VAR_VIEW_MARGIN_RIGHT){
+ strncpy(tmp, ps_global->VAR_VIEW_MARGIN_RIGHT, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ removing_leading_and_trailing_white_space(tmp);
+ if(tmp[0]){
+ l = strlen(tmp);
+ if(l > 0)
+ lastchar = tmp[l-1];
+
+ if(lastchar == 'c')
+ tmp[l-1] = '\0';
+
+ if((err = strtoval(tmp, &right, 0, 0, 0, e, sizeof(e), "Viewer-margin-right")) != NULL){
+ rightm = 0;
+ dprint((2, "%s\n", err));
+ }
+ else{
+ if(lastchar == 'c')
+ rightm = ps_global->ttyo->screen_cols - right;
+ else
+ rightm = right;
+
+ rightm = MIN(MAX(0, rightm), ps_global->ttyo->screen_cols);
+ }
+ }
+ }
+
+ if((rightm > 0 || leftm > 0) && rightm >= 0 && leftm >= 0
+ && ps_global->ttyo->screen_cols - rightm - leftm >= 8){
+ margin[0] = leftm;
+ margin[1] = rightm;
+ }
+
+ return(margin);
+}
+
+
+/*
+ * Give a margin for help and such
+ */
+int *
+non_messageview_margin(void)
+{
+ static int margin[2];
+
+ margin[0] = 0;
+ margin[1] = 4;
+
+ return(margin);
+}
diff --git a/pith/margin.h b/pith/margin.h
new file mode 100644
index 00000000..3c497c6f
--- /dev/null
+++ b/pith/margin.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: margin.h 1032 2008-04-11 00:30:04Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MARGIN_INCLUDED
+#define PITH_MARGIN_INCLUDED
+
+
+/* exported protoypes */
+int *format_view_margin(void);
+int *non_messageview_margin(void);
+
+
+#endif /* PITH_MARGIN_INCLUDED */
diff --git a/pith/mimedesc.c b/pith/mimedesc.c
new file mode 100644
index 00000000..66b39839
--- /dev/null
+++ b/pith/mimedesc.c
@@ -0,0 +1,879 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mimedesc.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mimedesc.h"
+#include "../pith/mimetype.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/mailview.h"
+#include "../pith/rfc2231.h"
+#include "../pith/editorial.h"
+#include "../pith/mailpart.h"
+#include "../pith/mailcap.h"
+#include "../pith/smime.h"
+
+
+/* internal prototypes */
+int mime_known_text_subtype(char *);
+ATTACH_S *next_attachment(void);
+void format_mime_size(char *, size_t, BODY *, int);
+int mime_show(BODY *);
+
+
+/*
+ * Def's to help in sorting out multipart/alternative
+ */
+#define SHOW_NONE 0
+#define SHOW_PARTS 1
+#define SHOW_ALL_EXT 2
+#define SHOW_ALL 3
+
+/*
+ * Def's to control format_mime_size output
+ */
+#define FMS_NONE 0x00
+#define FMS_SPACE 0x01
+
+
+/*----------------------------------------------------------------------
+ Add lines to the attachments structure
+
+ Args: body -- body of the part being described
+ prefix -- The prefix for numbering the parts
+ num -- The number of this specific part
+ should_show -- Flag indicating which of alternate parts should be shown
+ multalt -- Flag indicating the part is one of the multipart
+ alternative parts (so suppress editorial comment)
+
+Result: The ps_global->attachments data structure is filled in. This
+is called recursively to descend through all the parts of a message.
+The description strings filled in are malloced and should be freed.
+
+ ----*/
+void
+describe_mime(struct mail_bodystruct *body, char *prefix, int num,
+ int should_show, int multalt, int flags)
+{
+ PART *part;
+ char numx[512], string[800], *description;
+ int n, named = 0, can_display_ext;
+ ATTACH_S *a;
+
+ if(!body)
+ return;
+
+ if(body->type == TYPEMULTIPART){
+ int alt_to_show = 0;
+
+ if(strucmp(body->subtype, "alternative") == 0){
+ int effort, best_effort = SHOW_NONE;
+
+ /*---- Figure out which alternative part to display ---*/
+ /*
+ * This is kind of complicated because some TEXT types
+ * are more displayable than others. We don't want to
+ * commit to displaying a text-part alternative that we
+ * don't directly recognize unless that's all there is.
+ */
+ for(part=body->nested.part, n=1; part; part=part->next, n++)
+ if(flags & FM_FORCEPREFPLN
+ || (!(flags & FM_FORCENOPREFPLN)
+ && F_ON(F_PREFER_PLAIN_TEXT, ps_global)
+ && part->body.type == TYPETEXT
+ && (!part->body.subtype
+ || !strucmp(part->body.subtype, "PLAIN")))){
+ if((effort = mime_show(&part->body)) != SHOW_ALL_EXT){
+ best_effort = effort;
+ alt_to_show = n;
+ break;
+ }
+ }
+ else if((effort = mime_show(&part->body)) >= best_effort
+ && (part->body.type != TYPETEXT || mime_known_text_subtype(part->body.subtype))
+ && effort != SHOW_ALL_EXT){
+ best_effort = effort;
+ alt_to_show = n;
+ }
+ else if(part->body.type == TYPETEXT && alt_to_show == 0){
+ best_effort = effort;
+ alt_to_show = n;
+ }
+ }
+ else if(!strucmp(body->subtype, "digest")){
+ memset(a = next_attachment(), 0, sizeof(ATTACH_S));
+ if(*prefix){
+ prefix[n = strlen(prefix) - 1] = '\0';
+ a->number = cpystr(prefix);
+ prefix[n] = '.';
+ }
+ else
+ a->number = cpystr("");
+
+ a->description = cpystr("Multipart/Digest");
+ a->body = body;
+ a->can_display = MCD_INTERNAL;
+ (a+1)->description = NULL;
+ }
+#ifdef SMIME
+ else if(!strucmp(body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)){
+ memset(a = next_attachment(), 0, sizeof(ATTACH_S));
+ if(*prefix){
+ prefix[n = strlen(prefix) - 1] = '\0';
+ a->number = cpystr(prefix);
+ prefix[n] = '.';
+ }
+ else
+ a->number = cpystr("");
+
+ a->description = body->description ? cpystr(body->description)
+ : cpystr("");
+ a->body = body;
+ a->can_display = MCD_INTERNAL;
+ (a+1)->description = NULL;
+ }
+#endif /* SMIME */
+ else if(mailcap_can_display(body->type, body->subtype, body, 0)
+ || (can_display_ext
+ = mailcap_can_display(body->type, body->subtype, body, 1))){
+ memset(a = next_attachment(), 0, sizeof(ATTACH_S));
+ if(*prefix){
+ prefix[n = strlen(prefix) - 1] = '\0';
+ a->number = cpystr(prefix);
+ prefix[n] = '.';
+ }
+ else
+ a->number = cpystr("");
+
+ snprintf(string, sizeof(string), "%s/%s", body_type_names(body->type),
+ body->subtype);
+ string[sizeof(string)-1] = '\0';
+ a->description = cpystr(string);
+ a->body = body;
+ a->can_display = MCD_EXTERNAL;
+ if(can_display_ext)
+ a->can_display |= MCD_EXT_PROMPT;
+ (a+1)->description = NULL;
+ }
+
+ for(part=body->nested.part, n=1; part; part=part->next, n++){
+ snprintf(numx, sizeof(numx), "%s%d.", prefix, n);
+ numx[sizeof(numx)-1] = '\0';
+ /*
+ * Last arg to describe_mime here. If we have chosen one part
+ * of a multipart/alternative to display then we suppress
+ * the editorial messages on the other parts.
+ */
+ describe_mime(&(part->body),
+ (part->body.type == TYPEMULTIPART) ? numx : prefix,
+ n, should_show && (n == alt_to_show || !alt_to_show),
+ alt_to_show != 0, flags);
+ }
+ }
+ else{
+ char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN];
+ size_t ll;
+
+ a = next_attachment();
+ format_mime_size(a->size, sizeof(a->size), body, FMS_SPACE);
+
+ a->suppress_editorial = (multalt != 0);
+
+ snprintf(tmp1, sizeof(tmp1), "%s", body->description ? body->description : "");
+ tmp1[sizeof(tmp1)-1] = '\0';
+ snprintf(tmp2, sizeof(tmp2), "%s", (!body->description && body->type == TYPEMESSAGE && body->encoding <= ENCBINARY && body->subtype && strucmp(body->subtype, "rfc822") == 0 && body->nested.msg->env && body->nested.msg->env->subject) ? body->nested.msg->env->subject : "");
+ tmp2[sizeof(tmp2)-1] = '\0';
+
+ description = (body->description)
+ ? (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, tmp1)
+ : (body->type == TYPEMESSAGE
+ && body->encoding <= ENCBINARY
+ && body->subtype
+ && strucmp(body->subtype, "rfc822") == 0
+ && body->nested.msg->env
+ && body->nested.msg->env->subject)
+ ? (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp2)
+ : (body->type == TYPEMESSAGE
+ && body->subtype
+ && !strucmp(body->subtype, "delivery-status"))
+ ? "Delivery Status"
+ : NULL;
+
+ description = iutf8ncpy((char *)(tmp_20k_buf+1000), description, 1000);
+ snprintf(string, sizeof(string), "%s%s%s%s",
+ type_desc(body->type,body->subtype,body->parameter,
+ body->disposition.type ? body->disposition.parameter : NULL, 0),
+ (description && description[0]) ? ", \"" : "",
+ (description && description[0]) ? description : "",
+ (description && description[0]) ? "\"": "");
+ string[sizeof(string)-1] =- '\0';
+ a->description = cpystr(string);
+ a->body = body;
+
+ if(body->disposition.type){
+ named = strucmp(body->disposition.type, "inline");
+ }
+ else{
+ char *value;
+
+
+ /*
+ * This test remains for backward compatibility
+ */
+ if(body && (value = parameter_val(body->parameter, "name")) != NULL){
+ named = strucmp(value, "Message Body");
+ fs_give((void **) &value);
+ }
+ }
+
+ /*
+ * Make sure we have the tools available to display the
+ * type/subtype, *AND* that we can decode it if needed.
+ * Of course, if it's text, we display it anyway in the
+ * mail_view_screen so put off testing mailcap until we're
+ * explicitly asked to display that segment 'cause it could
+ * be expensive to test...
+ */
+ if((body->type == TYPETEXT && !named)
+ || MIME_VCARD(body->type,body->subtype)){
+ a->test_deferred = 1;
+ a->can_display = MCD_INTERNAL;
+ }
+ else{
+ a->test_deferred = 0;
+ a->can_display = mime_can_display(body->type, body->subtype, body);
+ }
+
+ /*
+ * Deferred means we can display it
+ */
+ a->shown = ((a->can_display & MCD_INTERNAL)
+ && !MIME_VCARD(body->type,body->subtype)
+ && (!named || multalt
+ || (body->type == TYPETEXT && num == 1
+ && !(*prefix && strcmp(prefix,"1."))))
+ && (body->type != TYPEMESSAGE
+ || (body->type == TYPEMESSAGE
+ && body->encoding <= ENCBINARY))
+ && should_show);
+ ll = (strlen(prefix) + 16) * sizeof(char);
+ a->number = (char *) fs_get(ll);
+ snprintf(a->number, ll, "%s%d",prefix, num);
+ a->number[ll-1] = '\0';
+ (a+1)->description = NULL;
+ if(body->type == TYPEMESSAGE && body->encoding <= ENCBINARY
+ && body->subtype && strucmp(body->subtype, "rfc822") == 0){
+ body = body->nested.msg->body;
+ snprintf(numx, sizeof(numx), "%.*s%d.", sizeof(numx)-20, prefix, num);
+ numx[sizeof(numx)-1] = '\0';
+ describe_mime(body, numx, 1, should_show, 0, flags);
+ }
+ }
+}
+
+
+int
+mime_known_text_subtype(char *subtype)
+{
+ char **p;
+ static char *known_types[] = {
+ "plain",
+ "html",
+ "enriched",
+ "richtext",
+ NULL
+ };
+
+ if(!(subtype && *subtype))
+ return(1);
+
+ for(p = known_types; *p; p++)
+ if(!strucmp(subtype, *p))
+ return(1);
+ return(0);
+}
+
+
+/*
+ * Returns attribute value or NULL.
+ * Value returned needs to be freed by caller
+ */
+char *
+parameter_val(PARAMETER *param, char *attribute)
+{
+ if(!(param && attribute && attribute[0]))
+ return(NULL);
+
+ return(rfc2231_get_param(param, attribute, NULL, NULL));
+}
+
+
+/*
+ * Get sender_filename, the filename set by the sender in the attachment.
+ * If a sender_filename buffer is passed in, the answer is copied to it
+ * and a pointer to it is returned. If sender_filename is passed in as NULL
+ * then an allocated copy of the sender filename is returned instead.
+ * If ext_ptr is non-NULL then it is set to point to the extension name.
+ * It is not a separate copy, it points into the string sender_filename.
+ */
+char *
+get_filename_parameter(char *sender_filename, size_t sfsize, BODY *body, char **ext_ptr)
+{
+ char *p = NULL;
+ char *decoded_name = NULL;
+ char *filename = NULL;
+ char tmp[1000];
+
+ if(!body)
+ return(NULL);
+
+ if(sender_filename){
+ if(sfsize <= 0)
+ return(NULL);
+
+ sender_filename[0] = '\0';
+ }
+
+ /*
+ * First check for Content-Disposition's "filename" parameter and
+ * if that isn't found for the deprecated Content-Type "name" parameter.
+ */
+ if((p = parameter_val(body->disposition.parameter, "filename"))
+ || (p = parameter_val(body->parameter, "name"))){
+
+ /*
+ * If somebody sent us and incorrectly rfc2047 encoded
+ * parameter value instead of what rfc2231 suggest we
+ * grudglingly try to fix it.
+ */
+ if(p[0] == '=' && p[1] == '?')
+ decoded_name = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp,
+ sizeof(tmp), p);
+
+ if(!decoded_name)
+ decoded_name = p;
+
+ filename = last_cmpnt(decoded_name);
+
+ if(!filename)
+ filename = decoded_name;
+ }
+
+ if(filename){
+ if(sender_filename){
+ strncpy(sender_filename, filename, sfsize-1);
+ sender_filename[sfsize-1] = '\0';
+ }
+ else
+ sender_filename = cpystr(filename);
+ }
+
+ if(p)
+ fs_give((void **) &p);
+
+ /* ext_ptr will end up pointing into sender_filename string */
+ if(ext_ptr && sender_filename)
+ mt_get_file_ext(sender_filename, ext_ptr);
+
+ return(sender_filename);
+}
+
+
+/*----------------------------------------------------------------------
+ Return a pointer to the next attachment struct
+
+ Args: none
+
+ ----*/
+ATTACH_S *
+next_attachment(void)
+{
+ ATTACH_S *a;
+ int n;
+
+ for(a = ps_global->atmts; a->description; a++)
+ ;
+
+ if((n = a - ps_global->atmts) + 1 >= ps_global->atmts_allocated){
+ ps_global->atmts_allocated *= 2;
+ fs_resize((void **)&ps_global->atmts,
+ ps_global->atmts_allocated * sizeof(ATTACH_S));
+ a = &ps_global->atmts[n];
+ }
+
+ return(a);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Zero out the attachments structure and free up storage
+ ----*/
+void
+zero_atmts(ATTACH_S *atmts)
+{
+ ATTACH_S *a;
+
+ for(a = atmts; a->description != NULL; a++){
+ fs_give((void **)&(a->description));
+ fs_give((void **)&(a->number));
+ }
+
+ atmts->description = NULL;
+}
+
+
+char *
+body_type_names(int t)
+{
+#define TLEN 31
+ static char body_type[TLEN + 1];
+ char *p;
+
+ body_type[0] = '\0';
+ strncpy(body_type, /* copy the given type */
+ (t > -1 && t < TYPEMAX && body_types[t])
+ ? body_types[t] : "Other", TLEN);
+ body_type[sizeof(body_type)-1] = '\0';
+
+ for(p = body_type + 1; *p; p++) /* make it presentable */
+ if(isascii((unsigned char) (*p)) && isupper((unsigned char) (*p)))
+ *p = tolower((unsigned char)(*p));
+
+ return(body_type); /* present it */
+}
+
+
+/*----------------------------------------------------------------------
+ Mapping table use to neatly display charset parameters
+ ----*/
+
+static struct set_names {
+ char *rfcname,
+ *humanname;
+} charset_names[] = {
+ {"US-ASCII", "Plain Text"},
+ {"ISO-8859-1", "Latin 1 (Western Europe)"},
+ {"ISO-8859-2", "Latin 2 (Eastern Europe)"},
+ {"ISO-8859-3", "Latin 3 (Southern Europe)"},
+ {"ISO-8859-4", "Latin 4 (Northern Europe)"},
+ {"ISO-8859-5", "Latin & Cyrillic"},
+ {"ISO-8859-6", "Latin & Arabic"},
+ {"ISO-8859-7", "Latin & Greek"},
+ {"ISO-8859-8", "Latin & Hebrew"},
+ {"ISO-8859-9", "Latin 5 (Turkish)"},
+ {"ISO-8859-10", "Latin 6 (Nordic)"},
+ {"ISO-8859-11", "Latin & Thai"},
+ {"ISO-8859-13", "Latin 7 (Baltic)"},
+ {"ISO-8859-14", "Latin 8 (Celtic)"},
+ {"ISO-8859-15", "Latin 9 (Euro)"},
+ {"KOI8-R", "Latin & Russian"},
+ {"KOI8-U", "Latin & Ukranian"},
+ {"VISCII", "Latin & Vietnamese"},
+ {"GB2312", "Latin & Simplified Chinese"},
+ {"BIG5", "Latin & Traditional Chinese"},
+ {"EUC-JP", "Latin & Japanese"},
+ {"Shift-JIS", "Latin & Japanese"},
+ {"Shift_JIS", "Latin & Japanese"},
+ {"EUC-KR", "Latin & Korean"},
+ {"ISO-2022-CN", "Latin & Chinese"},
+ {"ISO-2022-JP", "Latin & Japanese"},
+ {"ISO-2022-KR", "Latin & Korean"},
+ {"UTF-7", "7-bit encoded Unicode"},
+ {"UTF-8", "Internet-standard Unicode"},
+ {"ISO-2022-JP-2", "Multilingual"},
+ {NULL, NULL}
+};
+
+
+/*----------------------------------------------------------------------
+ Return a nicely formatted discription of the type of the part
+ ----*/
+
+char *
+type_desc(int type, char *subtype, PARAMETER *params, PARAMETER *disp_params, int full)
+{
+ static char type_d[200];
+ int i;
+ char *p, *parmval;
+
+ p = type_d;
+ sstrncpy(&p, body_type_names(type), sizeof(type_d)-(p-type_d));
+ if(full && subtype){
+ *p++ = '/';
+ sstrncpy(&p, subtype, sizeof(type_d)-(p-type_d));
+ }
+
+ type_d[sizeof(type_d)-1] = '\0';
+
+ switch(type){
+ case TYPETEXT:
+ parmval = parameter_val(params, "charset");
+
+ if(parmval){
+ for(i = 0; charset_names[i].rfcname; i++)
+ if(!strucmp(parmval, charset_names[i].rfcname)){
+ if(!strucmp(parmval, ps_global->display_charmap
+ ? ps_global->display_charmap : "us-ascii")
+ || !strucmp(parmval, "us-ascii"))
+ i = -1;
+
+ break;
+ }
+
+ if(i >= 0){ /* charset to write */
+ if(charset_names[i].rfcname){
+ sstrncpy(&p, " (charset: ", sizeof(type_d)-(p-type_d));
+ sstrncpy(&p, charset_names[i].rfcname
+ ? charset_names[i].rfcname : "Unknown", sizeof(type_d)-(p-type_d));
+ if(full){
+ sstrncpy(&p, " \"", sizeof(type_d)-(p-type_d));
+ sstrncpy(&p, charset_names[i].humanname
+ ? charset_names[i].humanname
+ : parmval, sizeof(type_d)-(p-type_d));
+ if(sizeof(type_d)-(p-type_d) > 0)
+ *p++ = '\"';
+ }
+
+ sstrncpy(&p, ")", sizeof(type_d)-(p-type_d));
+ }
+ else{
+ sstrncpy(&p, " (charset: ", sizeof(type_d)-(p-type_d));
+ sstrncpy(&p, parmval, sizeof(type_d)-(p-type_d));
+ sstrncpy(&p, ")", sizeof(type_d)-(p-type_d));
+ }
+ }
+
+ fs_give((void **) &parmval);
+ }
+
+ break;
+
+ case TYPEMESSAGE:
+ if(full && subtype && strucmp(subtype, "external-body") == 0)
+ if((parmval = parameter_val(params, "access-type")) != NULL){
+ snprintf(p, sizeof(type_d)-(p-type_d), " (%s%s)", full ? "Access: " : "", parmval);
+ fs_give((void **) &parmval);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if(full && type != TYPEMULTIPART && type != TYPEMESSAGE){
+ if((parmval = parameter_val(params, "name")) != NULL){
+ snprintf(p, sizeof(type_d)-(p-type_d), " (Name: \"%s\")", parmval);
+ fs_give((void **) &parmval);
+ }
+ else if((parmval = parameter_val(disp_params, "filename")) != NULL){
+ snprintf(p, sizeof(type_d)-(p-type_d), " (Filename: \"%s\")", parmval);
+ fs_give((void **) &parmval);
+ }
+ }
+
+ type_d[sizeof(type_d)-1] = '\0';
+
+ return(type_d);
+}
+
+
+
+
+void
+format_mime_size(char *string, size_t stringlen, struct mail_bodystruct *b, int flags)
+{
+ char tmp[10], *p = NULL;
+ char *origstring;
+
+
+ if(stringlen <= 0)
+ return;
+
+ origstring = string;
+
+ if(flags & FMS_SPACE)
+ *string++ = ' ';
+
+ switch(b->encoding){
+ case ENCBASE64 :
+ if(b->type == TYPETEXT){
+ if(flags & FMS_SPACE)
+ *(string-1) = '~';
+ else
+ *string++ = '~';
+ }
+
+ strncpy(p = string, byte_string((3 * b->size.bytes) / 4), stringlen-(string-origstring));
+ break;
+
+ default :
+ case ENCQUOTEDPRINTABLE :
+ if(flags & FMS_SPACE)
+ *(string-1) = '~';
+ else
+ *string++ = '~';
+
+ case ENC8BIT :
+ case ENC7BIT :
+ if(b->type == TYPETEXT)
+ /* lines with no CRLF aren't counted, just add one so it makes more sense */
+ snprintf(string, stringlen-(string-origstring), "%s lines", comatose(b->size.lines+1));
+ else
+ strncpy(p = string, byte_string(b->size.bytes), stringlen-(string-origstring));
+
+ break;
+ }
+
+ origstring[stringlen-1] = '\0';
+
+ if(p){
+ for(; *p && (isascii((unsigned char) *p) && (isdigit((unsigned char) *p)
+ || ispunct((unsigned char) *p))); p++)
+ ;
+
+ snprintf(tmp, sizeof(tmp), (flags & FMS_SPACE) ? " %-5.5s" : " %s", p);
+ tmp[sizeof(tmp)-1] = '\0';
+ strncpy(p, tmp, stringlen-(p-origstring));
+ }
+
+ origstring[stringlen-1] = '\0';
+}
+
+
+
+/*----------------------------------------------------------------------
+ Determine if we can show all, some or none of the parts of a body
+
+Args: body --- The message body to check
+
+Returns: SHOW_ALL, SHOW_ALL_EXT, SHOW_PART or SHOW_NONE depending on
+ how much of the body can be shown and who can show it.
+ ----*/
+int
+mime_show(struct mail_bodystruct *body)
+{
+ int effort, best_effort;
+ PART *p;
+
+ if(!body)
+ return(SHOW_NONE);
+
+ switch(body->type) {
+ case TYPEMESSAGE:
+ if(!strucmp(body->subtype, "rfc822"))
+ return(mime_show(body->nested.msg->body) == SHOW_ALL
+ ? SHOW_ALL: SHOW_PARTS);
+ /* else fall thru to default case... */
+
+ default:
+ /*
+ * Since we're testing for internal displayability, give the
+ * internal result over an external viewer
+ */
+ effort = mime_can_display(body->type, body->subtype, body);
+ if(effort == MCD_NONE)
+ return(SHOW_NONE);
+ else if(effort & MCD_INTERNAL)
+ return(SHOW_ALL);
+ else
+ return(SHOW_ALL_EXT);
+
+ case TYPEMULTIPART:
+ best_effort = SHOW_NONE;
+ for(p = body->nested.part; p; p = p->next)
+ if((effort = mime_show(&p->body)) > best_effort)
+ best_effort = effort;
+
+ return(best_effort);
+ }
+}
+
+
+/*
+ * fcc_size_guess
+ */
+long
+fcc_size_guess(struct mail_bodystruct *body)
+{
+ long size = 0L;
+
+ if(body){
+ if(body->type == TYPEMULTIPART){
+ PART *part;
+
+ for(part = body->nested.part; part; part = part->next)
+ size += fcc_size_guess(&part->body);
+ }
+ else{
+ size = body->size.bytes;
+ /*
+ * If it is ENCBINARY we will be base64 encoding it. This
+ * ideally increases the size by a factor of 4/3, but there
+ * is a per-line increase in that because of the CRLFs and
+ * because the number of characters in the line might not
+ * be a factor of 3. So push it up by 3/2 instead. This still
+ * won't catch all the cases. In particular, attachements with
+ * lots of short lines (< 10) will expand by more than that,
+ * but that's ok since this is an optimization. That's why
+ * so_cs_puts uses the 3/2 factor when it does a resize, so
+ * that it won't have to resize linearly until it gets there.
+ */
+ if(body->encoding == ENCBINARY)
+ size = 3*size/2;
+ }
+ }
+
+ return(size);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Format a strings describing one unshown part of a Mime message
+
+Args: number -- A string with the part number i.e. "3.2.1"
+ body -- The body part
+ type -- 1 - Not shown, but can be
+ 2 - Not shown, cannot be shown
+ 3 - Can't print
+ width -- allowed width per line of editorial comment
+ pc -- function used to write the description comment
+
+Result: formatted description written to object ref'd by "pc"
+ ----*/
+char *
+part_desc(char *number, BODY *body, int type, int width, int flags, gf_io_t pc)
+{
+ char *t;
+ char buftmp[MAILTMPLEN], sizebuf[256];
+
+ if(!gf_puts(NEWLINE, pc))
+ return("No space for description");
+
+ format_mime_size(sizebuf, 256, body, FMS_NONE);
+
+ snprintf(buftmp, sizeof(buftmp), "%s", body->description ? body->description : "");
+ buftmp[sizeof(buftmp)-1] = '\0';
+ snprintf(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, "Part %s, %s%.2048s%s%s %s.",
+ number,
+ body->description == NULL ? "" : "\"",
+ body->description == NULL ? ""
+ : (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, buftmp),
+ body->description == NULL ? "" : "\" ",
+ type_desc(body->type, body->subtype, body->parameter, NULL, 1),
+ sizebuf);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ iutf8ncpy((char *)tmp_20k_buf, (char *)(tmp_20k_buf+10000), 10000);
+ tmp_20k_buf[10000] = '\0';
+
+ t = &tmp_20k_buf[strlen(tmp_20k_buf)];
+
+#ifdef SMIME
+ /* if smime and not attempting print */
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && is_pkcs7_body(body) && type != 3){
+
+ sstrncpy(&t, "\015\012", SIZEOF_20KBUF-(t-tmp_20k_buf));
+
+ if(ps_global->smime && ps_global->smime->need_passphrase){
+ sstrncpy(&t,
+ "This part is a PKCS7 S/MIME enclosure. "
+ "You may be able to view it by entering the correct passphrase "
+ "with the \"Decrypt\" command.",
+ SIZEOF_20KBUF-(t-tmp_20k_buf));
+ }
+ else{
+ sstrncpy(&t,
+ "This part is a PKCS7 S/MIME enclosure. "
+ "Press \"^E\" for more information.",
+ SIZEOF_20KBUF-(t-tmp_20k_buf));
+ }
+
+ } else
+#endif
+
+ if(type){
+ sstrncpy(&t, "\015\012", SIZEOF_20KBUF-(t-tmp_20k_buf));
+ switch(type) {
+ case 1:
+ if(MIME_VCARD(body->type,body->subtype))
+ sstrncpy(&t,
+ /* TRANSLATORS: This is the description of an attachment that isn't being
+ shown but that can be viewed or saved. */
+ _("Not Shown. Use the \"V\" command to view or save to address book."), SIZEOF_20KBUF-(t-tmp_20k_buf));
+ else
+ sstrncpy(&t,
+ /* TRANSLATORS: This is the description of an attachment that isn't being
+ shown but that can be viewed or saved. */
+ _("Not Shown. Use the \"V\" command to view or save this part."), SIZEOF_20KBUF-(t-tmp_20k_buf));
+
+ break;
+
+ case 2:
+ sstrncpy(&t, "Cannot ", SIZEOF_20KBUF-(t-tmp_20k_buf));
+ if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
+ sstrncpy(&t, "dis", SIZEOF_20KBUF-(t-tmp_20k_buf));
+
+ sstrncpy(&t,
+ "play this part. Press \"V\" then \"S\" to save in a file.", SIZEOF_20KBUF-(t-tmp_20k_buf));
+ break;
+
+ case 3:
+ sstrncpy(&t, _("Unable to print this part."), SIZEOF_20KBUF-(t-tmp_20k_buf));
+ break;
+ }
+ }
+
+ if(!(t = format_editorial(tmp_20k_buf, width, flags, NULL, pc))){
+ if(!gf_puts(NEWLINE, pc))
+ t = "No space for description";
+ }
+
+ return(t);
+}
+
+
+/*----------------------------------------------------------------------
+ Can we display this type/subtype?
+
+ Args: type -- the MIME type to check
+ subtype -- the MIME subtype
+ params -- parameters
+ use_viewer -- tell caller he should run external viewer cmd to view
+
+ Result: Returns:
+
+ MCD_NONE if we can't display this type at all
+ MCD_INTERNAL if we can display it internally
+ MCD_EXTERNAL if it can be displayed via an external viewer
+
+ ----*/
+int
+mime_can_display(int type, char *subtype, BODY *body)
+{
+ return((mailcap_can_display(type, subtype, body, 0)
+ ? MCD_EXTERNAL
+ : (mailcap_can_display(type, subtype, body, 1)
+ ? (MCD_EXT_PROMPT | MCD_EXTERNAL) : MCD_NONE))
+ | ((type == TYPETEXT || type == TYPEMESSAGE
+ || MIME_VCARD(type,subtype))
+ ? MCD_INTERNAL : MCD_NONE));
+}
diff --git a/pith/mimedesc.h b/pith/mimedesc.h
new file mode 100644
index 00000000..39372b1d
--- /dev/null
+++ b/pith/mimedesc.h
@@ -0,0 +1,37 @@
+/*
+ * $Id: mimedesc.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MIMEDESC_INCLUDED
+#define PITH_MIMEDESC_INCLUDED
+
+
+#include "../pith/atttype.h"
+#include "../pith/filttype.h"
+
+
+/* exported prototypes */
+void describe_mime(BODY *, char *, int, int, int, int);
+void zero_atmts(ATTACH_S *);
+char *body_type_names(int);
+char *type_desc(int, char *, PARAMETER *, PARAMETER *, int);
+long fcc_size_guess(BODY *);
+char *part_desc(char *, BODY *, int, int, int, gf_io_t);
+char *parameter_val(PARAMETER *, char *);
+int mime_can_display(int, char *, BODY *);
+char *get_filename_parameter(char *, size_t, BODY *, char **);
+
+
+#endif /* PITH_MIMEDESC_INCLUDED */
diff --git a/pith/mimetype.c b/pith/mimetype.c
new file mode 100644
index 00000000..3da1362c
--- /dev/null
+++ b/pith/mimetype.c
@@ -0,0 +1,374 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mimetype.c 955 2008-03-06 23:52:36Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mimetype.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/mailcap.h"
+#include "../pith/util.h"
+
+/*
+ * We've decided not to implement the RFC1524 standard minimum path, because
+ * some of us think it is harder to debug a problem when you may be misled
+ * into looking at the wrong mailcap entry. Likewise for MIME.Types files.
+ */
+#if defined(DOS) || defined(OS2)
+#define MT_PATH_SEPARATOR ';'
+#define MT_USER_FILE "MIMETYPE"
+#define MT_STDPATH NULL
+#else /* !DOS */
+#define MT_PATH_SEPARATOR ':'
+#define MT_USER_FILE NULL
+#define MT_STDPATH \
+ ".mime.types:/etc/mime.types:/usr/local/lib/mime.types"
+#endif /* !DOS */
+
+#define LINE_BUF_SIZE 2000
+
+
+/*
+ * Types used to pass parameters and operator functions to the
+ * mime.types searching routines.
+ */
+#define MT_MAX_FILE_EXTENSION 3
+
+
+/*
+ * Internal prototypes
+ */
+int mt_browse_types_file(MT_OPERATORPROC, MT_MAP_T *, char *);
+int mt_srch_by_type(MT_MAP_T *, FILE *);
+
+
+
+/*
+ * Exported function that does the work of sniffing the mime.types
+ * files and filling in the body pointer if found. Returns 1 (TRUE) if
+ * extension found, and body pointer filled in, 0 (FALSE) otherwise.
+ */
+int
+set_mime_type_by_extension(struct mail_bodystruct *body, char *filename)
+{
+ MT_MAP_T e2b;
+
+ if(mt_get_file_ext(filename, &e2b.from.ext)
+ && mt_srch_mime_type(mt_srch_by_ext, &e2b)){
+ body->type = e2b.to.mime.type;
+ body->subtype = e2b.to.mime.subtype; /* NOTE: subtype was malloc'd */
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * Exported function that maps from mime types to file extensions.
+ */
+int
+set_mime_extension_by_type (char *ext, char *mtype)
+{
+ MT_MAP_T t2e;
+
+ t2e.from.mime_type = mtype;
+ t2e.to.ext = ext;
+ return (mt_srch_mime_type (mt_srch_by_type, &t2e));
+}
+
+
+
+
+/*
+ * Separate and return a pointer to the first character in the 'filename'
+ * character buffer that comes after the rightmost '.' character in the
+ * filename. (What I mean is a pointer to the filename - extension).
+ *
+ * Returns 1 if an extension is found, 0 otherwise.
+ */
+int
+mt_get_file_ext(char *filename, char **extension)
+{
+ dprint((5, "mt_get_file_ext : filename=\"%s\", ",
+ filename ? filename : "?"));
+
+ for(*extension = NULL; filename && *filename; filename++)
+ if(*filename == '.')
+ *extension = filename + 1;
+
+ dprint((5, "extension=\"%s\"\n",
+ (extension && *extension) ? *extension : "?"));
+
+ return(*extension ? 1 : 0);
+}
+
+
+/*
+ * Build a list of possible mime.type files. For each one that exists
+ * call the mt_operator function.
+ * Loop terminates when mt_operator returns non-zero.
+ */
+int
+mt_srch_mime_type(MT_OPERATORPROC mt_operator, MT_MAP_T *mt_map)
+{
+ char *s, *pathcopy, *path;
+ int rv = 0;
+
+ dprint((5, "- mt_srch_mime_type -\n"));
+
+ pathcopy = mc_conf_path(ps_global->VAR_MIMETYPE_PATH, getenv("MIMETYPES"),
+ MT_USER_FILE, MT_PATH_SEPARATOR, MT_STDPATH);
+
+ path = pathcopy; /* overloaded "path" */
+
+ dprint((7, "mime_types: path: %s\n", path ? path : "?"));
+ while(path){
+ if((s = strindex(path, MT_PATH_SEPARATOR)) != NULL)
+ *s++ = '\0';
+
+ if((rv = mt_browse_types_file(mt_operator, mt_map, path)) != 0)
+ break;
+
+ path = s;
+ }
+
+ if(pathcopy)
+ fs_give((void **)&pathcopy);
+
+ if(!rv && mime_os_specific_access()){
+ if(mt_operator == mt_srch_by_ext){
+ char buf[256];
+
+ buf[0] = '\0';
+ if(mime_get_os_mimetype_from_ext(mt_map->from.ext, buf, 256)){
+ if((s = strindex(buf, '/')) != NULL){
+ *s++ = '\0';
+ mt_map->to.mime.type = mt_translate_type(buf);
+ mt_map->to.mime.subtype = cpystr(s);
+ rv = 1;
+ }
+ }
+ }
+ else if(mt_operator == mt_srch_by_type){
+ if(mime_get_os_ext_from_mimetype(mt_map->from.mime_type,
+ mt_map->to.ext, 32)){
+ /* the 32 comes from var ext[] in display_attachment() */
+ if(*(s = mt_map->to.ext) == '.')
+ while((*s = *(s+1)) != '\0')
+ s++;
+
+ rv = 1;
+ }
+ }
+ else
+ panic("Unhandled mime type search");
+ }
+
+ /* if we still can not find the type, but it is a .docx (or alike) extension
+ set the type here. Do not use the grope function.
+ */
+ if(rv == 0){
+ rv = 1; /* assume success */
+ mt_map->to.mime.type = TYPEAPPLICATION;
+ if(!strucmp(mt_map->from.ext, "docx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT");
+ else if(!strucmp(mt_map->from.ext, "xslx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.SPREADSHEETML.SHEET");
+ else if(!strucmp(mt_map->from.ext, "xltx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.SPREADSHEETML.TEMPLATE");
+ else if(!strucmp(mt_map->from.ext, "potx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.TEMPLATE");
+ else if(!strucmp(mt_map->from.ext, "ppsx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.SLIDESHOW");
+ else if(!strucmp(mt_map->from.ext, "pptx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.PRESENTATION");
+ else if(!strucmp(mt_map->from.ext, "sldx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.SLIDE");
+ else if(!strucmp(mt_map->from.ext, "dotx"))
+ mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.TEMPLATE");
+ else if(!strucmp(mt_map->from.ext, "xlam"))
+ mt_map->to.mime.subtype = cpystr("VND.MS-EXCEL.ADDIN.MACROENABLED.12");
+ else if(!strucmp(mt_map->from.ext, "xslb"))
+ mt_map->to.mime.subtype = cpystr("VND.MS-EXCEL.SHEET.BINARY.MACROENABLED.12");
+ else rv = 0; /* else, failure */
+ }
+
+
+ return(rv);
+}
+
+
+/*
+ * Try to match a file extension against extensions found in the file
+ * ``filename'' if that file exists. return 1 if a match
+ * was found and 0 in all other cases.
+ */
+int
+mt_browse_types_file(MT_OPERATORPROC mt_operator, MT_MAP_T *mt_map, char *filename)
+{
+ int rv = 0;
+ FILE *file;
+
+ dprint((7, "mt_browse_types_file(%s)\n", filename ? filename : "?"));
+ if((file = our_fopen(filename, "rb")) != NULL){
+ rv = (*mt_operator)(mt_map, file);
+ fclose(file);
+ }
+ else{
+ dprint((1, "mt_browse: FAILED open(%s) : %s.\n",
+ filename ? filename : "?", error_description(errno)));
+ }
+
+ return(rv);
+}
+
+
+/*
+ * scan each line of the file. Treat each line as a mime type definition.
+ * The first word is a type/subtype specification. All following words
+ * are file extensions belonging to that type/subtype. Words are separated
+ * bij whitespace characters.
+ * If a file extension occurs more than once, then the first definition
+ * determines the file type and subtype.
+ */
+int
+mt_srch_by_ext(MT_MAP_T *e2b, FILE *file)
+{
+ char buffer[LINE_BUF_SIZE];
+
+ /* construct a loop reading the file line by line. Then check each
+ * line for a matching definition.
+ */
+ while(fgets(buffer,LINE_BUF_SIZE,file) != NULL){
+ char *typespec;
+ char *try_extension;
+
+ if(buffer[0] == '#')
+ continue; /* comment */
+
+ /* divide the input buffer into words separated by whitespace.
+ * The first words is the type and subtype. All following words
+ * are file extensions.
+ */
+ dprint((5, "traverse: buffer=\"%s\"\n", buffer));
+ typespec = strtok(buffer," \t"); /* extract type,subtype */
+ if(!typespec)
+ continue;
+
+ dprint((5, "typespec=\"%s\"\n", typespec ? typespec : "?"));
+ while((try_extension = strtok(NULL, " \t\n\r")) != NULL){
+ /* compare the extensions, and assign the type if a match
+ * is found.
+ */
+ dprint((5,"traverse: trying ext \"%s\"\n",try_extension));
+ if(strucmp(try_extension, e2b->from.ext) == 0){
+ /* split the 'type/subtype' specification */
+ char *type, *subtype = NULL;
+
+ type = strtok(typespec,"/");
+ if(type)
+ subtype = strtok(NULL,"/");
+
+ dprint((5, "traverse: type=%s, subtype=%s.\n",
+ type ? type : "<null>",
+ subtype ? subtype : "<null>"));
+ /* The type is encoded as a small integer. we have to
+ * translate the character string naming the type into
+ * the corresponding number.
+ */
+ e2b->to.mime.type = mt_translate_type(type);
+ e2b->to.mime.subtype = cpystr(subtype ? subtype : "x-unknown");
+ return 1; /* a match has been found */
+ }
+ }
+ }
+
+ dprint((5, "traverse: search failed.\n"));
+ return 0;
+}
+
+
+/*
+ * scan each line of the file. Treat each line as a mime type definition.
+ * Here we are looking for a matching type. When that is found return the
+ * first extension that is three chars or less.
+ */
+int
+mt_srch_by_type(MT_MAP_T *t2e, FILE *file)
+{
+ char buffer[LINE_BUF_SIZE];
+
+ /* construct a loop reading the file line by line. Then check each
+ * line for a matching definition.
+ */
+ while(fgets(buffer,LINE_BUF_SIZE,file) != NULL){
+ char *typespec;
+ char *try_extension;
+
+ if(buffer[0] == '#')
+ continue; /* comment */
+
+ /* divide the input buffer into words separated by whitespace.
+ * The first words is the type and subtype. All following words
+ * are file extensions.
+ */
+ dprint((5, "traverse: buffer=%s.\n", buffer));
+ typespec = strtok(buffer," \t"); /* extract type,subtype */
+ dprint((5, "typespec=%s.\n", typespec ? typespec : "?"));
+ if (strucmp (typespec, t2e->from.mime_type) == 0) {
+ while((try_extension = strtok(NULL, " \t\n\r")) != NULL){
+ if (strlen (try_extension) <= MT_MAX_FILE_EXTENSION) {
+ strncpy (t2e->to.ext, try_extension, 32);
+ /*
+ * not sure of the 32, so don't write to byte 32
+ * on purpose with
+ * t2e->to.ext[31] = '\0';
+ * in case that breaks something
+ */
+
+ return (1);
+ }
+ }
+ }
+ }
+
+ dprint((5, "traverse: search failed.\n"));
+ return 0;
+}
+
+
+/*
+ * Translate a character string representing a content type into a short
+ * integer number, according to the coding described in c-client/mail.h
+ * List of content types taken from rfc1521, September 1993.
+ */
+int
+mt_translate_type(char *type)
+{
+ int i;
+
+ for (i=0;(i<=TYPEMAX) && body_types[i] && strucmp(type,body_types[i]);i++)
+ ;
+
+ if (i > TYPEMAX)
+ i = TYPEOTHER;
+ else if (!body_types[i]) /* if empty slot, assign it to this type */
+ body_types[i] = cpystr (type);
+
+ return(i);
+}
diff --git a/pith/mimetype.h b/pith/mimetype.h
new file mode 100644
index 00000000..54981615
--- /dev/null
+++ b/pith/mimetype.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: mimetype.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MIMETYPE_INCLUDED
+#define PITH_MIMETYPE_INCLUDED
+
+
+/*
+ * Struct passed mime.types search functions
+ */
+typedef struct {
+ union {
+ char *ext;
+ char *mime_type;
+ } from;
+ union {
+ struct {
+ int type;
+ char *subtype;
+ } mime;
+ char *ext;
+ } to;
+} MT_MAP_T;
+
+
+typedef int (* MT_OPERATORPROC)(MT_MAP_T *, FILE *);
+
+
+/* exported protoypes */
+int set_mime_type_by_extension(BODY *, char *);
+int set_mime_extension_by_type(char *, char *);
+int mt_srch_by_ext(MT_MAP_T *, FILE *);
+int mt_get_file_ext(char *, char **);
+int mt_srch_mime_type(MT_OPERATORPROC, MT_MAP_T *);
+int mt_translate_type(char *);
+
+
+#endif /* PITH_MIMETYPE_INCLUDED */
diff --git a/pith/msgno.c b/pith/msgno.c
new file mode 100644
index 00000000..465a42e0
--- /dev/null
+++ b/pith/msgno.c
@@ -0,0 +1,941 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: msgno.c 854 2007-12-07 17:44:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/msgno.h"
+#include "../pith/flag.h"
+#include "../pith/mailindx.h"
+#include "../pith/pineelt.h"
+#include "../pith/icache.h"
+
+
+/* internal prototypes */
+void set_msg_score(MAILSTREAM *, long, long);
+
+
+/*
+ * * * * Message number management functions * * *
+ */
+
+
+/*----------------------------------------------------------------------
+ Initialize a message manipulation structure for the given total
+
+ Accepts: msgs - pointer to pointer to message manipulation struct
+ tot - number of messages to initialize with
+ ----*/
+void
+msgno_init(MSGNO_S **msgs, long int tot, SortOrder def_sort, int def_sort_rev)
+{
+ long slop = (tot + 1L) % 64;
+ size_t len;
+
+ if(!msgs)
+ return;
+
+ if(!(*msgs)){
+ (*msgs) = (MSGNO_S *)fs_get(sizeof(MSGNO_S));
+ memset((void *)(*msgs), 0, sizeof(MSGNO_S));
+ }
+
+ (*msgs)->sel_cur = 0L;
+ (*msgs)->sel_cnt = 1L;
+ (*msgs)->sel_size = 8L;
+ len = (size_t)(*msgs)->sel_size * sizeof(long);
+ if((*msgs)->select)
+ fs_resize((void **)&((*msgs)->select), len);
+ else
+ (*msgs)->select = (long *)fs_get(len);
+
+ (*msgs)->select[0] = (tot) ? 1L : 0L;
+
+ (*msgs)->sort_size = (tot + 1L) + (64 - slop);
+ len = (size_t)(*msgs)->sort_size * sizeof(long);
+ if((*msgs)->sort)
+ fs_resize((void **)&((*msgs)->sort), len);
+ else
+ (*msgs)->sort = (long *)fs_get(len);
+
+ memset((void *)(*msgs)->sort, 0, len);
+ for(slop = 1L ; slop <= tot; slop++) /* reusing "slop" */
+ (*msgs)->sort[slop] = slop;
+
+ /*
+ * If there is filtering happening, isort will become larger than sort.
+ * Sort is a list of raw message numbers in their sorted order. There
+ * are missing raw numbers because some of the messages are excluded
+ * (MN_EXLD) from the view. Isort has one entry for every raw message
+ * number, which maps to the corresponding msgno (the row in the sort
+ * array). Some of the entries in isort are not used because those
+ * messages are excluded, but the entry is still there because we want
+ * to map from rawno to message number and the row number is the rawno.
+ */
+ (*msgs)->isort_size = (*msgs)->sort_size;
+ if((*msgs)->isort)
+ fs_resize((void **)&((*msgs)->isort), len);
+ else
+ (*msgs)->isort = (long *)fs_get(len);
+
+ (*msgs)->max_msgno = tot;
+ (*msgs)->nmsgs = tot;
+
+ /* set the inverse array */
+ msgno_reset_isort(*msgs);
+
+ (*msgs)->sort_order = def_sort;
+ (*msgs)->reverse_sort = def_sort_rev;
+ (*msgs)->flagged_hid = 0L;
+ (*msgs)->flagged_exld = 0L;
+ (*msgs)->flagged_chid = 0L;
+ (*msgs)->flagged_chid2= 0L;
+ (*msgs)->flagged_coll = 0L;
+ (*msgs)->flagged_usor = 0L;
+ (*msgs)->flagged_tmp = 0L;
+ (*msgs)->flagged_stmp = 0L;
+
+ /*
+ * This one is the total number of messages which are flagged
+ * hid OR chid. It isn't the sum of those two because a
+ * message may be flagged both at the same time.
+ */
+ (*msgs)->flagged_invisible = 0L;
+
+ /*
+ * And this keeps track of visible threads in the THRD_INDX. This is
+ * weird because a thread is visible if any of its messages are
+ * not hidden, including those that are CHID hidden. You can't just
+ * count up all the messages that are hid or chid because you would
+ * miss a thread that has its top-level message hidden but some chid
+ * message not hidden.
+ */
+ (*msgs)->visible_threads = -1L;
+}
+
+
+/*
+ * Isort makes mn_raw2m fast. Alternatively, we could look through
+ * the sort array to do mn_raw2m.
+ */
+void
+msgno_reset_isort(MSGNO_S *msgs)
+{
+ long i;
+
+ if(msgs){
+ /*
+ * Zero isort so raw messages numbers which don't appear in the
+ * sort array show up as undefined.
+ */
+ memset((void *) msgs->isort, 0,
+ (size_t) msgs->isort_size * sizeof(long));
+
+ /* fill in all the defined entries */
+ for(i = 1L; i <= mn_get_total(msgs); i++)
+ msgs->isort[msgs->sort[i]] = i;
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Release resources of a message manipulation structure
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - number to test
+ Returns: with specified structure and its members free'd
+ ----*/
+void
+msgno_give(MSGNO_S **msgs)
+{
+ if(msgs && *msgs){
+ if((*msgs)->sort)
+ fs_give((void **) &((*msgs)->sort));
+
+ if((*msgs)->isort)
+ fs_give((void **) &((*msgs)->isort));
+
+ if((*msgs)->select)
+ fs_give((void **) &((*msgs)->select));
+
+ fs_give((void **) msgs);
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Release resources of a message part exception list
+
+ Accepts: parts -- list of parts to free
+ Returns: with specified structure and its members free'd
+ ----*/
+void
+msgno_free_exceptions(PARTEX_S **parts)
+{
+ if(parts && *parts){
+ if((*parts)->next)
+ msgno_free_exceptions(&(*parts)->next);
+
+ fs_give((void **) &(*parts)->partno);
+ fs_give((void **) parts);
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Increment the current message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ ----*/
+void
+msgno_inc(MAILSTREAM *stream, MSGNO_S *msgs, int flags)
+{
+ long i;
+
+ if(!msgs || mn_get_total(msgs) < 1L)
+ return;
+
+ for(i = msgs->select[msgs->sel_cur] + 1; i <= mn_get_total(msgs); i++){
+ if(!msgline_hidden(stream, msgs, i, flags)){
+ (msgs)->select[((msgs)->sel_cur)] = i;
+ break;
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Decrement the current message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ ----*/
+void
+msgno_dec(MAILSTREAM *stream, MSGNO_S *msgs, int flags)
+{
+ long i;
+
+ if(!msgs || mn_get_total(msgs) < 1L)
+ return;
+
+ for(i = (msgs)->select[((msgs)->sel_cur)] - 1L; i >= 1L; i--){
+ if(!msgline_hidden(stream, msgs, i, flags)){
+ (msgs)->select[((msgs)->sel_cur)] = i;
+ break;
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Got thru the message mapping table, and remove messages with DELETED flag
+
+ Accepts: stream -- mail stream to removed message references from
+ msgs -- pointer to message manipulation struct
+ f -- flags to use a purge criteria
+ ----*/
+void
+msgno_exclude_deleted(MAILSTREAM *stream, MSGNO_S *msgs)
+{
+ long i, rawno;
+ MESSAGECACHE *mc;
+ int need_isort_reset = 0;
+
+ if(!msgs || msgs->max_msgno < 1L)
+ return;
+
+ /*
+ * With 3.91 we're using a new strategy for finding and operating
+ * on all the messages with deleted status. The idea is to do a
+ * mail_search for deleted messages so the elt's "searched" bit gets
+ * set, and then to scan the elt's for them and set our local bit
+ * to indicate they're excluded...
+ */
+ (void) count_flagged(stream, F_DEL);
+
+ /*
+ * Start with the end of the folder and work backwards so that
+ * msgno_exclude doesn't have to shift the entire array each time when
+ * there are lots of deleteds. In fact, if everything is deleted (like
+ * might be the case in a huge newsgroup) then it never has to shift
+ * anything. It is always at the end of the array just eliminating the
+ * last one instead. So instead of an n**2 operation, it is n.
+ */
+ for(i = msgs->max_msgno; i >= 1L; i--)
+ if((rawno = mn_m2raw(msgs, i)) > 0L && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))
+ && ((mc->valid && mc->deleted) || (!mc->valid && mc->searched))){
+ msgno_exclude(stream, msgs, i, 0);
+ need_isort_reset++;
+ }
+
+ if(need_isort_reset)
+ msgno_reset_isort(msgs);
+
+ /*
+ * If we excluded away a zoomed display, unhide everything...
+ */
+ if(msgs->max_msgno > 0L && any_lflagged(msgs, MN_HIDE) >= msgs->max_msgno)
+ for(i = 1L; i <= msgs->max_msgno; i++)
+ set_lflag(stream, msgs, i, MN_HIDE, 0);
+}
+
+
+
+void
+msgno_exclude(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int reset_isort)
+{
+ long i;
+
+ /*--- clear all flags to keep our counts consistent ---*/
+ set_lflag(stream, msgmap, msgno, MN_HIDE | MN_CHID | MN_CHID2 | MN_SLCT, 0);
+ set_lflag(stream, msgmap, msgno, MN_EXLD, 1); /* mark excluded */
+
+ /* erase knowledge in sort array (shift array down) */
+ for(i = msgno + 1L; i <= msgmap->max_msgno; i++)
+ msgmap->sort[i-1L] = msgmap->sort[i];
+
+ msgmap->max_msgno = MAX(0L, msgmap->max_msgno - 1L);
+ if(reset_isort)
+ msgno_reset_isort(msgmap);
+
+ msgno_flush_selected(msgmap, msgno);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Accepts: stream -- mail stream to removed message references from
+ msgs -- pointer to message manipulation struct
+ flags
+ MI_REFILTERING -- do includes appropriate for refiltering
+ MI_STATECHGONLY -- when refiltering, maybe only re-include
+ messages which have had state changes
+ since they were originally filtered
+ Returns 1 if any new messages are included (indicating that we need
+ to re-sort)
+ 0 if no new messages are included
+ ----*/
+int
+msgno_include(MAILSTREAM *stream, MSGNO_S *msgs, int flags)
+{
+ long i, slop, old_total, old_size;
+ int exbits, ret = 0;
+ size_t len;
+ MESSAGECACHE *mc;
+
+ if(!msgs)
+ return(ret);
+
+ for(i = 1L; i <= stream->nmsgs; i++){
+ if(!msgno_exceptions(stream, i, "0", &exbits, FALSE))
+ exbits = 0;
+
+ if((((flags & MI_REFILTERING) && (exbits & MSG_EX_FILTERED)
+ && !(exbits & MSG_EX_FILED)
+ && (!(flags & MI_STATECHGONLY) || (exbits & MSG_EX_STATECHG)))
+ || (!(flags & MI_REFILTERING) && !(exbits & MSG_EX_FILTERED)))
+ && get_lflag(stream, NULL, i, MN_EXLD)){
+ old_total = msgs->max_msgno;
+ old_size = msgs->sort_size;
+ slop = (msgs->max_msgno + 1L) % 64;
+ msgs->sort_size = (msgs->max_msgno + 1L) + (64 - slop);
+ len = (size_t) msgs->sort_size * sizeof(long);
+ if(msgs->sort){
+ if(old_size != msgs->sort_size)
+ fs_resize((void **)&(msgs->sort), len);
+ }
+ else
+ msgs->sort = (long *)fs_get(len);
+
+ ret = 1;
+ msgs->sort[++msgs->max_msgno] = i;
+ msgs->isort[i] = msgs->max_msgno;
+ set_lflag(stream, msgs, msgs->max_msgno, MN_EXLD, 0);
+ if(flags & MI_REFILTERING){
+ exbits &= ~(MSG_EX_FILTERED | MSG_EX_TESTED);
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+
+ if(old_total <= 0L){ /* if no previous messages, */
+ if(!msgs->select){ /* select the new message */
+ msgs->sel_size = 8L;
+ len = (size_t)msgs->sel_size * sizeof(long);
+ msgs->select = (long *)fs_get(len);
+ }
+
+ msgs->sel_cnt = 1L;
+ msgs->sel_cur = 0L;
+ msgs->select[0] = 1L;
+ }
+ }
+ else if((flags & MI_REFILTERING)
+ && (exbits & (MSG_EX_FILTERED | MSG_EX_TESTED))
+ && !(exbits & MSG_EX_FILED)
+ && (!(exbits & MSG_EX_MANUNDEL)
+ || ((mc = mail_elt(stream, i)) && mc->deleted))
+ && (!(flags & MI_STATECHGONLY) || (exbits & MSG_EX_STATECHG))){
+ /*
+ * We get here if the message was filtered by a filter that
+ * just changes status bits (it wasn't excluded), and now also
+ * if the message was merely tested for filtering. It has also
+ * not been manually undeleted. If it was manually undeleted, we
+ * don't want to reprocess the filter, undoing the user's
+ * manual undeleting. Of course, a new pine will re check this
+ * message anyway, so the user had better be using this
+ * manual undeleting only to temporarily save him or herself
+ * from an expunge before Saving or printing or something.
+ * Also, we want to still try filtering if the message has at
+ * all been marked deleted, even if the there was any manual
+ * undeleting, since this directly precedes an expunge, we want
+ * to make sure the filter does the right thing before getting
+ * rid of the message forever.
+ */
+ exbits &= ~(MSG_EX_FILTERED | MSG_EX_TESTED);
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+ }
+
+ return(ret);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Add the given number of raw message numbers to the end of the
+ current list...
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - number to add
+ Returns: with fixed up msgno struct
+
+ Only have to adjust the sort array, as since new mail can't cause
+ selection!
+ ----*/
+void
+msgno_add_raw(MSGNO_S *msgs, long int n)
+{
+ long slop, islop, old_total, old_size, old_isize;
+ size_t len, ilen;
+
+ if(!msgs || n <= 0L)
+ return;
+
+ old_total = msgs->max_msgno;
+ old_size = msgs->sort_size;
+ old_isize = msgs->isort_size;
+ slop = (msgs->max_msgno + n + 1L) % 64;
+ islop = (msgs->nmsgs + n + 1L) % 64;
+ msgs->sort_size = (msgs->max_msgno + n + 1L) + (64 - slop);
+ msgs->isort_size = (msgs->nmsgs + n + 1L) + (64 - islop);
+ len = (size_t) msgs->sort_size * sizeof(long);
+ ilen = (size_t) msgs->isort_size * sizeof(long);
+ if(msgs->sort){
+ if(old_size != msgs->sort_size)
+ fs_resize((void **) &(msgs->sort), len);
+ }
+ else
+ msgs->sort = (long *) fs_get(len);
+
+ if(msgs->isort){
+ if(old_isize != msgs->isort_size)
+ fs_resize((void **) &(msgs->isort), ilen);
+ }
+ else
+ msgs->isort = (long *) fs_get(ilen);
+
+ while(n-- > 0){
+ msgs->sort[++msgs->max_msgno] = ++msgs->nmsgs;
+ msgs->isort[msgs->nmsgs] = msgs->max_msgno;
+ }
+
+ if(old_total <= 0L){ /* if no previous messages, */
+ if(!msgs->select){ /* select the new message */
+ msgs->sel_size = 8L;
+ len = (size_t) msgs->sel_size * sizeof(long);
+ msgs->select = (long *) fs_get(len);
+ }
+
+ msgs->sel_cnt = 1L;
+ msgs->sel_cur = 0L;
+ msgs->select[0] = 1L;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Remove all knowledge of the given raw message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ rawno - number to remove
+ Returns: with fixed up msgno struct
+
+ After removing *all* references, adjust the sort array and
+ various pointers accordingly...
+ ----*/
+void
+msgno_flush_raw(MSGNO_S *msgs, long int rawno)
+{
+ long i, old_sorted = 0L;
+ int shift = 0;
+
+ if(!msgs)
+ return;
+
+ /* blast rawno from sort array */
+ for(i = 1L; i <= msgs->max_msgno; i++){
+ if(msgs->sort[i] == rawno){
+ old_sorted = i;
+ shift++;
+ }
+
+ if(shift && i < msgs->max_msgno)
+ msgs->sort[i] = msgs->sort[i + 1L];
+
+ if(msgs->sort[i] > rawno)
+ msgs->sort[i] -= 1L;
+ }
+
+ /*---- now, fixup counts and select array ----*/
+ if(--msgs->nmsgs < 0)
+ msgs->nmsgs = 0L;
+
+ if(old_sorted){
+ if(--msgs->max_msgno < 0)
+ msgs->max_msgno = 0L;
+
+ msgno_flush_selected(msgs, old_sorted);
+ }
+
+ msgno_reset_isort(msgs);
+
+ { char b[100];
+ snprintf(b, sizeof(b),
+ "isort validity: end of msgno_flush_raw: rawno=%ld\n", rawno);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Remove all knowledge of the given selected message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - number to remove
+ Returns: with fixed up selec members in msgno struct
+
+ Remove reference and fix up selected message numbers beyond
+ the specified number
+ ----*/
+void
+msgno_flush_selected(MSGNO_S *msgs, long int n)
+{
+ long i;
+ int shift = 0;
+
+ for(i = 0L; i < msgs->sel_cnt; i++){
+ if(!shift && (msgs->select[i] == n))
+ shift++;
+
+ if(shift && i + 1L < msgs->sel_cnt)
+ msgs->select[i] = msgs->select[i + 1L];
+
+ if(n < msgs->select[i] || msgs->select[i] > msgs->max_msgno)
+ msgs->select[i] -= 1L;
+ }
+
+ if(shift && msgs->sel_cnt > 1L)
+ msgs->sel_cnt -= 1L;
+}
+
+
+void
+msgno_set_sort(MSGNO_S *msgs, SortOrder sort)
+{
+ if(msgs){
+ if(sort == SortScore)
+ scores_are_used(SCOREUSE_INVALID);
+
+ msgs->sort_order = sort;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Test to see if the given message number is in the selected message
+ list...
+
+ Accepts: msgs - pointer to message manipulation struct
+ n - number to test
+ Returns: true if n is in selected array, false otherwise
+
+ ----*/
+int
+msgno_in_select(MSGNO_S *msgs, long int n)
+{
+ long i;
+
+ if(msgs)
+ for(i = 0L; i < msgs->sel_cnt; i++)
+ if(msgs->select[i] == n)
+ return(1);
+
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return our index number for the given raw message number
+
+ Accepts: msgs - pointer to message manipulation struct
+ msgno - number that's important
+ part
+ Returns: our index number of given raw message
+
+ ----*/
+int
+msgno_exceptions(MAILSTREAM *stream, long int rawno, char *part, int *bits, int set)
+{
+ PINELT_S **peltp;
+ PARTEX_S **partp;
+ MESSAGECACHE *mc;
+
+ if(!stream || rawno < 1L || rawno > stream->nmsgs)
+ return FALSE;
+
+ /*
+ * Get pointer to exceptional part list, and scan down it
+ * for the requested part...
+ */
+ if((mc = mail_elt(stream, rawno)) && (*(peltp = (PINELT_S **) &mc->sparep)))
+ for(partp = &(*peltp)->exceptions; *partp; partp = &(*partp)->next){
+ if(part){
+ if(!strcmp(part, (*partp)->partno)){
+ if(bits){
+ if(set)
+ (*partp)->handling = *bits;
+ else
+ *bits = (*partp)->handling;
+ }
+
+ return(TRUE); /* bingo! */
+ }
+ }
+ else if(bits){
+ /*
+ * The caller provided flags, but no part.
+ * We are looking to see if the bits are set in any of the
+ * parts. This doesn't count parts with non-digit partno's (like
+ * scores) because those are used differently.
+ * any of the flags...
+ */
+ if((*partp)->partno && *(*partp)->partno &&
+ isdigit((unsigned char) *(*partp)->partno) &&
+ (*bits & (*partp)->handling) == *bits)
+ return(TRUE);
+ }
+ else
+ /*
+ * The caller didn't specify a part, so
+ * they must just be interested in whether
+ * the msg had any exceptions at all...
+ */
+ return(TRUE);
+ }
+
+ if(set && part){
+ if(!*peltp){
+ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
+ memset(*peltp, 0, sizeof(PINELT_S));
+ partp = &(*peltp)->exceptions;
+ }
+
+ (*partp) = (PARTEX_S *) fs_get(sizeof(PARTEX_S));
+ (*partp)->partno = cpystr(part);
+ (*partp)->next = NULL;
+ (*partp)->handling = *bits;
+ return(TRUE);
+ }
+
+ if(bits) /* init bits */
+ *bits = 0;
+
+ return(FALSE);
+}
+
+
+/*
+ * Checks whether any parts of any of the messages in msgmap are marked
+ * for deletion.
+ */
+int
+msgno_any_deletedparts(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long n, rawno;
+ PINELT_S *pelt;
+ PARTEX_S **partp;
+ MESSAGECACHE *mc;
+
+ for(n = mn_first_cur(msgmap); n > 0L; n = mn_next_cur(msgmap))
+ if((rawno = mn_m2raw(msgmap, n)) > 0L
+ && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))
+ && (pelt = (PINELT_S *) mc->sparep))
+ for(partp = &pelt->exceptions; *partp; partp = &(*partp)->next)
+ if(((*partp)->handling & MSG_EX_DELETE)
+ && (*partp)->partno
+ && *(*partp)->partno != '0'
+ && isdigit((unsigned char) *(*partp)->partno))
+ return(1);
+
+ return(0);
+}
+
+
+int
+msgno_part_deleted(MAILSTREAM *stream, long int rawno, char *part)
+{
+ char *p;
+ int expbits;
+
+ /*
+ * Is this attachment or any of it's parents in the
+ * MIME structure marked for deletion?
+ */
+ for(p = part; p && *p; p = strindex(++p, '.')){
+ if(*p == '.')
+ *p = '\0';
+
+ (void) msgno_exceptions(stream, rawno, part, &expbits, FALSE);
+ if(!*p)
+ *p = '.';
+
+ if(expbits & MSG_EX_DELETE)
+ return(TRUE);
+ }
+
+ /* Finally, check if the whole message body's deleted */
+ return(msgno_exceptions(stream, rawno, "", &expbits, FALSE)
+ ? (expbits & MSG_EX_DELETE) : FALSE);
+}
+
+
+
+/*
+ * Set the score for a message to score, which can be anything including
+ * SCORE_UNDEF.
+ */
+void
+set_msg_score(MAILSTREAM *stream, long int rawmsgno, long int score)
+{
+ int intscore;
+
+ /* scores are between SCORE_MIN and SCORE_MAX, so ok */
+ intscore = (int) score;
+
+ (void) msgno_exceptions(stream, rawmsgno, "S", &intscore, TRUE);
+}
+
+
+/*
+ * Returns the score for a message. If that score is undefined the value
+ * returned will be SCORE_UNDEF, so the caller has to be prepared for that.
+ * The caller should calculate the undefined scores before calling this.
+ */
+long
+get_msg_score(MAILSTREAM *stream, long int rawmsgno)
+{
+ int s;
+ long score;
+
+ if(msgno_exceptions(stream, rawmsgno, "S", &s, FALSE))
+ score = (long) s;
+ else
+ score = SCORE_UNDEF;
+
+ return(score);
+}
+
+
+void
+clear_msg_score(MAILSTREAM *stream, long int rawmsgno)
+{
+ if(!stream)
+ return;
+
+ set_msg_score(stream, rawmsgno, SCORE_UNDEF);
+}
+
+
+/*
+ * Set all the score values to undefined.
+ */
+void
+clear_folder_scores(MAILSTREAM *stream)
+{
+ long n;
+
+ if(!stream)
+ return;
+
+ for(n = 1L; n <= stream->nmsgs; n++)
+ clear_msg_score(stream, n);
+}
+
+
+/*
+ * Calculates all of the scores for the searchset and stores them in the
+ * mail elts. Careful, this function uses patterns so if the caller is using
+ * patterns then the caller will probably have to reset the pattern functions.
+ * That is, will have to call first_pattern again with the correct type.
+ *
+ * Args: stream
+ * searchset -- calculate scores for this set of messages
+ * no_fetch -- we're in a callback from c-client, don't call c-client
+ *
+ * Returns 1 -- ok
+ * 0 -- error, because of no_fetch
+ */
+int
+calculate_some_scores(MAILSTREAM *stream, SEARCHSET *searchset, int no_fetch)
+{
+ PAT_S *pat = NULL;
+ PAT_STATE pstate;
+ char *savebits;
+ long newscore, addtoscore, score;
+ int error = 0;
+ long rflags = ROLE_SCORE;
+ long n, i;
+ SEARCHSET *s;
+ MESSAGECACHE *mc;
+ HEADER_TOK_S *hdrtok;
+
+ dprint((7, "calculate_some_scores\n"));
+
+ if(nonempty_patterns(rflags, &pstate)){
+
+ /* calculate scores */
+ if(searchset){
+
+ /* this calls match_pattern which messes up searched bits */
+ savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char));
+ for(i = 1L; i <= stream->nmsgs; i++)
+ savebits[i] = (mc = mail_elt(stream, i)) ? mc->searched : 0;
+
+ /*
+ * First set all the scores in the searchset to zero so that they
+ * will no longer be undefined.
+ */
+ score = 0L;
+ for(s = searchset; s; s = s->next)
+ for(n = s->first; n <= s->last; n++)
+ set_msg_score(stream, n, score);
+
+ for(pat = first_pattern(&pstate);
+ !error && pat;
+ pat = next_pattern(&pstate)){
+
+ newscore = pat->action->scoreval;
+ hdrtok = pat->action->scorevalhdrtok;
+
+ /*
+ * This no_fetch probably isn't necessary since
+ * we will actually have fetched this with
+ * the envelope. Just making sure.
+ */
+ if(hdrtok && no_fetch){
+ error++;
+ break;
+ }
+
+ switch(match_pattern(pat->patgrp, stream, searchset, NULL, NULL,
+ (no_fetch ? MP_IN_CCLIENT_CB : 0)
+ | (SE_NOSERVER|SE_NOPREFETCH))){
+ case 1:
+ if(!pat->action || pat->action->bogus)
+ break;
+
+ for(s = searchset; s; s = s->next)
+ for(n = s->first; n <= s->last; n++)
+ if(n > 0L && stream && n <= stream->nmsgs
+ && (mc = mail_elt(stream, n)) && mc->searched){
+ if((score = get_msg_score(stream,n)) == SCORE_UNDEF)
+ score = 0L;
+
+ if(hdrtok)
+ addtoscore = scorevalfrommsg(stream, n, hdrtok, no_fetch);
+ else
+ addtoscore = newscore;
+
+ score += addtoscore;
+ set_msg_score(stream, n, score);
+ }
+
+ break;
+
+ case 0:
+ break;
+
+ case -1:
+ error++;
+ break;
+ }
+ }
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = savebits[i];
+
+ fs_give((void **)&savebits);
+
+ if(error){
+ /*
+ * Revert to undefined scores.
+ */
+ score = SCORE_UNDEF;
+ for(s = searchset; s; s = s->next)
+ for(n = s->first; n <= s->last; n++)
+ set_msg_score(stream, n, score);
+ }
+ }
+ }
+
+ return(error ? 0 : 1);
+}
+
+
+void
+free_pine_elt(void **sparep)
+{
+ PINELT_S **peltp;
+
+ peltp = (PINELT_S **) sparep;
+
+ if(peltp && *peltp){
+ msgno_free_exceptions(&(*peltp)->exceptions);
+ if((*peltp)->pthrd)
+ fs_give((void **) &(*peltp)->pthrd);
+
+ if((*peltp)->ice)
+ free_ice(&(*peltp)->ice);
+
+ fs_give((void **) peltp);
+ }
+}
diff --git a/pith/msgno.h b/pith/msgno.h
new file mode 100644
index 00000000..00b669c1
--- /dev/null
+++ b/pith/msgno.h
@@ -0,0 +1,207 @@
+/*
+ * $Id: msgno.h 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_MSGNO_INCLUDED
+#define PITH_MSGNO_INCLUDED
+
+
+#include "../pith/sorttype.h"
+
+
+/*
+ * Macros to support anything you'd ever want to do with a message
+ * number...
+ */
+#define mn_init(P, m) msgno_init((P), (m), \
+ ps_global->def_sort, ps_global->def_sort_rev)
+
+#define mn_get_cur(p) (((p) && (p)->select) \
+ ? (p)->select[(p)->sel_cur] : -1)
+
+#define mn_set_cur(p, m) do{ \
+ if(p){ \
+ (p)->select[(p)->sel_cur] = (m); \
+ } \
+ }while(0)
+
+#define mn_inc_cur(s, p, f) msgno_inc(s, p, f)
+
+#define mn_dec_cur(s, p, f) msgno_dec(s, p, f)
+
+#define mn_add_cur(p, m) do{ \
+ if(p){ \
+ if((p)->sel_cnt+1L > (p)->sel_size){ \
+ (p)->sel_size += 10L; \
+ fs_resize((void **)&((p)->select), \
+ (size_t)(p)->sel_size \
+ * sizeof(long)); \
+ } \
+ (p)->select[((p)->sel_cnt)++] = (m); \
+ } \
+ }while(0)
+
+#define mn_total_cur(p) ((p) ? (p)->sel_cnt : 0L)
+
+#define mn_first_cur(p) (((p) && (p)->sel_cnt > 0L) \
+ ? (p)->select[(p)->sel_cur = 0] : 0L)
+
+#define mn_next_cur(p) (((p) && ((p)->sel_cur + 1) < (p)->sel_cnt) \
+ ? (p)->select[++((p)->sel_cur)] : -1L)
+
+#define mn_is_cur(p, m) msgno_in_select((p), (m))
+
+#define mn_reset_cur(p, m) do{ \
+ if(p){ \
+ (p)->sel_cur = 0L; \
+ (p)->sel_cnt = 1L; \
+ (p)->sel_size = 8L; \
+ fs_resize((void **)&((p)->select), \
+ (size_t)(p)->sel_size * sizeof(long));\
+ (p)->select[0] = (m); \
+ } \
+ }while(0)
+
+#define mn_m2raw(p, m) (((p) && (p)->sort && (m) > 0 \
+ && (m) <= mn_get_total(p)) \
+ ? (p)->sort[m] : 0L)
+
+#define mn_raw2m(p, m) (((p) && (p)->isort && (m) > 0 \
+ && (m) <= mn_get_nmsgs(p)) \
+ ? (p)->isort[m] : 0L)
+
+#define mn_get_total(p) ((p) ? (p)->max_msgno : 0L)
+
+#define mn_set_total(p, m) do{ if(p) (p)->max_msgno = (m); }while(0)
+
+#define mn_get_nmsgs(p) ((p) ? (p)->nmsgs : 0L)
+
+#define mn_set_nmsgs(p, m) do{ if(p) (p)->nmsgs = (m); }while(0)
+
+#define mn_add_raw(p, m) msgno_add_raw((p), (m))
+
+#define mn_flush_raw(p, m) msgno_flush_raw((p), (m))
+
+#define mn_get_sort(p) ((p) ? (p)->sort_order : SortArrival)
+
+#define mn_set_sort(p, t) msgno_set_sort((p), (t))
+
+#define mn_get_revsort(p) ((p) ? (p)->reverse_sort : 0)
+
+#define mn_set_revsort(p, t) do{ \
+ if(p) \
+ (p)->reverse_sort = (t); \
+ }while(0)
+
+#define mn_get_mansort(p) ((p) ? (p)->manual_sort : 0)
+
+#define mn_set_mansort(p, t) do{ \
+ if(p) \
+ (p)->manual_sort = (t); \
+ }while(0)
+
+#define mn_give(P) msgno_give(P)
+
+
+/*
+ * This is *the* struct that keeps track of the pine message number to
+ * raw c-client sequence number mappings. The mapping is necessary
+ * because pine may re-sort or even hide (exclude) c-client numbers
+ * from the displayed list of messages. See mailindx.c:msgno_* and
+ * the mn_* macros above for how this things gets used. See
+ * mailcmd.c:pseudo_selected for an explanation of the funny business
+ * going on with the "hilited" field...
+ */
+typedef struct msg_nos {
+ long *select, /* selected message array */
+ sel_cur, /* current interesting msg */
+ sel_cnt, /* its size */
+ sel_size, /* its size */
+ *sort, /* sorted array of msgno's */
+ sort_size, /* its size */
+ *isort, /* inverse of sort array */
+ isort_size, /* its size */
+ max_msgno, /* total messages in table */
+ nmsgs, /* total msgs in folder */
+ hilited, /* holder for "current" msg*/
+ top, /* message at top of screen*/
+ max_thrdno,
+ top_after_thrd; /* top after thrd view */
+ SortOrder sort_order; /* list's current sort */
+ unsigned reverse_sort:1; /* whether that's reversed */
+ unsigned manual_sort:1; /* sorted with $ command */
+ long flagged_hid, /* hidden count */
+ flagged_exld, /* excluded count */
+ flagged_coll, /* collapsed count */
+ flagged_chid, /* collapsed-hidden count */
+ flagged_chid2, /* */
+ flagged_usor, /* new unsorted mail */
+ flagged_tmp, /* tmp flagged count */
+ flagged_stmp, /* stmp flagged count */
+ flagged_invisible, /* this one's different */
+ flagged_srch, /* search result/not slctd */
+ visible_threads; /* so is this one */
+} MSGNO_S;
+
+
+#define MSG_EX_DELETE 0x0001 /* part is deleted */
+#define MSG_EX_RECENT 0x0002
+#define MSG_EX_TESTED 0x0004 /* filtering has been run on this msg */
+#define MSG_EX_FILTERED 0x0008 /* msg has actually been filtered away*/
+#define MSG_EX_FILED 0x0010 /* msg has been filed */
+#define MSG_EX_FILTONCE 0x0020
+#define MSG_EX_FILEONCE 0x0040 /* These last two mean that the
+ message has been filtered or filed
+ already but the filter rule was
+ non-terminating so it is still
+ possible it will get filtered
+ again. When we're done, we flip
+ these two to EX_FILTERED and
+ EX_FILED, the permanent versions. */
+#define MSG_EX_PEND_EXLD 0x0080 /* pending exclusion */
+#define MSG_EX_MANUNDEL 0x0100 /* has been manually undeleted */
+#define MSG_EX_STATECHG 0x0200 /* state change since filtering */
+
+/* msgno_include flags */
+#define MI_NONE 0x00
+#define MI_REFILTERING 0x01
+#define MI_STATECHGONLY 0x02
+#define MI_CLOSING 0x04
+
+
+/* exported protoypes */
+void msgno_init(MSGNO_S **, long, SortOrder, int);
+void msgno_reset_isort(MSGNO_S *);
+void msgno_give(MSGNO_S **);
+void msgno_inc(MAILSTREAM *, MSGNO_S *, int);
+void msgno_dec(MAILSTREAM *, MSGNO_S *, int);
+void msgno_exclude_deleted(MAILSTREAM *, MSGNO_S *);
+void msgno_exclude(MAILSTREAM *, MSGNO_S *, long, int);
+int msgno_include(MAILSTREAM *, MSGNO_S *, int);
+void msgno_add_raw(MSGNO_S *, long);
+void msgno_flush_raw(MSGNO_S *, long);
+void msgno_flush_selected(MSGNO_S *, long);
+void msgno_set_sort(MSGNO_S *, SortOrder);
+int msgno_in_select(MSGNO_S *, long);
+int msgno_exceptions(MAILSTREAM *, long, char *, int *, int);
+int msgno_any_deletedparts(MAILSTREAM *, MSGNO_S *);
+int msgno_part_deleted(MAILSTREAM *, long, char *);
+long get_msg_score(MAILSTREAM *, long);
+void clear_msg_score(MAILSTREAM *, long);
+void clear_folder_scores(MAILSTREAM *);
+int calculate_some_scores(MAILSTREAM *, SEARCHSET *, int);
+void free_pine_elt(void **);
+
+
+#endif /* PITH_MSGNO_INCLUDED */
diff --git a/pith/newmail.c b/pith/newmail.c
new file mode 100644
index 00000000..26d9a986
--- /dev/null
+++ b/pith/newmail.c
@@ -0,0 +1,948 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: newmail.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/newmail.h"
+#include "../pith/conf.h"
+#include "../pith/flag.h"
+#include "../pith/mailindx.h"
+#include "../pith/msgno.h"
+#include "../pith/bldaddr.h"
+#include "../pith/stream.h"
+#include "../pith/sort.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+#include "../pith/thread.h"
+#include "../pith/options.h"
+#include "../pith/folder.h"
+#include "../pith/ablookup.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Hooks for various stuff
+ */
+void (*pith_opt_newmail_announce)(MAILSTREAM *, long, long);
+void (*pith_opt_newmail_check_cue)(int);
+void (*pith_opt_checkpoint_cue)(int);
+void (*pith_opt_icon_text)(char *, int);
+
+
+void fixup_flags(MAILSTREAM *, MSGNO_S *);
+int check_point(MAILSTREAM *, CheckPointTime, int);
+
+
+/*----------------------------------------------------------------------
+ new_mail() - check for new mail in the inbox
+
+ Input: force -- flag indicating we should check for new mail no matter
+ time_for_check_point -- 0: GoodTime, 1: BadTime, 2: VeryBadTime
+ flags -- whether to q a new mail status message or defer the sort
+
+ Result: returns -1 if there was no new mail. Otherwise returns the
+ sorted message number of the smallest message number that
+ was changed. That is the screen needs to be repainted from
+ that message down.
+
+ Limit frequency of checks because checks use some resources. That is
+ they generate an IMAP packet or require locking the local mailbox.
+ (Acutally the lock isn't required, a stat will do, but the current
+ c-client mail code locks before it stats.)
+
+ Returns >= 0 only if there is a change in the given mail stream. Otherwise
+ this returns -1. On return the message counts in the pine
+ structure are updated to reflect the current number of messages including
+ any new mail and any expunging.
+
+ --- */
+long
+new_mail(int force_arg, CheckPointTime time_for_check_point, int flags)
+{
+ static time_t last_check_point_call = 0;
+ long since_last_input;
+ time_t expunged_reaper_to, adj_idle_timeout, interval, adj;
+ static int nexttime = 0;
+ time_t now;
+ long n, rv = 0, t_nm_count = 0, exp_count;
+ MAILSTREAM *m;
+ int force, i, started_on;
+ int new_mail_was_announced = 0;
+ int have_pinged_non_special = 0;
+ int timeo;
+
+ dprint((9, "new mail called (force=%d %s flags=0x%x)\n",
+ force_arg,
+ time_for_check_point == GoodTime ? "GoodTime" :
+ time_for_check_point == BadTime ? "BadTime" :
+ time_for_check_point == VeryBadTime ? "VeryBad" :
+ time_for_check_point == DoItNow ? "DoItNow" : "?",
+ flags));
+
+ force = force_arg;
+
+ now = time(0);
+
+ timeo = get_input_timeout();
+
+ if(time_for_check_point == GoodTime)
+ adrbk_maintenance();
+
+ if(time_for_check_point == GoodTime || force_arg)
+ folder_unseen_count_updater(UFU_ANNOUNCE | (force_arg ? UFU_FORCE : 0));
+
+ if(sp_need_to_rethread(ps_global->mail_stream))
+ force = 1;
+
+ if(!force && sp_unsorted_newmail(ps_global->mail_stream))
+ force = !(flags & NM_DEFER_SORT);
+
+ if(!ps_global->mail_stream
+ || !(timeo || force || sp_a_locked_stream_changed()))
+ return(-1);
+
+ last_check_point_call = now;
+ since_last_input = (long) now - (long) time_of_last_input();
+
+ /*
+ * We have this for loop followed by the do-while so that we will prefer
+ * to ping the active streams before we ping the inactive ones, in cases
+ * where the pings or checks are taking a long time.
+ */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(!m || m->halfopen ||
+ (m != ps_global->mail_stream &&
+ !(force_arg && sp_flagged(m, SP_LOCKED))))
+ continue;
+
+ /*
+ * This for() loop is only the current folder, unless the user
+ * has forced the check, in which case it is all locked folders.
+ */
+
+ /*
+ * After some amount of inactivity on a stream, the server may
+ * close the stream. Each protocol has its own idea of how much
+ * inactivity should be allowed before the stream is closed. For
+ * example, IMAP specifies that the server should not close the
+ * stream unilaterally until at least 30 minutes of inactivity.
+ * The GET_IDLETIMEOUT call gives us that time in minutes. We
+ * want to be sure to keep the stream alive if it is about to die
+ * due to inactivity.
+ */
+ adj_idle_timeout = 60 * (long) mail_parameters(m,GET_IDLETIMEOUT,NULL);
+ if(adj_idle_timeout <= 0)
+ adj_idle_timeout = 600;
+
+ adj = (adj_idle_timeout >= 50 * FUDGE) ? 5 * FUDGE :
+ (adj_idle_timeout >= 30 * FUDGE) ? 3 * FUDGE :
+ (adj_idle_timeout >= 20 * FUDGE) ? 2 * FUDGE : FUDGE;
+ adj_idle_timeout = MAX(adj_idle_timeout - adj, 120);
+
+ /*
+ * Set interval to mail-check-interval unless
+ * mail-check-interval-noncurrent is nonzero and this is not inbox
+ * or current stream.
+ */
+ if(ps_global->check_interval_for_noncurr > 0
+ && m != ps_global->mail_stream
+ && !sp_flagged(m, SP_INBOX))
+ interval = ps_global->check_interval_for_noncurr;
+ else
+ interval = timeo;
+
+ /*
+ * We want to make sure that we notice expunges, but we don't have
+ * to be fanatical about it. Every once in a while we'll do a ping
+ * because we haven't had a command that notices expunges for a
+ * while. It's also a larger number than interval so it gives us a
+ * convenient interval to do slower pinging than interval if we
+ * are busy.
+ */
+ if(interval <= adj_idle_timeout)
+ expunged_reaper_to = MIN(MAX(2*interval,180), adj_idle_timeout);
+ else
+ expunged_reaper_to = interval;
+
+ /*
+ * User may want to avoid new mail checking while composing.
+ * In this case we will only do the keepalives.
+ */
+ if(timeo == 0
+ || (flags & NM_FROM_COMPOSER
+ && ((F_ON(F_QUELL_PINGS_COMPOSING, ps_global)
+ && !sp_flagged(m, SP_INBOX))
+ ||
+ (F_ON(F_QUELL_PINGS_COMPOSING_INBOX, ps_global)
+ && sp_flagged(m, SP_INBOX))))){
+ interval = expunged_reaper_to = 0;
+ }
+
+ dprint((9,
+ "%s: force=%d interval=%ld exp_reap_to=%ld adj_idle_to=%ld\n",
+ STREAMNAME(m), force_arg, (long) interval,
+ (long) expunged_reaper_to, (long) adj_idle_timeout));
+ dprint((9,
+ " since_last_ping=%ld since_last_reap=%ld\n",
+ (long) (now - sp_last_ping(m)),
+ (long) (now - sp_last_expunged_reaper(m))));
+
+ /* if check_point does a check it resets last_ping time */
+ if(force_arg || (timeo && expunged_reaper_to > 0))
+ (void) check_point(m, time_for_check_point, flags);
+
+ /*
+ * Remember that unless force_arg is set, this check is really
+ * only for the current folder. This is usually going to fire
+ * on the first test, which is the interval the user set.
+ */
+ if(force_arg
+ ||
+ (timeo
+ &&
+ ((interval && (now - sp_last_ping(m) >= interval-1))
+ ||
+ (expunged_reaper_to
+ && (now - sp_last_expunged_reaper(m)) >= expunged_reaper_to)
+ ||
+ (now - sp_last_ping(m) >= adj_idle_timeout)))){
+
+ if((flags & NM_STATUS_MSG) && pith_opt_newmail_check_cue)
+ (*pith_opt_newmail_check_cue)(TRUE);
+
+ dprint((7, "Mail_Ping(%s): lastping=%ld er=%ld%s%s %s\n",
+ STREAMNAME(m),
+ (long) (now - sp_last_ping(m)),
+ (long) (now - sp_last_expunged_reaper(m)),
+ force_arg ? " [forced]" :
+ interval && (now-sp_last_ping(m) >= interval-1)
+ ? " [it's time]" :
+ expunged_reaper_to
+ && (now - sp_last_expunged_reaper(m) >= expunged_reaper_to)
+ ? " [expunged reaper]"
+ : " [keepalive]",
+ m == ps_global->mail_stream ? " [current]" : "",
+ debug_time(0,1)));
+
+ /*
+ * We're about to ping the stream.
+ * If the stream is a #move Mail Drop there is a minimum time
+ * between re-opens of the mail drop to check for new mail.
+ * If the check is forced by the user, they want it to
+ * happen now. We use knowledge of c-client internals to
+ * make this happen.
+ */
+ if(force_arg && m && m->snarf.name)
+ m->snarf.time = 0;
+
+ /*-- Ping the stream to check for new mail --*/
+ if(sp_dead_stream(m)){
+ dprint((6, "no check: stream is dead\n"));
+ }
+ else if(!pine_mail_ping(m)){
+ dprint((6, "ping failed: stream is dead\n"));
+ sp_set_dead_stream(m, 1);
+ }
+
+ dprint((7, "Ping complete: %s\n", debug_time(0,1)));
+
+ if((flags & NM_STATUS_MSG) && pith_opt_newmail_check_cue)
+ (*pith_opt_newmail_check_cue)(FALSE);
+ }
+ }
+
+ if(nexttime < 0 || nexttime >= ps_global->s_pool.nstream)
+ nexttime = 0;
+
+ i = started_on = nexttime;
+ do {
+ m = ps_global->s_pool.streams[i];
+
+ nexttime = i;
+ if(ps_global->s_pool.nstream > 0)
+ i = (i + 1) % ps_global->s_pool.nstream;
+
+ /*
+ * This do() loop handles all the other streams that weren't covered
+ * in the for() loop above.
+ */
+ if((!m || m->halfopen) ||
+ (m == ps_global->mail_stream ||
+ (force_arg && sp_flagged(m, SP_LOCKED)))){
+ nexttime = i;
+ continue;
+ }
+
+ /*
+ * If it is taking an extra long time to do the pings and checks,
+ * think about skipping some of them. Always do at least one of
+ * these non-special streams (if they are due to be pinged).
+ * The nexttime variable keeps track of where we were so that we
+ * don't always ping the first one in the list and then skip out.
+ */
+ if((time(0) - now >= 5) && have_pinged_non_special){
+ dprint((7, "skipping pings due to delay: %s\n",
+ debug_time(0,1)));
+ break;
+ }
+
+ nexttime = i;
+ have_pinged_non_special++;
+
+ adj_idle_timeout = 60 * (long) mail_parameters(m,GET_IDLETIMEOUT,NULL);
+ if(adj_idle_timeout <= 0)
+ adj_idle_timeout = 600;
+
+ adj = (adj_idle_timeout >= 50 * FUDGE) ? 5 * FUDGE :
+ (adj_idle_timeout >= 30 * FUDGE) ? 3 * FUDGE :
+ (adj_idle_timeout >= 20 * FUDGE) ? 2 * FUDGE : FUDGE;
+ adj_idle_timeout = MAX(adj_idle_timeout - adj, 120);
+
+ if(ps_global->check_interval_for_noncurr > 0
+ && m != ps_global->mail_stream
+ && !sp_flagged(m, SP_INBOX))
+ interval = ps_global->check_interval_for_noncurr;
+ else
+ interval = timeo;
+
+ if(interval <= adj_idle_timeout)
+ expunged_reaper_to = MIN(MAX(2*interval,180), adj_idle_timeout);
+ else
+ expunged_reaper_to = interval;
+
+ if(timeo == 0
+ || (flags & NM_FROM_COMPOSER
+ && ((F_ON(F_QUELL_PINGS_COMPOSING, ps_global)
+ && !sp_flagged(m, SP_INBOX))
+ ||
+ (F_ON(F_QUELL_PINGS_COMPOSING_INBOX, ps_global)
+ && sp_flagged(m, SP_INBOX))))){
+ interval = expunged_reaper_to = 0;
+ }
+
+ dprint((9,
+ "%s: force=%d interval=%ld exp_reap_to=%ld adj_idle_to=%ld\n",
+ STREAMNAME(m), force_arg, (long) interval,
+ (long) expunged_reaper_to, (long) adj_idle_timeout));
+ dprint((9,
+ " since_last_ping=%ld since_last_reap=%ld since_last_input=%ld\n",
+ (long) (now - sp_last_ping(m)),
+ (long) (now - sp_last_expunged_reaper(m)),
+ (long) since_last_input));
+
+ /* if check_point does a check it resets last_ping time */
+ if(force_arg || (timeo && expunged_reaper_to > 0))
+ (void) check_point(m, time_for_check_point, flags);
+
+
+ /*
+ * The check here is a little bit different from the current folder
+ * check in the for() loop above. In particular, we defer our
+ * pinging for awhile if the last input was recent (except for the
+ * inbox!). We ping streams which are cached but not actively being
+ * used (that is, the non-locked streams) at a slower rate.
+ * If we don't use them for a long time we will eventually close them
+ * (in maybe_kill_old_stream()) but we do it when we want to instead
+ * of when the server wants us to by attempting to keep it alive here.
+ * The other reason to ping the cached streams is that we only get
+ * told the new mail in those streams is recent one time, the messages
+ * that weren't handled here will no longer be recent next time
+ * we open the folder.
+ */
+ if((force_arg && sp_flagged(m, SP_LOCKED))
+ ||
+ (timeo
+ &&
+ ((interval
+ && sp_flagged(m, SP_LOCKED)
+ && ((since_last_input >= 3
+ && (now-sp_last_ping(m) >= interval-1))
+ || (sp_flagged(m, SP_INBOX)
+ && (now-sp_last_ping(m) >= interval-1))))
+ ||
+ (expunged_reaper_to
+ && sp_flagged(m, SP_LOCKED)
+ && (now-sp_last_expunged_reaper(m) >= expunged_reaper_to))
+ ||
+ (expunged_reaper_to
+ && !sp_flagged(m, SP_LOCKED)
+ && since_last_input >= 3
+ && (now-sp_last_ping(m) >= expunged_reaper_to))
+ ||
+ (now - sp_last_ping(m) >= adj_idle_timeout)))){
+
+ if((flags & NM_STATUS_MSG) && pith_opt_newmail_check_cue)
+ (*pith_opt_newmail_check_cue)(TRUE);
+
+ dprint((7,
+ "Mail_Ping(%s): lastping=%ld er=%ld%s idle: %ld %s\n",
+ STREAMNAME(m),
+ (long) (now - sp_last_ping(m)),
+ (long) (now - sp_last_expunged_reaper(m)),
+ (force_arg && sp_flagged(m, SP_LOCKED)) ? " [forced]" :
+ (interval
+ && sp_flagged(m, SP_LOCKED)
+ && ((since_last_input >= 3
+ && (now-sp_last_ping(m) >= interval-1))
+ || (sp_flagged(m, SP_INBOX)
+ && (now-sp_last_ping(m) >= interval-1))))
+ ? " [it's time]" :
+ (expunged_reaper_to
+ && sp_flagged(m, SP_LOCKED)
+ && (now-sp_last_expunged_reaper(m) >= expunged_reaper_to))
+ ? " [expunged reaper]" :
+ (expunged_reaper_to
+ && !sp_flagged(m, SP_LOCKED)
+ && since_last_input >= 3
+ && (now-sp_last_ping(m) >= expunged_reaper_to))
+ ? " [slow ping]" : " [keepalive]",
+ since_last_input,
+ debug_time(0,1)));
+
+ /*
+ * We're about to ping the stream.
+ * If the stream is a #move Mail Drop there is a minimum time
+ * between re-opens of the mail drop to check for new mail.
+ * If the check is forced by the user, they want it to
+ * happen now. We use knowledge of c-client internals to
+ * make this happen.
+ */
+ if(force_arg && m && m->snarf.name)
+ m->snarf.time = 0;
+
+ /*-- Ping the stream to check for new mail --*/
+ if(sp_dead_stream(m)){
+ dprint((6, "no check: stream is dead\n"));
+ }
+ else if(!pine_mail_ping(m)){
+ dprint((6, "ping failed: stream is dead\n"));
+ sp_set_dead_stream(m, 1);
+ }
+
+ dprint((7, "Ping complete: %s\n", debug_time(0,1)));
+
+ if((flags & NM_STATUS_MSG) && pith_opt_newmail_check_cue)
+ (*pith_opt_newmail_check_cue)(FALSE);
+ }
+ } while(i != started_on);
+
+ /*
+ * Current mail box state changed, could be additions or deletions.
+ * Also check if we need to do sorting that has been deferred.
+ * We handle the current stream separately from the rest in the
+ * similar loop that follows this paragraph.
+ */
+ m = ps_global->mail_stream;
+ if(sp_mail_box_changed(m) || sp_unsorted_newmail(m)
+ || sp_need_to_rethread(m)){
+ dprint((7,
+ "Cur new mail, %s, new_mail_count: %ld expunge: %ld, max_msgno: %ld\n",
+ (m && m->mailbox) ? m->mailbox : "?",
+ sp_new_mail_count(m),
+ sp_expunge_count(m),
+ mn_get_total(sp_msgmap(m))));
+
+ new_mail_was_announced = 0;
+ if(sp_mail_box_changed(m))
+ fixup_flags(m, sp_msgmap(m));
+
+ if(sp_new_mail_count(m))
+ process_filter_patterns(m, sp_msgmap(m), sp_new_mail_count(m));
+
+ /* worry about sorting */
+ if((sp_new_mail_count(m) > 0L
+ || sp_unsorted_newmail(m)
+ || sp_need_to_rethread(m))
+ && !((flags & NM_DEFER_SORT)
+ || any_lflagged(sp_msgmap(m), MN_HIDE)))
+ refresh_sort(m, sp_msgmap(m),
+ (flags & NM_STATUS_MSG) ? SRT_VRB : SRT_NON);
+ else if(sp_new_mail_count(m) > 0L)
+ sp_set_unsorted_newmail(m, 1);
+
+ if(sp_new_mail_count(m) > 0L){
+ sp_set_mail_since_cmd(m, sp_mail_since_cmd(m)+sp_new_mail_count(m));
+ rv += (t_nm_count = sp_new_mail_count(m));
+ sp_set_new_mail_count(m, 0L);
+
+ if((flags & NM_STATUS_MSG) && pith_opt_newmail_announce){
+ for(n = m->nmsgs; n > 1L; n--)
+ if(!get_lflag(m, NULL, n, MN_EXLD))
+ break;
+
+ (*pith_opt_newmail_announce)(m, n, t_nm_count);
+
+ if(n)
+ new_mail_was_announced++;
+ }
+ }
+
+ update_folder_unseen_by_stream(m, new_mail_was_announced ? UFU_NONE : UFU_ANNOUNCE);
+
+ if(flags & NM_STATUS_MSG)
+ sp_set_mail_box_changed(m, 0);
+ }
+
+ /* the rest of the streams */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(!m || m == ps_global->mail_stream)
+ continue;
+
+ if(sp_mail_box_changed(m)){
+ /*-- New mail for this stream, queue up the notification --*/
+ dprint((7,
+ "New mail, %s, new_mail_count: %ld expunge: %ld, max_msgno: %ld\n",
+ (m && m->mailbox) ? m->mailbox : "?",
+ sp_new_mail_count(m),
+ sp_expunge_count(m),
+ mn_get_total(sp_msgmap(m))));
+
+ new_mail_was_announced = 0;
+ fixup_flags(m, sp_msgmap(m));
+
+ if(sp_new_mail_count(m))
+ process_filter_patterns(m, sp_msgmap(m), sp_new_mail_count(m));
+
+ if(sp_new_mail_count(m) > 0){
+ sp_set_unsorted_newmail(m, 1);
+ sp_set_mail_since_cmd(m, sp_mail_since_cmd(m) +
+ sp_new_mail_count(m));
+ if(sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR))
+ rv += (t_nm_count = sp_new_mail_count(m));
+
+ sp_set_new_mail_count(m, 0L);
+
+ /* messages only for user's streams */
+ if(flags & NM_STATUS_MSG
+ && pith_opt_newmail_announce
+ && sp_flagged(m, SP_LOCKED)
+ && sp_flagged(m, SP_USERFLDR)){
+ for(n = m->nmsgs; n > 1L; n--)
+ if(!get_lflag(m, NULL, n, MN_EXLD))
+ break;
+
+ (*pith_opt_newmail_announce)(m, n, t_nm_count);
+
+ if(n)
+ new_mail_was_announced++;
+ }
+ }
+
+ update_folder_unseen_by_stream(m, new_mail_was_announced ? UFU_NONE : UFU_ANNOUNCE);
+
+ if(flags & NM_STATUS_MSG)
+ sp_set_mail_box_changed(m, 0);
+ }
+ }
+
+ /* so quit_screen can tell new mail from expunged mail */
+ exp_count = sp_expunge_count(ps_global->mail_stream);
+
+ /* see if we want to kill any cached streams */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(sp_last_ping(m) >= now)
+ maybe_kill_old_stream(m);
+ }
+
+ /*
+ * This is here to prevent banging on the down arrow key (or holding
+ * it down and repeating) at the end of the index from causing
+ * a whole bunch of new mail checks. Last_nextitem_forcechk does get
+ * set at the place it is tested in mailcmd.c, but this is here to
+ * reset it to a little bit later in case it takes a second or more
+ * to check for the mail. If we didn't do this, we'd just check every
+ * keystroke as long as the checks took more than a second.
+ */
+ if(force_arg)
+ ps_global->last_nextitem_forcechk = time(0);
+
+ dprint((6, "******** new mail returning %ld ********\n",
+ rv ? rv : (exp_count ? 0 : -1)));
+ return(rv ? rv : (exp_count ? 0 : -1));
+}
+
+
+/*
+ * format_new_mail_msg - actual work of generating intro,
+ * from, subject and expanded subject
+ */
+void
+format_new_mail_msg(char *folder, long int number, ENVELOPE *e,
+ char *intro, char *from, char *subj, char *subjex,
+ size_t buflen) /* min length of each of the 4 above if they're non-null */
+{
+ char *p, tmp[MAILTMPLEN+1], subj_leadin[MAILTMPLEN];
+ static char *carray[] = { "regarding",
+ "concerning",
+ "about",
+ "as to",
+ "as regards",
+ "as respects",
+ "in re",
+ "re",
+ "respecting",
+ "in point of",
+ "with regard to",
+ "subject:"
+ };
+
+ if(buflen > 0){
+ if(intro)
+ intro[0] = '\0';
+
+ if(from)
+ from[0] = '\0';
+
+ if(subj)
+ subj[0] = '\0';
+
+ if(subjex)
+ subjex[0] = '\0';
+ }
+
+ if(from && e && e->from){
+ if(e->from->personal && e->from->personal[0]){
+ /*
+ * The reason we use so many characters for tmp is because we
+ * may have multiple MIME3 chunks and we don't want to truncate
+ * in the middle of one of them before decoding.
+ */
+ snprintf(tmp, sizeof(tmp), "%s", e->from->personal);
+ p = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ removing_leading_and_trailing_white_space(p);
+ if(*p)
+ snprintf(from, buflen, "%.200s", p);
+ }
+
+ if(!from[0]){
+ snprintf(tmp, sizeof(tmp), "%s%s%s",
+ e->from->mailbox,
+ e->from->host ? "@" : "",
+ e->from->host ? e->from->host : "");
+ snprintf(from, buflen, "%.200s", tmp);
+ }
+ }
+
+ if(number <= 1L){
+ if(e && e->subject && e->subject[0]){
+ snprintf(tmp, sizeof(tmp), "%s", e->subject);
+ p = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ if(subj)
+ snprintf(subj, buflen, "%.200s", p);
+ }
+
+ snprintf(subj_leadin, sizeof(subj_leadin), " %s ", carray[(unsigned)random()%12]);
+ if(!(from && from[0]))
+ subj_leadin[1] = toupper((unsigned char)subj_leadin[1]);
+ }
+
+ if(subj && subjex){
+ if(subj[0]){
+ snprintf(subjex, buflen, "%s%.200s%s",
+ (number <= 1L) ? (subj[0] ? subj_leadin : "")
+ : "",
+ (number <= 1L) ? (subj[0] ? subj
+ : from ? " w" : " W")
+ : "",
+ (number <= 1L) ? (subj[0] ? "" : "ith no subject")
+ : "");
+ }
+ else
+ subj[0] = subjex[0] = '\0';
+ }
+
+ if(intro){
+ if(!folder){
+ if(number > 1)
+ /* TRANSLATORS: The argument is the number of new messages */
+ snprintf(intro, buflen, _("%ld new messages!"), number);
+ else
+ /* TRANSLATORS: The argument is either " to you" or nothing */
+ snprintf(intro, buflen, _("New mail%s!"),
+ (e && address_is_us(e->to, ps_global)) ? _(" to you") : "");
+ }
+ else {
+ long fl, tot, newfl;
+ char *fname = folder ? (char *) folder_name_decoded((unsigned char *) folder) : "";
+
+ if(number > 1)
+ snprintf(intro, buflen, _("%ld messages saved to folder \"%.80s\""),
+ number, fname);
+ else
+ snprintf(intro, buflen, _("Mail saved to folder \"%.80s\""), fname);
+
+ if((fl=utf8_width(fname)) > 10 &&
+ (tot=utf8_width(intro) + utf8_width(from ? from : "") + utf8_width(subj ? subj : "")) >
+ ps_global->ttyo->screen_cols - 2){
+ char *f = fs_get((strlen(fname) + 1)*sizeof(char));
+ newfl = MAX(10, fl-(tot-(ps_global->ttyo->screen_cols - 2)));
+ utf8_to_width_rhs(f, fname, strlen(fname) + 1, newfl-3);
+ if(number > 1)
+ snprintf(intro, buflen, _("%ld messages saved to folder \"...%.80s\""), number, f);
+ else
+ snprintf(intro, buflen, _("Mail saved to folder \"...%.80s\""), f);
+ if(f) fs_give((void **)&f);
+ }
+
+ if (fname && *fname)
+ fs_give((void **)&fname);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Straighten out any local flag problems here. We can't take care of
+ them in the mm_exists or mm_expunged callbacks since the flags
+ themselves are in an MESSAGECACHE and we're not allowed to reenter
+ c-client from a callback...
+
+ Args: stream -- mail stream to operate on
+ msgmap -- messages in that stream to fix up
+
+ Result: returns with local flags as they should be
+
+ ----*/
+void
+fixup_flags(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ /*
+ * Deal with the case where expunged away all of the
+ * zoomed messages. Unhide everything in that case...
+ */
+ if(mn_get_total(msgmap) > 0L){
+ long i;
+
+ if(any_lflagged(msgmap, MN_HIDE) >= mn_get_total(msgmap)){
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ set_lflag(stream, msgmap, i, MN_HIDE, 0);
+
+ mn_set_cur(msgmap, THREADING()
+ ? first_sorted_flagged(F_NONE, stream, 0L,
+ (THREADING() ? 0 : FSF_SKIP_CHID)
+ | FSF_LAST)
+ : mn_get_total(msgmap));
+ }
+ else if(any_lflagged(msgmap, MN_HIDE)){
+ /*
+ * if we got here, there are some hidden messages and
+ * some not. Make sure the current message is one
+ * that's not...
+ */
+ for(i = mn_get_cur(msgmap); i <= mn_get_total(msgmap); i++)
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ mn_set_cur(msgmap, i);
+ break;
+ }
+
+ for(i = mn_get_cur(msgmap); i > 0L; i--)
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ mn_set_cur(msgmap, i);
+ break;
+ }
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Force write of the main file so user doesn't lose too much when
+ something bad happens. The only thing that can get lost is flags, such
+ as when new mail arrives, is read, deleted or answered.
+
+ Args: timing -- indicates if it's a good time for to do a checkpoint
+
+ Result: returns 1 if checkpoint was written,
+ 0 if not.
+
+NOTE: mail_check will also notice new mail arrival, so it's imperative that
+code exist after this function is called that can deal with updating the
+various pieces of pine's state associated with the message count and such.
+
+Only need to checkpoint current stream because there are no changes going
+on with other streams when we're not manipulating them.
+ ----*/
+
+int
+check_point(MAILSTREAM *stream, CheckPointTime timing, int flags)
+{
+ int freq, tm, check_count, tst1 = 0, tst2 = 0, accel;
+ long since_last_input, since_first_change;
+ time_t now, then;
+#define AT_LEAST (180)
+#define MIN_LONG_CHK_IDLE (10)
+
+ if(!stream || stream->rdonly || stream->halfopen
+ || (check_count = sp_check_cnt(stream)) == 0)
+ return(0);
+
+ since_last_input = (long) time(0) - (long) time_of_last_input();
+
+ if(timing == GoodTime && since_last_input <= 0)
+ timing = VeryBadTime;
+ else if(timing == GoodTime && since_last_input <= 4)
+ timing = BadTime;
+
+ dprint((9, "check_point(%s: %s)\n",
+ timing == GoodTime ? "GoodTime" :
+ timing == BadTime ? "BadTime" :
+ timing == VeryBadTime ? "VeryBadTime" : "DoItNow",
+ STREAMNAME(stream)));
+
+ freq = CHECK_POINT_FREQ * (timing==GoodTime ? 1 : timing==BadTime ? 3 : 4);
+ tm = CHECK_POINT_TIME * (timing==GoodTime ? 2 : timing==BadTime ? 4 : 6);
+ tm = MAX(100, tm);
+
+ if(timing == DoItNow){
+ dprint((9, "DoItNow\n"));
+ }
+ else{
+ since_first_change = (long) (time(0) - sp_first_status_change(stream));
+ accel = MIN(3, MAX(1, (4 * since_first_change)/tm));
+ tst1 = ((check_count * since_last_input) >= (30/accel) * freq);
+ tst2 = ((since_first_change >= tm)
+ && (since_last_input >= MIN_LONG_CHK_IDLE));
+ dprint((9,
+ "Chk changes(%d) x since_last_input(%ld) (=%ld) >= freq(%d) x 30/%d (=%d) ? %s\n",
+ check_count, since_last_input,
+ check_count * since_last_input, freq, accel, (30/accel)*freq,
+ tst1 ? "Yes" : "No"));
+
+ dprint((9,
+ " or since_1st_change(%ld) >= tm(%d) && since_last_input >= %d ? %s\n",
+ since_first_change, tm, MIN_LONG_CHK_IDLE,
+ tst2 ? "Yes" : "No"));
+ }
+
+ if(timing == DoItNow || tst1 || tst2){
+
+ if(timing == DoItNow
+ || time(0) - sp_last_chkpnt_done(stream) >= AT_LEAST){
+ then = time(0);
+ dprint((2, "Checkpoint: %s Since 1st change: %ld secs idle: %ld secs\n",
+ debug_time(0,1),
+ (long) (then - sp_first_status_change(stream)),
+ since_last_input))
+;
+ if((flags & NM_STATUS_MSG) && pith_opt_checkpoint_cue)
+ (*pith_opt_checkpoint_cue)(TRUE);
+
+ pine_mail_check(stream); /* write file state */
+
+ now = time(0);
+ dprint((2,
+ "Checkpoint complete: %s%s%s%s\n",
+ debug_time(0,1),
+ (now-then > 0) ? " (elapsed: " : "",
+ (now-then > 0) ? comatose((long)(now-then)) : "",
+ (now-then > 0) ? " secs)" : ""));
+
+ if((flags & NM_STATUS_MSG) && pith_opt_checkpoint_cue)
+ (*pith_opt_checkpoint_cue)(FALSE);
+
+ return(1);
+ }
+ else{
+ dprint((9,
+ "Skipping checkpoint since last was only %ld secs ago (< %d)\n",
+ (long) (time(0) - sp_last_chkpnt_done(stream)), AT_LEAST));
+ }
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Call this when we need to tell the check pointing mechanism about
+ mailbox state changes.
+ ----------------------------------------------------------------------*/
+void
+check_point_change(MAILSTREAM *stream)
+{
+ if(!sp_check_cnt(stream))
+ sp_set_first_status_change(stream, time(0));
+
+ sp_set_check_cnt(stream, sp_check_cnt(stream)+1);
+ dprint((9, "check_point_change(%s): increment to %d\n",
+ STREAMNAME(stream), sp_check_cnt(stream)));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Call this when a mail file is written to reset timer and counter
+ for next check_point.
+ ----------------------------------------------------------------------*/
+void
+reset_check_point(MAILSTREAM *stream)
+{
+ time_t now;
+
+ now = time(0);
+
+ sp_set_check_cnt(stream, 0);
+ sp_set_first_status_change(stream, 0);
+ sp_set_last_chkpnt_done(stream, now);
+ sp_set_last_ping(stream, now);
+ sp_set_last_expunged_reaper(stream, now);
+}
+
+
+int
+changes_to_checkpoint(MAILSTREAM *stream)
+{
+ return(sp_check_cnt(stream) > 0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Zero the counters that keep track of mail accumulated between
+ commands.
+ ----*/
+void
+zero_new_mail_count(void)
+{
+ int i;
+ MAILSTREAM *m;
+
+ dprint((9, "New_mail_count zeroed\n"));
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && sp_mail_since_cmd(m)){
+ if(pith_opt_icon_text)
+ (*pith_opt_icon_text)(NULL, IT_NEWMAIL);
+
+ break;
+ }
+ }
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED))
+ sp_set_mail_since_cmd(m, 0L);
+ }
+}
diff --git a/pith/newmail.h b/pith/newmail.h
new file mode 100644
index 00000000..4ee6c982
--- /dev/null
+++ b/pith/newmail.h
@@ -0,0 +1,59 @@
+/*
+ * $Id: newmail.h 807 2007-11-09 01:21:33Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_NEWMAIL_INCLUDED
+#define PITH_NEWMAIL_INCLUDED
+
+
+typedef enum {GoodTime = 0, BadTime, VeryBadTime, DoItNow} CheckPointTime;
+
+
+/*
+ * Macro to help with new mail check timing...
+ */
+#define NM_TIMING(X) (((X)==NO_OP_IDLE) ? GoodTime : \
+ (((X)==NO_OP_COMMAND) ? BadTime : VeryBadTime))
+#define NM_NONE 0x00
+#define NM_STATUS_MSG 0x01
+#define NM_DEFER_SORT 0x02
+#define NM_FROM_COMPOSER 0x04
+
+
+/*
+ * Icon text types, to tell which icon to use
+ */
+#define IT_NEWMAIL 0
+#define IT_MCLOSED 1
+
+
+/* exported protoypes */
+long new_mail(int, CheckPointTime, int);
+void format_new_mail_msg(char *, long, ENVELOPE *, char *, char *, char *, char *, size_t);
+void check_point_change(MAILSTREAM *);
+void reset_check_point(MAILSTREAM *);
+int changes_to_checkpoint(MAILSTREAM *);
+void zero_new_mail_count(void);
+void init_newmailfifo(char *);
+void close_newmailfifo(void);
+
+
+/* mandatory to implement prototypes */
+void newmail_check_cue(int);
+void newmail_check_point_cue(int);
+void icon_text(char *, int);
+time_t time_of_last_input(void);
+
+
+#endif /* PITH_NEWMAIL_INCLUDED */
diff --git a/pith/news.c b/pith/news.c
new file mode 100644
index 00000000..c0077c2c
--- /dev/null
+++ b/pith/news.c
@@ -0,0 +1,459 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: news.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/news.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/context.h"
+#include "../pith/stream.h"
+#include "../pith/util.h"
+
+
+typedef enum {NotChecked, NotInCache, Found, Missing, End} NgCacheReturns;
+
+
+/*
+ * Internal prototypes
+ */
+NgCacheReturns chk_newsgrp_cache(char *);
+void add_newsgrp_cache(char *, NgCacheReturns);
+
+
+/*----------------------------------------------------------------------
+ Function to see if a given MAILSTREAM mailbox is in the news namespace
+
+ Input: stream -- mail stream to test
+
+ Result:
+ ----*/
+int
+ns_test(char *mailbox, char *namespace)
+{
+ if(mailbox){
+ switch(*mailbox){
+ case '#' :
+ return(!struncmp(mailbox + 1, namespace, strlen(namespace)));
+
+ case '{' :
+ {
+ NETMBX mbx;
+
+ if(mail_valid_net_parse(mailbox, &mbx))
+ return(ns_test(mbx.mailbox, namespace));
+ }
+
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ return(0);
+}
+
+
+int
+news_in_folders(struct variable *var)
+{
+ int i, found_news = 0;
+ CONTEXT_S *tc;
+
+ if(!(var && var->current_val.l))
+ return(found_news);
+
+ for(i=0; !found_news && var->current_val.l[i]; i++){
+ if((tc = new_context(var->current_val.l[i], NULL)) != NULL){
+ if(tc->use & CNTXT_NEWS)
+ found_news++;
+
+ free_context(&tc);
+ }
+ }
+
+ return(found_news);
+}
+
+
+/*----------------------------------------------------------------------
+ Verify and canonicalize news groups names.
+ Called from the message composer
+
+Args: given_group -- List of groups typed by user
+ expanded_group -- pointer to point to expanded list, which will be
+ allocated here and freed in caller. If this is
+ NULL, don't attempt to validate.
+ error -- pointer to store error message
+ fcc -- pointer to point to fcc, which will be
+ allocated here and freed in caller
+
+Returns: 0 if all is OK
+ -1 if addresses weren't valid
+
+Test the given list of newstroups against those recognized by our nntp
+servers. Testing by actually trying to open the list is much cheaper, both
+in bandwidth and memory, than yanking the whole list across the wire.
+ ----*/
+int
+news_grouper(char *given_group, char **expanded_group, char **error,
+ char **fccptr, void (*delay_warning)(void))
+{
+ char ng_error[90], *p1, *p2, *name, *end, *ep, **server,
+ ng_ref[MAILTMPLEN];
+ int expanded_len = 0, num_in_error = 0, cnt_errs;
+
+ MAILSTREAM *stream = NULL;
+ struct ng_list {
+ char *groupname;
+ NgCacheReturns found;
+ struct ng_list *next;
+ }*nglist = NULL, **ntmpp, *ntmp;
+#ifdef SENDNEWS
+ static int no_servers = 0;
+#endif
+
+ dprint((5,
+ "- news_build - (%s)\n", given_group ? given_group : "nul"));
+
+ if(error)
+ *error = NULL;
+
+ ng_ref[0] = '\0';
+
+ /*------ parse given entries into a list ----*/
+ ntmpp = &nglist;
+ for(name = given_group; *name; name = end){
+
+ /* find start of next group name */
+ while(*name && (isspace((unsigned char)*name) || *name == ','))
+ name++;
+
+ /* find end of group name */
+ end = name;
+ while(*end && !isspace((unsigned char)*end) && *end != ',')
+ end++;
+
+ if(end != name){
+ *ntmpp = (struct ng_list *)fs_get(sizeof(struct ng_list));
+ (*ntmpp)->next = NULL;
+ (*ntmpp)->found = NotChecked;
+ (*ntmpp)->groupname = fs_get(end - name + 1);
+ strncpy((*ntmpp)->groupname, name, end - name);
+ (*ntmpp)->groupname[end - name] = '\0';
+ ntmpp = &(*ntmpp)->next;
+ if(!expanded_group)
+ break; /* no need to continue if just doing fcc */
+ }
+ }
+
+ /*
+ * If fcc is not set or is set to default, then replace it if
+ * one of the recipient rules is in effect.
+ */
+ if(fccptr){
+ if((ps_global->fcc_rule == FCC_RULE_RECIP ||
+ ps_global->fcc_rule == FCC_RULE_NICK_RECIP) &&
+ (nglist && nglist->groupname)){
+ if(*fccptr)
+ fs_give((void **) fccptr);
+
+ *fccptr = cpystr(nglist->groupname);
+ }
+ else if(!*fccptr) /* already default otherwise */
+ *fccptr = cpystr(ps_global->VAR_DEFAULT_FCC);
+ }
+
+ if(!nglist){
+ if(expanded_group)
+ *expanded_group = cpystr("");
+ return 0;
+ }
+
+ if(!expanded_group)
+ return 0;
+
+#ifdef DEBUG
+ for(ntmp = nglist; debug >= 9 && ntmp; ntmp = ntmp->next)
+ dprint((9, "Parsed group: --[%s]--\n",
+ ntmp->groupname ? ntmp->groupname : "?"));
+#endif
+
+ /* If we are doing validation */
+ if(F_OFF(F_NO_NEWS_VALIDATION, ps_global)){
+ int need_to_talk_to_server = 0;
+
+ /*
+ * First check our cache of validated newsgroups to see if we even
+ * have to open a stream.
+ */
+ for(ntmp = nglist; ntmp; ntmp = ntmp->next){
+ ntmp->found = chk_newsgrp_cache(ntmp->groupname);
+ if(ntmp->found == NotInCache)
+ need_to_talk_to_server++;
+ }
+
+ if(need_to_talk_to_server){
+
+#ifdef SENDNEWS
+ if(no_servers == 0)
+#endif
+ if(delay_warning)
+ (*delay_warning)();
+
+ /*
+ * Build a stream to the first server that'll talk to us...
+ */
+ for(server = ps_global->VAR_NNTP_SERVER;
+ server && *server && **server;
+ server++){
+ snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.",
+ sizeof(ng_ref)-30, *server);
+ if((stream = pine_mail_open(stream, ng_ref,
+ OP_HALFOPEN|SP_USEPOOL|SP_TEMPUSE,
+ NULL)) != NULL)
+ break;
+ }
+ if(!server || !stream){
+ if(error)
+#ifdef SENDNEWS
+ {
+ /* don't say this over and over */
+ if(no_servers == 0){
+ if(!server || !*server || !**server)
+ no_servers++;
+
+ *error = cpystr(no_servers
+ /* TRANSLATORS: groups refers to news groups */
+ ? _("Can't validate groups. No servers defined")
+ /* TRANSLATORS: groups refers to news groups */
+ : _("Can't validate groups. No servers responding"));
+ }
+ }
+#else
+ *error = cpystr((!server || !*server || !**server)
+ ? _("No servers defined for posting to newsgroups")
+ /* TRANSLATORS: groups refers to news groups */
+ : _("Can't validate groups. No servers responding"));
+#endif
+ *expanded_group = cpystr(given_group);
+ goto done;
+ }
+ }
+
+ /*
+ * Now, go thru the list, making sure we can at least open each one...
+ */
+ for(server = ps_global->VAR_NNTP_SERVER;
+ server && *server && **server; server++){
+ /*
+ * It's faster and easier right now just to open the stream and
+ * do our own finds than to use the current folder_exists()
+ * interface...
+ */
+ for(ntmp = nglist; ntmp; ntmp = ntmp->next){
+ if(ntmp->found == NotInCache){
+ snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.%.*s",
+ sizeof(ng_ref)/2 - 10, *server,
+ sizeof(ng_ref)/2 - 10, ntmp->groupname);
+ ps_global->noshow_error = 1;
+ stream = pine_mail_open(stream, ng_ref,
+ OP_SILENT|SP_USEPOOL|SP_TEMPUSE,
+ NULL);
+ ps_global->noshow_error = 0;
+ if(stream)
+ add_newsgrp_cache(ntmp->groupname, ntmp->found = Found);
+ }
+
+ }
+
+ if(stream){
+ pine_mail_close(stream);
+ stream = NULL;
+ }
+
+ }
+
+ }
+
+ /* figure length of string for matching groups */
+ for(ntmp = nglist; ntmp; ntmp = ntmp->next){
+ if(ntmp->found == Found || F_ON(F_NO_NEWS_VALIDATION, ps_global))
+ expanded_len += strlen(ntmp->groupname) + 2;
+ else{
+ num_in_error++;
+ if(ntmp->found == NotInCache)
+ add_newsgrp_cache(ntmp->groupname, ntmp->found = Missing);
+ }
+ }
+
+ /*
+ * allocate and write the allowed, and error lists...
+ */
+ p1 = *expanded_group = fs_get((expanded_len + 1) * sizeof(char));
+ if(error && num_in_error){
+ cnt_errs = num_in_error;
+ memset((void *)ng_error, 0, sizeof(ng_error));
+ snprintf(ng_error, sizeof(ng_error), "Unknown news group%s: ", plural(num_in_error));
+ ep = ng_error + strlen(ng_error);
+ }
+ for(ntmp = nglist; ntmp; ntmp = ntmp->next){
+ p2 = ntmp->groupname;
+ if(ntmp->found == Found || F_ON(F_NO_NEWS_VALIDATION, ps_global)){
+ while(*p2)
+ *p1++ = *p2++;
+
+ if(ntmp->next){
+ *p1++ = ',';
+ *p1++ = ' ';
+ }
+ }
+ else if (error){
+ while(*p2 && (ep - ng_error < sizeof(ng_error)-1))
+ *ep++ = *p2++;
+
+ if(--cnt_errs > 0 && (ep - ng_error < sizeof(ng_error)-3)){
+ strncpy(ep, ", ", sizeof(ng_error)-(ep-ng_error));
+ ep += 2;
+ }
+ }
+ }
+
+ *p1 = '\0';
+
+ if(error && num_in_error)
+ *error = cpystr(ng_error);
+
+done:
+ while((ntmp = nglist) != NULL){
+ nglist = nglist->next;
+ fs_give((void **)&ntmp->groupname);
+ fs_give((void **)&ntmp);
+ }
+
+ return(num_in_error ? -1 : 0);
+}
+
+
+typedef struct ng_cache {
+ char *name;
+ NgCacheReturns val;
+}NgCache;
+
+static NgCache *ng_cache_ptr;
+#if defined(DOS) && !defined(_WINDOWS)
+#define MAX_NGCACHE_ENTRIES 15
+#else
+#define MAX_NGCACHE_ENTRIES 40
+#endif
+/*
+ * Simple newsgroup validity cache. Opening a newsgroup to see if it
+ * exists can be very slow on a heavily loaded NNTP server, so we cache
+ * the results.
+ */
+NgCacheReturns
+chk_newsgrp_cache(char *group)
+{
+ register NgCache *ngp;
+
+ for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
+ if(strcmp(group, ngp->name) == 0)
+ return(ngp->val);
+ }
+
+ return NotInCache;
+}
+
+
+/*
+ * Add an entry to the newsgroup validity cache.
+ *
+ * LRU entry is the one on the bottom, oldest on the top.
+ * A slot has an entry in it if name is not NULL.
+ */
+void
+add_newsgrp_cache(char *group, NgCacheReturns result)
+{
+ register NgCache *ngp;
+ NgCache save_ngp;
+
+ /* first call, initialize cache */
+ if(!ng_cache_ptr){
+ int i;
+
+ ng_cache_ptr =
+ (NgCache *)fs_get((MAX_NGCACHE_ENTRIES+1)*sizeof(NgCache));
+ for(i = 0; i <= MAX_NGCACHE_ENTRIES; i++){
+ ng_cache_ptr[i].name = NULL;
+ ng_cache_ptr[i].val = NotInCache;
+ }
+ ng_cache_ptr[MAX_NGCACHE_ENTRIES].val = End;
+ }
+
+ if(chk_newsgrp_cache(group) == NotInCache){
+ /* find first empty slot or End */
+ for(ngp = ng_cache_ptr; ngp->name; ngp++)
+ ;/* do nothing */
+ if(ngp->val == End){
+ /*
+ * Cache is full, throw away top entry, move everything up,
+ * and put new entry on the bottom.
+ */
+ ngp = ng_cache_ptr;
+ if(ngp->name) /* just making sure */
+ fs_give((void **)&ngp->name);
+
+ for(; (ngp+1)->name; ngp++){
+ ngp->name = (ngp+1)->name;
+ ngp->val = (ngp+1)->val;
+ }
+ }
+ ngp->name = cpystr(group);
+ ngp->val = result;
+ }
+ else{
+ /*
+ * Move this entry from current location to last to preserve
+ * LRU order.
+ */
+ for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
+ if(strcmp(group, ngp->name) == 0) /* found it */
+ break;
+ }
+ save_ngp.name = ngp->name;
+ save_ngp.val = ngp->val;
+ for(; (ngp+1)->name; ngp++){
+ ngp->name = (ngp+1)->name;
+ ngp->val = (ngp+1)->val;
+ }
+ ngp->name = save_ngp.name;
+ ngp->val = save_ngp.val;
+ }
+}
+
+
+void
+free_newsgrp_cache(void)
+{
+ register NgCache *ngp;
+
+ for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++)
+ fs_give((void **)&ngp->name);
+ if(ng_cache_ptr)
+ fs_give((void **)&ng_cache_ptr);
+}
diff --git a/pith/news.h b/pith/news.h
new file mode 100644
index 00000000..95ba4026
--- /dev/null
+++ b/pith/news.h
@@ -0,0 +1,37 @@
+/*
+ * $Id: news.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_NEWS_INCLUDED
+#define PITH_NEWS_INCLUDED
+
+
+#include "../pith/conftype.h"
+
+
+/*
+ * Useful macro to test if current folder is a bboard type (meaning
+ * netnews for now) collection...
+ */
+#define IS_NEWS(S) ((S) ? ns_test((S)->mailbox, "news") : 0)
+
+
+/* exported protoypes */
+int ns_test(char *, char *);
+int news_in_folders(struct variable *);
+int news_grouper(char *, char **, char **, char **, void (*)(void));
+void free_newsgrp_cache(void);
+
+
+#endif /* PITH_NEWS_INCLUDED */
diff --git a/pith/options.h b/pith/options.h
new file mode 100644
index 00000000..a4ed3fd3
--- /dev/null
+++ b/pith/options.h
@@ -0,0 +1,228 @@
+/*
+ * $Id: options.h 101 2006-08-10 22:53:04Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OPTIONS_INCLUDED
+#define PITH_OPTIONS_INCLUDED
+
+/*
+ * function hooks to fill in optional/user-selectable behaviors in pith functions
+ *
+ * You'll find a very brief explanation of what they do here, but you'll need to
+ * look at the places they're called to fully understand how they're intended to
+ * be called and what they're intended to provide.
+ */
+
+
+#include "indxtype.h" /* for ICE_S */
+#include "thread.h" /* for PINETHRD_S */
+#include "handle.h" /* for HANDLE_S */
+#include "filttype.h" /* for gf_io_t */
+#include "state.h" /* for struct pine */
+#include "adrbklib.h" /* for SAVE_STATE_S */
+#ifdef ENABLE_LDAP
+#include "ldap.h"
+#endif
+
+
+/*
+ * optional call in mailindx.c:{from,subject}_str to shrink thread
+ * relationship cue if desired
+ */
+extern int (*pith_opt_condense_thread_cue)(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
+
+
+/*
+ * optional call in mailindx.c:setup_header_widths to save various bits
+ * of format state
+ */
+extern void (*pith_opt_save_index_state)(int);
+
+
+/*
+ * optional call in mailindx.c:load_overview to paint the gathered overview data
+ * on the display
+ */
+extern void (*pith_opt_paint_index_hline)(MAILSTREAM *, long, ICE_S *);
+
+/*
+ * optional hook in mailview.c:format_message to allow for inserting an
+ * [editorial commment] in message text to indicate the message contains
+ * list-management pointers
+ */
+extern int (*pith_opt_rfc2369_editorial)(long, HANDLE_S **, int, int, gf_io_t);
+
+#ifdef ENABLE_LDAP
+/*
+ * optional hook in ldap.c:wp_lookups to ask user where to save chosen LDAP result
+ */
+extern void (*pith_opt_save_ldap_entry)(struct pine *, LDAP_CHOOSE_S *, int);
+#endif
+
+/*
+ * optional hook in addrbook.c:bunch-of-funcs to allow saving/restoring
+ * state (screen state and such) before and after calls that might be
+ * reentered
+ */
+extern void (*pith_opt_save_and_restore)(int, SAVE_STATE_S *);
+
+/*
+ * optional hooks in newmail.c:new_mail to allow for various indicators
+ * during the new mail check/arrival and checkpoint process
+ */
+extern void (*pith_opt_newmail_announce)(MAILSTREAM *, long, long);
+extern void (*pith_opt_newmail_check_cue)(int);
+extern void (*pith_opt_checkpoint_cue)(int);
+extern void (*pith_opt_icon_text)(char *, int);
+
+/*
+ * optional hook in remote.c to provide name for storing address book
+ * metadata
+ */
+extern char *(*pith_opt_rd_metadata_name)(void);
+
+/*
+ * optional hook in conf.c:read_pinerc to let caller deal with hard
+ * unreadable remote config file error
+ * Return TRUE to continue, FALSE otherwise
+ */
+extern int (*pith_opt_remote_pinerc_failure)(void);
+
+/*
+ * optional hook in mailcmd.c:do_broach_folder allowing for user prompt
+ * of closed folder open.
+ * Return -1 on cancel, zero otherwise. Set second arg by reference
+ * to TRUE for reopen.
+ */
+extern int (*pith_opt_reopen_folder)(struct pine *, int *);
+
+/*
+ * optional call in mailcmd.c:expunge_and_close to prompt for read message removal
+ */
+extern int (*pith_opt_read_msg_prompt)(long, char *);
+
+/*
+ * optional hook in mailcmd.c:expunge_and_close to prompt for expunge
+ * confirmation. Return 'y' to expunge/delete. Do not allow cancel.
+ */
+extern int (*pith_opt_expunge_prompt)(MAILSTREAM *, char *, long);
+
+/*
+ * optional hook in mailcmd.c:expunge_and_close called when a folder is
+ * about to be closed and expunged. Flags passed to expunge_and_close are
+ * in turn passed in this call.
+ */
+extern void (*pith_opt_begin_closing)(int, char *);
+
+/*
+ * optional hook in reply.c:reply_harvet to allow for user selection
+ * of reply-to vs. from address
+ * Return 'y' to use "reply-to" field.
+ */
+extern int (*pith_opt_replyto_prompt)(void);
+
+
+/*
+ * optional hook in reply.c:reply_harvet to allow for user choice
+ * of reply to all vs just sender
+ * Return -1 to cancel reply altogether, set reply flag by reference
+ */
+extern int (*pith_opt_reply_to_all_prompt)(int *);
+
+
+/*
+ * optional hook in save.c:create_for_save to allow for user confirmation
+ * of folder being created.
+ * Return: 1 for proceed, -1 for decline, 0 for error
+ */
+extern int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
+
+
+/*
+ * optional hook in send.c:check_addresses to allow for user confirmation
+ * of sending to MAILER-DAEMON
+ */
+extern int (*pith_opt_daemon_confirm)(void);
+
+
+/*
+ * optional hook in save.c to prompt for permission to continue save
+ * in spite of size error. Return 'y' to continue or 'a' to answer
+ * yes to all until next reinitialization of the function.
+ */
+extern int (*pith_opt_save_size_changed_prompt)(long, int);
+
+
+/*
+ * optional hook to process filter patterns using external command
+ * on message contents
+ */
+extern void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
+
+
+/*
+ * Hook to read signature from local file
+ */
+extern char *(*pith_opt_get_signature_file)(char *, int, int, int);
+
+
+/*
+ * Hook to make variable names pretty in help text
+ */
+extern char *(*pith_opt_pretty_var_name)(char *);
+
+
+/*
+ * Hook to make feature names pretty in help text
+ */
+extern char *(*pith_opt_pretty_feature_name)(char *, int);
+
+
+/*
+ * optional hook in mailindx.c:{from,subject}_str to cause the returned
+ * string to be truncated at the formatted width. 1 truncates.
+ * This is useful because the truncated string ends slightly
+ * differently than it would if it weren't truncated, for
+ * example it might end ... or something like that.
+ */
+extern int (*pith_opt_truncate_sfstr)(void);
+
+
+/*
+ * A stream is being closed. If there is something in the
+ * application that needs to react to that handle it here.
+ */
+extern void (*pith_opt_closing_stream)(MAILSTREAM *);
+
+
+/*
+ * Callback from mm_expunged to let us know the "current"
+ * message was expunged
+ */
+extern void (*pith_opt_current_expunged)(long unsigned int);
+
+/*
+ * Option User-Agent Header Prefix
+ */
+extern char *(*pith_opt_user_agent_prefix)(void);
+
+
+/*
+ * optional call to prompt for S/MIME passphase
+ */
+extern int (*pith_opt_smime_get_passphrase)(void);
+
+
+#endif /* PITH_OPTIONS_INCLUDED */
diff --git a/pith/osdep/Makefile.am b/pith/osdep/Makefile.am
new file mode 100644
index 00000000..1d790f3c
--- /dev/null
+++ b/pith/osdep/Makefile.am
@@ -0,0 +1,22 @@
+## Process this file with automake to produce Makefile.in
+## Use aclocal -I m4; automake
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+noinst_LIBRARIES = libpithosd.a
+
+libpithosd_a_SOURCES = bldpath.c canaccess.c canonicl.c collate.c color.c coredump.c \
+ creatdir.c debugtime.c domnames.c err_desc.c fgetpos.c filesize.c \
+ fnexpand.c hostname.c lstcmpnt.c mimedisp.c pipe.c pw_stuff.c \
+ rename.c tempfile.c temp_nam.c writ_dir.c
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
diff --git a/pith/osdep/Makefile.in b/pith/osdep/Makefile.in
new file mode 100644
index 00000000..436101c8
--- /dev/null
+++ b/pith/osdep/Makefile.in
@@ -0,0 +1,558 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = pith/osdep
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/VERSION \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+libpithosd_a_AR = $(AR) $(ARFLAGS)
+libpithosd_a_LIBADD =
+am_libpithosd_a_OBJECTS = bldpath.$(OBJEXT) canaccess.$(OBJEXT) \
+ canonicl.$(OBJEXT) collate.$(OBJEXT) color.$(OBJEXT) \
+ coredump.$(OBJEXT) creatdir.$(OBJEXT) debugtime.$(OBJEXT) \
+ domnames.$(OBJEXT) err_desc.$(OBJEXT) fgetpos.$(OBJEXT) \
+ filesize.$(OBJEXT) fnexpand.$(OBJEXT) hostname.$(OBJEXT) \
+ lstcmpnt.$(OBJEXT) mimedisp.$(OBJEXT) pipe.$(OBJEXT) \
+ pw_stuff.$(OBJEXT) rename.$(OBJEXT) tempfile.$(OBJEXT) \
+ temp_nam.$(OBJEXT) writ_dir.$(OBJEXT)
+libpithosd_a_OBJECTS = $(am_libpithosd_a_OBJECTS)
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(libpithosd_a_SOURCES)
+DIST_SOURCES = $(libpithosd_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@
+AM_LDFLAGS = @AM_LDFLAGS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CP = @CP@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+C_CLIENT_CFLAGS = @C_CLIENT_CFLAGS@
+C_CLIENT_GCCOPTLEVEL = @C_CLIENT_GCCOPTLEVEL@
+C_CLIENT_LDFLAGS = @C_CLIENT_LDFLAGS@
+C_CLIENT_SPECIALS = @C_CLIENT_SPECIALS@
+C_CLIENT_TARGET = @C_CLIENT_TARGET@
+C_CLIENT_WITH_IPV6 = @C_CLIENT_WITH_IPV6@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISPELLPROG = @ISPELLPROG@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN = @LN@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKE = @MAKE@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NPA_PROG = @NPA_PROG@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PWPROG = @PWPROG@
+RANLIB = @RANLIB@
+REGEX_BUILD = @REGEX_BUILD@
+RM = @RM@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPELLPROG = @SPELLPROG@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEB_BINDIR = @WEB_BINDIR@
+WEB_BUILD = @WEB_BUILD@
+WEB_PUBCOOKIE_BUILD = @WEB_PUBCOOKIE_BUILD@
+WEB_PUBCOOKIE_LIB = @WEB_PUBCOOKIE_LIB@
+WEB_PUBCOOKIE_LINK = @WEB_PUBCOOKIE_LINK@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+acx_pthread_config = @acx_pthread_config@
+alpine_interactive_spellcheck = @alpine_interactive_spellcheck@
+alpine_simple_spellcheck = @alpine_simple_spellcheck@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libpithosd.a
+libpithosd_a_SOURCES = bldpath.c canaccess.c canonicl.c collate.c color.c coredump.c \
+ creatdir.c debugtime.c domnames.c err_desc.c fgetpos.c filesize.c \
+ fnexpand.c hostname.c lstcmpnt.c mimedisp.c pipe.c pw_stuff.c \
+ rename.c tempfile.c temp_nam.c writ_dir.c
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign pith/osdep/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign pith/osdep/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libpithosd.a: $(libpithosd_a_OBJECTS) $(libpithosd_a_DEPENDENCIES)
+ -rm -f libpithosd.a
+ $(libpithosd_a_AR) libpithosd.a $(libpithosd_a_OBJECTS) $(libpithosd_a_LIBADD)
+ $(RANLIB) libpithosd.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bldpath.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/canaccess.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/canonicl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/collate.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/coredump.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/creatdir.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debugtime.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/domnames.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/err_desc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fgetpos.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filesize.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fnexpand.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hostname.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lstcmpnt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mimedisp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pipe.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pw_stuff.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rename.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/temp_nam.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tempfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/writ_dir.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-noinstLIBRARIES ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pith/osdep/ReadMe b/pith/osdep/ReadMe
new file mode 100644
index 00000000..30d9c876
--- /dev/null
+++ b/pith/osdep/ReadMe
@@ -0,0 +1,13 @@
+
+README FOR DIRECTORY: pith/osdep/
+
+ Modules in this directory should provide OS-independent interfaces
+for various OS-dependent methods of getting at resources or whatever.
+They should compile on their own without pine, c-client or pico
+dependencies.
+
+NOTES:
+
+ "_WINDOWS" currently differentiates windows from unix code.
+
+ "BUG:" comments mean there's something that needs to get worked on
diff --git a/pith/osdep/bldpath.c b/pith/osdep/bldpath.c
new file mode 100644
index 00000000..62d59e30
--- /dev/null
+++ b/pith/osdep/bldpath.c
@@ -0,0 +1,190 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: bldpath.c 934 2008-02-23 00:44:29Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#if STDC_HEADERS
+#include <ctype.h>
+#endif
+
+#include "bldpath.h"
+
+/*
+ * Useful definitions
+ */
+#ifdef _WINDOWS
+#define ROOTED(S) (*(S) == '\\' || (isalpha((unsigned char) (S)[0]) && (S)[1] == ':'))
+#define HOMERELATIVE(S) (FALSE)
+#else /* UNIX */
+#define ROOTED(S) (*(S) == '/')
+#define HOMERELATIVE(S) (*(S) == '~')
+#endif
+
+
+
+
+/*----------------------------------------------------------------------
+ Paste together two pieces of a file name path
+
+ Args: pathbuf -- Put the result here
+ first_part -- of path name
+ second_part -- of path name
+ len -- Length of pathbuf
+
+ Result: New path is in pathbuf. Note that
+ we don't have to check for /'s at end of first_part and beginning
+ of second_part since multiple slashes are ok.
+
+BUGS: This is a first stab at dealing with fs naming dependencies, and others
+still exist.
+ ----*/
+void
+build_path(char *pathbuf, char *first_part, char *second_part, size_t len)
+{
+ if(!(pathbuf && len > 0))
+ return;
+
+ pathbuf[0] = '\0';
+
+ if(!first_part || is_rooted_path(second_part)){
+ if(second_part)
+ strncpy(pathbuf, second_part, len-1);
+
+ pathbuf[len-1] = '\0';
+ }
+ else{
+#ifdef _WINDOWS
+ int i;
+ char *orig_pathbuf = pathbuf;
+
+ for(i = 0; i < len-2 && first_part[i]; i++)
+ *pathbuf++ = first_part[i];
+
+ if(second_part){
+ if(i && first_part[i-1] == '\\'){ /* first part ended with \ */
+ if(*second_part == '\\') /* and second starts with \ */
+ second_part++; /* else just append second */
+ }
+ else if(*second_part != '\\') /* no slash at all, so */
+ *pathbuf++ = '\\'; /* insert one... */
+
+ while(pathbuf-orig_pathbuf < len-1 && *second_part)
+ *pathbuf++ = *second_part++;
+ }
+
+ *pathbuf = '\0';
+
+#else /* UNIX */
+
+ size_t fpl = 0;
+
+ strncpy(pathbuf, first_part, len-2);
+ pathbuf[len-2] = '\0';
+ if(second_part){
+ if(*pathbuf && pathbuf[(fpl=strlen(pathbuf))-1] != '/'){
+ pathbuf[fpl++] = '/';
+ pathbuf[fpl] = '\0';
+ }
+
+ strncat(pathbuf, second_part, len-1-strlen(pathbuf));
+ }
+
+ pathbuf[len-1] = '\0';
+
+#endif
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Test to see if the given file path is absolute
+
+ Args: file -- file path to test
+
+ Result: TRUE if absolute, FALSE otw
+
+ ----*/
+int
+is_absolute_path(char *path)
+{
+ return(path && (ROOTED(path) || HOMERELATIVE(path)));
+}
+
+
+/*
+ * homedir_in_path - return pointer to point in path string where home dir
+ * reference starts
+ */
+int
+is_rooted_path(char *path)
+{
+ return(path && ROOTED(path));
+}
+
+
+/*
+ * homedir_in_path - return pointer to point in path string where home dir
+ * reference starts
+ */
+int
+is_homedir_path(char *path)
+{
+ return(path && HOMERELATIVE(path));
+}
+
+
+/*
+ * homedir_in_path - return pointer to point in path string where home dir
+ * reference starts
+ */
+char *
+homedir_in_path(char *path)
+{
+#ifdef _WINDOWS
+ return(NULL);
+#else /* UNIX */
+ char *p;
+
+ return((p = strchr(path, '~')) ? p : NULL);
+#endif
+}
+
+
+/*
+ *
+ */
+char *
+filename_parent_ref(char *s)
+{
+#ifdef _WINDOWS
+ return(strstr(s, "\\..\\"));
+#else /* UNIX */
+ return(strstr(s, "/../"));
+#endif
+}
+
+
+int
+filename_is_restricted(char *s)
+{
+#ifdef _WINDOWS
+ return(filename_parent_ref(s) != NULL);
+#else /* UNIX */
+ return(strchr("./~", s[0]) != NULL || filename_parent_ref(s) != NULL);
+#endif
+}
diff --git a/pith/osdep/bldpath.h b/pith/osdep/bldpath.h
new file mode 100644
index 00000000..16ec8226
--- /dev/null
+++ b/pith/osdep/bldpath.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: bldpath.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_BLDPATH_INCLUDED
+#define PITH_OSDEP_BLDPATH_INCLUDED
+
+
+void build_path(char *, char *, char *, size_t);
+int is_absolute_path(char *);
+int is_rooted_path(char *);
+int is_homedir_path(char *);
+char *homedir_in_path(char *);
+char *filename_parent_ref(char *);
+int filename_is_restricted(char *);
+
+
+#endif /* PITH_OSDEP_BLDPATH_INCLUDED */
diff --git a/pith/osdep/canaccess.c b/pith/osdep/canaccess.c
new file mode 100644
index 00000000..4d79c407
--- /dev/null
+++ b/pith/osdep/canaccess.c
@@ -0,0 +1,160 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: canaccess.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "bldpath.h"
+#include "fnexpand.h"
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "canaccess.h"
+
+
+/*
+ * Useful definitions
+ */
+#ifdef _WINDOWS
+
+#define ACCESS_IN_CWD(F,M) (can_access((F), (M)))
+#define PATH_SEP ';'
+#define FILE_SEP '\\'
+
+#else /* UNIX */
+
+#define ACCESS_IN_CWD(F,M) (-1)
+#define PATH_SEP ':'
+#define FILE_SEP '/'
+
+#endif /* UNIX */
+
+
+
+/*
+ * Check if we can access a file in a given way
+ *
+ * Args: file -- The file to check
+ * mode -- The mode ala the access() system call, see ACCESS_EXISTS
+ * and friends in alpine.h.
+ *
+ * Result: returns 0 if the user can access the file according to the mode,
+ * -1 if he can't (and errno is set).
+ *
+ *
+ */
+int
+can_access(char *file, int mode)
+{
+#ifdef _WINDOWS
+ struct stat buf;
+
+ /*
+ * NOTE: The WinNT access call returns that every directory is readable and
+ * writable. We actually want to know if the write is going to fail, so we
+ * try it. We don't read directories in Windows so we skip implementing that.
+ */
+ if(mode & WRITE_ACCESS && file && !our_stat(file, &buf) && (buf.st_mode & S_IFMT) == S_IFDIR){
+ char *testname;
+ int fd;
+ size_t l = 0;
+
+ /*
+ * We'd like to just call temp_nam here, since it creates a file
+ * and does what we want. However, temp_nam calls us!
+ */
+ if((testname = malloc(MAXPATH * sizeof(char)))){
+ strncpy(testname, file, MAXPATH-1);
+ testname[MAXPATH-1] = '\0';
+ if(testname[0] && testname[(l=strlen(testname))-1] != '\\' &&
+ l+1 < MAXPATH){
+ l++;
+ strncat(testname, "\\", MAXPATH-strlen(testname)-1);
+ testname[MAXPATH-1] = '\0';
+ }
+
+ if(l+8 < MAXPATH &&
+ strncat(testname, "caXXXXXX", MAXPATH-strlen(testname)-1) && mktemp(testname)){
+ if((fd = our_open(testname, O_CREAT|O_EXCL|O_WRONLY|O_BINARY, 0600)) >= 0){
+ (void)close(fd);
+ our_unlink(testname);
+ free(testname);
+ /* success, drop through to access call */
+ }
+ else{
+ free(testname);
+ /* can't write in the directory */
+ return(-1);
+ }
+ }
+ else{
+ free(testname);
+ return(-1);
+ }
+ }
+ }
+ if(mode & EXECUTE_ACCESS) /* Windows access has no execute mode */
+ mode &= ~EXECUTE_ACCESS; /* and crashes because of it */
+#endif /* WINDOWS */
+
+ return(our_access(file, mode));
+}
+
+
+/*----------------------------------------------------------------------
+ Check if we can access a file in a given way in the given path
+
+ Args: path -- The path to look for "file" in
+ file -- The file to check
+ mode -- The mode ala the access() system call, see ACCESS_EXISTS
+ and friends in alpine.h.
+
+ Result: returns 0 if the user can access the file according to the mode,
+ -1 if he can't (and errno is set).
+ ----*/
+int
+can_access_in_path(char *path, char *file, int mode)
+{
+ char tmp[MAXPATH];
+ int rv = -1;
+
+ if(!path || !*path || is_rooted_path(file)){
+ rv = can_access(file, mode);
+ }
+ else if(is_homedir_path(file)){
+ strncpy(tmp, file, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ rv = fnexpand(tmp, sizeof(tmp)) ? can_access(tmp, mode) : -1;
+ }
+ else if((rv = ACCESS_IN_CWD(file,mode)) < 0){
+ char path_copy[MAXPATH + 1], *p, *t;
+
+ if(strlen(path) < MAXPATH){
+ strncpy(path_copy, path, sizeof(path_copy));
+ path_copy[sizeof(path_copy)-1] = '\0';
+
+ for(p = path_copy; p && *p; p = t){
+ if((t = strchr(p, PATH_SEP)) != NULL)
+ *t++ = '\0';
+
+ snprintf(tmp, sizeof(tmp), "%s%c%s", p, FILE_SEP, file);
+ if((rv = can_access(tmp, mode)) == 0)
+ break;
+ }
+ }
+ }
+
+ return(rv);
+}
diff --git a/pith/osdep/canaccess.h b/pith/osdep/canaccess.h
new file mode 100644
index 00000000..13ff7cf1
--- /dev/null
+++ b/pith/osdep/canaccess.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: canaccess.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_CANACCESS_INCLUDED
+#define PITH_OSDEP_CANACCESS_INCLUDED
+
+
+#define EXECUTE_ACCESS 0x01 /* These five are */
+#define WRITE_ACCESS 0x02 /* for the calls */
+#define READ_ACCESS 0x04 /* to access() */
+#define ACCESS_EXISTS 0x00 /* <etc> */
+#define EDIT_ACCESS (WRITE_ACCESS + READ_ACCESS)
+
+/*
+ * These flags are used in calls to so_get.
+ * They're OR'd together with the ACCESS flags above but
+ * are otherwise unrelated.
+ */
+#define OWNER_ONLY 0x08 /* open with mode 0600 */
+/*
+ * If the storage object being written to needs to be converted to
+ * the character set of the user's locale, then use this flag. For
+ * example, when exporting to a file. Do not use this if the data
+ * will eventually go to the display because the data is expected
+ * to remain as UTF-8 until it gets to Writechar where it will be
+ * converted.
+ */
+#define WRITE_TO_LOCALE 0x10 /* convert to locale-specific charset */
+#define READ_FROM_LOCALE 0x20 /* convert from locale-specific charset */
+
+
+/*
+ * Exported Prototypes
+ */
+int can_access(char *, int);
+int can_access_in_path(char *, char *, int);
+
+
+#endif /* PITH_OSDEP_CANACCESS_INCLUDED */
diff --git a/pith/osdep/canonicl.c b/pith/osdep/canonicl.c
new file mode 100644
index 00000000..09791148
--- /dev/null
+++ b/pith/osdep/canonicl.c
@@ -0,0 +1,71 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: canonicl.c 764 2007-10-23 23:44:49Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../../c-client/mail.h"
+extern unsigned char *lcase(unsigned char *);
+
+#ifdef _WINDOWS
+/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */
+#undef ERROR
+#endif
+
+#include <system.h>
+
+#include "canonicl.h"
+
+
+/*----------------------------------------------------------------------
+ Return canonical form of host name ala c-client (UNIX version).
+
+ Args: host -- The host name
+
+ Result: Canonical form, or input argument (worst case)
+
+ You can call it twice without worrying about copying
+ the results, but not more than twice.
+ ----*/
+char *
+canonical_name(char *host)
+{
+ struct hostent *hent;
+ char tmp[MAILTMPLEN];
+ static int whichbuf = 0;
+ static char buf[2][NETMAXHOST+1];
+ char *b;
+
+ whichbuf = (whichbuf + 1) % 2;
+ b = buf[whichbuf];
+
+ /* domain literal is easy */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']')
+ strncpy(b, host, NETMAXHOST);
+ else{
+ strncpy(tmp, host, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ hent = gethostbyname((char *) lcase((unsigned char *) tmp));
+ if(hent && hent->h_name)
+ strncpy(b, hent->h_name, NETMAXHOST);
+ else
+ strncpy(b, host, NETMAXHOST);
+ }
+
+ b[NETMAXHOST] = '\0';
+ return(b);
+}
+
+
diff --git a/pith/osdep/canonicl.h b/pith/osdep/canonicl.h
new file mode 100644
index 00000000..97c05b93
--- /dev/null
+++ b/pith/osdep/canonicl.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: canonicl.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_CANONICL_INCLUDED
+#define PITH_OSDEP_CANONICL_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *canonical_name(char *);
+
+
+#endif /* PITH_OSDEP_CANONICL_INCLUDED */
diff --git a/pith/osdep/collate.c b/pith/osdep/collate.c
new file mode 100644
index 00000000..9a60379c
--- /dev/null
+++ b/pith/osdep/collate.c
@@ -0,0 +1,170 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: collate.c 766 2007-10-23 23:59:00Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#include "collate.h"
+
+
+/*
+ * global hook
+ */
+int (*pcollator)();
+
+
+void
+set_collation(int collation, int ctype)
+{
+ extern int collator(); /* set to strcoll if available in system.h */
+
+ pcollator = strucmp;
+
+#ifdef LC_COLLATE
+ if(collation){
+ char *status = NULL;
+
+ /*
+ * This may not have the desired effect, if collator is not
+ * defined to be strcoll in os.h and strcmp and friends
+ * don't know about locales. If your system does have strcoll
+ * but we haven't defined collator to be strcoll in os.h, let us know.
+ */
+ status = setlocale(LC_COLLATE, "");
+
+ /*
+ * If there is an error or if the locale is the "C" locale, then we
+ * don't want to use strcoll because in the default "C" locale strcoll
+ * uses strcmp ordering and we want strucmp ordering.
+ *
+ * The problem with this is that setlocale returns a string which is
+ * not equal to "C" on some systems even when the locale is "C", so we
+ * can't really tell on those systems. On some systems like that, we
+ * may end up with a strcmp-style collation instead of a strucmp-style.
+ * We recommend that the users of those systems explicitly set
+ * LC_COLLATE in their environment.
+ */
+ if(status && !(status[0] == 'C' && status[1] == '\0'))
+ pcollator = collator;
+ }
+#endif
+#ifdef LC_CTYPE
+ if(ctype){
+ (void)setlocale(LC_CTYPE, "");
+ }
+#endif
+
+#ifdef LC_TIME
+ setlocale(LC_TIME, "");
+#endif
+}
+
+
+/*
+ * sstrcasecmp - compare two pointers to strings case independently
+ */
+int
+sstrcasecmp(const qsort_t *s1, const qsort_t *s2)
+{
+ return((*pcollator)(*(char **)s1, *(char **)s2));
+}
+
+
+#ifndef _WINDOWS
+
+/*--------------------------------------------------
+ A case insensitive strcmp()
+
+ Args: o, r -- The two strings to compare
+
+ Result: integer indicating which is greater
+ ---*/
+int
+strucmp(register char *o, register char *r)
+{
+ if(o == NULL){
+ if(r == NULL)
+ return 0;
+ else
+ return -1;
+ }
+ else if(r == NULL)
+ return 1;
+
+ while(*o && *r
+ && ((isupper((unsigned char)(*o))
+ ? (unsigned char)tolower((unsigned char)(*o))
+ : (unsigned char)(*o))
+ == (isupper((unsigned char)(*r))
+ ? (unsigned char)tolower((unsigned char)(*r))
+ : (unsigned char)(*r)))){
+ o++;
+ r++;
+ }
+
+ return((isupper((unsigned char)(*o))
+ ? tolower((unsigned char)(*o))
+ : (int)(unsigned char)(*o))
+ - (isupper((unsigned char)(*r))
+ ? tolower((unsigned char)(*r))
+ : (int)(unsigned char)(*r)));
+}
+
+/*----------------------------------------------------------------------
+ A case insensitive strncmp()
+
+ Args: o, r -- The two strings to compare
+ n -- length to stop comparing strings at
+
+ Result: integer indicating which is greater
+
+ ----*/
+int
+struncmp(register char *o, register char *r, register int n)
+{
+ if(n < 1)
+ return 0;
+
+ if(o == NULL){
+ if(r == NULL)
+ return 0;
+ else
+ return -1;
+ }
+ else if(r == NULL)
+ return 1;
+
+ n--;
+ while(n && *o && *r
+ && ((isupper((unsigned char)(*o))
+ ? (unsigned char)tolower((unsigned char)(*o))
+ : (unsigned char)(*o))
+ == (isupper((unsigned char)(*r))
+ ? (unsigned char)tolower((unsigned char)(*r))
+ : (unsigned char)(*r)))){
+ o++;
+ r++;
+ n--;
+ }
+
+ return((isupper((unsigned char)(*o))
+ ? tolower((unsigned char)(*o))
+ : (int)(unsigned char)(*o))
+ - (isupper((unsigned char)(*r))
+ ? tolower((unsigned char)(*r))
+ : (int)(unsigned char)(*r)));
+}
+#endif
diff --git a/pith/osdep/collate.h b/pith/osdep/collate.h
new file mode 100644
index 00000000..80aaab90
--- /dev/null
+++ b/pith/osdep/collate.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: collate.h 925 2008-02-06 02:03:01Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_COLLATE_INCLUDED
+#define PITH_OSDEP_COLLATE_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+void set_collation(int, int);
+int strucmp(char *, char *);
+int struncmp(char *, char *, int);
+int sstrcasecmp(const qsort_t *, const qsort_t *);
+
+extern int (*pcollator)();
+
+
+#endif /* PITH_OSDEP_COLLATE_INCLUDED */
diff --git a/pith/osdep/color.c b/pith/osdep/color.c
new file mode 100644
index 00000000..faf3c675
--- /dev/null
+++ b/pith/osdep/color.c
@@ -0,0 +1,93 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: color.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+ /*
+ *
+ * These routines themselves aren't necessarily OS-specific, they
+ * are all called from within pico, pine and webpine.
+ *
+ * They used to be in pico source (osdep/unix, mswin.c), but considering
+ * webpine uses color as well and it should *not* have to be linked
+ * against libpico and considering pico uses these routines but should
+ * not have to link against libpith (and in turn c-client) we put them
+ * in pith/osdep which should only have to link against system libraries
+ * and thus be include freely in all of pine, pico and webpine.
+ */
+
+
+#include <system.h>
+#include "./color.h"
+
+
+
+/*
+ * new_color_pair - allocate a new color pair structure assigning
+ * given foreground and background color strings
+ */
+COLOR_PAIR *
+new_color_pair(char *fg, char *bg)
+{
+ COLOR_PAIR *ret;
+
+ if((ret = (COLOR_PAIR *) malloc(sizeof(*ret))) != NULL){
+ memset(ret, 0, sizeof(*ret));
+ if(fg){
+ strncpy(ret->fg, fg, MAXCOLORLEN);
+ ret->fg[MAXCOLORLEN] = '\0';
+ }
+
+ if(bg){
+ strncpy(ret->bg, bg, MAXCOLORLEN);
+ ret->bg[MAXCOLORLEN] = '\0';
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * free_color_pair - release resources associated with given
+ * color pair structure
+ */
+void
+free_color_pair(COLOR_PAIR **cp)
+{
+ if(cp && *cp){
+ free(*cp);
+ *cp = NULL;
+ }
+}
+
+
+/*
+ * Just like pico_set_color except it doesn't set the color, it just
+ * returns the value. Assumes def of PSC_NONE, since otherwise we always
+ * succeed and don't need to call this.
+ */
+int
+pico_is_good_colorpair(COLOR_PAIR *cp)
+{
+ return(cp && pico_is_good_color(cp->fg) && pico_is_good_color(cp->bg));
+}
+
+
+COLOR_PAIR *
+pico_set_colorp(COLOR_PAIR *col, int flags)
+{
+ return(pico_set_colors(col ? col->fg : NULL, col ? col->bg : NULL, flags));
+}
diff --git a/pith/osdep/color.h b/pith/osdep/color.h
new file mode 100644
index 00000000..32c86242
--- /dev/null
+++ b/pith/osdep/color.h
@@ -0,0 +1,98 @@
+/*
+ * $Id: color.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_COLOR_INCLUDED
+#define PITH_OSDEP_COLOR_INCLUDED
+
+
+#define RGBLEN 11
+#define MAXCOLORLEN 11 /* longest string a color can be */
+
+typedef struct COLOR_PAIR {
+ char fg[MAXCOLORLEN+1];
+ char bg[MAXCOLORLEN+1];
+} COLOR_PAIR;
+
+#define COL_BLACK 0
+#define COL_RED 1
+#define COL_GREEN 2
+#define COL_YELLOW 3
+#define COL_BLUE 4
+#define COL_MAGENTA 5
+#define COL_CYAN 6
+#define COL_WHITE 7
+
+#define DEFAULT_NORM_FORE_RGB "000,000,000"
+#define DEFAULT_NORM_BACK_RGB "255,255,255"
+
+/* flags for pico_set_color() */
+#define PSC_NONE 0x0
+#define PSC_NORM 0x1
+#define PSC_REV 0x2
+#define PSC_RET 0x4 /* return an allocated copy of previous color */
+
+
+/*
+ * MATCH_NORM_COLOR means that the color that is set to this value
+ * will actually use the corresponding fg or bg color from the
+ * so called Normal Color. A MATCH_NONE_COLOR means that the
+ * corresponding fg or bg color will just be left alone, so that
+ * it will stay the same as it was. This is useful when you want
+ * to change the foreground color but let the background match
+ * whatever it was before, for example in colored index lines.
+ *
+ * Note: these need to be RGBLEN in length because they are sometimes
+ * used in places where an RGB value is expected.
+ */
+#define MATCH_NORM_COLOR "norm_padded"
+#define MATCH_NONE_COLOR "none_padded"
+
+#define MATCH_TRAN_COLOR "transparent"
+
+
+/* exported prototypes */
+COLOR_PAIR *new_color_pair(char *, char *);
+void free_color_pair(COLOR_PAIR **cp);
+int pico_is_good_colorpair(COLOR_PAIR *);
+COLOR_PAIR *pico_set_colorp(COLOR_PAIR *, int);
+
+
+/* required prototypes (os/app dependent ) */
+int pico_usingcolor(void);
+int pico_hascolor(void);
+char *colorx(int);
+char *color_to_asciirgb(char *);
+int pico_is_good_color(char *);
+COLOR_PAIR *pico_set_colors(char *, char *, int);
+int pico_set_fg_color(char *);
+int pico_set_bg_color(char *);
+void pico_nfcolor(char *);
+void pico_nbcolor(char *);
+void pico_rfcolor(char *);
+void pico_rbcolor(char *);
+COLOR_PAIR *pico_get_cur_color(void);
+COLOR_PAIR *pico_get_rev_color(void);
+void pico_set_normal_color(void);
+void pico_set_color_options(unsigned);
+unsigned pico_get_color_options(void);
+int pico_trans_is_on(void);
+char *pico_get_last_fg_color(void);
+char *pico_get_last_bg_color(void);
+char *color_to_canonical_name(char *);
+int pico_count_in_color_table(void);
+
+
+#endif /* PITH_OSDEP_COLOR_INCLUDED */
diff --git a/pith/osdep/coredump.c b/pith/osdep/coredump.c
new file mode 100644
index 00000000..52ad27d7
--- /dev/null
+++ b/pith/osdep/coredump.c
@@ -0,0 +1,31 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: coredump.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "coredump.h"
+
+
+/*----------------------------------------------------------------------
+ Abort with a core dump
+ ----*/
+void
+coredump(void)
+{
+ abort();
+}
+
+
diff --git a/pith/osdep/coredump.h b/pith/osdep/coredump.h
new file mode 100644
index 00000000..05c95a62
--- /dev/null
+++ b/pith/osdep/coredump.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: coredump.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_COREDUMP_INCLUDED
+#define PITH_OSDEP_COREDUMP_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+void coredump(void);
+
+
+#endif /* PITH_OSDEP_COREDUMP_INCLUDED */
diff --git a/pith/osdep/creatdir.c b/pith/osdep/creatdir.c
new file mode 100644
index 00000000..bc15da21
--- /dev/null
+++ b/pith/osdep/creatdir.c
@@ -0,0 +1,55 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: creatdir.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "creatdir.h"
+
+
+#ifdef S_IRWXU
+#define MAILDIR_MODE S_IRWXU
+#else
+#define MAILDIR_MODE 0700
+#endif
+
+
+
+/*----------------------------------------------------------------------
+ Create the mail subdirectory.
+
+ Args: dir -- Name of the directory to create
+
+ Result: Directory is created. Returns 0 on success, else -1 on error
+ and errno is valid.
+ ----*/
+int
+create_mail_dir(char *dir)
+{
+ if(our_mkdir(dir, MAILDIR_MODE) < 0)
+ return(-1);
+
+#ifndef _WINDOWS
+ our_chmod(dir, MAILDIR_MODE);
+
+ /* Some systems need this, on others we don't care if it fails */
+ our_chown(dir, getuid(), getgid());
+#endif /* !_WINDOWS */
+
+ return(0);
+}
diff --git a/pith/osdep/creatdir.h b/pith/osdep/creatdir.h
new file mode 100644
index 00000000..496d1f6c
--- /dev/null
+++ b/pith/osdep/creatdir.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: creatdir.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_CREATDIR_INCLUDED
+#define PITH_OSDEP_CREATDIR_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+int create_mail_dir(char *);
+
+
+#endif /* PITH_OSDEP_CREATDIR_INCLUDED */
diff --git a/pith/osdep/debugtime.c b/pith/osdep/debugtime.c
new file mode 100644
index 00000000..a1ee3b2a
--- /dev/null
+++ b/pith/osdep/debugtime.c
@@ -0,0 +1,133 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: debugtime.c 770 2007-10-24 00:23:09Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <stdio.h>
+
+#include <system.h>
+#include "debugtime.h"
+
+#ifdef DEBUG
+
+/*
+ * Returns a pointer to static string for a timestamp.
+ *
+ * If timestamp is set .subseconds are added if available.
+ * If include_date is set the date is appended.
+ */
+char *
+debug_time(int include_date, int include_subseconds)
+{
+ time_t t;
+ struct tm *tm_now;
+#if HAVE_GETTIMEOFDAY
+ struct timeval tp;
+ struct timezone tzp;
+#else
+ struct _timeb timebuffer;
+#endif
+ static char timestring[23];
+ char subsecond[8];
+ char datestr[7];
+
+ timestring[0] = '\0';
+
+#if HAVE_GETTIMEOFDAY
+ if(gettimeofday(&tp, &tzp) == 0){
+ t = (time_t)tp.tv_sec;
+ if(include_date){
+ tm_now = localtime(&t);
+ snprintf(datestr, sizeof(datestr), " %d/%d", tm_now->tm_mon+1, tm_now->tm_mday);
+ }
+ else
+ datestr[0] = '\0';
+
+ if(include_subseconds)
+ snprintf(subsecond, sizeof(subsecond), ".%06ld", tp.tv_usec);
+ else
+ subsecond[0] = '\0';
+
+ snprintf(timestring, sizeof(timestring), "%.8s%.7s%.6s", ctime(&t)+11, subsecond, datestr);
+ }
+#else /* !HAVE_GETTIMEOFDAY */
+ /* Should be _WINDOWS */
+ t = time((time_t *)0);
+ if(include_date){
+ tm_now = localtime(&t);
+ snprintf(datestr, sizeof(datestr), " %d/%d", tm_now->tm_mon+1, tm_now->tm_mday);
+ }
+ else
+ datestr[0] = '\0';
+
+ if(include_subseconds){
+ _ftime(&timebuffer);
+ snprintf(subsecond, sizeof(subsecond), ".%03ld", timebuffer.millitm);
+ }
+ else
+ subsecond[0] = '\0';
+
+ snprintf(timestring, sizeof(timestring), "%.8s%.7s%.6s", ctime(&t)+11, subsecond, datestr);
+#endif /* HAVE_GETTIMEOFDAY */
+
+ return(timestring);
+}
+#endif /* DEBUG */
+
+
+/*
+ * Fills in the passed in structure with the current time.
+ *
+ * Returns 0 if ok
+ * -1 if can't do it
+ */
+int
+get_time(TIMEVAL_S *our_time_val)
+{
+#if HAVE_GETTIMEOFDAY
+ struct timeval tp;
+ struct timezone tzp;
+
+ if(gettimeofday(&tp, &tzp) == 0){
+ our_time_val->sec = tp.tv_sec;
+ our_time_val->usec = tp.tv_usec;
+ return 0;
+ }
+#else /* !HAVE_GETTIMEOFDAY */
+#ifdef _WINDOWS
+ struct _timeb timebuffer;
+
+ _ftime(&timebuffer);
+ our_time_val->sec = (long)timebuffer.time;
+ our_time_val->usec = 1000L * (long)timebuffer.millitm;
+ return 0;
+#endif
+#endif
+
+ return -1;
+}
+
+
+/*
+ * Returns the difference between the two values, in microseconds.
+ * Value returned is first - second.
+ */
+long
+time_diff(TIMEVAL_S *first, TIMEVAL_S *second)
+{
+ return(1000000L*(first->sec - second->sec) + (first->usec - second->usec));
+}
+
+
diff --git a/pith/osdep/debugtime.h b/pith/osdep/debugtime.h
new file mode 100644
index 00000000..6a3eda1e
--- /dev/null
+++ b/pith/osdep/debugtime.h
@@ -0,0 +1,31 @@
+/*
+ * $Id: debugtime.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_DEBUGTIME_INCLUDED
+#define PITH_OSDEP_DEBUGTIME_INCLUDED
+
+#include "../adjtime.h"
+
+/*
+ * Exported Prototypes
+ */
+#ifdef DEBUG
+char *debug_time(int, int);
+#endif
+int get_time(TIMEVAL_S *);
+long time_diff(TIMEVAL_S *, TIMEVAL_S *);
+
+
+#endif /* PITH_OSDEP_DEBUGTIME_INCLUDED */
diff --git a/pith/osdep/domnames.c b/pith/osdep/domnames.c
new file mode 100644
index 00000000..47a95278
--- /dev/null
+++ b/pith/osdep/domnames.c
@@ -0,0 +1,145 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: domnames.c 1176 2008-09-29 21:16:42Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "domnames.h"
+
+
+/*----------------------------------------------------------------------
+ Get the current host and domain names
+
+ Args: hostname -- buffer to return the hostname in
+ hsize -- size of buffer above
+ domainname -- buffer to return domain name in
+ dsize -- size of buffer above
+
+ Result: The system host and domain names are returned. If the full host
+ name is akbar.cac.washington.edu then the domainname is
+ cac.washington.edu.
+
+On Internet connected hosts this look up uses /etc/hosts and DNS to
+figure all this out. On other less well connected machines some other
+file may be read. If there is no notion of a domain name the domain
+name may be left blank. On a PC where there really isn't a host name
+this should return blank strings. The .pinerc will take care of
+configuring the domain names. That is, this should only return the
+native system's idea of what the names are if the system has such
+a concept.
+ ----*/
+void
+getdomainnames(char *hostname, int hsize, char *domainname, int dsize)
+{
+#if HAVE_NETDB_H
+ char *dn, hname[MAX_ADDRESS+1];
+ struct hostent *he;
+ char **alias;
+ char *maybe = NULL;
+
+ if(gethostname(hname, MAX_ADDRESS))
+ hname[0] = 0xff;
+
+ /* sanity check of hostname string */
+ for(dn = hname; (*dn > 0x20) && (*dn < 0x7f); ++dn)
+ ;
+
+ if(*dn){ /* if invalid string returned, return "unknown" */
+ strncpy(domainname, "unknown", dsize-1);
+ domainname[dsize-1] = '\0';
+ strncpy(hostname, "unknown", hsize-1);
+ hostname[hsize-1] = '\0';
+ return;
+ }
+
+ he = gethostbyname(hname);
+ hostname[0] = '\0';
+
+ if(he == NULL){
+ strncpy(hostname, hname, hsize-1);
+ hostname[hsize-1] = '\0';
+ }
+ else{
+ /*
+ * If no dot in hostname it may be the case that there
+ * is an alias which is really the fully-qualified
+ * hostname. This could happen if the administrator has
+ * (incorrectly) put the unqualified name first in the
+ * hosts file, for example. The problem with looking for
+ * an alias with a dot is that now we're guessing, since
+ * the aliases aren't supposed to be the official hostname.
+ * We'll compromise and only use an alias if the primary
+ * name has no dot and exactly one of the aliases has a
+ * dot.
+ */
+ strncpy(hostname, he->h_name, hsize-1);
+ hostname[hsize-1] = '\0';
+ if(strchr(hostname, '.') == NULL){ /* no dot in hostname */
+ for(alias = he->h_aliases; *alias; alias++){
+ if(strchr(*alias, '.') != NULL){ /* found one */
+ if(maybe){ /* oops, this is the second one */
+ maybe = NULL;
+ break;
+ }
+ else
+ maybe = *alias;
+ }
+ }
+
+ if(maybe){
+ strncpy(hostname, maybe, hsize-1);
+ hostname[hsize-1] = '\0';
+ }
+ }
+ }
+
+ hostname[hsize-1] = '\0';
+
+ if((dn = strchr(hostname, '.')) != NULL)
+ strncpy(domainname, dn+1, dsize-1);
+ else
+ strncpy(domainname, hostname, dsize-1);
+
+ domainname[dsize-1] = '\0';
+#else /* !HAVE_NETDB_H */
+ /* should only be _WINDOWS */
+
+#ifdef _WINDOWS
+ char *p;
+ extern char *mylocalhost(void);
+
+ hostname[0] = domainname[0] = '\0';
+ if(p = mylocalhost())
+ snprintf(hostname, hsize, "%s", p);
+
+ snprintf(domainname, dsize, "%s",
+ (hostname[0] && hostname[0] != '[' && (p = strchr(hostname,'.')))
+ ? p+1 : hostname);
+#else /* !_WINDOWS */
+
+ char *p, hname[MAX_ADDRESS+1];
+
+ hostname[0] = domainname[0] = '\0';
+ if(gethostname(hname, MAX_ADDRESS) == 0)
+ snprintf(hostname, hsize, "%s", hname);
+
+ snprintf(domainname, dsize, "%s",
+ (hostname[0] && hostname[0] != '[' && (p = strchr(hostname,'.')))
+ ? p+1 : hostname);
+#endif /* !_WINDOWS */
+#endif /* !HAVE_NETDB_H */
+}
diff --git a/pith/osdep/domnames.h b/pith/osdep/domnames.h
new file mode 100644
index 00000000..303561a7
--- /dev/null
+++ b/pith/osdep/domnames.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: domnames.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_DOMNAMES_INCLUDED
+#define PITH_OSDEP_DOMNAMES_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+void getdomainnames(char *, int, char *, int);
+
+
+#endif /* PITH_OSDEP_DOMNAMES_INCLUDED */
diff --git a/pith/osdep/err_desc.c b/pith/osdep/err_desc.c
new file mode 100644
index 00000000..645953e9
--- /dev/null
+++ b/pith/osdep/err_desc.c
@@ -0,0 +1,44 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: err_desc.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "err_desc.h"
+
+
+/*----------------------------------------------------------------------
+ Return string describing the error
+
+ Args: errnumber -- The system error number (errno)
+
+ Result: long string describing the error is returned
+ ----*/
+char *
+error_description(int errnumber)
+{
+ static char buffer[50+1];
+
+ buffer[0] = '\0';
+
+ if(errnumber >= 0)
+ snprintf(buffer, sizeof(buffer), "%s", strerror(errnumber));
+ else
+ snprintf(buffer, sizeof(buffer), "Unknown error #%d", errnumber);
+
+ return ( (char *) buffer);
+}
+
+
diff --git a/pith/osdep/err_desc.h b/pith/osdep/err_desc.h
new file mode 100644
index 00000000..9fff814e
--- /dev/null
+++ b/pith/osdep/err_desc.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: err_desc.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_ERR_DESC_INCLUDED
+#define PITH_OSDEP_ERR_DESC_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *error_description(int);
+
+
+#endif /* PITH_OSDEP_ERR_DESC_INCLUDED */
diff --git a/pith/osdep/fgetpos.c b/pith/osdep/fgetpos.c
new file mode 100644
index 00000000..94dcc6c0
--- /dev/null
+++ b/pith/osdep/fgetpos.c
@@ -0,0 +1,50 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: fgetpos.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <stdio.h>
+#include "fgetpos.h"
+
+
+/*----------------------------------------------------------------------
+ This is just a call to the ANSI C fgetpos function.
+ ----*/
+int
+fget_pos(FILE *stream, fpos_t *ptr)
+{
+#ifdef IS_NON_ANSI
+ *ptr = (fpos_t)ftell(stream);
+ return (*ptr == -1L ? -1 : 0);
+#else /* ANSI */
+ return(fgetpos(stream, ptr));
+#endif /* ANSI */
+}
+
+
+/*----------------------------------------------------------------------
+ This is just a call to the ANSI C fsetpos function.
+ ----*/
+int
+fset_pos(FILE *stream, fpos_t *ptr)
+{
+#ifdef IS_NON_ANSI
+ return fseek(stream, *ptr, 0);
+#else /* ANSI */
+ return(fsetpos(stream, ptr));
+#endif /* ANSI */
+}
+
+
diff --git a/pith/osdep/fgetpos.h b/pith/osdep/fgetpos.h
new file mode 100644
index 00000000..32fb7f33
--- /dev/null
+++ b/pith/osdep/fgetpos.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: fgetpos.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_FGETPOS_INCLUDED
+#define PITH_OSDEP_FGETPOS_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+int fget_pos(FILE *, fpos_t *);
+int fset_pos(FILE *, fpos_t *);
+
+
+#endif /* PITH_OSDEP_FGETPOS_INCLUDED */
diff --git a/pith/osdep/filesize.c b/pith/osdep/filesize.c
new file mode 100644
index 00000000..e96479e9
--- /dev/null
+++ b/pith/osdep/filesize.c
@@ -0,0 +1,123 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: filesize.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "filesize.h"
+
+
+/*----------------------------------------------------------------------
+ Return the number of bytes in given file
+
+ Args: file -- file name
+
+ Result: the number of bytes in the file is returned or
+ -1 on error, in which case errno is valid
+ ----*/
+long
+name_file_size(char *file)
+{
+ struct stat buffer;
+
+ if(our_stat(file, &buffer) != 0)
+ return(-1L);
+
+ return((long)buffer.st_size);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the number of bytes in given file
+
+ Args: fp -- FILE * for open file
+
+ Result: the number of bytes in the file is returned or
+ -1 on error, in which case errno is valid
+ ----*/
+long
+fp_file_size(FILE *fp)
+{
+ struct stat buffer;
+
+ if(fstat(fileno(fp), &buffer) != 0)
+ return(-1L);
+
+ return((long)buffer.st_size);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the modification time of given file
+
+ Args: file -- file name
+
+ Result: the time of last modification (mtime) of the file is returned or
+ -1 on error, in which case errno is valid
+ ----*/
+time_t
+name_file_mtime(char *file)
+{
+ struct stat buffer;
+
+ if(our_stat(file, &buffer) != 0)
+ return((time_t)(-1));
+
+ return(buffer.st_mtime);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the modification time of given file
+
+ Args: fp -- FILE * for open file
+
+ Result: the time of last modification (mtime) of the file is returned or
+ -1 on error, in which case errno is valid
+ ----*/
+time_t
+fp_file_mtime(FILE *fp)
+{
+ struct stat buffer;
+
+ if(fstat(fileno(fp), &buffer) != 0)
+ return((time_t)(-1));
+
+ return(buffer.st_mtime);
+}
+
+
+/*----------------------------------------------------------------------
+ Copy the mode, owner, and group of sourcefile to targetfile.
+
+ Args: targetfile --
+ sourcefile --
+
+ We don't bother keeping track of success or failure because we don't care.
+ ----*/
+void
+file_attrib_copy(char *targetfile, char *sourcefile)
+{
+ struct stat buffer;
+
+ if(our_stat(sourcefile, &buffer) == 0){
+ our_chmod(targetfile, buffer.st_mode);
+#if HAVE_CHOWN
+ our_chown(targetfile, buffer.st_uid, buffer.st_gid);
+#endif
+ }
+}
diff --git a/pith/osdep/filesize.h b/pith/osdep/filesize.h
new file mode 100644
index 00000000..a80297dc
--- /dev/null
+++ b/pith/osdep/filesize.h
@@ -0,0 +1,31 @@
+/*
+ * $Id: filesize.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_FILESIZE_INCLUDED
+#define PITH_OSDEP_FILESIZE_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+long name_file_size(char *);
+long fp_file_size(FILE *);
+time_t name_file_mtime(char *);
+time_t fp_file_mtime(FILE *);
+void file_attrib_copy(char *, char *);
+
+
+
+#endif /* PITH_OSDEP_FILESIZE_INCLUDED */
diff --git a/pith/osdep/fnexpand.c b/pith/osdep/fnexpand.c
new file mode 100644
index 00000000..1856c231
--- /dev/null
+++ b/pith/osdep/fnexpand.c
@@ -0,0 +1,96 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: fnexpand.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#include "../charconv/filesys.h"
+
+#include "fnexpand.h"
+
+
+
+
+/*----------------------------------------------------------------------
+ Expand the ~ in a file ala the csh (as home directory)
+
+ Args: buf -- The filename to expand (nothing happens unless begins with ~)
+ len -- The length of the buffer passed in (expansion is in place)
+
+ Result: Expanded string is returned using same storage as passed in.
+ If expansion fails, NULL is returned
+ ----*/
+char *
+fnexpand(char *buf, int len)
+{
+#ifdef _WINDOWS
+ /* We used to use ps_global->home_dir, now we have to build it */
+ if(*buf == '~' && *(buf+1) == '\\'){
+ char temp_path[_MAX_PATH], home_buf[_MAX_PATH], *temp_home_str;
+
+ if(getenv("HOME") != NULL)
+ temp_home_str = getenv("HOME");
+ else{
+ /* should eventually strip this out of get_user_info */
+ char *p, *q;
+
+ if((p = (char *) getenv("HOMEDRIVE"))
+ && (q = (char *) getenv("HOMEPATH")))
+ snprintf(home_buf, sizeof(home_buf), "%s%s", p, q);
+ else
+ snprintf(home_buf, sizeof(home_buf), "%c:\\", '@' + _getdrive());
+
+ temp_home_str = home_buf;
+ }
+ snprintf(temp_path, sizeof(temp_path), "%s", buf+1);
+ snprintf(buf, sizeof(buf), "%s%s", temp_path, fname_to_utf8(temp_home_str));
+ }
+ return(buf);
+#else /* UNIX */
+ struct passwd *pw;
+ register char *x,*y;
+ char name[20], *tbuf;
+
+ if(*buf == '~') {
+ for(x = buf+1, y = name;
+ *x != '/' && *x != '\0' && y < name + sizeof(name)-1;
+ *y++ = *x++)
+ ;
+
+ *y = '\0';
+ if(x == buf + 1)
+ pw = getpwuid(getuid());
+ else
+ pw = getpwnam(name);
+
+ if(pw == NULL)
+ return((char *)NULL);
+ if(strlen(pw->pw_dir) + strlen(buf) > len) {
+ return((char *)NULL);
+ }
+
+ if((tbuf = (char *) malloc((len+1)*sizeof(char))) != NULL){
+ snprintf(tbuf, len, "%s%s", pw->pw_dir, x);
+ snprintf(buf, len, "%s", tbuf);
+ free((void *)tbuf);
+ }
+ }
+
+ return(len ? buf : (char *)NULL);
+#endif /* UNIX */
+}
+
+
diff --git a/pith/osdep/fnexpand.h b/pith/osdep/fnexpand.h
new file mode 100644
index 00000000..df2dd899
--- /dev/null
+++ b/pith/osdep/fnexpand.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: fnexpand.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_FNEXPAND_INCLUDED
+#define PITH_OSDEP_FNEXPAND_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *fnexpand(char *, int);
+
+
+#endif /* PITH_OSDEP_FNEXPAND_INCLUDED */
diff --git a/pith/osdep/forkwait.h b/pith/osdep/forkwait.h
new file mode 100644
index 00000000..e1aafa84
--- /dev/null
+++ b/pith/osdep/forkwait.h
@@ -0,0 +1,39 @@
+/*
+ * $Id: forkwait.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_FORKWAIT_INCLUDED
+#define PITH_OSDEP_FORKWAIT_INCLUDED
+
+
+#if HAVE_UNION_WAIT
+#define WAITSTATUS_T union wait
+#else
+#define WAITSTATUS_T int
+#endif
+
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(X) (((X) >> 8) & 0xff) /* high bits tell exit value */
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(X) (!((X) & 0xff)) /* low bits tell how it died */
+#endif
+
+#if !HAVE_WORKING_VFORK
+#define vfork fork
+#endif
+
+
+#endif /* PITH_OSDEP_FORKWAIT_INCLUDED */
+
diff --git a/pith/osdep/hostname.c b/pith/osdep/hostname.c
new file mode 100644
index 00000000..6ba445d3
--- /dev/null
+++ b/pith/osdep/hostname.c
@@ -0,0 +1,119 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: hostname.c 1176 2008-09-29 21:16:42Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#if HAVE_GETHOSTNAME
+
+#elif HAVE_UNAME
+
+#include <sys/utsname.h>
+
+#elif defined(SYSTEMID)
+
+#elif defined(XENIX)
+
+#endif
+
+#include "hostname.h"
+
+
+
+/*----------------------------------------------------------------------
+ Call system gethostname
+
+ Args: hostname -- buffer to return host name in
+ size -- Size of buffer hostname is to be returned in
+
+ Result: returns 0 if the hostname is correctly set,
+ -1 if not (and errno is set).
+ ----*/
+int
+hostname(char *hostname, int size)
+{
+#if HAVE_GETHOSTNAME
+
+ if (gethostname(hostname, size))
+ return -1;
+
+ /* sanity check of hostname string */
+ for (*dn = hname; (*dn > 0x20) && (*dn < 0x7f); ++dn)
+ ;
+
+ if (*dn) /* non-ascii in hostname */
+ strncpy(hostname, "unknown", size-1);
+
+ hostname[size - 1] = '\0';
+ return 0;
+
+#elif HAVE_UNAME
+
+ /** This routine compliments of Scott McGregor at the HP
+ Corporate Computing Center **/
+
+ int uname(struct utsname *);
+ struct utsname name;
+
+ (void)uname(&name);
+ (void)strncpy(hostname,name.nodename,size-1);
+
+ hostname[size - 1] = '\0';
+ return 0;
+
+#elif defined(SYSTEMID)
+ char buf[32];
+ FILE *fp;
+ char *p;
+
+ if ((fp = our_fopen("/etc/systemid", "rb")) != 0) {
+ fgets(buf, sizeof(buf) - 1, fp);
+ fclose(fp);
+ if ((p = strindex(buf, '\n')) != NULL)
+ *p = '\0';
+ (void) strncpy(hostname, buf, size - 1);
+ hostname[size - 1] = '\0';
+ return 0;
+ }
+
+#elif defined(XENIX)
+
+#ifdef DOUNAME
+ /** This routine compliments of Scott McGregor at the HP
+ Corporate Computing Center **/
+
+ int uname();
+ struct utsname name;
+
+ (void) uname(&name);
+ (void) strncpy(hostname,name.nodename,size-1);
+#else
+ (void) strncpy(hostname, HOSTNAME, size-1);
+#endif /* DOUNAME */
+
+ hostname[size - 1] = '\0';
+ return 0;
+#else
+ /* We shouldn't get here except for the windows
+ * case, which currently doesn't use this (as
+ * it appears nothing else does as well)
+ */
+ return -1;
+
+#endif
+}
+
+
diff --git a/pith/osdep/hostname.h b/pith/osdep/hostname.h
new file mode 100644
index 00000000..65303ce4
--- /dev/null
+++ b/pith/osdep/hostname.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: hostname.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_HOSTNAME_INCLUDED
+#define PITH_OSDEP_HOSTNAME_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+int hostname(char *, int);
+
+
+#endif /* PITH_OSDEP_HOSTNAME_INCLUDED */
diff --git a/pith/osdep/lstcmpnt.c b/pith/osdep/lstcmpnt.c
new file mode 100644
index 00000000..2a920939
--- /dev/null
+++ b/pith/osdep/lstcmpnt.c
@@ -0,0 +1,103 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: lstcmpnt.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include <string.h>
+#include "../../pith/charconv/filesys.h"
+#include "canaccess.h"
+#include "lstcmpnt.h"
+
+
+#ifdef _WINDOWS
+
+#define FILE_SEP '\\'
+
+#else /* UNIX */
+
+#define FILE_SEP '/'
+
+#endif /* UNIX */
+
+
+
+/*----------------------------------------------------------------------
+ Return pointer to last component of pathname.
+
+ Args: filename -- The pathname.
+
+ Result: Returned pointer points to last component in the input argument.
+ ----*/
+char *
+last_cmpnt(char *filename)
+{
+ char *p = NULL, *q = filename;
+
+ if(filename == NULL)
+ return(filename);
+
+ while((q = strchr(q, FILE_SEP)) != NULL)
+ if(*++q)
+ p = q;
+
+#ifdef _WINDOWS
+
+ if(!p && isalpha((unsigned char) *filename) && *(filename+1) == ':' && *(filename+2))
+ p = filename + 2;
+
+#endif
+
+ return(p);
+}
+
+
+/*
+ * Like our_mkdir but it makes subdirs as well as the final dir
+ */
+int
+our_mkpath(char *path, mode_t mode)
+{
+ char save, *q = path;
+
+#ifdef _WINDOWS
+ if(isalpha((unsigned char) q[0]) && q[1] == ':' && q[2])
+ q = path + 3;
+#endif
+
+ if(q == path && q[0] == FILE_SEP)
+ q = path + 1;
+
+ while((q = strchr(q, FILE_SEP)) != NULL){
+ save = *q;
+ *q = '\0';
+ if(can_access(path, ACCESS_EXISTS) != 0)
+ if(our_mkdir(path, mode) != 0){
+ *q = save;
+ return -1;
+ }
+
+ *q = save;
+ q++;
+ }
+
+ if(can_access(path, ACCESS_EXISTS) != 0 && our_mkdir(path, mode) != 0)
+ return -1;
+
+ return 0;
+}
diff --git a/pith/osdep/lstcmpnt.h b/pith/osdep/lstcmpnt.h
new file mode 100644
index 00000000..f2d9ec59
--- /dev/null
+++ b/pith/osdep/lstcmpnt.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: lstcmpnt.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_LSTCMPNT_INCLUDED
+#define PITH_OSDEP_LSTCMPNT_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *last_cmpnt(char *);
+int our_mkpath(char *, mode_t);
+
+
+#endif /* PITH_OSDEP_LSTCMPNT_INCLUDED */
diff --git a/pith/osdep/makefile.wnt b/pith/osdep/makefile.wnt
new file mode 100644
index 00000000..5b0cc0c8
--- /dev/null
+++ b/pith/osdep/makefile.wnt
@@ -0,0 +1,65 @@
+# $Id: makefile.wnt 14098 2005-10-03 18:54:13Z jpf@u.washington.edu $
+#
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+#
+#
+# Makefile for WIN NT version of the libpith.lib
+#
+#
+CC=cl
+RM=del
+CP=copy
+RC=rc
+
+#includes symbol info for debugging
+CDEBUG= #-Zi -Od
+LDEBUG= /DEBUG /DEBUGTYPE:CV
+
+STDCFLAGS= -I..\..\include -I..\..\regex -nologo -MT -DWIN32 -DDOS -D_WINDOWS -DJOB_CONTROL -DMSC_MALLOC
+
+CFLAGS= $(CDEBUG) $(STDCFLAGS) $(NET) $(EXTRACFLAGS)
+
+LFLAGS= $(LDEBUG) $(EXTRALDFLAGS)
+
+RCFLAGS =
+
+LIBER=lib
+LIBARGS=/nologo /verbose
+
+HFILES= ../../include/system.h ../../include/general.h \
+ bldpath.h canaccess.h canonicl.h collate.h color.h coredump.h \
+ creatdir.h debugtime.h domnames.h err_desc.h fgetpos.h filesize.h \
+ fnexpand.h forkwait.h hostname.h lstcmpnt.h mimedisp.h pipe.h \
+ pithosd.h pw_stuff.h rename.h tempfile.h temp_nam.h \
+ writ_dir.h
+
+OFILES= bldpath.obj canaccess.obj canonicl.obj collate.obj color.obj coredump.obj \
+ creatdir.obj debugtime.obj domnames.obj err_desc.obj fgetpos.obj filesize.obj \
+ fnexpand.obj hostname.obj lstcmpnt.obj mimedisp.obj pipe.obj \
+ pw_stuff.obj rename.obj tempfile.obj temp_nam.obj \
+ writ_dir.obj
+
+all: libpithosd.lib
+
+.c.obj:
+ $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c
+
+$(OFILES): $(HFILES)
+
+libpithosd.lib: $(OFILES)
+ $(RM) libpithosd.lib || rem
+ $(LIBER) /out:libpithosd.lib $(OFILES)
+
+clean:
+ $(RM) *.lib
+ $(RM) *.obj
diff --git a/pith/osdep/mimedisp.c b/pith/osdep/mimedisp.c
new file mode 100644
index 00000000..69ff3337
--- /dev/null
+++ b/pith/osdep/mimedisp.c
@@ -0,0 +1,473 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mimedisp.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/fs.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+#include "mimedisp.h"
+#include "../charconv/utf8.h"
+#ifdef OSX_TARGET
+#include <Security/AuthSession.h>
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+#ifdef _WINDOWS
+int mswin_reg_viewer(LPTSTR mime_type, LPTSTR mime_ext,
+ char *cmd, int clen, int chk);
+int mswin_reg_mime_type(LPTSTR file_ext, LPTSTR mime_type, size_t mime_type_len);
+int mswin_reg_mime_ext(LPTSTR mime_type, LPTSTR file_ext, size_t file_ext_len);
+
+#endif /* WINDOWS */
+
+#if OSX_TARGET
+
+int osx_build_mime_type_cmd(char *, char *, int, int *);
+int osx_build_mime_ext_cmd(char *, char *, int, int *);
+
+#endif /* OSX_TARGET */
+
+
+
+/*
+ * Determine if there is an OS-specific mechanism for accessing
+ * MIME and extension data. In the general *nix case this is all
+ * done through mailcap and mime.types files.
+ *
+ * Returns: 0 if there is no support (most *nix cases)
+ * 1 if there is support (Mac OS X, Windows)
+ */
+int
+mime_os_specific_access(void)
+{
+#ifdef _WINDOWS
+ return 1;
+#elif OSX_TARGET
+# ifdef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER
+ {
+ /*
+ * if we don't have the WidowSession then we should avoid using
+ * frameworks unless they call themselves daemon-safe
+ */
+ SecuritySessionId session_id;
+ SessionAttributeBits session_bits;
+
+ if((SessionGetInfo(callerSecuritySession, &session_id, &session_bits)
+ == errSessionSuccess)
+ && session_bits & sessionHasGraphicAccess)
+ return 1;
+ else
+ return 0;
+ }
+# else
+ return 0;
+# endif
+#else
+ return 0;
+#endif
+}
+
+
+/*
+ * Return the command based on either the mimetype or the file
+ * extension. Mime-type always takes precedence.
+ *
+ * mime_type - mime-type of the file we're looking at
+ * mime_ext - file extension given us by the mime data
+ * cmd - buffer to copy the resulting command into
+ * chk - whether or not we should check the file extension
+ *
+ * Returns: 1 on success, 0 on failure
+ */
+int
+mime_get_os_mimetype_command(char *mime_type, char *mime_ext, char *cmd,
+ int clen, int chk, int *sp_hndlp)
+{
+#ifdef _WINDOWS
+ int ret;
+ LPTSTR mime_type_lpt, mime_ext_lpt;
+
+ mime_type_lpt = utf8_to_lptstr(mime_type);
+ mime_ext_lpt = utf8_to_lptstr(mime_ext);
+
+ ret = mswin_reg_viewer(mime_type_lpt, mime_ext_lpt, cmd, clen, chk);
+
+ if(mime_type_lpt)
+ fs_give((void **) &mime_type_lpt);
+
+ if(mime_ext_lpt)
+ fs_give((void **) &mime_ext_lpt);
+
+ return ret;
+
+#elif OSX_TARGET
+
+ /*
+ * if we wanted to be more like PC-Pine, we'd try checking
+ * the mime-type of mime_ext and seeing if that matches
+ * with our mime-type, which is safe for opening
+ */
+ if(!mime_os_specific_access())
+ return(0);
+
+ /* don't want to use Mail or something for a part alpine is good at */
+ if(!strucmp(mime_type, "message/rfc822"))
+ return(0);
+
+ return(osx_build_mime_type_cmd(mime_type, cmd, clen, sp_hndlp)
+ || (chk && mime_ext && *mime_ext &&
+ osx_build_mime_ext_cmd(mime_ext, cmd, clen, sp_hndlp)));
+#else
+ return 0;
+#endif
+}
+
+
+/*
+ * Given a file extension, return the mime-type if there is one
+ *
+ * Returns: 1 on success, 0 on failure
+ */
+int
+mime_get_os_mimetype_from_ext(char *file_ext, char *mime_type, int mime_type_len)
+{
+#ifdef _WINDOWS
+ int ret;
+ LPTSTR x, file_ext_lpt, mime_type_lpt;
+
+ file_ext_lpt = utf8_to_lptstr(file_ext);
+
+ if(file_ext_lpt){
+ if(mime_type){
+ mime_type_lpt = (LPTSTR) fs_get(mime_type_len * sizeof(TCHAR));
+ mime_type_lpt[0] = '\0';
+ }
+ else
+ mime_type_lpt = NULL;
+ }
+
+ ret = mswin_reg_mime_type(file_ext_lpt, mime_type_lpt, (size_t) mime_type_len);
+
+ /* convert answer back to UTF-8 */
+ if(ret && mime_type_lpt && mime_type){
+ char *u;
+
+ u = lptstr_to_utf8(mime_type_lpt);
+ if(u){
+ strncpy(mime_type, u, mime_type_len);
+ mime_type[mime_type_len-1] = '\0';
+ fs_give((void **) &u);
+ }
+ }
+
+ if(file_ext_lpt)
+ fs_give((void **) &file_ext_lpt);
+
+ if(mime_type_lpt)
+ fs_give((void **) &mime_type_lpt);
+
+ return ret;
+
+#elif OSX_TARGET
+
+ if(!mime_os_specific_access())
+ return(0);
+#ifdef AVAILABLE_MAC_OS_X_VERSION_10_3_AND_LATER
+ CFStringRef mime_ref = NULL, type_id_ref = NULL, ext_ref = NULL;
+ char buf[1024];
+
+ if(!file_ext || !*file_ext)
+ return 0;
+
+ /* This for if we built on OS X >= 10.3 but run on < 10.3 */
+ if(&UTTypeCreatePreferredIdentifierForTag == NULL)
+ return 0;
+ if((ext_ref = CFStringCreateWithCString(NULL, *file_ext == '.' ?
+ file_ext + 1 : file_ext,
+ kCFStringEncodingASCII)) == NULL)
+ return 0;
+ if((type_id_ref
+ = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
+ ext_ref, NULL)) == NULL)
+ return 0;
+
+ if((mime_ref = UTTypeCopyPreferredTagWithClass(type_id_ref,
+ kUTTagClassMIMEType)) == NULL)
+ return 0;
+ if(CFStringGetCString(mime_ref, mime_type,
+ (CFIndex)mime_type_len - 1,
+ kCFStringEncodingASCII) == false)
+ return 0;
+
+ mime_type[mime_type_len - 1] = '\0';
+
+ return 1;
+#else
+ return 0;
+#endif /* AVAILABLE_MAC_OS_X_VERSION_10_3_AND_LATER */
+
+#else
+ return 0;
+#endif /* OSX_TARGET */
+}
+
+
+/*
+ * Given a mime-type, return the file extension if there is one
+ *
+ * Returns: 1 on success, 0 on failure
+ */
+int
+mime_get_os_ext_from_mimetype(char *mime_type, char *file_ext, int file_ext_len)
+{
+#ifdef _WINDOWS
+ int ret;
+ LPTSTR x, mime_type_lpt, file_ext_lpt;
+
+ mime_type_lpt = utf8_to_lptstr(mime_type);
+
+ if(mime_type_lpt){
+ if(file_ext){
+ file_ext_lpt = (LPTSTR) fs_get(file_ext_len * sizeof(TCHAR));
+ file_ext_lpt[0] = '\0';
+ }
+ else
+ file_ext_lpt = NULL;
+ }
+
+ ret = mswin_reg_mime_ext(mime_type_lpt, file_ext_lpt, (size_t) file_ext_len);
+
+ /* convert answer back to UTF-8 */
+ if(ret && file_ext_lpt && file_ext){
+ char *u;
+
+ u = lptstr_to_utf8(file_ext_lpt);
+ if(u){
+ strncpy(file_ext, u, file_ext_len);
+ file_ext[file_ext_len-1] = '\0';
+ fs_give((void **) &u);
+ }
+ }
+
+ if(mime_type_lpt)
+ fs_give((void **) &mime_type_lpt);
+
+ if(file_ext_lpt)
+ fs_give((void **) &file_ext_lpt);
+
+ return ret;
+
+#elif OSX_TARGET
+
+ if(!mime_os_specific_access())
+ return(0);
+#ifdef AVAILABLE_MAC_OS_X_VERSION_10_3_AND_LATER
+ CFStringRef mime_ref = NULL, type_id_ref = NULL, ext_ref = NULL;
+
+ if(!mime_type || !*mime_type)
+ return 0;
+ /* This for if we built on OS X >= 10.3 but run on < 10.3 */
+ if(&UTTypeCreatePreferredIdentifierForTag == NULL)
+ return 0;
+ if((mime_ref = CFStringCreateWithCString(NULL, mime_type,
+ kCFStringEncodingASCII)) == NULL)
+ return 0;
+ if((type_id_ref
+ = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
+ mime_ref, NULL)) == NULL)
+ return 0;
+
+ if((ext_ref = UTTypeCopyPreferredTagWithClass(type_id_ref,
+ kUTTagClassFilenameExtension)) == NULL)
+ return 0;
+ if((CFStringGetCString(ext_ref, file_ext, (CFIndex)file_ext_len - 1,
+ kCFStringEncodingASCII)) == false)
+ return 0;
+
+ file_ext[file_ext_len - 1] = '\0';
+
+ return 1;
+#else
+ return 0;
+#endif /* AVAILABLE_MAC_OS_X_VERSION_10_3_AND_LATER */
+
+#else
+ return 0;
+#endif /* OSX_TARGET */
+}
+
+
+
+#ifdef _WINDOWS
+
+/*
+ * mswin_reg_viewer -
+ */
+int
+mswin_reg_viewer(LPTSTR mime_type, LPTSTR mime_ext, char *cmd, int clen, int chk)
+{
+ TCHAR tmp[256];
+ LPTSTR ext;
+
+ tmp[0] = '\0';
+
+ /*
+ * Everything's based on the file extension.
+ * So, sniff at registry's mapping for the given MIME type/subtype
+ * and sniff at clues on how to launch it, or
+ * extension mapping and then
+ * look for clues that some app will handle it.
+ *
+ */
+ return(((mswin_reg_mime_ext(mime_type, ext = tmp, sizeof(tmp)/sizeof(TCHAR))
+ || ((ext = mime_ext) && *mime_ext
+ && ((mswin_reg_mime_type(mime_ext, tmp, sizeof(tmp)/sizeof(TCHAR))
+ && mime_type && !_tcsicmp(mime_type, tmp))
+ || mime_type && !_tcsicmp(mime_type, TEXT("application/octet-stream")))))
+ && MSWRShellCanOpen(ext, cmd, clen, 0) == TRUE)
+ || (chk && MSWRShellCanOpen(ext, cmd, clen, 1) == TRUE));
+}
+
+
+/*
+ * given a file name extension, fill in the provided buf with its
+ * corresponding MIME type
+ */
+int
+mswin_reg_mime_type(LPTSTR file_ext, LPTSTR mime_type, size_t mime_type_len)
+{
+ TCHAR buf[64];
+ DWORD len = mime_type_len;
+
+ if(file_ext[0] != '.'){
+ *buf = '.';
+ _tcsncpy(buf + 1, file_ext, sizeof(buf)/sizeof(TCHAR)-1);
+ buf[sizeof(buf)/sizeof(TCHAR)-1] = '\0';
+ file_ext = buf;
+ }
+
+ return(MSWRPeek(HKEY_CLASSES_ROOT, file_ext, TEXT("content type"),
+ mime_type, &len) == TRUE);
+}
+
+
+/*
+ * given a mime_type, fill in the provided buf with its
+ * corresponding file name extension
+ */
+int
+mswin_reg_mime_ext(LPTSTR mime_type, LPTSTR file_ext, size_t file_ext_len)
+{
+ TCHAR keybuf[128];
+ DWORD len = file_ext_len;
+
+ if(mime_type && _tcslen(mime_type) < 50){
+ _sntprintf(keybuf, sizeof(keybuf), TEXT("MIME\\Database\\Content Type\\%s"), mime_type);
+ return(MSWRPeek(HKEY_CLASSES_ROOT, keybuf,
+ TEXT("extension"), file_ext, &len) == TRUE);
+ }
+
+ return FALSE;
+}
+#endif /* _WINDOWS */
+
+
+
+#if OSX_TARGET
+/* returns: 1 if success, 0 if failure */
+int
+osx_build_mime_type_cmd(mime_type, cmd, cmdlen, sp_hndlp)
+ char *mime_type, *cmd;
+ int cmdlen, *sp_hndlp;
+{
+ int rv = 0;
+
+ if(!mime_os_specific_access())
+ return(0);
+#ifdef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER
+ CFStringRef str_ref = NULL, ret_str_ref = NULL;
+ CFURLRef url_ref = NULL;
+
+ if(&LSCopyApplicationForMIMEType == NULL)
+ return 0;
+ if((str_ref = CFStringCreateWithCString(NULL, mime_type,
+ kCFStringEncodingASCII)) == NULL)
+ return 0;
+ if(LSCopyApplicationForMIMEType(str_ref, kLSRolesAll, &url_ref)
+ != kLSApplicationNotFoundErr){
+ if((ret_str_ref = CFURLGetString(url_ref)) == NULL)
+ return 0;
+ if(CFStringGetCString(ret_str_ref, cmd, (CFIndex)cmdlen,
+ kCFStringEncodingASCII) == false)
+ return 0;
+ if(sp_hndlp)
+ *sp_hndlp = 1;
+ rv = 1;
+ if(url_ref)
+ CFRelease(url_ref);
+ }
+#endif /* AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER */
+ return rv;
+}
+
+
+/* returns: 1 if success, 0 if failure */
+int
+osx_build_mime_ext_cmd(mime_ext, cmd, cmdlen, sp_hndlp)
+ char *mime_ext, *cmd;
+ int cmdlen, *sp_hndlp;
+{
+ int rv = 0;
+
+ if(!mime_os_specific_access())
+ return 0;
+
+#ifdef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER
+ CFStringRef str_ref = NULL, ret_str_ref = NULL;
+ CFURLRef url_ref = NULL;
+
+ if((str_ref = CFStringCreateWithCString(NULL, (*mime_ext) == '.'
+ ? mime_ext+1 : mime_ext,
+ kCFStringEncodingASCII)) == NULL)
+ return 0;
+ if(LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator,
+ str_ref, kLSRolesAll, NULL, &url_ref)
+ != kLSApplicationNotFoundErr){
+ if((ret_str_ref = CFURLGetString(url_ref)) == NULL)
+ return 0;
+ if(CFStringGetCString(ret_str_ref, cmd, (CFIndex)cmdlen,
+ kCFStringEncodingASCII) == false)
+ return 0;
+ if(sp_hndlp)
+ *sp_hndlp = 1;
+ rv = 1;
+ if(url_ref)
+ CFRelease(url_ref);
+ }
+#endif
+ return rv;
+}
+#endif /* OSX_TARGET */
diff --git a/pith/osdep/mimedisp.h b/pith/osdep/mimedisp.h
new file mode 100644
index 00000000..36ddb7b1
--- /dev/null
+++ b/pith/osdep/mimedisp.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: mimedisp.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_MIMEDISP_INCLUDED
+#define PITH_OSDEP_MIMEDISP_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+int mime_os_specific_access(void);
+int mime_get_os_mimetype_command(char *, char *, char *, int, int, int *);
+int mime_get_os_mimetype_from_ext(char *, char *, int);
+int mime_get_os_ext_from_mimetype(char *, char *, int);
+
+#endif /* PITH_OSDEP_MIMEDISP_INCLUDED */
diff --git a/pith/osdep/pipe.c b/pith/osdep/pipe.c
new file mode 100644
index 00000000..2183f529
--- /dev/null
+++ b/pith/osdep/pipe.c
@@ -0,0 +1,811 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pipe.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#include "err_desc.h"
+
+#include "canaccess.h"
+#include "temp_nam.h"
+#include "forkwait.h"
+#include "pipe.h"
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "../debug.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+
+
+/*======================================================================
+ pipe
+
+ Initiate I/O to and from a process. These functions used to be
+ similar to popen and pclose, but both an incoming stream and an
+ output file are provided.
+
+ ====*/
+
+
+
+/*
+ * Global's to helpsignal handler tell us child's status has changed...
+ */
+static pid_t child_pid;
+
+
+/*
+ * Internal Protos
+ */
+void zot_pipe(PIPE_S **);
+#ifdef _WINDOWS
+int pipe_mswin_exec_wrapper(char *, PIPE_S *, unsigned,
+ void (*)(PIPE_S *, int, void *),
+ void (*)(char *));
+#else /* UNIX */
+char *pipe_error_msg(char *, char *, char *);
+RETSIGTYPE pipe_alarm(int);
+#endif /* UNIX */
+
+
+
+
+/*----------------------------------------------------------------------
+ Spawn a child process and optionally connect read/write pipes to it
+
+ Args: command -- string to hand the shell
+ outfile -- address of pointer containing file to receive output
+ errfile -- address of pointer containing file to receive error output
+ mode -- mode for type of shell, signal protection etc...
+ Returns: pointer to alloc'd PIPE_S on success, NULL otherwise
+
+ The outfile is either NULL, a pointer to a NULL value, or a pointer
+ to the requested name for the output file. In the pointer-to-NULL case
+ the caller doesn't care about the name, but wants to see the pipe's
+ results so we make one up. It's up to the caller to make sure the
+ free storage containing the name is cleaned up.
+
+ Mode bits serve several purposes.
+ PIPE_WRITE tells us we need to open a pipe to write the child's
+ stdin.
+ PIPE_READ tells us we need to open a pipe to read from the child's
+ stdout/stderr. *NOTE* Having neither of the above set means
+ we're not setting up any pipes, just forking the child and exec'ing
+ the command. Also, this takes precedence over any named outfile.
+ PIPE_STDERR means we're to tie the childs stderr to the same place
+ stdout is going. *NOTE* This only makes sense then if PIPE_READ
+ or an outfile is provided. Also, this takes precedence over any
+ named errfile.
+ PIPE_RESET means we reset the terminal mode to what it was before
+ we started pine and then exec the command. In PC-Pine, _RESET
+ was a shortcut for just executing a command. We'll try to pay
+ attention to the above flags to make sure we do the right thing.
+ PIPE_PROT means to protect the child from the usual nasty signals
+ that might cause premature death. Otherwise, the default signals are
+ set so the child can deal with the nasty signals in its own way.
+ NOT USED UNDER WINDOWS
+ PIPE_NOSHELL means we're to exec the command without the aid of
+ a system shell. *NOTE* This negates the affect of PIPE_USER.
+ NOT USED UNDER WINDOWS
+ PIPE_USER means we're to try executing the command in the user's
+ shell. Right now we only look in the environment, but that may get
+ more sophisticated later.
+ NOT USED UNDER WINDOWS
+ PIPE_RUNNOW was added for WINDOWS for the case pipe is called to run
+ a shell program (like for url viewing). This is the only option
+ where we don't wait for child termination, and is only obeyed if
+ PIPE_WRITE and PIPE_READ aren't set
+ ----*/
+PIPE_S *
+open_system_pipe(char *command, char **outfile, char **errfile, int mode,
+ int timeout, void (*pipecb_f)(PIPE_S *, int, void *),
+ void (*piperr_f)(char *))
+{
+ PIPE_S *syspipe = NULL;
+#ifdef _WINDOWS
+ int exit_code = 0;
+ char cmdbuf[1024];
+ unsigned flags = 0;
+#else
+ char shellpath[MAXPATH+1], *shell;
+ int p[2], oparentd = -1, ochildd = -1, iparentd = -1, ichildd = -1;
+#endif
+
+#ifdef _WINDOWS
+ if(mode & PIPE_STDERR)
+ flags |= MSWIN_EAW_CAPT_STDERR;
+ /*
+ * It'll be a lot more difficult to support READing and WRITing.
+ * This was never supported, and there don't look to be any cases
+ * that set both of these flags anymore for win32.
+ *
+ * errfile could probably be supported pretty easily
+ */
+
+ if(errfile){
+ if(piperr_f)
+ (*piperr_f)("Pipe arg not yet supported: Error File");
+
+ return(NULL);
+ }
+
+
+ if((mode & PIPE_RUNNOW)
+ && !(mode & (PIPE_WRITE | PIPE_READ | PIPE_STDERR))){
+ if(mswin_shell_exec(command, NULL) == 0
+ && (syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) != NULL){
+ memset(syspipe, 0, sizeof(PIPE_S));
+ return(syspipe);
+ }
+
+ return(NULL);
+ }
+
+ strncpy(cmdbuf, command, sizeof(cmdbuf));
+ cmdbuf[sizeof(cmdbuf)-1] = '\0';
+
+ if((syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) == NULL)
+ return(NULL);
+
+ memset(syspipe, 0, sizeof(PIPE_S));
+ syspipe->mode = mode;
+ if(!outfile){
+ syspipe->deloutfile = 1;
+ if(mode & PIPE_READ){
+ syspipe->outfile = temp_nam(NULL, "po");
+ our_unlink(syspipe->outfile);
+ }
+ }
+ else{
+ if(!*outfile) /* asked for, but not named? */
+ *outfile = temp_nam(NULL, "po");
+
+ our_unlink(*outfile);
+ syspipe->outfile = (char *) malloc((strlen(*outfile)+1)*sizeof(char));
+ snprintf(syspipe->outfile, strlen(*outfile)+1, "%s", *outfile);
+ }
+
+ if(mode & PIPE_WRITE){
+ /*
+ * Create tmp file to write, spawn child in close_pipe
+ * after tmp file's written...
+ */
+ syspipe->infile = temp_nam(NULL, "pw");
+ syspipe->out.f = our_fopen(syspipe->infile, "wb");
+ syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
+ snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
+ dprint((1, "pipe write: %s", cmdbuf));
+ }
+ else if(mode & PIPE_READ){
+ /*
+ * Create a tmp file for command result, exec the command
+ * here into temp file, and return file pointer to it...
+ */
+ syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
+ snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
+ dprint((1, "pipe read: %s", cmdbuf));
+ if(pipe_mswin_exec_wrapper("pipe command", syspipe,
+ flags, pipecb_f, piperr_f)){
+ if(syspipe->outfile){
+ free((void *) syspipe->outfile);
+ syspipe->outfile = NULL;
+ }
+
+ zot_pipe(&syspipe);
+ }
+ else{
+ syspipe->in.f = our_fopen(syspipe->outfile, "rb");
+ syspipe->exit_code = exit_code;
+ }
+ }
+ else{
+ /* we just run the command taking outfile into account */
+ syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
+ snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
+ if(pipe_mswin_exec_wrapper("pipe command", syspipe,
+ flags, pipecb_f, piperr_f)){
+ if(syspipe->outfile){
+ free((void *) syspipe->outfile);
+ syspipe->outfile = NULL;
+ }
+
+ zot_pipe(&syspipe);
+ }
+ else
+ syspipe->exit_code = exit_code;
+ }
+
+#else /* !_WINDOWS */
+
+ if((syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) == NULL)
+ return(NULL);
+
+ memset(syspipe, 0, sizeof(PIPE_S));
+
+ syspipe->mode = mode;
+
+ /*
+ * If we're not using the shell's command parsing smarts, build
+ * argv by hand...
+ */
+ if(mode & PIPE_NOSHELL){
+ char **ap, *p;
+ size_t n;
+
+ /* parse the arguments into argv */
+ for(p = command; *p && isspace((unsigned char)(*p)); p++)
+ ; /* swallow leading ws */
+
+ if(*p){
+ int l = strlen(p);
+
+ if((syspipe->args = (char *) malloc((l + 1) * sizeof(char))) != NULL){
+ strncpy(syspipe->args, p, l);
+ syspipe->args[l] = '\0';
+ }
+ else{
+ if(piperr_f)
+ (*piperr_f)(pipe_error_msg("<null>", "execute",
+ "Can't allocate command string"));
+ zot_pipe(&syspipe);
+ return(NULL);
+ }
+ }
+ else{
+ if(piperr_f)
+ (*piperr_f)(pipe_error_msg("<null>", "execute",
+ "No command name found"));
+ zot_pipe(&syspipe);
+ return(NULL);
+ }
+
+ for(p = syspipe->args, n = 2; *p; p++) /* count the args */
+ if(isspace((unsigned char)(*p))
+ && *(p+1) && !isspace((unsigned char)(*(p+1))))
+ n++;
+
+ if ((syspipe->argv = ap = (char **)malloc(n * sizeof(char *))) == NULL){
+ zot_pipe(&syspipe);
+ return(NULL);
+ }
+
+ memset(syspipe->argv, 0, n * sizeof(char *));
+
+ for(p = syspipe->args; *p; ){ /* collect args */
+ while(*p && isspace((unsigned char)(*p)))
+ *p++ = '\0';
+
+ *ap++ = (*p) ? p : NULL;
+ while(*p && !isspace((unsigned char)(*p)))
+ p++;
+ }
+
+ /* make sure argv[0] exists in $PATH */
+ if(can_access_in_path(getenv("PATH"), syspipe->argv[0],
+ EXECUTE_ACCESS) < 0){
+ if(piperr_f)
+ (*piperr_f)(pipe_error_msg(syspipe->argv[0], "access",
+ error_description(errno)));
+ zot_pipe(&syspipe);
+ return(NULL);
+ }
+ }
+
+ /* fill in any output filenames */
+ if(!(mode & PIPE_READ)){
+ if(outfile && !*outfile)
+ *outfile = temp_nam(NULL, "pine_p"); /* asked for, but not named? */
+
+ if(errfile && !*errfile)
+ *errfile = temp_nam(NULL, "pine_p"); /* ditto */
+ }
+
+ /* create pipes */
+ if(mode & (PIPE_WRITE | PIPE_READ)){
+ if(mode & PIPE_WRITE){
+ pipe(p); /* alloc pipe to write child */
+ oparentd = p[STDOUT_FILENO];
+ ichildd = p[STDIN_FILENO];
+ }
+
+ if(mode & PIPE_READ){
+ pipe(p); /* alloc pipe to read child */
+ iparentd = p[STDIN_FILENO];
+ ochildd = p[STDOUT_FILENO];
+ }
+ }
+
+ if(pipecb_f) /* let caller prep display */
+ (*pipecb_f)(syspipe, OSB_PRE_OPEN, NULL);
+
+
+ if((syspipe->pid = vfork()) == 0){
+ /* reset child's handlers in requested fashion... */
+ (void)signal(SIGINT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
+ (void)signal(SIGQUIT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
+ (void)signal(SIGHUP, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
+#ifdef SIGCHLD
+ (void) signal(SIGCHLD, SIG_DFL);
+#endif
+
+ /* if parent isn't reading, and we have a filename to write */
+ if(!(mode & PIPE_READ) && outfile){ /* connect output to file */
+ int output = our_creat(*outfile, 0600);
+ dup2(output, STDOUT_FILENO);
+ if(mode & PIPE_STDERR)
+ dup2(output, STDERR_FILENO);
+ else if(errfile)
+ dup2(our_creat(*errfile, 0600), STDERR_FILENO);
+ }
+
+ if(mode & PIPE_WRITE){ /* connect process input */
+ close(oparentd);
+ dup2(ichildd, STDIN_FILENO); /* tie stdin to pipe */
+ close(ichildd);
+ }
+
+ if(mode & PIPE_READ){ /* connect process output */
+ close(iparentd);
+ dup2(ochildd, STDOUT_FILENO); /* tie std{out,err} to pipe */
+ if(mode & PIPE_STDERR)
+ dup2(ochildd, STDERR_FILENO);
+ else if(errfile)
+ dup2(our_creat(*errfile, 0600), STDERR_FILENO);
+
+ close(ochildd);
+ }
+
+ if(mode & PIPE_NOSHELL){
+ execvp(syspipe->argv[0], syspipe->argv);
+ }
+ else{
+ if(mode & PIPE_USER){
+ char *env, *sh;
+ if((env = getenv("SHELL")) && (sh = strrchr(env, '/'))){
+ shell = sh + 1;
+ strncpy(shellpath, env, sizeof(shellpath)-1);
+ shellpath[sizeof(shellpath)-1] = '\0';
+ }
+ else{
+ shell = "csh";
+ strncpy(shellpath, "/bin/csh", sizeof(shellpath)-1);
+ shellpath[sizeof(shellpath)-1] = '\0';
+ }
+ }
+ else{
+ shell = "sh";
+ strncpy(shellpath, "/bin/sh", sizeof(shellpath)-1);
+ shellpath[sizeof(shellpath)-1] = '\0';
+ }
+
+ execl(shellpath, shell, command ? "-c" : (char *)NULL, fname_to_locale(command), (char *)NULL);
+ }
+
+ fprintf(stderr, "Can't exec %s\nReason: %s",
+ command, error_description(errno));
+ _exit(-1);
+ }
+
+ if((child_pid = syspipe->pid) > 0){
+ syspipe->isig = signal(SIGINT, SIG_IGN); /* Reset handlers to make */
+ syspipe->qsig = signal(SIGQUIT, SIG_IGN); /* sure we don't come to */
+ syspipe->hsig = signal(SIGHUP, SIG_IGN); /* a premature end... */
+ if((syspipe->timeout = timeout) != 0){
+ syspipe->alrm = signal(SIGALRM, pipe_alarm);
+ syspipe->old_timeo = alarm(timeout);
+ }
+
+ if(mode & PIPE_WRITE){
+ close(ichildd);
+ if(mode & PIPE_DESC)
+ syspipe->out.d = oparentd;
+ else
+ syspipe->out.f = fdopen(oparentd, "w");
+ }
+
+ if(mode & PIPE_READ){
+ close(ochildd);
+ if(mode & PIPE_DESC)
+ syspipe->in.d = iparentd;
+ else
+ syspipe->in.f = fdopen(iparentd, "r");
+ }
+ }
+ else{
+ if(mode & (PIPE_WRITE | PIPE_READ)){
+ if(mode & PIPE_WRITE){
+ close(oparentd);
+ close(ichildd);
+ }
+
+ if(mode & PIPE_READ){
+ close(iparentd);
+ close(ochildd);
+ }
+ }
+
+ if(pipecb_f) /* let caller fixup display */
+ (*pipecb_f)(syspipe, OSB_POST_OPEN, NULL);
+
+ if(outfile && *outfile){
+ our_unlink(*outfile);
+ free((void *) *outfile);
+ *outfile = NULL;
+ }
+
+ if(errfile && *errfile){
+ our_unlink(*errfile);
+ free((void *) *errfile);
+ *errfile = NULL;
+ }
+
+ if(piperr_f)
+ (*piperr_f)(pipe_error_msg(command, "fork",
+ error_description(errno)));
+ zot_pipe(&syspipe);
+ }
+
+#endif /* UNIX */
+
+ return(syspipe);
+}
+
+
+
+#ifndef _WINDOWS
+/*----------------------------------------------------------------------
+ Return appropriate error message
+
+ Args: cmd -- command we were trying to exec
+ op -- operation leading up to the exec
+ res -- result of that operation
+
+ ----*/
+char *
+pipe_error_msg(char *cmd, char *op, char *res)
+{
+ static char ebuf[512];
+
+ snprintf(ebuf, 256, "Pipe can't %.256s \"%.32sb\": %.223s",
+ op ? op : "?", cmd ? cmd : "?", res ? res : "?");
+
+ return(ebuf);
+}
+#endif /* !_WINDOWS */
+
+
+/*----------------------------------------------------------------------
+ Free resources associated with the given pipe struct
+
+ Args: syspipe -- address of pointer to struct to clean up
+
+ ----*/
+void
+zot_pipe(PIPE_S **syspipe)
+{
+ if((*syspipe)->args){
+ free((void *) (*syspipe)->args);
+ (*syspipe)->args = NULL;
+ }
+
+ if((*syspipe)->argv){
+ free((void *) (*syspipe)->argv);
+ (*syspipe)->argv = NULL;
+ }
+
+ if((*syspipe)->tmp){
+ free((void *) (*syspipe)->tmp);
+ (*syspipe)->tmp = NULL;
+ }
+
+#ifdef _WINDOWS
+
+ if((*syspipe)->outfile){
+ free((void *) (*syspipe)->outfile);
+ (*syspipe)->outfile = NULL;
+ }
+
+ if((*syspipe)->command){
+ free((void *) (*syspipe)->command);
+ (*syspipe)->command = NULL;
+ }
+
+#endif /* _WINDOWS */
+
+ free((void *) *syspipe);
+ *syspipe = NULL;
+}
+
+
+
+/*
+ * Returns: 0 if all went well, -1 otherwise
+ */
+int
+pipe_close_write(PIPE_S *syspipe)
+{
+ int rv = 0;
+
+ if(!syspipe || !syspipe->out.f)
+ return -1;
+
+#ifdef _WINDOWS
+
+ {
+ unsigned flags = 0;
+
+ if(syspipe->mode & PIPE_STDERR)
+ flags |= MSWIN_EAW_CAPT_STDERR;
+
+ rv = fclose(syspipe->out.f);
+ syspipe->out.f = NULL;
+ if(syspipe->mode & PIPE_WRITE){
+ /*
+ * PIPE_WRITE should always be set if we're trying to close
+ * the write end.
+ * PIPE_WRITE can't start process till now, all the others
+ * will have already run
+ */
+ if(pipe_mswin_exec_wrapper("pipe command", syspipe,
+ flags, NULL, NULL))
+ /* some horrible error just occurred */
+ rv = -1;
+ else
+ syspipe->in.f = our_fopen(syspipe->outfile, "rb");
+ }
+ else
+ rv = -1;
+ }
+
+#else /* UNIX */
+
+ rv = fclose(syspipe->out.f) ? -1 : 0;
+ syspipe->out.f = NULL;
+
+#endif
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Close pipe previously allocated and wait for child's death
+
+ Args: syspipe -- address of pointer to struct returned by open_system_pipe
+ exitval -- return exit status here.
+
+ Returns:
+ Two modes of return values for backcompat.
+ If exitval == NULL
+ returns exit status of child or -1 if invalid syspipe
+ If exitval != NULL
+ returns -1 if invalid syspipe or 0 if ok. In that case, exitval
+ of child is returned in exitval
+ ----*/
+int
+close_system_pipe(PIPE_S **syspipe, int *exitval, void (*pipecb_f) (PIPE_S *, int, void *))
+{
+#ifdef _WINDOWS
+ int rv = 0;
+ unsigned flags = 0;
+
+ if(!(syspipe && *syspipe))
+ return(-1);
+
+ if((*syspipe)->mode & PIPE_STDERR)
+ flags |= MSWIN_EAW_CAPT_STDERR;
+
+ if(((*syspipe)->mode & PIPE_WRITE) && (*syspipe)->out.f){
+ fclose((*syspipe)->out.f);
+ /*
+ * PIPE_WRITE can't start process till now, all the others
+ * will have already run
+ */
+ if(pipe_mswin_exec_wrapper("pipe command", (*syspipe),
+ flags, pipecb_f, NULL))
+ /* some horrible error just occurred */
+ rv = -1;
+ }
+ else if((*syspipe)->mode & PIPE_READ)
+ if((*syspipe)->in.f)
+ fclose((*syspipe)->in.f);
+
+ if(exitval){
+ *exitval = (*syspipe)->exit_code;
+ dprint((5, "Closed pipe: exitval=%d\n", *exitval));
+ }
+
+ if((*syspipe)->infile)
+ our_unlink((*syspipe)->infile);
+
+ if((*syspipe)->outfile && (*syspipe)->deloutfile)
+ our_unlink((*syspipe)->outfile);
+
+ if(rv != -1 && !exitval)
+ rv = (*syspipe)->exit_code;
+
+ zot_pipe(syspipe);
+
+#ifdef DEBUG
+ if(!exitval){
+ dprint((5, "Closed pipe: rv=%d\n", rv));
+ }
+#endif /* DEBUG */
+
+ return(rv);
+
+#else /* UNIX */
+ int status;
+
+ if(!(syspipe && *syspipe))
+ return -1;
+
+ if(((*syspipe)->mode) & PIPE_WRITE){
+ if(((*syspipe)->mode) & PIPE_DESC){
+ if((*syspipe)->out.d >= 0)
+ close((*syspipe)->out.d);
+ }
+ else if((*syspipe)->out.f)
+ fclose((*syspipe)->out.f);
+ }
+
+ if(((*syspipe)->mode) & PIPE_READ){
+ if(((*syspipe)->mode) & PIPE_DESC){
+ if((*syspipe)->in.d >= 0)
+ close((*syspipe)->in.d);
+ }
+ else if((*syspipe)->in.f)
+ fclose((*syspipe)->in.f);
+ }
+
+ if(pipecb_f)
+ (*pipecb_f)(*syspipe, OSB_PRE_CLOSE, NULL);
+
+ /* wait on the child */
+ (void) process_reap((*syspipe)->pid, &status, PR_NONE);
+
+ /* restore original handlers... */
+ (void) signal(SIGINT, (*syspipe)->isig);
+ (void) signal(SIGHUP, (*syspipe)->hsig);
+ (void) signal(SIGQUIT, (*syspipe)->qsig);
+
+ if((*syspipe)->timeout){
+ (void)signal(SIGALRM, (*syspipe)->alrm);
+ alarm((*syspipe)->old_timeo);
+ child_pid = 0;
+ }
+
+ if(pipecb_f)
+ (*pipecb_f)(*syspipe, OSB_POST_CLOSE, NULL);
+
+ zot_pipe(syspipe);
+
+ if(exitval){
+ *exitval = status;
+ return 0;
+ }
+ else{
+ return(status);
+ }
+#endif /* UNIX */
+}
+
+
+/*
+ * process_reap - manage child demise and return exit status
+ *
+ * Args: pid -- id of process to reap
+ * esp -- pointer to exist status
+ * flags -- special reaping considerations
+ *
+ * Returns:
+ * < 0 -- if there's a problem
+ * 0 -- if no child to reap
+ * > 0 -- process id of the child
+ */
+pid_t
+process_reap(pid_t pid, int *esp, int flags)
+{
+#ifdef _WINDOWS
+
+ return 0;
+
+#else /* UNIX */
+ WAITSTATUS_T wstatus;
+ pid_t rv;
+ int wflags;
+
+#if HAVE_WAITPID
+
+ wflags = 0;
+
+#ifdef WNOHANG
+ if(flags & PR_NOHANG)
+ wflags |= WNOHANG;
+#endif
+
+ while (((rv = waitpid(pid, &wstatus, wflags)) < 0) && (errno != ECHILD));
+
+#elif HAVE_WAIT4
+
+ wflags = 0;
+
+#ifdef WNOHANG
+ if(flags & PR_NOHANG)
+ wflags |= WNOHANG;
+#endif
+
+ while (((rv = wait4(pid,&wstatus,wflags,NULL)) < 0) && (errno != ECHILD));
+
+#elif HAVE_WAIT
+
+ while (((rv = wait(&wstatus)) != pid) && ((rv > 0) || (errno != ECHILD)));
+
+#else
+
+ /* BUG: BAIL */
+
+#endif
+
+ if(rv > 0)
+ *esp = (WIFEXITED(wstatus)) ? (int) WEXITSTATUS(wstatus) : -1;
+
+ return(rv);
+#endif /* UNIX */
+}
+
+
+#ifndef _WINDOWS
+RETSIGTYPE
+pipe_alarm(int sig)
+{
+ if(child_pid)
+ kill(child_pid, SIGINT);
+}
+#endif /* !_WINDOWS */
+
+
+#ifdef _WINDOWS
+/*
+ * Wrapper around mswin_exec_and_wait()
+ */
+int
+pipe_mswin_exec_wrapper(char *whatsit,
+ PIPE_S *syspipe, unsigned flags,
+ void (*pipecb_f)(PIPE_S *, int, void *),
+ void (*piperr_f)(char *))
+{
+ int rv;
+
+ flags |= MSWIN_EAW_CTRL_C_CANCELS;
+
+ if(pipecb_f)
+ (*pipecb_f)(syspipe, OSB_PRE_OPEN, NULL);
+
+ rv = mswin_exec_and_wait(whatsit, syspipe->command,
+ syspipe->infile, syspipe->outfile,
+ &syspipe->exit_code, flags);
+
+ if(pipecb_f)
+ (*pipecb_f)(syspipe, OSB_POST_OPEN, (void *)rv);
+
+ return rv;
+}
+#endif
diff --git a/pith/osdep/pipe.h b/pith/osdep/pipe.h
new file mode 100644
index 00000000..589fc839
--- /dev/null
+++ b/pith/osdep/pipe.h
@@ -0,0 +1,117 @@
+/*
+ * $Id: pipe.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_PIPE_INCLUDED
+#define PITH_OSDEP_PIPE_INCLUDED
+
+/* Standard I/O File Descriptor Definitions */
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+
+/*
+ * Flags for the pipe command routines...
+ */
+#define PIPE_WRITE 0x0001 /* set up pipe for reading */
+#define PIPE_READ 0x0002 /* set up pipe for reading */
+#define PIPE_NOSHELL 0x0004 /* don't exec in shell */
+#define PIPE_USER 0x0008 /* user mode */
+#define PIPE_STDERR 0x0010 /* stderr to child output */
+#define PIPE_PROT 0x0020 /* protected mode */
+#define PIPE_RESET 0x0040 /* reset terminal mode */
+#define PIPE_DESC 0x0080 /* no stdio desc wrapping */
+#define PIPE_SILENT 0x0100 /* no screen clear, etc */
+#define PIPE_RUNNOW 0x0200 /* don't wait for child (PC-Pine) */
+#define PIPE_RAW 0x0400 /* don't convert to locale */
+#define PIPE_NONEWMAIL 0x0800 /* don't call new_mail */
+
+#ifdef _WINDOWS
+/*
+ * Flags for mswin_exec_and_wait
+ */
+#define MSWIN_EAW_CAPT_STDERR 0x0001
+#define MSWIN_EAW_CTRL_C_CANCELS 0x0002
+#endif
+
+
+/*
+ * Reaper flags
+ */
+#define PR_NONE 0x0000
+#ifdef WNOHANG
+#define PR_NOHANG 0x0001
+#endif
+
+/*
+ * open_system_pipe callback so caller can insert code, typically interface
+ * stuff right before/after the fork and before/after wait
+ */
+#define OSB_PRE_OPEN 0x0001
+#define OSB_POST_OPEN 0x0002
+#define OSB_PRE_CLOSE 0x0004
+#define OSB_POST_CLOSE 0x0008
+
+/*
+ * stucture required for the pipe commands...
+ */
+typedef struct pipe_s {
+ pid_t pid; /* child's process id */
+ int mode, /* mode flags used to open */
+ timeout, /* wait this long for child */
+ old_timeo; /* previous active alarm */
+ RETSIGTYPE (*hsig)(int), /* previously installed... */
+ (*isig)(int), /* handlers */
+ (*qsig)(int),
+ (*alrm)(int),
+ (*chld)(int);
+ union {
+ FILE *f;
+ int d;
+ } in; /* input data handle */
+ union {
+ FILE *f;
+ int d;
+ } out; /* output data handle */
+ char **argv, /* any necessary args */
+ *args,
+ *tmp; /* pointer to stuff */
+#ifdef _WINDOWS
+ char *infile; /* file containing pipe's stdin */
+ char *outfile; /* file containing pipe's stdout */
+ char *command; /* command to execute */
+ int exit_code; /* proc rv if run right away */
+ int deloutfile; /* need to rm outfile at close */
+#endif
+} PIPE_S;
+
+
+/*
+ * Exported Prototypes
+ */
+PIPE_S *open_system_pipe(char *, char **, char **, int, int,
+ void (*)(PIPE_S *, int, void *), void (*)(char *));
+int close_system_pipe(PIPE_S **, int *, void (*)(PIPE_S *, int, void *));
+int pipe_close_write(PIPE_S *);
+pid_t process_reap(pid_t, int *, int);
+
+
+#endif /* PITH_OSDEP_PIPE_INCLUDED */
diff --git a/pith/osdep/pithosd.h b/pith/osdep/pithosd.h
new file mode 100644
index 00000000..adf71e8d
--- /dev/null
+++ b/pith/osdep/pithosd.h
@@ -0,0 +1,43 @@
+/*
+ * $Id: pithosd.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_PITHOSD_INCLUDED
+#define PITH_PITHOSD_INCLUDED
+
+#include "bldpath.h"
+#include "canaccess.h"
+#include "canonicl.h"
+#include "collate.h"
+#include "color.h"
+#include "coredump.h"
+#include "creatdir.h"
+#include "debugtime.h"
+#include "domnames.h"
+#include "err_desc.h"
+#include "fgetpos.h"
+#include "filesize.h"
+#include "fnexpand.h"
+#include "hostname.h"
+#include "lstcmpnt.h"
+#include "mimedisp.h"
+#include "pipe.h"
+#include "pw_stuff.h"
+#include "rename.h"
+#include "tempfile.h"
+#include "temp_nam.h"
+#include "writ_dir.h"
+
+
+#endif /* PITH_PITHOSD_INCLUDED */
diff --git a/pith/osdep/pw_stuff.c b/pith/osdep/pw_stuff.c
new file mode 100644
index 00000000..f8f779c5
--- /dev/null
+++ b/pith/osdep/pw_stuff.c
@@ -0,0 +1,207 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pw_stuff.c 763 2007-10-23 23:37:34Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "pw_stuff.h"
+
+/*
+ * internal prototypes
+ */
+#ifndef _WINDOWS
+
+static char *gcos_name(char *, char *);
+
+
+/*----------------------------------------------------------------------
+ Pull the name out of the gcos field if we have that sort of /etc/passwd
+
+ Args: gcos_field -- The long name or GCOS field to be parsed
+ logname -- Replaces occurances of & with logname string
+
+ Result: returns pointer to buffer with name
+ ----*/
+static char *
+gcos_name(char *gcos_field, char *logname)
+{
+ static char fullname[MAX_FULLNAME+1];
+ register char *fncp, *gcoscp, *lncp, *end;
+
+ /*
+ * Full name is all chars up to first ',' (or whole gcos, if no ',').
+ * Replace any & with Logname.
+ */
+
+ for(fncp = fullname, gcoscp= gcos_field, end = fullname + MAX_FULLNAME;
+ (*gcoscp != ',' && *gcoscp != '\0' && fncp < end);
+ gcoscp++){
+
+ if(*gcoscp == '&'){
+ for(lncp = logname; *lncp && fncp < end; fncp++, lncp++)
+ *fncp = (lncp == logname) ? toupper((unsigned char) (*lncp))
+ : (*lncp);
+ }else
+ *fncp++ = *gcoscp;
+ }
+
+ *fncp = '\0';
+ return(fullname);
+}
+
+#endif /* !_WINDOWS */
+
+
+
+/*----------------------------------------------------------------------
+ Fill in homedir, login, and fullname for the logged in user.
+ These are all pointers to static storage so need to be copied
+ in the caller.
+
+ Args: ui -- struct pointer to pass back answers
+
+ Result: fills in the fields
+ ----*/
+void
+get_user_info(struct user_info *ui)
+{
+#ifndef _WINDOWS
+ struct passwd *unix_pwd;
+
+ unix_pwd = getpwuid(getuid());
+ if(unix_pwd == NULL) {
+ ui->homedir = (char *) malloc(sizeof(char));
+ ui->homedir[0] = '\0';
+ ui->login = (char *) malloc(sizeof(char));
+ ui->login[0] = '\0';
+ ui->fullname = (char *) malloc(sizeof(char));
+ ui->fullname[0] = '\0';
+ }else {
+ char *s;
+ size_t len;
+
+ len = strlen(fname_to_utf8(unix_pwd->pw_dir));
+ ui->homedir = (char *) malloc((len+1) * sizeof(char));
+ snprintf(ui->homedir, len+1, "%s", fname_to_utf8(unix_pwd->pw_dir));
+
+ len = strlen(fname_to_utf8(unix_pwd->pw_name));
+ ui->login = (char *) malloc((len+1) * sizeof(char));
+ snprintf(ui->login, len+1, "%s", fname_to_utf8(unix_pwd->pw_name));
+
+ if((s = gcos_name(unix_pwd->pw_gecos, unix_pwd->pw_name)) != NULL){
+ len = strlen(fname_to_utf8(s));
+ ui->fullname = (char *) malloc((len+1) * sizeof(char));
+ snprintf(ui->fullname, len+1, "%s", fname_to_utf8(s));
+ }
+ }
+
+#else /* _WINDOWS */
+ char buf[_MAX_PATH], *p, *q;
+ TCHAR lptstr_buf[_MAX_PATH];
+ int len = _MAX_PATH;
+
+ if(GetUserName(lptstr_buf, &len))
+ ui->login = lptstr_to_utf8(lptstr_buf);
+ else
+ ui->login = our_getenv("USERNAME");
+
+ if((p = our_getenv("HOMEDRIVE"))
+ && (q = our_getenv("HOMEPATH")))
+ snprintf(buf, sizeof(buf), "%s%s", p, q);
+ else
+ snprintf(buf, sizeof(buf), "%c:\\", '@' + _getdrive());
+
+ if(p)
+ free((void *)p);
+
+ if(q)
+ free((void *)q);
+
+ ui->homedir = (char *) malloc((strlen(buf)+1) * sizeof(char));
+ if(ui->homedir){
+ strncpy(ui->homedir, buf, strlen(buf));
+ ui->homedir[strlen(buf)] = '\0';
+ }
+
+ ui->fullname = (char *) malloc(sizeof(char));
+ if(ui->fullname)
+ ui->fullname[0] = '\0';
+#endif /* _WINDOWS */
+}
+
+
+/*----------------------------------------------------------------------
+ Look up a userid on the local system and return rfc822 address
+
+ Args: name -- possible login name on local system
+
+ Result: returns NULL or pointer to alloc'd string rfc822 address.
+ ----*/
+char *
+local_name_lookup(char *name)
+{
+#ifndef _WINDOWS
+ struct passwd *pw = getpwnam(name);
+
+ if(pw == NULL){
+ char *p;
+
+ for(p = name; *p; p++)
+ if(isupper((unsigned char)*p))
+ break;
+
+ /* try changing it to all lower case */
+ if(p && *p){
+ char lcase[256];
+ size_t l;
+
+ snprintf(lcase, sizeof(lcase), "%s", name);
+
+ l = strlen(name);
+ for(p = lcase; *p; p++)
+ if(isupper((unsigned char)*p))
+ *p = tolower((unsigned char)*p);
+
+ pw = getpwnam(lcase);
+
+ if(pw){
+ strncpy(name, lcase, l+1);
+ name[l] = '\0';
+ }
+ }
+ }
+
+ if(pw != NULL){
+ char *gn, *s = NULL;
+ size_t l;
+
+ if((gn = gcos_name(pw->pw_gecos, name)) != NULL
+ && (s = (char *) malloc(l = ((strlen(gn) + 1) * sizeof(char)))) != NULL)
+ snprintf(s, l, "%s", gn);
+
+ return(s);
+ }
+ else
+ return((char *) NULL);
+#else /* _WINDOWS */
+ return(NULL);
+#endif /* _WINDOWS */
+}
+
+
diff --git a/pith/osdep/pw_stuff.h b/pith/osdep/pw_stuff.h
new file mode 100644
index 00000000..7a1191b7
--- /dev/null
+++ b/pith/osdep/pw_stuff.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: pw_stuff.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_PW_STUFF_INCLUDED
+#define PITH_OSDEP_PW_STUFF_INCLUDED
+
+#include "../user.h"
+
+
+/*
+ * Exported Prototypes
+ */
+void get_user_info(USER_S *);
+char *local_name_lookup(char *);
+
+
+#endif /* PITH_OSDEP_PW_STUFF_INCLUDED */
diff --git a/pith/osdep/rename.c b/pith/osdep/rename.c
new file mode 100644
index 00000000..8ca993b4
--- /dev/null
+++ b/pith/osdep/rename.c
@@ -0,0 +1,69 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: rename.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "err_desc.h"
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "rename.h"
+
+
+/*----------------------------------------------------------------------
+ Rename a file
+
+ Args: tmpfname -- Old name of file
+ fname -- New name of file
+
+ Result: File is renamed. Returns 0 on success, else -1 on error
+ and errno is valid.
+ ----*/
+int
+rename_file(char *tmpfname, char *fname)
+{
+#if HAVE_RENAME
+ return(our_rename(tmpfname, fname));
+#else
+# if defined(_WINDOWS)
+ int ret;
+
+ /*
+ * DOS rename doesn't unlink destination for us...
+ */
+ if((ret = our_unlink(fname)) && (errno == EPERM)){
+ ret = -5;
+ }
+ else{
+ ret = our_rename(tmpfname, fname);
+ if(ret)
+ ret = -1;
+ }
+
+ return(ret);
+# else
+ int status;
+
+ our_unlink(fname);
+ if ((status = link(tmpfname, fname)) != 0)
+ return(status);
+
+ our_unlink(tmpfname);
+ return(0);
+# endif
+#endif
+}
+
+
diff --git a/pith/osdep/rename.h b/pith/osdep/rename.h
new file mode 100644
index 00000000..e8472495
--- /dev/null
+++ b/pith/osdep/rename.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: rename.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_RENAME_INCLUDED
+#define PITH_OSDEP_RENAME_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+int rename_file(char *, char *);
+
+
+#endif /* PITH_OSDEP_RENAME_INCLUDED */
diff --git a/pith/osdep/temp_nam.c b/pith/osdep/temp_nam.c
new file mode 100644
index 00000000..03136ea6
--- /dev/null
+++ b/pith/osdep/temp_nam.c
@@ -0,0 +1,345 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: temp_nam.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "canaccess.h"
+#include "temp_nam.h"
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+
+
+#ifdef _WINDOWS
+
+#include <process.h>
+
+#define ACCESSIBLE (WRITE_ACCESS)
+#define PATH_SEP "\\"
+
+#else /* UNIX */
+
+#define ACCESSIBLE (WRITE_ACCESS|EXECUTE_ACCESS)
+#define PATH_SEP "/"
+
+#endif /* UNIX */
+
+
+/*
+ * Internal Prototypes
+ */
+char *was_nonexistent_tmp_name(char *, size_t, char *);
+
+
+
+/*
+ * This routine is derived from BSD4.3 code,
+ * Copyright (c) 1987 Regents of the University of California.
+ * All rights reserved.
+ */
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)mktemp.c 5.7 (Berkeley) 6/27/88";
+#endif /* LIBC_SCCS and not lint */
+
+char *
+was_nonexistent_tmp_name(char *as, size_t aslen, char *ext)
+{
+ register char *start, *trv;
+ struct stat sbuf;
+ unsigned pid;
+ static unsigned n = 0;
+ int i;
+ int fd, tries = 0;
+ int f;
+ static unsigned long r = 0;
+
+ if(r == 0L)
+ r = (unsigned)getpid() + time((time_t *)0);
+
+ for(i = 5; i > 0; i--)
+ r = 1664525 * r + 1013904223;
+
+ pid = ((unsigned)getpid() * 100) + n++;
+
+ pid += (r % 50000L);
+
+ /* extra X's get set to 0's */
+ for(trv = as; *trv; ++trv)
+ ;
+
+ /*
+ * We should probably make the name random instead of having it
+ * be the pid.
+ */
+ while(*--trv == 'X'){
+ *trv = (pid % 10) + '0';
+ pid /= 10;
+ }
+
+ /* add the extension, enough room guaranteed by caller */
+ if(ext && *ext){
+ strncat(as, ".", aslen-strlen(as)-1);
+ as[aslen-1] = '\0';
+ strncat(as, ext, aslen-strlen(as)-1);
+ as[aslen-1] = '\0';
+ }
+
+ /*
+ * Check for write permission on target directory; if you have
+ * six X's and you can't write the directory, this will run for
+ * a *very* long time.
+ */
+ for(start = ++trv; trv > as && *trv != PATH_SEP[0]; --trv)
+ ;
+
+ if(*trv == PATH_SEP[0]){
+#ifdef _WINDOWS
+ char treplace;
+
+ if((trv - as == 2) && isalpha(as[0]) && as[1] == ':')
+ trv++;
+ treplace = *trv;
+ *trv = '\0';
+ if(our_stat(as==trv ? PATH_SEP : as, &sbuf) || !(sbuf.st_mode & S_IFDIR))
+ return((char *)NULL);
+
+ *trv = treplace;
+
+#else /* UNIX */
+
+ *trv = '\0';
+
+ if(our_stat(as==trv ? PATH_SEP : as, &sbuf) || !(sbuf.st_mode & S_IFDIR))
+ return((char *)NULL);
+
+ *trv = PATH_SEP[0];
+
+#endif
+ }
+ else if (our_stat(".", &sbuf) == -1)
+ return((char *)NULL);
+
+ for(;;){
+ /*
+ * Check with lstat to be sure we don't have
+ * a symlink. If lstat fails and no such file, then we
+ * have a winner. Otherwise, lstat shouldn't fail.
+ * If lstat succeeds, then skip it because it exists.
+ */
+#ifndef _WINDOWS
+ if(our_lstat(as, &sbuf)){ /* lstat failed */
+ if(errno == ENOENT){ /* no such file, success */
+#endif /* !_WINDOWS */
+ /*
+ * Create the file so that the
+ * evil ones don't have a chance to put something there
+ * that they can read or write before we create it
+ * ourselves.
+ */
+ f = O_CREAT|O_EXCL|O_WRONLY|O_BINARY;
+
+ if((fd=our_open(as, f, 0600)) >= 0 && close(fd) == 0)
+ return(as);
+ else if(++tries > 3) /* open failed unexpectedly */
+ return((char *)NULL);
+#ifndef _WINDOWS
+ }
+ else /* failed for unknown reason */
+ return((char *)NULL);
+ }
+#endif /* !_WINDOWS */
+
+ for(trv = start;;){
+ if(!*trv)
+ return((char *)NULL);
+
+ /*
+ * Change the digits from the initial values into
+ * lower case letters and try again.
+ */
+ if(*trv == 'z')
+ *trv++ = 'a';
+ else{
+ if(isdigit((unsigned char)*trv))
+ *trv = 'a';
+ else
+ ++*trv;
+
+ break;
+ }
+ }
+ }
+ /*NOTREACHED*/
+}
+
+
+/*
+ * This routine is derived from BSD4.3 code,
+ * Copyright (c) 1988 Regents of the University of California.
+ * All rights reserved.
+ */
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)tmpnam.c 4.5 (Berkeley) 6/27/88";
+#endif /* LIBC_SCCS and not lint */
+/*----------------------------------------------------------------------
+ Return a unique file name in a given directory. This is not quite
+ the same as the usual tempnam() function, though it is similar.
+ We want it to use the TMPDIR/TMP/TEMP environment variable only if dir
+ is NULL, instead of using it regardless if it is set.
+ We also want it to be safer than tempnam().
+ If we return a filename, we are saying that the file did not exist
+ at the time this function was called (and it wasn't a symlink pointing
+ to a file that didn't exist, either).
+ If dir is NULL this is a temp file in a public directory. In that
+ case we create the file with permission 0600 before returning.
+
+ Args: dir -- The directory to create the name in
+ prefix -- Prefix of the name
+
+ Result: Malloc'd string equal to new name is returned. It must be free'd
+ by the caller. Returns the string on success and NULL on failure.
+ ----*/
+char *
+temp_nam(char *dir, char *prefix)
+{
+ return(temp_nam_ext(dir, prefix, NULL));
+}
+
+
+/*----------------------------------------------------------------------
+
+ Like temp_nam but create a unique name with an extension.
+
+ Result: Malloc'd string equal to new name is returned. It must be free'd
+ by the caller. Returns the string on success and NULL on failure.
+ ----*/
+char *
+temp_nam_ext(char *dir, char *prefix, char *ext)
+{
+ struct stat buf;
+ size_t l, ll;
+ char *f, *name;
+
+ if(ext == NULL)
+ ext = "";
+
+ if(!(name = (char *)malloc(MAXPATH * sizeof(char))))
+ return((char *)NULL);
+
+ if(!dir && (f = getenv("TMPDIR")) && !our_stat(f, &buf) &&
+ (buf.st_mode&S_IFMT) == S_IFDIR &&
+ !can_access(f, ACCESSIBLE)){
+ strncpy(name, f, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+
+ if(!dir && (f = getenv("TMP")) && !our_stat(f, &buf) &&
+ (buf.st_mode&S_IFMT) == S_IFDIR &&
+ !can_access(f, ACCESSIBLE)){
+ strncpy(name, f, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+
+ if(!dir && (f = getenv("TEMP")) && !our_stat(f, &buf) &&
+ (buf.st_mode&S_IFMT) == S_IFDIR &&
+ !can_access(f, ACCESSIBLE)){
+ strncpy(name, f, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+
+ if(dir){
+ strncpy(name, dir, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+
+#ifdef _WINDOWS
+ if(!*dir || (isalpha(*dir) && *(dir+1) == ':' && !*(dir+2))){
+ strncat(name, PATH_SEP, MAXPATH-strlen(name)-1);
+ name[MAXPATH-1] = '\0';
+ }
+#endif
+
+ if(!our_stat(name, &buf)
+ && (buf.st_mode&S_IFMT) == S_IFDIR
+ && !can_access(name, ACCESSIBLE)){
+ strncpy(name, dir, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+ }
+
+#ifndef P_tmpdir
+#ifdef _WINDOWS
+#define P_tmpdir "\\tmp"
+#else /* UNIX */
+#define P_tmpdir "/usr/tmp"
+#endif /* UNIX */
+#endif
+
+ if(!our_stat(P_tmpdir, &buf) &&
+ (buf.st_mode&S_IFMT) == S_IFDIR &&
+ !can_access(P_tmpdir, ACCESSIBLE)){
+ strncpy(name, P_tmpdir, MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+
+#ifndef _WINDOWS
+ if(!our_stat("/tmp", &buf) &&
+ (buf.st_mode&S_IFMT) == S_IFDIR &&
+ !can_access("/tmp", ACCESSIBLE)){
+ strncpy(name, "/tmp", MAXPATH-1);
+ name[MAXPATH-1] = '\0';
+ goto done;
+ }
+#endif
+
+ free((void *)name);
+ return((char *)NULL);
+
+done:
+ f = NULL;
+ if(name[0] && *((f = &name[l=strlen(name)]) - 1) != PATH_SEP[0] && l+1 < MAXPATH){
+ *f++ = PATH_SEP[0];
+ *f = '\0';
+ l++;
+ }
+
+ if(prefix && (ll = strlen(prefix)) && l+ll < MAXPATH){
+ strncpy(f, prefix, MAXPATH-(f-name));
+ name[MAXPATH-1] = '\0';
+ f += ll;
+ l += ll;
+ }
+
+ if(l+5+(ext[0] ? strlen(ext)+1 : 0) < MAXPATH){
+ strncpy(f, "XXXXX", MAXPATH-(f-name));
+ name[MAXPATH-1] = '\0';
+ }
+ else{
+ free((void *)name);
+ return((char *)NULL);
+ }
+
+ return(was_nonexistent_tmp_name(name, MAXPATH, ext));
+}
diff --git a/pith/osdep/temp_nam.h b/pith/osdep/temp_nam.h
new file mode 100644
index 00000000..fef56f33
--- /dev/null
+++ b/pith/osdep/temp_nam.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: temp_nam.h 770 2007-10-24 00:23:09Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_TEMP_NAM_INCLUDED
+#define PITH_OSDEP_TEMP_NAM_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *temp_nam(char *, char *);
+char *temp_nam_ext(char *, char *, char *);
+
+
+#endif /* PITH_OSDEP_TEMP_NAM_INCLUDED */
diff --git a/pith/osdep/tempfile.c b/pith/osdep/tempfile.c
new file mode 100644
index 00000000..4aa83e26
--- /dev/null
+++ b/pith/osdep/tempfile.c
@@ -0,0 +1,55 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: tempfile.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#if HAVE_TMPFILE
+#else
+#include "temp_nam.h"
+#endif
+
+#include "../charconv/filesys.h"
+
+#include "tempfile.h"
+
+
+/*----------------------------------------------------------------------
+ Create a temporary file, the name of which we don't care about
+and that goes away when it is closed. Just like ANSI C tmpfile.
+ ----*/
+FILE *
+create_tmpfile(void)
+{
+#if HAVE_TMPFILE
+ return(tmpfile());
+#else
+ char *file_name;
+ FILE *stream = NULL;
+
+ file_name = temp_nam(NULL, "pine-tmp");
+ if(file_name){
+ stream = our_fopen(file_name, "w+b");
+ our_unlink(file_name);
+ fs_give((void **) &file_name);
+ }
+
+ return(stream);
+#endif
+}
+
+
diff --git a/pith/osdep/tempfile.h b/pith/osdep/tempfile.h
new file mode 100644
index 00000000..22573f85
--- /dev/null
+++ b/pith/osdep/tempfile.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: tempfile.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_TEMPFILE_INCLUDED
+#define PITH_OSDEP_TEMPFILE_INCLUDED
+
+
+
+/*
+ * Exported Prototypes
+ */
+FILE *create_tmpfile(void);
+
+
+
+#endif /* PITH_OSDEP_TEMPFILE_INCLUDED */
diff --git a/pith/osdep/writ_dir.c b/pith/osdep/writ_dir.c
new file mode 100644
index 00000000..955839c1
--- /dev/null
+++ b/pith/osdep/writ_dir.c
@@ -0,0 +1,54 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: writ_dir.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include "../charconv/utf8.h"
+#include "../charconv/filesys.h"
+#include "canaccess.h"
+#include "writ_dir.h"
+
+
+/*----------------------------------------------------------------------
+ Check to see if a directory exists and is writable by us
+
+ Args: dir -- directory name
+
+ Result: returns 0 if it exists and is writable
+ 1 if it is a directory, but is not writable
+ 2 if it is not a directory
+ 3 it doesn't exist.
+ ----*/
+int
+is_writable_dir(char *dir)
+{
+ struct stat sb;
+
+ if(our_stat(dir, &sb) < 0)
+ /*--- It doesn't exist ---*/
+ return(3);
+
+ if(!(sb.st_mode & S_IFDIR))
+ /*---- it's not a directory ---*/
+ return(2);
+
+ if(can_access(dir, 07))
+ return(1);
+ else
+ return(0);
+}
+
+
diff --git a/pith/osdep/writ_dir.h b/pith/osdep/writ_dir.h
new file mode 100644
index 00000000..c3ed238e
--- /dev/null
+++ b/pith/osdep/writ_dir.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: writ_dir.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_OSDEP_WRIT_DIR_INCLUDED
+#define PITH_OSDEP_WRIT_DIR_INCLUDED
+
+
+
+/*
+ * Exported Prototypes
+ */
+int is_writable_dir(char *);
+
+
+#endif /* PITH_OSDEP_WRIT_DIR_INCLUDED */
diff --git a/pith/pattern.c b/pith/pattern.c
new file mode 100644
index 00000000..84a32c41
--- /dev/null
+++ b/pith/pattern.c
@@ -0,0 +1,8226 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pattern.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/pattern.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/string.h"
+#include "../pith/msgno.h"
+#include "../pith/status.h"
+#include "../pith/list.h"
+#include "../pith/flag.h"
+#include "../pith/tempfile.h"
+#include "../pith/addrstring.h"
+#include "../pith/search.h"
+#include "../pith/mailcmd.h"
+#include "../pith/filter.h"
+#include "../pith/save.h"
+#include "../pith/mimedesc.h"
+#include "../pith/reply.h"
+#include "../pith/folder.h"
+#include "../pith/maillist.h"
+#include "../pith/sort.h"
+#include "../pith/copyaddr.h"
+#include "../pith/pipe.h"
+#include "../pith/list.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/sequence.h"
+#include "../pith/detoken.h"
+#include "../pith/busy.h"
+#include "../pith/indxtype.h"
+#include "../pith/mailindx.h"
+#include "../pith/send.h"
+#include "../pith/icache.h"
+#include "../pith/ablookup.h"
+#include "../pith/keyword.h"
+
+
+/*
+ * Internal prototypes
+ */
+void open_any_patterns(long);
+void sub_open_any_patterns(long);
+void sub_close_patterns(long);
+int sub_any_patterns(long, PAT_STATE *);
+PAT_LINE_S *parse_pat_lit(char *);
+PAT_LINE_S *parse_pat_inherit(void);
+PAT_S *parse_pat(char *);
+void parse_patgrp_slash(char *, PATGRP_S *);
+void parse_action_slash(char *, ACTION_S *);
+ARBHDR_S *parse_arbhdr(char *);
+char *next_arb(char *);
+PAT_S *first_any_pattern(PAT_STATE *);
+PAT_S *last_any_pattern(PAT_STATE *);
+PAT_S *prev_any_pattern(PAT_STATE *);
+PAT_S *next_any_pattern(PAT_STATE *);
+int sub_write_patterns(long);
+int write_pattern_file(char **, PAT_LINE_S *);
+int write_pattern_lit(char **, PAT_LINE_S *);
+int write_pattern_inherit(char **, PAT_LINE_S *);
+char *data_for_patline(PAT_S *);
+int charsets_present_in_msg(MAILSTREAM *, unsigned long, STRLIST_S *);
+void collect_charsets_from_subj(ENVELOPE *, STRLIST_S **);
+void collect_charsets_from_body(BODY *, STRLIST_S **);
+SEARCHPGM *next_not(SEARCHPGM *);
+SEARCHOR *next_or(SEARCHOR **);
+void set_up_search_pgm(char *, PATTERN_S *, SEARCHPGM *);
+void add_type_to_pgm(char *, PATTERN_S *, SEARCHPGM *);
+void set_srch(char *, char *, SEARCHPGM *);
+void set_srch_hdr(char *, char *, SEARCHPGM *);
+void set_search_by_age(INTVL_S *, SEARCHPGM *, int);
+void set_search_by_size(INTVL_S *, SEARCHPGM *);
+int non_eh(char *);
+void add_eh(char **, char **, char *, int *);
+void set_extra_hdrs(char *);
+int is_ascii_string(char *);
+ACTION_S *combine_inherited_role_guts(ACTION_S *);
+int move_filtered_msgs(MAILSTREAM *, MSGNO_S *, char *, int, char *);
+void set_some_flags(MAILSTREAM *, MSGNO_S *, long, char **, char **, int, char *);
+
+
+/*
+ * optional hook for external-program filter test
+ */
+void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
+
+
+void
+role_process_filters(void)
+{
+ int i;
+ MAILSTREAM *stream;
+ MSGNO_S *msgmap;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ stream = ps_global->s_pool.streams[i];
+ if(stream && pine_mail_ping(stream)){
+ msgmap = sp_msgmap(stream);
+ if(msgmap)
+ reprocess_filter_patterns(stream, msgmap, MI_REFILTERING);
+ }
+ }
+}
+
+
+int
+add_to_pattern(PAT_S *pat, long int rflags)
+{
+ PAT_LINE_S *new_patline, *patline;
+ PAT_S *new_pat;
+ PAT_STATE dummy;
+
+ if(!any_patterns(rflags, &dummy))
+ return(0);
+
+ /* need a new patline */
+ new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
+ memset((void *)new_patline, 0, sizeof(*new_patline));
+ new_patline->type = Literal;
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ /* and a copy of pat */
+ new_pat = copy_pat(pat);
+
+ /* tie together */
+ new_patline->first = new_patline->last = new_pat;
+ new_pat->patline = new_patline;
+
+
+ /*
+ * Manipulate bits directly in pattern list.
+ * Cur_pat_h is set by any_patterns.
+ */
+
+
+ /* find last patline */
+ for(patline = (*cur_pat_h)->patlinehead;
+ patline && patline->next;
+ patline = patline->next)
+ ;
+
+ /* add new patline to end of list */
+ if(patline){
+ patline->next = new_patline;
+ new_patline->prev = patline;
+ }
+ else
+ (*cur_pat_h)->patlinehead = new_patline;
+
+ return(1);
+}
+
+
+/*
+ * Does pattern quoting. Takes the string that the user sees and converts
+ * it to the config file string.
+ *
+ * Args: src -- The source string.
+ *
+ * The last arg to add_escapes causes \, and \\ to be replaced with hex
+ * versions of comma and backslash. That's so we can embed commas in
+ * list variables without having them act as separators. If the user wants
+ * a literal comma, they type backslash comma.
+ * If /, \, or " appear (other than the special cases in previous sentence)
+ * they are backslash-escaped like \/, \\, or \".
+ *
+ * Returns: An allocated string with quoting added.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_pat_escapes(char *src)
+{
+ return(add_escapes(src, "/\\\"", '\\', "", ",\\"));
+}
+
+
+/*
+ * Undoes the escape quoting done by add_pat_escapes.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting removed or NULL. The string starts
+ * at src and goes until the end of src or until a / is reached. The
+ * / is not included in the string. /'s may be quoted by preceding
+ * them with a backslash (\) and \'s may also be quoted by
+ * preceding them with a \. In fact, \ quotes any character.
+ * Not quite, \nnn is octal escape, \xXX is hex escape.
+ * Hex escapes are undone but left with a backslash in front.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+remove_pat_escapes(char *src)
+{
+ char *ans = NULL, *q, *p;
+ int done = 0;
+
+ if(src){
+ p = q = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case '\\':
+ src++;
+ if(*src){
+ if(isdigit((unsigned char)*src)){ /* octal escape */
+ *p++ = '\\';
+ *p++ = (char)read_octal(&src);
+ }
+ else if((*src == 'x' || *src == 'X') &&
+ *(src+1) && *(src+2) && isxpair(src+1)){
+ *p++ = '\\';
+ *p++ = (char)read_hex(src+1);
+ src += 3;
+ }
+ else
+ *p++ = *src++;
+ }
+
+ break;
+
+ case '\0':
+ case '/':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+
+ ans = cpystr(q);
+ fs_give((void **)&q);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * This takes envelope data and adds the backslash escapes that the user
+ * would have been responsible for adding if editing manually.
+ * It just escapes commas and backslashes.
+ *
+ * Caller must free result.
+ */
+char *
+add_roletake_escapes(char *src)
+{
+ return(add_escapes(src, ",\\", '\\', "", ""));
+}
+
+/*
+ * This function only escapes commas.
+ */
+char *
+add_comma_escapes(char *src)
+{
+ return(add_escapes(src, ",", '\\', "", ""));
+}
+
+
+/*
+ * These are the global pattern handles which all of the pattern routines
+ * use. Once we open one of these we usually leave it open until exiting
+ * pine. The _any versions are only used if we are altering our configuration,
+ * the _ne (NonEmpty) versions are used routinely. We open the patterns by
+ * calling either nonempty_patterns (normal use) or any_patterns (config).
+ *
+ * There are eight different pinerc variables which contain patterns. They are
+ * patterns-filters2, patterns-roles, patterns-scores2, patterns-indexcolors,
+ * patterns-other, and the old patterns, patterns-filters, and patterns-scores.
+ * The first five are the active patterns variables and the old variable are
+ * kept around so that we can convert old patterns to new. The reason we
+ * split it into five separate variables is so that each can independently
+ * be controlled by the main pinerc or by the exception pinerc. The reason
+ * for the change to filters2 and scores2 was so we could change the semantics
+ * of how rules work when there are pieces in the rule that we don't
+ * understand. We added a rule to detect 8bitSubjects. So a user might have
+ * a filter that deletes messages with 8bitSubjects. The problem was that
+ * that same filter in a old patterns-filters pine would match because it
+ * would ignore the 8bitSubject part of the pattern and match on the rest.
+ * So we changed the semantics so that rules with unknown pieces would be
+ * ignored instead of used. We had to change variable names at the same time
+ * because we were adding the 8bit thing and the old pines are still out
+ * there. Filters and Scores can both be dangerous. Roles, Colors, and Other
+ * seem less dangerous so not worth adding a new variable for them.
+ *
+ * Each of the eight variables has its own handle and status variables below.
+ * That means that they operate independently.
+ *
+ * Looking at just a single one of those variables, it has four possible
+ * values. In normal use, we use the current_val of the variable to set
+ * up the patterns. We do that by calling nonempty_patterns() with the
+ * appropriate rflags. When editing configurations, we have the other two
+ * variables to deal with: main_user_val and post_user_val.
+ * We only ever deal with one of those at a time, so we re-use the variables.
+ * However, we do sometimes want to deal with one of those and at the same
+ * time refer to the current current_val. For example, if we are editing
+ * the post or main user_val for the filters variable, we still want
+ * to check for new mail. If we find new mail we'll want to call
+ * process_filter_patterns which uses the current_val for filter patterns.
+ * That means we have to provide for the case where we are using current_val
+ * at the same time as we're using one of the user_vals. That's why we have
+ * both the _ne variables (NonEmpty) and the _any variables.
+ *
+ * In any_patterns (and first_pattern...) use_flags may only be set to
+ * one value at a time, whereas rflags may be more than one value OR'd together.
+ */
+PAT_HANDLE **cur_pat_h;
+static PAT_HANDLE *pattern_h_roles_ne, *pattern_h_roles_any,
+ *pattern_h_scores_ne, *pattern_h_scores_any,
+ *pattern_h_filts_ne, *pattern_h_filts_any,
+ *pattern_h_filts_cfg,
+ *pattern_h_filts_ne, *pattern_h_filts_any,
+ *pattern_h_incol_ne, *pattern_h_incol_any,
+ *pattern_h_other_ne, *pattern_h_other_any,
+ *pattern_h_srch_ne, *pattern_h_srch_any,
+ *pattern_h_oldpat_ne, *pattern_h_oldpat_any;
+
+/*
+ * These contain the PAT_OPEN_MASK open status and the PAT_USE_MASK use status.
+ */
+static long *cur_pat_status;
+static long pat_status_roles_ne, pat_status_roles_any,
+ pat_status_scores_ne, pat_status_scores_any,
+ pat_status_filts_ne, pat_status_filts_any,
+ pat_status_incol_ne, pat_status_incol_any,
+ pat_status_other_ne, pat_status_other_any,
+ pat_status_srch_ne, pat_status_srch_any,
+ pat_status_oldpat_ne, pat_status_oldpat_any,
+ pat_status_oldfilt_ne, pat_status_oldfilt_any,
+ pat_status_oldscore_ne, pat_status_oldscore_any;
+
+#define SET_PATTYPE(rflags) \
+ set_pathandle(rflags); \
+ cur_pat_status = \
+ ((rflags) & PAT_USE_CURRENT) \
+ ? (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_ne : \
+ ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_ne : \
+ ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_ne : \
+ ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_ne : \
+ ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_ne : \
+ ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_ne : \
+ ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_ne : \
+ ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_ne :\
+ &pat_status_oldpat_ne) \
+ : (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_any : \
+ ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_any : \
+ ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_any : \
+ ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_any : \
+ ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_any : \
+ ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_any : \
+ ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_any :\
+ ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_any:\
+ &pat_status_oldpat_any);
+#define CANONICAL_RFLAGS(rflags) \
+ ((((rflags) & (ROLE_DO_ROLES | ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE)) \
+ ? ROLE_DO_ROLES : 0) | \
+ (((rflags) & (ROLE_DO_INCOLS | ROLE_INCOL)) \
+ ? ROLE_DO_INCOLS : 0) | \
+ (((rflags) & (ROLE_DO_SCORES | ROLE_SCORE)) \
+ ? ROLE_DO_SCORES : 0) | \
+ (((rflags) & (ROLE_DO_FILTER)) \
+ ? ROLE_DO_FILTER : 0) | \
+ (((rflags) & (ROLE_DO_OTHER)) \
+ ? ROLE_DO_OTHER : 0) | \
+ (((rflags) & (ROLE_DO_SRCH)) \
+ ? ROLE_DO_SRCH : 0) | \
+ (((rflags) & (ROLE_OLD_FILT)) \
+ ? ROLE_OLD_FILT : 0) | \
+ (((rflags) & (ROLE_OLD_SCORE)) \
+ ? ROLE_OLD_SCORE : 0) | \
+ (((rflags) & (ROLE_OLD_PAT)) \
+ ? ROLE_OLD_PAT : 0))
+
+#define SETPGMSTATUS(val,yes,no) \
+ switch(val){ \
+ case PAT_STAT_YES: \
+ (yes) = 1; \
+ break; \
+ case PAT_STAT_NO: \
+ (no) = 1; \
+ break; \
+ case PAT_STAT_EITHER: \
+ default: \
+ break; \
+ }
+
+#define SET_STATUS(srchin,srchfor,assignto) \
+ {char *qq, *pp; \
+ int ii; \
+ NAMEVAL_S *vv; \
+ if((qq = srchstr(srchin, srchfor)) != NULL){ \
+ if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
+ for(ii = 0; (vv = role_status_types(ii)); ii++) \
+ if(!strucmp(pp, vv->shortname)){ \
+ assignto = vv->value; \
+ break; \
+ } \
+ \
+ fs_give((void **)&pp); \
+ } \
+ } \
+ }
+
+#define SET_MSGSTATE(srchin,srchfor,assignto) \
+ {char *qq, *pp; \
+ int ii; \
+ NAMEVAL_S *vv; \
+ if((qq = srchstr(srchin, srchfor)) != NULL){ \
+ if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
+ for(ii = 0; (vv = msg_state_types(ii)); ii++) \
+ if(!strucmp(pp, vv->shortname)){ \
+ assignto = vv->value; \
+ break; \
+ } \
+ \
+ fs_give((void **)&pp); \
+ } \
+ } \
+ }
+
+#define PATTERN_N (9)
+
+
+void
+set_pathandle(long int rflags)
+{
+ cur_pat_h = (rflags & PAT_USE_CURRENT)
+ ? ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_ne :
+ (rflags & ROLE_DO_OTHER) ? &pattern_h_other_ne :
+ (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_ne :
+ (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_ne :
+ (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_ne :
+ (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_ne :
+ &pattern_h_oldpat_ne)
+ : ((rflags & PAT_USE_CHANGED)
+ ? &pattern_h_filts_cfg
+ : ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_any :
+ (rflags & ROLE_DO_OTHER) ? &pattern_h_other_any :
+ (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_any :
+ (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_any :
+ (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_any :
+ (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_any :
+ &pattern_h_oldpat_any));
+}
+
+
+/*
+ * Rflags may be more than one pattern type OR'd together. It also contains
+ * the "use" parameter.
+ */
+void
+open_any_patterns(long int rflags)
+{
+ long canon_rflags;
+
+ dprint((7, "open_any_patterns(0x%x)\n", rflags));
+
+ canon_rflags = CANONICAL_RFLAGS(rflags);
+
+ if(canon_rflags & ROLE_DO_INCOLS)
+ sub_open_any_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_FILTER)
+ sub_open_any_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_OTHER)
+ sub_open_any_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_SCORES)
+ sub_open_any_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_ROLES)
+ sub_open_any_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_SRCH)
+ sub_open_any_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_FILT)
+ sub_open_any_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_SCORE)
+ sub_open_any_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_PAT)
+ sub_open_any_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
+}
+
+
+/*
+ * This should only be called with a single pattern type (plus use flags).
+ * We assume that patterns of this type are closed before this is called.
+ * This always succeeds unless we run out of memory, in which case fs_get
+ * never returns.
+ */
+void
+sub_open_any_patterns(long int rflags)
+{
+ PAT_LINE_S *patline = NULL, *pl = NULL;
+ char **t = NULL;
+ struct variable *var;
+
+ SET_PATTYPE(rflags);
+
+ *cur_pat_h = (PAT_HANDLE *)fs_get(sizeof(**cur_pat_h));
+ memset((void *)*cur_pat_h, 0, sizeof(**cur_pat_h));
+
+ if(rflags & ROLE_DO_ROLES)
+ var = &ps_global->vars[V_PAT_ROLES];
+ else if(rflags & ROLE_DO_FILTER)
+ var = &ps_global->vars[V_PAT_FILTS];
+ else if(rflags & ROLE_DO_OTHER)
+ var = &ps_global->vars[V_PAT_OTHER];
+ else if(rflags & ROLE_DO_SCORES)
+ var = &ps_global->vars[V_PAT_SCORES];
+ else if(rflags & ROLE_DO_INCOLS)
+ var = &ps_global->vars[V_PAT_INCOLS];
+ else if(rflags & ROLE_DO_SRCH)
+ var = &ps_global->vars[V_PAT_SRCH];
+ else if(rflags & ROLE_OLD_FILT)
+ var = &ps_global->vars[V_PAT_FILTS_OLD];
+ else if(rflags & ROLE_OLD_SCORE)
+ var = &ps_global->vars[V_PAT_SCORES_OLD];
+ else if(rflags & ROLE_OLD_PAT)
+ var = &ps_global->vars[V_PATTERNS];
+
+ switch(rflags & PAT_USE_MASK){
+ case PAT_USE_CURRENT:
+ t = var->current_val.l;
+ break;
+ case PAT_USE_CHANGED:
+ /*
+ * some trickery to only use changed if actually changed.
+ * otherwise, use current_val
+ */
+ t = var->is_changed_val ? var->changed_val.l : var->current_val.l;
+ break;
+ case PAT_USE_MAIN:
+ t = var->main_user_val.l;
+ break;
+ case PAT_USE_POST:
+ t = var->post_user_val.l;
+ break;
+ }
+
+ if(t){
+ for(; t[0] && t[0][0]; t++){
+ if(*t && !strncmp("LIT:", *t, 4))
+ patline = parse_pat_lit(*t + 4);
+ else if(*t && !strncmp("FILE:", *t, 5))
+ patline = parse_pat_file(*t + 5);
+ else if(rflags & (PAT_USE_MAIN | PAT_USE_POST) &&
+ patline == NULL && *t && !strcmp(INHERIT, *t))
+ patline = parse_pat_inherit();
+ else
+ patline = NULL;
+
+ if(patline){
+ if(pl){
+ pl->next = patline;
+ patline->prev = pl;
+ pl = pl->next;
+ }
+ else{
+ (*cur_pat_h)->patlinehead = patline;
+ pl = patline;
+ }
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ "Invalid patterns line \"%.200s\"", *t);
+ }
+ }
+
+ *cur_pat_status = PAT_OPENED | (rflags & PAT_USE_MASK);
+}
+
+
+void
+close_every_pattern(void)
+{
+ close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
+ | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
+ | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
+ | PAT_USE_CURRENT);
+ /*
+ * Since there is only one set of variables for the other three uses
+ * we can just close any one of them. There can only be one open at
+ * a time.
+ */
+ close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
+ | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
+ | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
+ | PAT_USE_MAIN);
+}
+
+
+/*
+ * Can be called with more than one pattern type.
+ */
+void
+close_patterns(long int rflags)
+{
+ long canon_rflags;
+
+ dprint((7, "close_patterns(0x%x)\n", rflags));
+
+ canon_rflags = CANONICAL_RFLAGS(rflags);
+
+ if(canon_rflags & ROLE_DO_INCOLS)
+ sub_close_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_OTHER)
+ sub_close_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_FILTER)
+ sub_close_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_SCORES)
+ sub_close_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_ROLES)
+ sub_close_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_DO_SRCH)
+ sub_close_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_FILT)
+ sub_close_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_SCORE)
+ sub_close_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
+ if(canon_rflags & ROLE_OLD_PAT)
+ sub_close_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
+}
+
+
+/*
+ * Can be called with only a single pattern type.
+ */
+void
+sub_close_patterns(long int rflags)
+{
+ SET_PATTYPE(rflags);
+
+ if(*cur_pat_h != NULL){
+ free_patline(&(*cur_pat_h)->patlinehead);
+ fs_give((void **)cur_pat_h);
+ }
+
+ *cur_pat_status = PAT_CLOSED;
+
+ scores_are_used(SCOREUSE_INVALID);
+}
+
+
+/*
+ * Can be called with more than one pattern type.
+ * Nonempty always uses PAT_USE_CURRENT (the current_val).
+ */
+int
+nonempty_patterns(long int rflags, PAT_STATE *pstate)
+{
+ return(any_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT, pstate));
+}
+
+
+/*
+ * Initializes pstate and parses and sets up appropriate pattern variables.
+ * May be called with more than one pattern type OR'd together in rflags.
+ * Pstate will keep track of that and next_pattern et. al. will increment
+ * through all of those pattern types.
+ */
+int
+any_patterns(long int rflags, PAT_STATE *pstate)
+{
+ int ret = 0;
+ long canon_rflags;
+
+ dprint((7, "any_patterns(0x%x)\n", rflags));
+
+ memset((void *)pstate, 0, sizeof(*pstate));
+ pstate->rflags = rflags;
+
+ canon_rflags = CANONICAL_RFLAGS(pstate->rflags);
+
+ if(canon_rflags & ROLE_DO_INCOLS)
+ ret += sub_any_patterns(ROLE_DO_INCOLS, pstate);
+ if(canon_rflags & ROLE_DO_OTHER)
+ ret += sub_any_patterns(ROLE_DO_OTHER, pstate);
+ if(canon_rflags & ROLE_DO_FILTER)
+ ret += sub_any_patterns(ROLE_DO_FILTER, pstate);
+ if(canon_rflags & ROLE_DO_SCORES)
+ ret += sub_any_patterns(ROLE_DO_SCORES, pstate);
+ if(canon_rflags & ROLE_DO_ROLES)
+ ret += sub_any_patterns(ROLE_DO_ROLES, pstate);
+ if(canon_rflags & ROLE_DO_SRCH)
+ ret += sub_any_patterns(ROLE_DO_SRCH, pstate);
+ if(canon_rflags & ROLE_OLD_FILT)
+ ret += sub_any_patterns(ROLE_OLD_FILT, pstate);
+ if(canon_rflags & ROLE_OLD_SCORE)
+ ret += sub_any_patterns(ROLE_OLD_SCORE, pstate);
+ if(canon_rflags & ROLE_OLD_PAT)
+ ret += sub_any_patterns(ROLE_OLD_PAT, pstate);
+
+ return(ret);
+}
+
+
+int
+sub_any_patterns(long int rflags, PAT_STATE *pstate)
+{
+ SET_PATTYPE(rflags | (pstate->rflags & PAT_USE_MASK));
+
+ if(*cur_pat_h &&
+ (((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
+ (*cur_pat_status & PAT_USE_MASK) != PAT_USE_CURRENT) ||
+ ((pstate->rflags & PAT_USE_MASK) != PAT_USE_CURRENT &&
+ ((*cur_pat_status & PAT_OPEN_MASK) != PAT_OPENED ||
+ (*cur_pat_status & PAT_USE_MASK) !=
+ (pstate->rflags & PAT_USE_MASK)))))
+ close_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
+
+ /* open_any always succeeds */
+ if(!*cur_pat_h && ((*cur_pat_status & PAT_OPEN_MASK) == PAT_CLOSED))
+ open_any_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
+
+ if(!*cur_pat_h){ /* impossible */
+ *cur_pat_status = PAT_CLOSED;
+ return(0);
+ }
+
+ /*
+ * Opening nonempty can fail. That just means there aren't any
+ * patterns of that type.
+ */
+ if((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
+ !(*cur_pat_h)->patlinehead)
+ *cur_pat_status = (PAT_OPEN_FAILED | PAT_USE_CURRENT);
+
+ return(((*cur_pat_status & PAT_OPEN_MASK) == PAT_OPENED) ? 1 : 0);
+}
+
+
+int
+edit_pattern(PAT_S *newpat, int pos, long int rflags)
+{
+ PAT_S *oldpat;
+ PAT_LINE_S *tpatline;
+ int i;
+ PAT_STATE pstate;
+
+ if(!any_patterns(rflags, &pstate)) return(1);
+
+ for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
+ i < pos && tpatline; tpatline = tpatline->next, i++);
+ if(i != pos) return(1);
+ oldpat = tpatline->first;
+ free_pat(&oldpat);
+ tpatline->first = tpatline->last = newpat;
+ newpat->patline = tpatline;
+ tpatline->dirty = 1;
+
+ (*cur_pat_h)->dirtypinerc = 1;
+ write_patterns(rflags);
+
+ return(0);
+}
+
+int
+add_pattern(PAT_S *newpat, long int rflags)
+{
+ PAT_LINE_S *tpatline, *newpatline;
+ PAT_STATE pstate;
+
+ any_patterns(rflags, &pstate);
+
+ for(tpatline = (*cur_pat_h)->patlinehead;
+ tpatline && tpatline->next ; tpatline = tpatline->next);
+ newpatline = (PAT_LINE_S *)fs_get(sizeof(PAT_LINE_S));
+ if(tpatline)
+ tpatline->next = newpatline;
+ else
+ (*cur_pat_h)->patlinehead = newpatline;
+ memset((void *)newpatline, 0, sizeof(PAT_LINE_S));
+ newpatline->prev = tpatline;
+ newpatline->first = newpatline->last = newpat;
+ newpatline->type = Literal;
+ newpat->patline = newpatline;
+ newpatline->dirty = 1;
+
+ (*cur_pat_h)->dirtypinerc = 1;
+ write_patterns(rflags);
+
+ return(0);
+}
+
+int
+delete_pattern(int pos, long int rflags)
+{
+ PAT_LINE_S *tpatline;
+ int i;
+ PAT_STATE pstate;
+
+ if(!any_patterns(rflags, &pstate)) return(1);
+
+ for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
+ i < pos && tpatline; tpatline = tpatline->next, i++);
+ if(i != pos) return(1);
+
+ if(tpatline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = tpatline->next;
+ if(tpatline->prev) tpatline->prev->next = tpatline->next;
+ if(tpatline->next) tpatline->next->prev = tpatline->prev;
+ tpatline->prev = NULL;
+ tpatline->next = NULL;
+
+ free_patline(&tpatline);
+
+ (*cur_pat_h)->dirtypinerc = 1;
+ write_patterns(rflags);
+
+ return(0);
+}
+
+int
+shuffle_pattern(int pos, int up, long int rflags)
+{
+ PAT_LINE_S *tpatline, *shufpatline;
+ int i;
+ PAT_STATE pstate;
+
+ if(!any_patterns(rflags, &pstate)) return(1);
+
+ for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
+ i < pos && tpatline; tpatline = tpatline->next, i++);
+ if(i != pos) return(1);
+
+ if(up == 1){
+ if(tpatline->prev == NULL) return(1);
+ shufpatline = tpatline->prev;
+ tpatline->prev = shufpatline->prev;
+ if(shufpatline->prev)
+ shufpatline->prev->next = tpatline;
+ if(tpatline->next)
+ tpatline->next->prev = shufpatline;
+ shufpatline->next = tpatline->next;
+ shufpatline->prev = tpatline;
+ tpatline->next = shufpatline;
+ if(shufpatline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = tpatline;
+ }
+ else if(up == -1){
+ if(tpatline->next == NULL) return(1);
+ shufpatline = tpatline->next;
+ tpatline->next = shufpatline->next;
+ if(shufpatline->next)
+ shufpatline->next->prev = tpatline;
+ if(tpatline->prev)
+ tpatline->prev->next = shufpatline;
+ shufpatline->prev = tpatline->prev;
+ shufpatline->next = tpatline;
+ tpatline->prev = shufpatline;
+ if(tpatline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = shufpatline;
+ }
+ else return(1);
+
+ shufpatline->dirty = 1;
+ tpatline->dirty = 1;
+
+ (*cur_pat_h)->dirtypinerc = 1;
+ write_patterns(rflags);
+
+ return(0);
+}
+
+PAT_LINE_S *
+parse_pat_lit(char *litpat)
+{
+ PAT_LINE_S *patline;
+ PAT_S *pat;
+
+ patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
+ memset((void *)patline, 0, sizeof(*patline));
+ patline->type = Literal;
+
+
+ if((pat = parse_pat(litpat)) != NULL){
+ pat->patline = patline;
+ patline->first = pat;
+ patline->last = pat;
+ }
+
+ return(patline);
+}
+
+
+/*
+ * This always returns a patline even if we can't read the file. The patline
+ * returned will say readonly in the worst case and there will be no patterns.
+ * If the file doesn't exist, this creates it if possible.
+ */
+PAT_LINE_S *
+parse_pat_file(char *filename)
+{
+#define BUF_SIZE 5000
+ PAT_LINE_S *patline;
+ PAT_S *pat, *p;
+ char path[MAXPATH+1], buf[BUF_SIZE];
+ char *dir, *q;
+ FILE *fp;
+ int ok = 0, some_pats = 0;
+ struct variable *vars = ps_global->vars;
+
+ signature_path(filename, path, MAXPATH);
+
+ if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Can't use Roles file outside of %.200s",
+ VAR_OPER_DIR);
+ return(NULL);
+ }
+
+ patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
+ memset((void *)patline, 0, sizeof(*patline));
+ patline->type = File;
+ patline->filename = cpystr(filename);
+ patline->filepath = cpystr(path);
+
+ if((q = last_cmpnt(path)) != NULL){
+ int save;
+
+ save = *--q;
+ *q = '\0';
+ dir = cpystr(*path ? path : "/");
+ *q = save;
+ }
+ else
+ dir = cpystr(".");
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * If the dir has become a drive letter and : (e.g. "c:")
+ * then append a "\". The library function access() in the
+ * win 16 version of MSC seems to require this.
+ */
+ if(isalpha((unsigned char) *dir)
+ && *(dir+1) == ':' && *(dir+2) == '\0'){
+ *(dir+2) = '\\';
+ *(dir+3) = '\0';
+ }
+#endif /* DOS || OS2 */
+
+ /*
+ * Even if we can edit the file itself, we aren't going
+ * to be able to change it unless we can also write in
+ * the directory that contains it (because we write into a
+ * temp file and then rename).
+ */
+ if(can_access(dir, EDIT_ACCESS) != 0)
+ patline->readonly = 1;
+
+ if(can_access(path, EDIT_ACCESS) == 0){
+ if(patline->readonly)
+ q_status_message1(SM_ORDER, 0, 3,
+ "Pattern file directory (%.200s) is ReadOnly", dir);
+ }
+ else if(can_access(path, READ_ACCESS) == 0)
+ patline->readonly = 1;
+
+ if(can_access(path, ACCESS_EXISTS) == 0){
+ if((fp = our_fopen(path, "rb")) != NULL){
+ /* Check to see if this is a valid patterns file */
+ if(fp_file_size(fp) <= 0L)
+ ok++;
+ else{
+ size_t len;
+
+ len = strlen(PATTERN_MAGIC);
+ if(fread(buf, sizeof(char), len+3, fp) == len+3){
+ buf[len+3] = '\0';
+ buf[len] = '\0';
+ if(strcmp(buf, PATTERN_MAGIC) == 0){
+ if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1))
+ q_status_message1(SM_ORDER, 0, 4,
+ "Pattern file \"%.200s\" is made by newer Alpine, will try to use it anyway",
+ filename);
+
+ ok++;
+ some_pats++;
+ /* toss rest of first line */
+ (void)fgets(buf, BUF_SIZE, fp);
+ }
+ }
+ }
+
+ if(!ok){
+ patline->readonly = 1;
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "\"%.200s\" is not a Pattern file", path);
+ }
+
+ p = NULL;
+ while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){
+ if((pat = parse_pat(buf)) != NULL){
+ pat->patline = patline;
+ if(!patline->first)
+ patline->first = pat;
+
+ patline->last = pat;
+
+ if(p){
+ p->next = pat;
+ pat->prev = p;
+ p = p->next;
+ }
+ else
+ p = pat;
+ }
+ }
+
+ (void)fclose(fp);
+ }
+ else{
+ patline->readonly = 1;
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Error \"%.200s\" reading pattern file \"%.200s\"",
+ error_description(errno), path);
+ }
+ }
+ else{ /* doesn't exist yet, try to create it */
+ if(patline->readonly)
+ q_status_message1(SM_ORDER, 0, 3,
+ "Pattern file directory (%.200s) is ReadOnly", dir);
+ else{
+ /*
+ * We try to create it by making up an empty patline and calling
+ * write_pattern_file.
+ */
+ patline->dirty = 1;
+ if(write_pattern_file(NULL, patline) != 0){
+ patline->readonly = 1;
+ patline->dirty = 0;
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Error creating pattern file \"%.200s\"",
+ path);
+ }
+ }
+ }
+
+ if(dir)
+ fs_give((void **)&dir);
+
+ return(patline);
+}
+
+
+PAT_LINE_S *
+parse_pat_inherit(void)
+{
+ PAT_LINE_S *patline;
+ PAT_S *pat;
+
+ patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
+ memset((void *)patline, 0, sizeof(*patline));
+ patline->type = Inherit;
+
+ pat = (PAT_S *)fs_get(sizeof(*pat));
+ memset((void *)pat, 0, sizeof(*pat));
+ pat->inherit = 1;
+
+ pat->patline = patline;
+ patline->first = pat;
+ patline->last = pat;
+
+ return(patline);
+}
+
+
+/*
+ * There are three forms that a PATTERN_S has at various times. There is
+ * the actual PATTERN_S struct which is used internally and is used whenever
+ * we are actually doing something with the pattern, like filtering or
+ * something. There is the version that goes in the config file. And there
+ * is the version the user edits.
+ *
+ * To go between these three forms we have the helper routines
+ *
+ * pattern_to_config
+ * config_to_pattern
+ * pattern_to_editlist
+ * editlist_to_pattern
+ *
+ * Here's what is supposed to be happening. A PATTERN_S is a linked list
+ * of strings with nothing escaped. That is, a backslash or a comma is
+ * just in there as a backslash or comma.
+ *
+ * The version the user edits is very similar. Because we have historically
+ * used commas as separators the user has always had to enter a \, in order
+ * to put a real comma in one of the items. That is the only difference
+ * between a PATTERN_S string and the editlist strings. Note that backslashes
+ * themselves are not escaped. A backslash which is not followed by a comma
+ * is a backslash. It doesn't escape the following character. That's a bit
+ * odd, it is that way because most people will never know about this
+ * backslash stuff but PC-Pine users may have backslashes in folder names.
+ *
+ * The version that goes in the config file has a few transformations made.
+ * PATTERN_S intermediate_form Config string
+ * , \, \x2C
+ * \ \\ \x5C
+ * / \/
+ * " \"
+ *
+ * The commas are turned into hex commas so that we can tell the separators
+ * in the comma-separated lists from those commas.
+ * The backslashes are escaped because they escape commas.
+ * The /'s are escaped because they separate pattern pieces.
+ * The "'s are escaped because they are significant to parse_list when
+ * parsing the config file.
+ * hubert - 2004-04-01
+ * (date is only coincidental!)
+ *
+ * Addendum. The not's are handled separately from all the strings. Not sure
+ * why that is or if there is a good reason. Nevertheless, now is not the
+ * time to figure it out so leave it that way.
+ * hubert - 2004-07-14
+ */
+PAT_S *
+parse_pat(char *str)
+{
+ PAT_S *pat = NULL;
+ char *p, *q, *astr, *pstr;
+ int backslashed;
+#define PTRN "pattern="
+#define PTRNLEN 8
+#define ACTN "action="
+#define ACTNLEN 7
+
+ if(str)
+ removing_trailing_white_space(str);
+
+ if(!str || !*str || *str == '#')
+ return(pat);
+
+ pat = (PAT_S *)fs_get(sizeof(*pat));
+ memset((void *)pat, 0, sizeof(*pat));
+
+ if((p = srchstr(str, PTRN)) != NULL){
+ pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp));
+ memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp));
+ pat->patgrp->fldr_type = FLDR_DEFL;
+ pat->patgrp->inabook = IAB_DEFL;
+ pat->patgrp->cat_lim = -1L;
+
+ if((pstr = copy_quoted_string_asis(p+PTRNLEN)) != NULL){
+ /* move to next slash */
+ for(q=pstr, backslashed=0; *q; q++){
+ switch(*q){
+ case '\\':
+ backslashed = !backslashed;
+ break;
+
+ case '/':
+ if(!backslashed){
+ parse_patgrp_slash(q, pat->patgrp);
+ if(pat->patgrp->bogus && !pat->raw)
+ pat->raw = cpystr(str);
+ }
+
+ /* fall through */
+
+ default:
+ backslashed = 0;
+ break;
+ }
+ }
+
+ /* we always force a nickname */
+ if(!pat->patgrp->nick)
+ pat->patgrp->nick = cpystr("Alternate Role");
+
+ fs_give((void **)&pstr);
+ }
+ }
+
+ if((p = srchstr(str, ACTN)) != NULL){
+ pat->action = (ACTION_S *)fs_get(sizeof(*pat->action));
+ memset((void *)pat->action, 0, sizeof(*pat->action));
+ pat->action->startup_rule = IS_NOTSET;
+ pat->action->repl_type = ROLE_REPL_DEFL;
+ pat->action->forw_type = ROLE_FORW_DEFL;
+ pat->action->comp_type = ROLE_COMP_DEFL;
+ pat->action->nick = cpystr((pat->patgrp && pat->patgrp->nick
+ && pat->patgrp->nick[0])
+ ? pat->patgrp->nick : "Alternate Role");
+
+ if((astr = copy_quoted_string_asis(p+ACTNLEN)) != NULL){
+ /* move to next slash */
+ for(q=astr, backslashed=0; *q; q++){
+ switch(*q){
+ case '\\':
+ backslashed = !backslashed;
+ break;
+
+ case '/':
+ if(!backslashed){
+ parse_action_slash(q, pat->action);
+ if(pat->action->bogus && !pat->raw)
+ pat->raw = cpystr(str);
+ }
+
+ /* fall through */
+
+ default:
+ backslashed = 0;
+ break;
+ }
+ }
+
+ fs_give((void **)&astr);
+
+ if(!pat->action->is_a_score)
+ pat->action->scoreval = 0L;
+
+ if(pat->action->is_a_filter)
+ pat->action->kill = (pat->action->folder
+ || pat->action->kill == -1) ? 0 : 1;
+ else{
+ if(pat->action->folder)
+ free_pattern(&pat->action->folder);
+ }
+
+ if(!pat->action->is_a_role){
+ pat->action->repl_type = ROLE_NOTAROLE_DEFL;
+ pat->action->forw_type = ROLE_NOTAROLE_DEFL;
+ pat->action->comp_type = ROLE_NOTAROLE_DEFL;
+ if(pat->action->from)
+ mail_free_address(&pat->action->from);
+ if(pat->action->replyto)
+ mail_free_address(&pat->action->replyto);
+ if(pat->action->fcc)
+ fs_give((void **)&pat->action->fcc);
+ if(pat->action->litsig)
+ fs_give((void **)&pat->action->litsig);
+ if(pat->action->sig)
+ fs_give((void **)&pat->action->sig);
+ if(pat->action->template)
+ fs_give((void **)&pat->action->template);
+ if(pat->action->cstm)
+ free_list_array(&pat->action->cstm);
+ if(pat->action->smtp)
+ free_list_array(&pat->action->smtp);
+ if(pat->action->nntp)
+ free_list_array(&pat->action->nntp);
+ if(pat->action->inherit_nick)
+ fs_give((void **)&pat->action->inherit_nick);
+ }
+
+ if(!pat->action->is_a_incol){
+ if(pat->action->incol)
+ free_color_pair(&pat->action->incol);
+ }
+
+ if(!pat->action->is_a_other){
+ pat->action->sort_is_set = 0;
+ pat->action->sortorder = 0;
+ pat->action->revsort = 0;
+ pat->action->startup_rule = IS_NOTSET;
+ if(pat->action->index_format)
+ fs_give((void **)&pat->action->index_format);
+ }
+ }
+ }
+
+ return(pat);
+}
+
+
+/*
+ * Fill in one member of patgrp from str.
+ *
+ * The multiple constant strings are lame but it evolved this way from
+ * previous versions and isn't worth fixing.
+ */
+void
+parse_patgrp_slash(char *str, PATGRP_S *patgrp)
+{
+ char *p;
+
+ if(!patgrp)
+ panic("NULL patgrp to parse_patgrp_slash");
+ else if(!(str && *str)){
+ panic("NULL or empty string to parse_patgrp_slash");
+ patgrp->bogus = 1;
+ }
+ else if(!strncmp(str, "/NICK=", 6))
+ patgrp->nick = remove_pat_escapes(str+6);
+ else if(!strncmp(str, "/COMM=", 6))
+ patgrp->comment = remove_pat_escapes(str+6);
+ else if(!strncmp(str, "/TO=", 4) || !strncmp(str, "/!TO=", 5))
+ patgrp->to = parse_pattern("TO", str, 1);
+ else if(!strncmp(str, "/CC=", 4) || !strncmp(str, "/!CC=", 5))
+ patgrp->cc = parse_pattern("CC", str, 1);
+ else if(!strncmp(str, "/RECIP=", 7) || !strncmp(str, "/!RECIP=", 8))
+ patgrp->recip = parse_pattern("RECIP", str, 1);
+ else if(!strncmp(str, "/PARTIC=", 8) || !strncmp(str, "/!PARTIC=", 9))
+ patgrp->partic = parse_pattern("PARTIC", str, 1);
+ else if(!strncmp(str, "/FROM=", 6) || !strncmp(str, "/!FROM=", 7))
+ patgrp->from = parse_pattern("FROM", str, 1);
+ else if(!strncmp(str, "/SENDER=", 8) || !strncmp(str, "/!SENDER=", 9))
+ patgrp->sender = parse_pattern("SENDER", str, 1);
+ else if(!strncmp(str, "/NEWS=", 6) || !strncmp(str, "/!NEWS=", 7))
+ patgrp->news = parse_pattern("NEWS", str, 1);
+ else if(!strncmp(str, "/SUBJ=", 6) || !strncmp(str, "/!SUBJ=", 7))
+ patgrp->subj = parse_pattern("SUBJ", str, 1);
+ else if(!strncmp(str, "/ALL=", 5) || !strncmp(str, "/!ALL=", 6))
+ patgrp->alltext = parse_pattern("ALL", str, 1);
+ else if(!strncmp(str, "/BODY=", 6) || !strncmp(str, "/!BODY=", 7))
+ patgrp->bodytext = parse_pattern("BODY", str, 1);
+ else if(!strncmp(str, "/KEY=", 5) || !strncmp(str, "/!KEY=", 6))
+ patgrp->keyword = parse_pattern("KEY", str, 1);
+ else if(!strncmp(str, "/CHAR=", 6) || !strncmp(str, "/!CHAR=", 7))
+ patgrp->charsets = parse_pattern("CHAR", str, 1);
+ else if(!strncmp(str, "/FOLDER=", 8) || !strncmp(str, "/!FOLDER=", 9))
+ patgrp->folder = parse_pattern("FOLDER", str, 1);
+ else if(!strncmp(str, "/ABOOKS=", 8) || !strncmp(str, "/!ABOOKS=", 9))
+ patgrp->abooks = parse_pattern("ABOOKS", str, 1);
+ /*
+ * A problem with arbhdrs is that more than one of them can appear in
+ * the string. We come back here the second time, but we already took
+ * care of the whole thing on the first pass. Hence the check for
+ * arbhdr already set.
+ */
+ else if(!strncmp(str, "/ARB", 4) || !strncmp(str, "/!ARB", 5)
+ || !strncmp(str, "/EARB", 5) || !strncmp(str, "/!EARB", 6)){
+ if(!patgrp->arbhdr)
+ patgrp->arbhdr = parse_arbhdr(str);
+ /* else do nothing */
+ }
+ else if(!strncmp(str, "/SENTDATE=", 10))
+ patgrp->age_uses_sentdate = 1;
+ else if(!strncmp(str, "/SCOREI=", 8)){
+ if((p = remove_pat_escapes(str+8)) != NULL){
+ if((patgrp->score = parse_intvl(p)) != NULL)
+ patgrp->do_score = 1;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/AGE=", 5)){
+ if((p = remove_pat_escapes(str+5)) != NULL){
+ if((patgrp->age = parse_intvl(p)) != NULL)
+ patgrp->do_age = 1;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/SIZE=", 6)){
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ if((patgrp->size = parse_intvl(p)) != NULL)
+ patgrp->do_size = 1;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/CATCMD=", 8)){
+ if((p = remove_pat_escapes(str+8)) != NULL){
+ int commas = 0;
+ char *q;
+
+ /* count elements in list */
+ for(q = p; q && *q; q++)
+ if(*q == ',')
+ commas++;
+
+ patgrp->category_cmd = parse_list(p, commas+1, PL_REMSURRQUOT,NULL);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/CATVAL=", 8)){
+ if((p = remove_pat_escapes(str+8)) != NULL){
+ if((patgrp->cat = parse_intvl(p)) != NULL)
+ patgrp->do_cat = 1;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/CATLIM=", 8)){
+ if((p = remove_pat_escapes(str+8)) != NULL){
+ long i;
+
+ i = atol(p);
+ patgrp->cat_lim = i;
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/FLDTYPE=", 9)){
+ if((p = remove_pat_escapes(str+9)) != NULL){
+ int i;
+ NAMEVAL_S *v;
+
+ for(i = 0; (v = pat_fldr_types(i)); i++)
+ if(!strucmp(p, v->shortname)){
+ patgrp->fldr_type = v->value;
+ break;
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/AFROM=", 7)){
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ int i;
+ NAMEVAL_S *v;
+
+ for(i = 0; (v = inabook_fldr_types(i)); i++)
+ if(!strucmp(p, v->shortname)){
+ patgrp->inabook |= v->value;
+ break;
+ }
+
+ /* to match old semantics */
+ patgrp->inabook |= IAB_FROM;
+ patgrp->inabook |= IAB_REPLYTO;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/AFROMA=", 8)){
+ if((p = remove_pat_escapes(str+8)) != NULL){
+
+ /* make sure AFROMA comes after AFROM in config lines */
+ patgrp->inabook &= ~IAB_ADDR_MASK;
+
+ if(strchr(p, 'F'))
+ patgrp->inabook |= IAB_FROM;
+ if(strchr(p, 'R'))
+ patgrp->inabook |= IAB_REPLYTO;
+ if(strchr(p, 'S'))
+ patgrp->inabook |= IAB_SENDER;
+ if(strchr(p, 'T'))
+ patgrp->inabook |= IAB_TO;
+ if(strchr(p, 'C'))
+ patgrp->inabook |= IAB_CC;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/STATN=", 7)){
+ SET_STATUS(str,"/STATN=",patgrp->stat_new);
+ }
+ else if(!strncmp(str, "/STATR=", 7)){
+ SET_STATUS(str,"/STATR=",patgrp->stat_rec);
+ }
+ else if(!strncmp(str, "/STATI=", 7)){
+ SET_STATUS(str,"/STATI=",patgrp->stat_imp);
+ }
+ else if(!strncmp(str, "/STATA=", 7)){
+ SET_STATUS(str,"/STATA=",patgrp->stat_ans);
+ }
+ else if(!strncmp(str, "/STATD=", 7)){
+ SET_STATUS(str,"/STATD=",patgrp->stat_del);
+ }
+ else if(!strncmp(str, "/8BITS=", 7)){
+ SET_STATUS(str,"/8BITS=",patgrp->stat_8bitsubj);
+ }
+ else if(!strncmp(str, "/BOM=", 5)){
+ SET_STATUS(str,"/BOM=",patgrp->stat_bom);
+ }
+ else if(!strncmp(str, "/BOY=", 5)){
+ SET_STATUS(str,"/BOY=",patgrp->stat_boy);
+ }
+ else{
+ char save;
+
+ patgrp->bogus = 1;
+
+ if((p = strindex(str, '=')) != NULL){
+ save = *(p+1);
+ *(p+1) = '\0';
+ }
+
+ dprint((1,
+ "parse_patgrp_slash(%.20s): unrecognized in \"%s\"\n",
+ str ? str : "?",
+ (patgrp && patgrp->nick) ? patgrp->nick : ""));
+ q_status_message4(SM_ORDER, 1, 3,
+ "Warning: unrecognized pattern element \"%.20s\"%.20s%.20s%.20s",
+ str, patgrp->nick ? " in rule \"" : "",
+ patgrp->nick ? patgrp->nick : "", patgrp->nick ? "\"" : "");
+
+ if(p)
+ *(p+1) = save;
+ }
+}
+
+
+/*
+ * Fill in one member of action struct from str.
+ *
+ * The multiple constant strings are lame but it evolved this way from
+ * previous versions and isn't worth fixing.
+ */
+void
+parse_action_slash(char *str, ACTION_S *action)
+{
+ char *p;
+ int stateval, i;
+ NAMEVAL_S *v;
+
+ if(!action)
+ panic("NULL action to parse_action_slash");
+ else if(!(str && *str))
+ panic("NULL or empty string to parse_action_slash");
+ else if(!strncmp(str, "/ROLE=1", 7))
+ action->is_a_role = 1;
+ else if(!strncmp(str, "/OTHER=1", 8))
+ action->is_a_other = 1;
+ else if(!strncmp(str, "/ISINCOL=1", 10))
+ action->is_a_incol = 1;
+ else if(!strncmp(str, "/ISSRCH=1", 9))
+ action->is_a_srch = 1;
+ /*
+ * This is unfortunate. If a new filter is set to only set
+ * state bits it will be interpreted by an older pine which
+ * doesn't have that feature like a filter that is set to Delete.
+ * So we change the filter indicator to FILTER=2 to disable the
+ * filter for older versions.
+ */
+ else if(!strncmp(str, "/FILTER=1", 9) || !strncmp(str, "/FILTER=2", 9))
+ action->is_a_filter = 1;
+ else if(!strncmp(str, "/ISSCORE=1", 10))
+ action->is_a_score = 1;
+ else if(!strncmp(str, "/SCORE=", 7)){
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ long i;
+
+ i = atol(p);
+ if(i >= SCORE_MIN && i <= SCORE_MAX)
+ action->scoreval = i;
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/SCOREHDRTOK=", 13))
+ action->scorevalhdrtok = config_to_hdrtok(str+13);
+ else if(!strncmp(str, "/FOLDER=", 8))
+ action->folder = parse_pattern("FOLDER", str, 1);
+ else if(!strncmp(str, "/KEYSET=", 8))
+ action->keyword_set = parse_pattern("KEYSET", str, 1);
+ else if(!strncmp(str, "/KEYCLR=", 8))
+ action->keyword_clr = parse_pattern("KEYCLR", str, 1);
+ else if(!strncmp(str, "/NOKILL=", 8))
+ action->kill = -1;
+ else if(!strncmp(str, "/NOTDEL=", 8))
+ action->move_only_if_not_deleted = 1;
+ else if(!strncmp(str, "/NONTERM=", 9))
+ action->non_terminating = 1;
+ else if(!strncmp(str, "/STATI=", 7)){
+ stateval = ACT_STAT_LEAVE;
+ SET_MSGSTATE(str,"/STATI=",stateval);
+ switch(stateval){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ action->state_setting_bits |= F_FLAG;
+ break;
+ case ACT_STAT_CLEAR:
+ action->state_setting_bits |= F_UNFLAG;
+ break;
+ }
+ }
+ else if(!strncmp(str, "/STATD=", 7)){
+ stateval = ACT_STAT_LEAVE;
+ SET_MSGSTATE(str,"/STATD=",stateval);
+ switch(stateval){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ action->state_setting_bits |= F_DEL;
+ break;
+ case ACT_STAT_CLEAR:
+ action->state_setting_bits |= F_UNDEL;
+ break;
+ }
+ }
+ else if(!strncmp(str, "/STATA=", 7)){
+ stateval = ACT_STAT_LEAVE;
+ SET_MSGSTATE(str,"/STATA=",stateval);
+ switch(stateval){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ action->state_setting_bits |= F_ANS;
+ break;
+ case ACT_STAT_CLEAR:
+ action->state_setting_bits |= F_UNANS;
+ break;
+ }
+ }
+ else if(!strncmp(str, "/STATN=", 7)){
+ stateval = ACT_STAT_LEAVE;
+ SET_MSGSTATE(str,"/STATN=",stateval);
+ switch(stateval){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ action->state_setting_bits |= F_UNSEEN;
+ break;
+ case ACT_STAT_CLEAR:
+ action->state_setting_bits |= F_SEEN;
+ break;
+ }
+ }
+ else if(!strncmp(str, "/RTYPE=", 7)){
+ /* reply type */
+ action->repl_type = ROLE_REPL_DEFL;
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ for(i = 0; (v = role_repl_types(i)); i++)
+ if(!strucmp(p, v->shortname)){
+ action->repl_type = v->value;
+ break;
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/FTYPE=", 7)){
+ /* forward type */
+ action->forw_type = ROLE_FORW_DEFL;
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ for(i = 0; (v = role_forw_types(i)); i++)
+ if(!strucmp(p, v->shortname)){
+ action->forw_type = v->value;
+ break;
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/CTYPE=", 7)){
+ /* compose type */
+ action->comp_type = ROLE_COMP_DEFL;
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ for(i = 0; (v = role_comp_types(i)); i++)
+ if(!strucmp(p, v->shortname)){
+ action->comp_type = v->value;
+ break;
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/FROM=", 6)){
+ /* get the from */
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ rfc822_parse_adrlist(&action->from, p,
+ ps_global->maildomain);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/REPL=", 6)){
+ /* get the reply-to */
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ rfc822_parse_adrlist(&action->replyto, p,
+ ps_global->maildomain);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/FCC=", 5))
+ action->fcc = remove_pat_escapes(str+5);
+ else if(!strncmp(str, "/LSIG=", 6))
+ action->litsig = remove_pat_escapes(str+6);
+ else if(!strncmp(str, "/SIG=", 5))
+ action->sig = remove_pat_escapes(str+5);
+ else if(!strncmp(str, "/TEMPLATE=", 10))
+ action->template = remove_pat_escapes(str+10);
+ /* get the custom headers */
+ else if(!strncmp(str, "/CSTM=", 6)){
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ int commas = 0;
+ char *q;
+
+ /* count elements in list */
+ for(q = p; q && *q; q++)
+ if(*q == ',')
+ commas++;
+
+ action->cstm = parse_list(p, commas+1, 0, NULL);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/SMTP=", 6)){
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ int commas = 0;
+ char *q;
+
+ /* count elements in list */
+ for(q = p; q && *q; q++)
+ if(*q == ',')
+ commas++;
+
+ action->smtp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/NNTP=", 6)){
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ int commas = 0;
+ char *q;
+
+ /* count elements in list */
+ for(q = p; q && *q; q++)
+ if(*q == ',')
+ commas++;
+
+ action->nntp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
+ fs_give((void **)&p);
+ }
+ }
+ else if(!strncmp(str, "/INICK=", 7))
+ action->inherit_nick = remove_pat_escapes(str+7);
+ else if(!strncmp(str, "/INCOL=", 7)){
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ char *fg = NULL, *bg = NULL, *z;
+
+ /*
+ * Color should look like
+ * /FG=white/BG=red
+ */
+ if((z = srchstr(p, "/FG=")) != NULL)
+ fg = remove_pat_escapes(z+4);
+ if((z = srchstr(p, "/BG=")) != NULL)
+ bg = remove_pat_escapes(z+4);
+
+ if(fg && *fg && bg && *bg)
+ action->incol = new_color_pair(fg, bg);
+
+ if(fg)
+ fs_give((void **)&fg);
+ if(bg)
+ fs_give((void **)&bg);
+ fs_give((void **)&p);
+ }
+ }
+ /* per-folder sort */
+ else if(!strncmp(str, "/SORT=", 6)){
+ if((p = remove_pat_escapes(str+6)) != NULL){
+ SortOrder def_sort;
+ int def_sort_rev;
+
+ if(decode_sort(p, &def_sort, &def_sort_rev) != -1){
+ action->sort_is_set = 1;
+ action->sortorder = def_sort;
+ action->revsort = (def_sort_rev ? 1 : 0);
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ /* per-folder index-format */
+ else if(!strncmp(str, "/IFORM=", 7))
+ action->index_format = remove_pat_escapes(str+7);
+ /* per-folder startup-rule */
+ else if(!strncmp(str, "/START=", 7)){
+ if((p = remove_pat_escapes(str+7)) != NULL){
+ for(i = 0; (v = startup_rules(i)); i++)
+ if(!strucmp(p, S_OR_L(v))){
+ action->startup_rule = v->value;
+ break;
+ }
+
+ fs_give((void **)&p);
+ }
+ }
+ else{
+ char save;
+
+ action->bogus = 1;
+
+ if((p = strindex(str, '=')) != NULL){
+ save = *(p+1);
+ *(p+1) = '\0';
+ }
+
+ dprint((1,
+ "parse_action_slash(%.20s): unrecognized in \"%s\"\n",
+ str ? str : "?",
+ (action && action->nick) ? action->nick : ""));
+ q_status_message4(SM_ORDER, 1, 3,
+ "Warning: unrecognized pattern action \"%.20s\"%.20s%.20s%.20s",
+ str, action->nick ? " in rule \"" : "",
+ action->nick ? action->nick : "", action->nick ? "\"" : "");
+
+ if(p)
+ *(p+1) = save;
+ }
+}
+
+
+/*
+ * Str looks like (min,max) or a comma-separated list of these.
+ *
+ * Parens are optional if unambiguous, whitespace is ignored.
+ * If min is left out it is -INF. If max is left out it is INF.
+ * If only one number and no comma number is min and max is INF.
+ *
+ * Returns the INTVL_S list.
+ */
+INTVL_S *
+parse_intvl(char *str)
+{
+ char *q;
+ long left, right;
+ INTVL_S *ret = NULL, **next;
+
+ if(!str)
+ return(ret);
+
+ q = str;
+
+ for(;;){
+ left = right = INTVL_UNDEF;
+
+ /* skip to first number */
+ while(isspace((unsigned char) *q) || *q == LPAREN)
+ q++;
+
+ /* min number */
+ if(*q == COMMA || !struncmp(q, "-INF", 4))
+ left = - INTVL_INF;
+ else if(*q == '-' || isdigit((unsigned char) *q))
+ left = atol(q);
+
+ if(left != INTVL_UNDEF){
+ /* skip to second number */
+ while(*q && *q != COMMA && *q != RPAREN)
+ q++;
+ if(*q == COMMA)
+ q++;
+ while(isspace((unsigned char) *q))
+ q++;
+
+ /* max number */
+ if(*q == '\0' || *q == RPAREN || !struncmp(q, "INF", 3))
+ right = INTVL_INF;
+ else if(*q == '-' || isdigit((unsigned char) *q))
+ right = atol(q);
+ }
+
+ if(left == INTVL_UNDEF || right == INTVL_UNDEF
+ || left > right){
+ if(left != INTVL_UNDEF || right != INTVL_UNDEF || *q){
+ if(left != INTVL_UNDEF && right != INTVL_UNDEF
+ && left > right)
+ q_status_message1(SM_ORDER, 3, 5,
+ _("Error: Interval \"%s\", min > max"), str);
+ else
+ q_status_message1(SM_ORDER, 3, 5,
+ _("Error: Interval \"%s\": syntax is (min,max)"), str);
+
+ if(ret)
+ free_intvl(&ret);
+
+ ret = NULL;
+ }
+
+ break;
+ }
+ else{
+ if(!ret){
+ ret = (INTVL_S *) fs_get(sizeof(*ret));
+ memset((void *) ret, 0, sizeof(*ret));
+ ret->imin = left;
+ ret->imax = right;
+ next = &ret->next;
+ }
+ else{
+ *next = (INTVL_S *) fs_get(sizeof(*ret));
+ memset((void *) *next, 0, sizeof(*ret));
+ (*next)->imin = left;
+ (*next)->imax = right;
+ next = &(*next)->next;
+ }
+
+ /* skip to next interval in list */
+ while(*q && *q != COMMA && *q != RPAREN)
+ q++;
+ if(*q == RPAREN)
+ q++;
+ while(*q && *q != COMMA)
+ q++;
+ if(*q == COMMA)
+ q++;
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns string that looks like "(left,right),(left2,right2)".
+ * Caller is responsible for freeing memory.
+ */
+char *
+stringform_of_intvl(INTVL_S *intvl)
+{
+ char *res = NULL;
+
+ if(intvl && intvl->imin != INTVL_UNDEF && intvl->imax != INTVL_UNDEF
+ && intvl->imin <= intvl->imax){
+ char lbuf[20], rbuf[20], buf[45], *p;
+ INTVL_S *iv;
+ int count = 0;
+ size_t reslen;
+
+ /* find a max size and allocate it for the result */
+ for(iv = intvl;
+ (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
+ && iv->imin <= iv->imax);
+ iv = iv->next)
+ count++;
+
+ reslen = count * 50 * sizeof(char);
+ res = (char *) fs_get(reslen+1);
+ memset((void *) res, 0, reslen+1);
+ p = res;
+
+ for(iv = intvl;
+ (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
+ && iv->imin <= iv->imax);
+ iv = iv->next){
+
+ if(iv->imin == - INTVL_INF){
+ strncpy(lbuf, "-INF", sizeof(lbuf));
+ lbuf[sizeof(lbuf)-1] = '\0';
+ }
+ else
+ snprintf(lbuf, sizeof(lbuf), "%ld", iv->imin);
+
+ if(iv->imax == INTVL_INF){
+ strncpy(rbuf, "INF", sizeof(rbuf));
+ rbuf[sizeof(rbuf)-1] = '\0';
+ }
+ else
+ snprintf(rbuf, sizeof(rbuf), "%ld", iv->imax);
+
+ snprintf(buf, sizeof(buf), "%.1s(%.20s,%.20s)", (p == res) ? "" : ",",
+ lbuf, rbuf);
+
+ sstrncpy(&p, buf, reslen+1 -(p-res));
+ }
+
+ res[reslen] = '\0';
+ }
+
+ return(res);
+}
+
+
+char *
+hdrtok_to_stringform(HEADER_TOK_S *hdrtok)
+{
+ char buf[1024], nbuf[10];
+ char *res = NULL;
+ char *p, *ptr;
+
+ if(hdrtok){
+ snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
+ ptr = buf;
+ sstrncpy(&ptr, hdrtok->hdrname ? hdrtok->hdrname : "", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, "(", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, nbuf, sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, ",\"", sizeof(buf)-(ptr-buf));
+ p = hdrtok->fieldseps;
+ while(p && *p){
+ if((*p == '\"' || *p == '\\') && ptr-buf < sizeof(buf))
+ *ptr++ = '\\';
+
+ if(ptr-buf < sizeof(buf))
+ *ptr++ = *p++;
+ }
+
+ sstrncpy(&ptr, "\")", sizeof(buf)-(ptr-buf));
+
+ if(ptr-buf < sizeof(buf))
+ *ptr = '\0';
+ else
+ buf[sizeof(buf)-1] = '\0';
+
+ res = cpystr(buf);
+ }
+
+ return(res);
+}
+
+
+HEADER_TOK_S *
+stringform_to_hdrtok(char *str)
+{
+ char *p, *q, *w, hdrname[200];
+ HEADER_TOK_S *hdrtok = NULL;
+
+ if(str && *str){
+ p = str;
+ hdrname[0] = '\0';
+ w = hdrname;
+
+ if(*p == '\"'){ /* quoted name */
+ p++;
+ while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
+ if(*p == '\\')
+ p++;
+
+ *w++ = *p++;
+ }
+
+ *w = '\0';
+ if(*p == '\"')
+ p++;
+ }
+ else{
+ while(w < hdrname + sizeof(hdrname)-1 &&
+ !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
+ *p != '(')
+ *w++ = *p++;
+
+ *w = '\0';
+ }
+
+ if(hdrname[0])
+ hdrtok = new_hdrtok(hdrname);
+
+ if(hdrtok){
+ if(*p == '('){
+ p++;
+
+ if(*p && isdigit((unsigned char) *p)){
+ q = p;
+ while(*p && isdigit((unsigned char) *p))
+ p++;
+
+ hdrtok->fieldnum = atoi(q);
+
+ if(*p == ','){
+ int j;
+
+ p++;
+ /* don't use default */
+ if(*p && *p != ')' && hdrtok->fieldseps){
+ hdrtok->fieldseps[0] = '\0';
+ hdrtok->fieldsepcnt = 0;
+ }
+
+ j = 0;
+ if(*p == '\"' && strchr(p+1, '\"')){ /* quoted */
+ p++;
+ while(*p && *p != '\"'){
+ if(hdrtok->fieldseps)
+ fs_resize((void **) &hdrtok->fieldseps, j+2);
+
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(hdrtok->fieldseps){
+ hdrtok->fieldseps[j++] = *p++;
+ hdrtok->fieldseps[j] = '\0';
+ hdrtok->fieldsepcnt = j;
+ }
+ }
+ }
+ else{
+ while(*p && *p != ')'){
+ if(hdrtok->fieldseps)
+ fs_resize((void **) &hdrtok->fieldseps, j+2);
+
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(hdrtok->fieldseps){
+ hdrtok->fieldseps[j++] = *p++;
+ hdrtok->fieldseps[j] = '\0';
+ hdrtok->fieldsepcnt = j;
+ }
+ }
+ }
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 3, "Missing 2nd argument should be field number, a non-negative digit");
+ }
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 3, "1st argument should be field number, a non-negative digit");
+ }
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing left parenthesis in %s", str);
+ }
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing header name in %s", str);
+ }
+ }
+
+ return(hdrtok);
+}
+
+
+char *
+hdrtok_to_config(HEADER_TOK_S *hdrtok)
+{
+ char *ptr, buf[1024], nbuf[10], *p1, *p2, *p3;
+ char *res = NULL;
+
+ if(hdrtok){
+ snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
+ memset(buf, 0, sizeof(buf));
+ ptr = buf;
+ sstrncpy(&ptr, "/HN=", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, (p1=add_pat_escapes(hdrtok->hdrname ? hdrtok->hdrname : "")), sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, "/FN=", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, (p2=add_pat_escapes(nbuf)), sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, "/FS=", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, (p3=add_pat_escapes(hdrtok->fieldseps ? hdrtok->fieldseps : "")), sizeof(buf)-(ptr-buf));
+
+ buf[sizeof(buf)-1] = '\0';
+ res = add_pat_escapes(buf);
+
+ if(p1)
+ fs_give((void **)&p1);
+
+ if(p2)
+ fs_give((void **)&p2);
+
+ if(p3)
+ fs_give((void **)&p3);
+ }
+
+ return(res);
+}
+
+
+HEADER_TOK_S *
+config_to_hdrtok(char *str)
+{
+ HEADER_TOK_S *hdrtok = NULL;
+ char *p, *q;
+ int j;
+
+ if(str && *str){
+ if((q = remove_pat_escapes(str)) != NULL){
+ char *hn = NULL, *fn = NULL, *fs = NULL, *z;
+
+ if((z = srchstr(q, "/HN=")) != NULL)
+ hn = remove_pat_escapes(z+4);
+ if((z = srchstr(q, "/FN=")) != NULL)
+ fn = remove_pat_escapes(z+4);
+ if((z = srchstr(q, "/FS=")) != NULL)
+ fs = remove_pat_escapes(z+4);
+
+ hdrtok = new_hdrtok(hn);
+ if(fn)
+ hdrtok->fieldnum = atoi(fn);
+
+ if(fs && *fs){
+ if(hdrtok->fieldseps){
+ hdrtok->fieldseps[0] = '\0';
+ hdrtok->fieldsepcnt = 0;
+ }
+
+ p = fs;
+ j = 0;
+ if(*p == '\"' && strchr(p+1, '\"')){
+ p++;
+ while(*p && *p != '\"'){
+ if(hdrtok->fieldseps)
+ fs_resize((void **) &hdrtok->fieldseps, j+2);
+
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(hdrtok->fieldseps){
+ hdrtok->fieldseps[j++] = *p++;
+ hdrtok->fieldseps[j] = '\0';
+ hdrtok->fieldsepcnt = j;
+ }
+ }
+ }
+ else{
+ while(*p){
+ if(hdrtok->fieldseps)
+ fs_resize((void **) &hdrtok->fieldseps, j+2);
+
+ if(*p == '\\' && *(p+1))
+ p++;
+
+ if(hdrtok->fieldseps){
+ hdrtok->fieldseps[j++] = *p++;
+ hdrtok->fieldseps[j] = '\0';
+ hdrtok->fieldsepcnt = j;
+ }
+ }
+ }
+ }
+
+ if(hn)
+ fs_give((void **)&hn);
+ if(fn)
+ fs_give((void **)&fn);
+ if(fs)
+ fs_give((void **)&fs);
+
+ fs_give((void **)&q);
+ }
+ }
+
+ return(hdrtok);
+}
+
+
+/*
+ * Args -- flags - SCOREUSE_INVALID Mark scores_in_use invalid so that we'll
+ * recalculate if we want to use it again.
+ * - SCOREUSE_GET Return whether scores are being used or not.
+ *
+ * Returns -- 0 - Scores not being used at all.
+ * >0 - Scores are used. The return value consists of flag values
+ * OR'd together. Possible values are:
+ *
+ * SCOREUSE_INCOLS - scores needed for index line colors
+ * SCOREUSE_ROLES - scores needed for roles
+ * SCOREUSE_FILTERS - scores needed for filters
+ * SCOREUSE_OTHER - scores needed for other stuff
+ * SCOREUSE_INDEX - scores needed for index drawing
+ *
+ * SCOREUSE_STATEDEP - scores depend on message state
+ */
+int
+scores_are_used(int flags)
+{
+ static int scores_in_use = -1;
+ long type1, type2;
+ int scores_are_defined, scores_are_used_somewhere = 0;
+ PAT_STATE pstate1, pstate2;
+
+ if(flags & SCOREUSE_INVALID) /* mark invalid so we recalculate next time */
+ scores_in_use = -1;
+ else if(scores_in_use == -1){
+
+ /*
+ * Check the patterns to see if scores are potentially
+ * being used.
+ * The first_pattern() in the if checks whether there are any
+ * non-zero scorevals. The loop checks whether any patterns
+ * use those non-zero scorevals.
+ */
+ type1 = ROLE_SCORE;
+ type2 = (ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE |
+ ROLE_INCOL | ROLE_DO_FILTER);
+ scores_are_defined = nonempty_patterns(type1, &pstate1)
+ && first_pattern(&pstate1);
+ if(scores_are_defined)
+ scores_are_used_somewhere =
+ ((nonempty_patterns(type2, &pstate2) && first_pattern(&pstate2))
+ || ps_global->a_format_contains_score
+ || mn_get_sort(ps_global->msgmap) == SortScore);
+
+ if(scores_are_used_somewhere){
+ PAT_S *pat;
+
+ /*
+ * Careful. nonempty_patterns() may call close_pattern()
+ * which will set scores_in_use to -1! So we have to be
+ * sure to reset it after we call nonempty_patterns().
+ */
+ scores_in_use = 0;
+ if(ps_global->a_format_contains_score
+ || mn_get_sort(ps_global->msgmap) == SortScore)
+ scores_in_use |= SCOREUSE_INDEX;
+
+ if(nonempty_patterns(type2, &pstate2))
+ for(pat = first_pattern(&pstate2);
+ pat;
+ pat = next_pattern(&pstate2))
+ if(pat->patgrp && !pat->patgrp->bogus && pat->patgrp->do_score){
+ if(pat->action && pat->action->is_a_incol)
+ scores_in_use |= SCOREUSE_INCOLS;
+ if(pat->action && pat->action->is_a_role)
+ scores_in_use |= SCOREUSE_ROLES;
+ if(pat->action && pat->action->is_a_filter)
+ scores_in_use |= SCOREUSE_FILTERS;
+ if(pat->action && pat->action->is_a_other)
+ scores_in_use |= SCOREUSE_OTHER;
+ }
+
+ /*
+ * Note whether scores depend on message state or not.
+ */
+ if(scores_in_use)
+ for(pat = first_pattern(&pstate1);
+ pat;
+ pat = next_pattern(&pstate1))
+ if(patgrp_depends_on_active_state(pat->patgrp)){
+ scores_in_use |= SCOREUSE_STATEDEP;
+ break;
+ }
+
+ }
+ else
+ scores_in_use = 0;
+ }
+
+ return((scores_in_use == -1) ? 0 : scores_in_use);
+}
+
+
+int
+patgrp_depends_on_state(PATGRP_S *patgrp)
+{
+ return(patgrp && (patgrp_depends_on_active_state(patgrp)
+ || patgrp->stat_rec != PAT_STAT_EITHER));
+}
+
+
+/*
+ * Recent doesn't count for this function because it doesn't change while
+ * the mailbox is open.
+ */
+int
+patgrp_depends_on_active_state(PATGRP_S *patgrp)
+{
+ return(patgrp && !patgrp->bogus
+ && (patgrp->stat_new != PAT_STAT_EITHER ||
+ patgrp->stat_del != PAT_STAT_EITHER ||
+ patgrp->stat_imp != PAT_STAT_EITHER ||
+ patgrp->stat_ans != PAT_STAT_EITHER ||
+ patgrp->keyword));
+}
+
+
+/*
+ * Look for label in str and return a pointer to parsed string.
+ * Actually, we look for "label=" or "!label=", the second means NOT.
+ * Converts from string from patterns file which looks like
+ * /NEWS=comp.mail.,comp.mail.pine/TO=...
+ * This is the string that came from pattern="string" with the pattern=
+ * and outer quotes removed.
+ * This converts the string to a PATTERN_S list and returns
+ * an allocated copy.
+ */
+PATTERN_S *
+parse_pattern(char *label, char *str, int hex_to_backslashed)
+{
+ char copy[50]; /* local copy of label */
+ char copynot[50]; /* local copy of label, NOT'ed */
+ char *q, *labeled_str;
+ PATTERN_S *head = NULL;
+
+ if(!label || !str)
+ return(NULL);
+
+ q = copy;
+ sstrncpy(&q, "/", sizeof(copy));
+ sstrncpy(&q, label, sizeof(copy) - (q-copy));
+ sstrncpy(&q, "=", sizeof(copy) - (q-copy));
+ copy[sizeof(copy)-1] = '\0';
+ q = copynot;
+ sstrncpy(&q, "/!", sizeof(copynot));
+ sstrncpy(&q, label, sizeof(copynot) - (q-copynot));
+ sstrncpy(&q, "=", sizeof(copynot) - (q-copynot));
+ copynot[sizeof(copynot)-1] = '\0';
+
+ if(hex_to_backslashed){
+ if((q = srchstr(str, copy)) != NULL){
+ head = config_to_pattern(q+strlen(copy));
+ }
+ else if((q = srchstr(str, copynot)) != NULL){
+ head = config_to_pattern(q+strlen(copynot));
+ head->not = 1;
+ }
+ }
+ else{
+ if((q = srchstr(str, copy)) != NULL){
+ if((labeled_str =
+ remove_backslash_escapes(q+strlen(copy))) != NULL){
+ head = string_to_pattern(labeled_str);
+ fs_give((void **)&labeled_str);
+ }
+ }
+ else if((q = srchstr(str, copynot)) != NULL){
+ if((labeled_str =
+ remove_backslash_escapes(q+strlen(copynot))) != NULL){
+ head = string_to_pattern(labeled_str);
+ head->not = 1;
+ fs_give((void **)&labeled_str);
+ }
+ }
+ }
+
+ return(head);
+}
+
+
+/*
+ * Look for /ARB's in str and return a pointer to parsed ARBHDR_S.
+ * Actually, we look for /!ARB and /!EARB as well. Those mean NOT.
+ * Converts from string from patterns file which looks like
+ * /ARB<fieldname1>=pattern/.../ARB<fieldname2>=pattern...
+ * This is the string that came from pattern="string" with the pattern=
+ * and outer quotes removed.
+ * This converts the string to a ARBHDR_S list and returns
+ * an allocated copy.
+ */
+ARBHDR_S *
+parse_arbhdr(char *str)
+{
+ char *q, *s, *equals, *noesc;
+ int not, empty, skip;
+ ARBHDR_S *ahdr = NULL, *a, *aa;
+ PATTERN_S *p = NULL;
+
+ if(!str)
+ return(NULL);
+
+ aa = NULL;
+ for(s = str; (q = next_arb(s)); s = q+1){
+ not = (q[1] == '!') ? 1 : 0;
+ empty = (q[not+1] == 'E') ? 1 : 0;
+ skip = 4 + not + empty;
+ if((noesc = remove_pat_escapes(q+skip)) != NULL){
+ if(*noesc != '=' && (equals = strindex(noesc, '=')) != NULL){
+ a = (ARBHDR_S *)fs_get(sizeof(*a));
+ memset((void *)a, 0, sizeof(*a));
+ *equals = '\0';
+ a->isemptyval = empty;
+ a->field = cpystr(noesc);
+ if(empty)
+ a->p = string_to_pattern("");
+ else if(*(equals+1) &&
+ (p = string_to_pattern(equals+1)) != NULL)
+ a->p = p;
+
+ if(not && a->p)
+ a->p->not = 1;
+
+ /* keep them in the same order */
+ if(aa){
+ aa->next = a;
+ aa = aa->next;
+ }
+ else{
+ ahdr = a;
+ aa = ahdr;
+ }
+ }
+
+ fs_give((void **)&noesc);
+ }
+ }
+
+ return(ahdr);
+}
+
+
+char *
+next_arb(char *start)
+{
+ char *q1, *q2, *q3, *q4, *p;
+
+ q1 = srchstr(start, "/ARB");
+ q2 = srchstr(start, "/!ARB");
+ q3 = srchstr(start, "/EARB");
+ q4 = srchstr(start, "/!EARB");
+
+ p = q1;
+ if(!p || (q2 && q2 < p))
+ p = q2;
+ if(!p || (q3 && q3 < p))
+ p = q3;
+ if(!p || (q4 && q4 < p))
+ p = q4;
+
+ return(p);
+}
+
+
+/*
+ * Converts a string to a PATTERN_S list and returns an
+ * allocated copy. The source string looks like
+ * string1,string2,...
+ * Commas and backslashes may be backslash-escaped in the original string
+ * in order to include actual commas and backslashes in the pattern.
+ * So \, is an actual comma and , is the separator character.
+ */
+PATTERN_S *
+string_to_pattern(char *str)
+{
+ char *q, *s, *workspace;
+ PATTERN_S *p, *head = NULL, **nextp;
+
+ if(!str)
+ return(head);
+
+ /*
+ * We want an empty string to cause an empty substring in the pattern
+ * instead of returning a NULL pattern. That can be used as a way to
+ * match any header. For example, if all the patterns but the news
+ * pattern were null and the news pattern was a substring of "" then
+ * we use that to match any message with a newsgroups header.
+ */
+ if(!*str){
+ head = (PATTERN_S *)fs_get(sizeof(*p));
+ memset((void *)head, 0, sizeof(*head));
+ head->substring = cpystr("");
+ }
+ else{
+ nextp = &head;
+ workspace = (char *)fs_get((strlen(str)+1) * sizeof(char));
+ s = workspace;
+ *s = '\0';
+ q = str;
+ do {
+ switch(*q){
+ case COMMA:
+ case '\0':
+ *s = '\0';
+ removing_leading_and_trailing_white_space(workspace);
+ p = (PATTERN_S *)fs_get(sizeof(*p));
+ memset((void *)p, 0, sizeof(*p));
+ p->substring = cpystr(workspace);
+
+ convert_possibly_encoded_str_to_utf8(&p->substring);
+
+ *nextp = p;
+ nextp = &p->next;
+ s = workspace;
+ *s = '\0';
+ break;
+
+ case BSLASH:
+ if(*(q+1) == COMMA || *(q+1) == BSLASH)
+ *s++ = *(++q);
+ else
+ *s++ = *q;
+
+ break;
+
+ default:
+ *s++ = *q;
+ break;
+ }
+ } while(*q++);
+
+ fs_give((void **)&workspace);
+ }
+
+ return(head);
+}
+
+
+/*
+ * Converts a PATTERN_S list to a string.
+ * The resulting string is allocated here and looks like
+ * string1,string2,...
+ * Commas and backslashes in the original pattern
+ * end up backslash-escaped in the string.
+ */
+char *
+pattern_to_string(PATTERN_S *pattern)
+{
+ PATTERN_S *p;
+ char *result = NULL, *q, *s;
+ size_t n;
+
+ if(!pattern)
+ return(result);
+
+ /* how much space is needed? */
+ n = 0;
+ for(p = pattern; p; p = p->next){
+ n += (p == pattern) ? 0 : 1;
+ for(s = p->substring; s && *s; s++){
+ if(*s == COMMA || *s == BSLASH)
+ n++;
+
+ n++;
+ }
+ }
+
+ q = result = (char *)fs_get(++n);
+ for(p = pattern; p; p = p->next){
+ if(p != pattern)
+ *q++ = COMMA;
+
+ for(s = p->substring; s && *s; s++){
+ if(*s == COMMA || *s == BSLASH)
+ *q++ = '\\';
+
+ *q++ = *s;
+ }
+ }
+
+ *q = '\0';
+
+ return(result);
+}
+
+
+/*
+ * Do the escaping necessary to take a string for a pattern into a comma-
+ * separated string with escapes suitable for the config file.
+ * Returns an allocated copy of that string.
+ * In particular
+ * , -> \, -> \x2C
+ * \ -> \\ -> \x5C
+ * " -> \"
+ * / -> \/
+ */
+char *
+pattern_to_config(PATTERN_S *pat)
+{
+ char *s, *res = NULL;
+
+ s = pattern_to_string(pat);
+ if(s){
+ res = add_pat_escapes(s);
+ fs_give((void **) &s);
+ }
+
+ return(res);
+}
+
+/*
+ * Opposite of pattern_to_config.
+ */
+PATTERN_S *
+config_to_pattern(char *str)
+{
+ char *s;
+ PATTERN_S *pat = NULL;
+
+ s = remove_pat_escapes(str);
+ if(s){
+ pat = string_to_pattern(s);
+ fs_give((void **) &s);
+ }
+
+ return(pat);
+}
+
+
+/*
+ * Converts an array of strings to a PATTERN_S list and returns an
+ * allocated copy.
+ * The list strings may not contain commas directly, because the UI turns
+ * those into separate list members. Instead, the user types \, and
+ * that backslash comma is converted to a comma here.
+ * It is a bit odd. Backslash itself is not escaped. A backslash which is
+ * not followed by a comma is a literal backslash, a backslash followed by
+ * a comma is a comma.
+ */
+PATTERN_S *
+editlist_to_pattern(char **list)
+{
+ PATTERN_S *head = NULL;
+
+ if(!(list && *list))
+ return(head);
+
+ /*
+ * We want an empty string to cause an empty substring in the pattern
+ * instead of returning a NULL pattern. That can be used as a way to
+ * match any header. For example, if all the patterns but the news
+ * pattern were null and the news pattern was a substring of "" then
+ * we use that to match any message with a newsgroups header.
+ */
+ if(!list[0][0]){
+ head = (PATTERN_S *) fs_get(sizeof(*head));
+ memset((void *) head, 0, sizeof(*head));
+ head->substring = cpystr("");
+ }
+ else{
+ char *str, *s, *q, *workspace = NULL;
+ size_t l = 0;
+ PATTERN_S *p, **nextp;
+ int i;
+
+ nextp = &head;
+ for(i = 0; (str = list[i]); i++){
+ if(str[0]){
+ if(!workspace){
+ l = strlen(str) + 1;
+ workspace = (char *) fs_get(l * sizeof(char));
+ }
+ else if(strlen(str) + 1 > l){
+ l = strlen(str) + 1;
+ fs_give((void **) &workspace);
+ workspace = (char *) fs_get(l * sizeof(char));
+ }
+
+ s = workspace;
+ *s = '\0';
+ q = str;
+ do {
+ switch(*q){
+ case '\0':
+ *s = '\0';
+ removing_leading_and_trailing_white_space(workspace);
+ p = (PATTERN_S *) fs_get(sizeof(*p));
+ memset((void *) p, 0, sizeof(*p));
+ p->substring = cpystr(workspace);
+ *nextp = p;
+ nextp = &p->next;
+ s = workspace;
+ *s = '\0';
+ break;
+
+ case BSLASH:
+ if(*(q+1) == COMMA)
+ *s++ = *(++q);
+ else
+ *s++ = *q;
+
+ break;
+
+ default:
+ *s++ = *q;
+ break;
+ }
+ } while(*q++);
+ }
+ }
+ }
+
+ return(head);
+}
+
+
+/*
+ * Converts a PATTERN_S to an array of strings and returns an allocated copy.
+ * Commas are converted to backslash-comma, because the text_tool UI uses
+ * commas to separate items.
+ * It is a bit odd. Backslash itself is not escaped. A backslash which is
+ * not followed by a comma is a literal backslash, a backslash followed by
+ * a comma is a comma.
+ */
+char **
+pattern_to_editlist(PATTERN_S *pat)
+{
+ int cnt, i;
+ PATTERN_S *p;
+ char **list = NULL;
+
+ if(!pat)
+ return(list);
+
+ /* how many in list? */
+ for(cnt = 0, p = pat; p; p = p->next)
+ cnt++;
+
+ list = (char **) fs_get((cnt + 1) * sizeof(*list));
+ memset((void *) list, 0, (cnt + 1) * sizeof(*list));
+
+ for(i = 0, p = pat; p; p = p->next, i++)
+ list[i] = add_comma_escapes(p->substring);
+
+ return(list);
+}
+
+
+PATGRP_S *
+nick_to_patgrp(char *nick, int rflags)
+{
+ PAT_S *pat;
+ PAT_STATE pstate;
+ PATGRP_S *patgrp = NULL;
+
+ if(!(nick && *nick
+ && nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
+ return(patgrp);
+
+ for(pat = first_pattern(&pstate);
+ !patgrp && pat;
+ pat = next_pattern(&pstate))
+ if(pat->patgrp && pat->patgrp->nick && !strcmp(pat->patgrp->nick, nick))
+ patgrp = copy_patgrp(pat->patgrp);
+
+ return(patgrp);
+}
+
+
+/*
+ * Must be called with a pstate, we don't check for it.
+ * It respects the cur_rflag_num in pstate. That is, it doesn't start over
+ * at i=1, it starts at cur_rflag_num.
+ */
+PAT_S *
+first_any_pattern(PAT_STATE *pstate)
+{
+ PAT_LINE_S *patline = NULL;
+ int i;
+ long local_rflag;
+
+ /*
+ * The rest of pstate should be set before coming here.
+ * In particular, the rflags should be set by a call to nonempty_patterns
+ * or any_patterns, and cur_rflag_num should be set.
+ */
+ pstate->patlinecurrent = NULL;
+ pstate->patcurrent = NULL;
+
+ /*
+ * The order of these is important. It is the same as the order
+ * used for next_any_pattern and opposite of the order used by
+ * last and prev. For next_any's benefit, we allow cur_rflag_num to
+ * start us out past the first set.
+ */
+ for(i = pstate->cur_rflag_num; i <= PATTERN_N; i++){
+
+ local_rflag = 0L;
+
+ switch(i){
+ case 1:
+ local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 2:
+ local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 3:
+ local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 4:
+ local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 5:
+ local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 6:
+ local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 7:
+ local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 8:
+ local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case PATTERN_N:
+ local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+ }
+
+ if(local_rflag){
+ SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
+
+ if(*cur_pat_h){
+ /* Find first patline with a pat */
+ for(patline = (*cur_pat_h)->patlinehead;
+ patline && !patline->first;
+ patline = patline->next)
+ ;
+ }
+
+ if(patline){
+ pstate->cur_rflag_num = i;
+ pstate->patlinecurrent = patline;
+ pstate->patcurrent = patline->first;
+ }
+ }
+
+ if(pstate->patcurrent)
+ break;
+ }
+
+ return(pstate->patcurrent);
+}
+
+
+/*
+ * Return first pattern of the specified types. These types were set by a
+ * previous call to any_patterns or nonempty_patterns.
+ *
+ * Args -- pstate pattern state. This is set here and passed back for
+ * use by next_pattern. Must be non-null.
+ * It must have been initialized previously by a call to
+ * nonempty_patterns or any_patterns.
+ */
+PAT_S *
+first_pattern(PAT_STATE *pstate)
+{
+ PAT_S *pat;
+ long rflags;
+
+ pstate->cur_rflag_num = 1;
+
+ rflags = pstate->rflags;
+
+ for(pat = first_any_pattern(pstate);
+ pat && !((pat->action &&
+ ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
+ (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
+ pat->action->is_a_incol) ||
+ (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
+ (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
+ (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
+ (rflags & ROLE_SCORE && (pat->action->scoreval
+ || pat->action->scorevalhdrtok)) ||
+ (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
+ (rflags & ROLE_REPLY &&
+ (pat->action->repl_type == ROLE_REPL_YES ||
+ pat->action->repl_type == ROLE_REPL_NOCONF)) ||
+ (rflags & ROLE_FORWARD &&
+ (pat->action->forw_type == ROLE_FORW_YES ||
+ pat->action->forw_type == ROLE_FORW_NOCONF)) ||
+ (rflags & ROLE_COMPOSE &&
+ (pat->action->comp_type == ROLE_COMP_YES ||
+ pat->action->comp_type == ROLE_COMP_NOCONF)) ||
+ (rflags & ROLE_OLD_FILT) ||
+ (rflags & ROLE_OLD_SCORE) ||
+ (rflags & ROLE_OLD_PAT)))
+ ||
+ pat->inherit);
+ pat = next_any_pattern(pstate))
+ ;
+
+ return(pat);
+}
+
+
+/*
+ * Just like first_any_pattern.
+ */
+PAT_S *
+last_any_pattern(PAT_STATE *pstate)
+{
+ PAT_LINE_S *patline = NULL;
+ int i;
+ long local_rflag;
+
+ /*
+ * The rest of pstate should be set before coming here.
+ * In particular, the rflags should be set by a call to nonempty_patterns
+ * or any_patterns, and cur_rflag_num should be set.
+ */
+ pstate->patlinecurrent = NULL;
+ pstate->patcurrent = NULL;
+
+ for(i = pstate->cur_rflag_num; i >= 1; i--){
+
+ local_rflag = 0L;
+
+ switch(i){
+ case 1:
+ local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 2:
+ local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 3:
+ local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 4:
+ local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 5:
+ local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 6:
+ local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 7:
+ local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case 8:
+ local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+
+ case PATTERN_N:
+ local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
+ break;
+ }
+
+ if(local_rflag){
+ SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
+
+ pstate->patlinecurrent = NULL;
+ pstate->patcurrent = NULL;
+
+ if(*cur_pat_h){
+ /* Find last patline with a pat */
+ for(patline = (*cur_pat_h)->patlinehead;
+ patline;
+ patline = patline->next)
+ if(patline->last)
+ pstate->patlinecurrent = patline;
+
+ if(pstate->patlinecurrent)
+ pstate->patcurrent = pstate->patlinecurrent->last;
+ }
+
+ if(pstate->patcurrent)
+ pstate->cur_rflag_num = i;
+
+ if(pstate->patcurrent)
+ break;
+ }
+ }
+
+ return(pstate->patcurrent);
+}
+
+
+/*
+ * Return last pattern of the specified types. These types were set by a
+ * previous call to any_patterns or nonempty_patterns.
+ *
+ * Args -- pstate pattern state. This is set here and passed back for
+ * use by prev_pattern. Must be non-null.
+ * It must have been initialized previously by a call to
+ * nonempty_patterns or any_patterns.
+ */
+PAT_S *
+last_pattern(PAT_STATE *pstate)
+{
+ PAT_S *pat;
+ long rflags;
+
+ pstate->cur_rflag_num = PATTERN_N;
+
+ rflags = pstate->rflags;
+
+ for(pat = last_any_pattern(pstate);
+ pat && !((pat->action &&
+ ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
+ (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
+ pat->action->is_a_incol) ||
+ (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
+ (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
+ (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
+ (rflags & ROLE_SCORE && (pat->action->scoreval
+ || pat->action->scorevalhdrtok)) ||
+ (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
+ (rflags & ROLE_REPLY &&
+ (pat->action->repl_type == ROLE_REPL_YES ||
+ pat->action->repl_type == ROLE_REPL_NOCONF)) ||
+ (rflags & ROLE_FORWARD &&
+ (pat->action->forw_type == ROLE_FORW_YES ||
+ pat->action->forw_type == ROLE_FORW_NOCONF)) ||
+ (rflags & ROLE_COMPOSE &&
+ (pat->action->comp_type == ROLE_COMP_YES ||
+ pat->action->comp_type == ROLE_COMP_NOCONF)) ||
+ (rflags & ROLE_OLD_FILT) ||
+ (rflags & ROLE_OLD_SCORE) ||
+ (rflags & ROLE_OLD_PAT)))
+ ||
+ pat->inherit);
+ pat = prev_any_pattern(pstate))
+ ;
+
+ return(pat);
+}
+
+
+/*
+ * This assumes that pstate is valid.
+ */
+PAT_S *
+next_any_pattern(PAT_STATE *pstate)
+{
+ PAT_LINE_S *patline;
+
+ if(pstate->patlinecurrent){
+ if(pstate->patcurrent && pstate->patcurrent->next)
+ pstate->patcurrent = pstate->patcurrent->next;
+ else{
+ /* Find next patline with a pat */
+ for(patline = pstate->patlinecurrent->next;
+ patline && !patline->first;
+ patline = patline->next)
+ ;
+
+ if(patline){
+ pstate->patlinecurrent = patline;
+ pstate->patcurrent = patline->first;
+ }
+ else{
+ pstate->patlinecurrent = NULL;
+ pstate->patcurrent = NULL;
+ }
+ }
+ }
+
+ /* we've reached the last, try the next rflag_num (the next pattern type) */
+ if(!pstate->patcurrent){
+ pstate->cur_rflag_num++;
+ pstate->patcurrent = first_any_pattern(pstate);
+ }
+
+ return(pstate->patcurrent);
+}
+
+
+/*
+ * Return next pattern of the specified types. These types were set by a
+ * previous call to any_patterns or nonempty_patterns.
+ *
+ * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
+ */
+PAT_S *
+next_pattern(PAT_STATE *pstate)
+{
+ PAT_S *pat;
+ long rflags;
+
+ rflags = pstate->rflags;
+
+ for(pat = next_any_pattern(pstate);
+ pat && !((pat->action &&
+ ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
+ (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
+ pat->action->is_a_incol) ||
+ (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
+ (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
+ (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
+ (rflags & ROLE_SCORE && (pat->action->scoreval
+ || pat->action->scorevalhdrtok)) ||
+ (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
+ (rflags & ROLE_REPLY &&
+ (pat->action->repl_type == ROLE_REPL_YES ||
+ pat->action->repl_type == ROLE_REPL_NOCONF)) ||
+ (rflags & ROLE_FORWARD &&
+ (pat->action->forw_type == ROLE_FORW_YES ||
+ pat->action->forw_type == ROLE_FORW_NOCONF)) ||
+ (rflags & ROLE_COMPOSE &&
+ (pat->action->comp_type == ROLE_COMP_YES ||
+ pat->action->comp_type == ROLE_COMP_NOCONF)) ||
+ (rflags & ROLE_OLD_FILT) ||
+ (rflags & ROLE_OLD_SCORE) ||
+ (rflags & ROLE_OLD_PAT)))
+ ||
+ pat->inherit);
+ pat = next_any_pattern(pstate))
+ ;
+
+ return(pat);
+}
+
+
+/*
+ * This assumes that pstate is valid.
+ */
+PAT_S *
+prev_any_pattern(PAT_STATE *pstate)
+{
+ PAT_LINE_S *patline;
+
+ if(pstate->patlinecurrent){
+ if(pstate->patcurrent && pstate->patcurrent->prev)
+ pstate->patcurrent = pstate->patcurrent->prev;
+ else{
+ /* Find prev patline with a pat */
+ for(patline = pstate->patlinecurrent->prev;
+ patline && !patline->last;
+ patline = patline->prev)
+ ;
+
+ if(patline){
+ pstate->patlinecurrent = patline;
+ pstate->patcurrent = patline->last;
+ }
+ else{
+ pstate->patlinecurrent = NULL;
+ pstate->patcurrent = NULL;
+ }
+ }
+ }
+
+ if(!pstate->patcurrent){
+ pstate->cur_rflag_num--;
+ pstate->patcurrent = last_any_pattern(pstate);
+ }
+
+ return(pstate->patcurrent);
+}
+
+
+/*
+ * Return prev pattern of the specified types. These types were set by a
+ * previous call to any_patterns or nonempty_patterns.
+ *
+ * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
+ */
+PAT_S *
+prev_pattern(PAT_STATE *pstate)
+{
+ PAT_S *pat;
+ long rflags;
+
+ rflags = pstate->rflags;
+
+ for(pat = prev_any_pattern(pstate);
+ pat && !((pat->action &&
+ ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
+ (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
+ pat->action->is_a_incol) ||
+ (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
+ (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
+ (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
+ (rflags & ROLE_SCORE && (pat->action->scoreval
+ || pat->action->scorevalhdrtok)) ||
+ (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
+ (rflags & ROLE_REPLY &&
+ (pat->action->repl_type == ROLE_REPL_YES ||
+ pat->action->repl_type == ROLE_REPL_NOCONF)) ||
+ (rflags & ROLE_FORWARD &&
+ (pat->action->forw_type == ROLE_FORW_YES ||
+ pat->action->forw_type == ROLE_FORW_NOCONF)) ||
+ (rflags & ROLE_COMPOSE &&
+ (pat->action->comp_type == ROLE_COMP_YES ||
+ pat->action->comp_type == ROLE_COMP_NOCONF)) ||
+ (rflags & ROLE_OLD_FILT) ||
+ (rflags & ROLE_OLD_SCORE) ||
+ (rflags & ROLE_OLD_PAT)))
+ ||
+ pat->inherit);
+ pat = prev_any_pattern(pstate))
+ ;
+
+ return(pat);
+}
+
+
+/*
+ * Rflags may be more than one pattern type OR'd together.
+ */
+int
+write_patterns(long int rflags)
+{
+ int canon_rflags;
+ int err = 0;
+
+ dprint((7, "write_patterns(0x%x)\n", rflags));
+
+ canon_rflags = CANONICAL_RFLAGS(rflags);
+
+ if(canon_rflags & ROLE_DO_INCOLS)
+ err += sub_write_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
+ if(!err && canon_rflags & ROLE_DO_OTHER)
+ err += sub_write_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
+ if(!err && canon_rflags & ROLE_DO_FILTER)
+ err += sub_write_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
+ if(!err && canon_rflags & ROLE_DO_SCORES)
+ err += sub_write_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
+ if(!err && canon_rflags & ROLE_DO_ROLES)
+ err += sub_write_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
+ if(!err && canon_rflags & ROLE_DO_SRCH)
+ err += sub_write_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
+
+ if(!err && !(rflags & PAT_USE_CHANGED))
+ write_pinerc(ps_global, (rflags & PAT_USE_MAIN) ? Main : Post, WRP_NONE);
+
+ return(err);
+}
+
+
+int
+sub_write_patterns(long int rflags)
+{
+ int err = 0, lineno = 0;
+ char **lvalue = NULL;
+ PAT_LINE_S *patline;
+
+ SET_PATTYPE(rflags);
+
+ if(!(*cur_pat_h)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Unknown error saving patterns");
+ return(-1);
+ }
+
+ if((*cur_pat_h)->dirtypinerc){
+ /* Count how many lines will be in patterns variable */
+ for(patline = (*cur_pat_h)->patlinehead;
+ patline;
+ patline = patline->next)
+ lineno++;
+
+ lvalue = (char **)fs_get((lineno+1)*sizeof(char *));
+ memset(lvalue, 0, (lineno+1) * sizeof(char *));
+ }
+
+ for(patline = (*cur_pat_h)->patlinehead, lineno = 0;
+ !err && patline;
+ patline = patline->next, lineno++){
+ if(patline->type == File)
+ err = write_pattern_file((*cur_pat_h)->dirtypinerc
+ ? &lvalue[lineno] : NULL, patline);
+ else if(patline->type == Literal && (*cur_pat_h)->dirtypinerc)
+ err = write_pattern_lit(&lvalue[lineno], patline);
+ else if(patline->type == Inherit)
+ err = write_pattern_inherit((*cur_pat_h)->dirtypinerc
+ ? &lvalue[lineno] : NULL, patline);
+ }
+
+ if((*cur_pat_h)->dirtypinerc){
+ if(err)
+ free_list_array(&lvalue);
+ else{
+ char ***alval;
+ struct variable *var;
+
+ if(rflags & ROLE_DO_ROLES)
+ var = &ps_global->vars[V_PAT_ROLES];
+ else if(rflags & ROLE_DO_OTHER)
+ var = &ps_global->vars[V_PAT_OTHER];
+ else if(rflags & ROLE_DO_FILTER)
+ var = &ps_global->vars[V_PAT_FILTS];
+ else if(rflags & ROLE_DO_SCORES)
+ var = &ps_global->vars[V_PAT_SCORES];
+ else if(rflags & ROLE_DO_INCOLS)
+ var = &ps_global->vars[V_PAT_INCOLS];
+ else if(rflags & ROLE_DO_SRCH)
+ var = &ps_global->vars[V_PAT_SRCH];
+
+ alval = (rflags & PAT_USE_CHANGED) ? &(var->changed_val.l)
+ : ALVAL(var, (rflags & PAT_USE_MAIN) ? Main : Post);
+ if(*alval)
+ free_list_array(alval);
+
+ if(rflags & PAT_USE_CHANGED) var->is_changed_val = 1;
+
+ *alval = lvalue;
+
+ if(!(rflags & PAT_USE_CHANGED))
+ set_current_val(var, TRUE, TRUE);
+ }
+ }
+
+ if(!err)
+ (*cur_pat_h)->dirtypinerc = 0;
+
+ return(err);
+}
+
+
+/*
+ * Write pattern lines into a file.
+ *
+ * Args lvalue -- Pointer to char * to fill in variable value
+ * patline --
+ *
+ * Returns 0 -- all is ok, lvalue has been filled in, file has been written
+ * else -- error, lvalue untouched, file not written
+ */
+int
+write_pattern_file(char **lvalue, PAT_LINE_S *patline)
+{
+ char *p, *tfile;
+ int fd = -1, err = 0;
+ FILE *fp_new;
+ PAT_S *pat;
+
+ dprint((7, "write_pattern_file(%s)\n",
+ (patline && patline->filepath) ? patline->filepath : "?"));
+
+ if(lvalue){
+ size_t l;
+
+ l = strlen(patline->filename) + 5;
+ p = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(p, "FILE:", l+1);
+ p[l] = '\0';
+ strncat(p, patline->filename, l+1-1-strlen(p));
+ p[l] = '\0';
+ *lvalue = p;
+ }
+
+ if(patline->readonly || !patline->dirty) /* doesn't need writing */
+ return(err);
+
+ /* Get a tempfile to write the patterns into */
+ if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL)
+ || ((fd = our_open(tfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0)
+ || ((fp_new = fdopen(fd, "w")) == NULL)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Can't write in directory containing file \"%.200s\"",
+ patline->filepath);
+ if(tfile){
+ our_unlink(tfile);
+ fs_give((void **)&tfile);
+ }
+
+ if(fd >= 0)
+ close(fd);
+
+ return(-1);
+ }
+
+ dprint((9, "write_pattern_file: writing into %s\n",
+ tfile ? tfile : "?"));
+
+ if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF)
+ err--;
+
+ for(pat = patline->first; !err && pat; pat = pat->next){
+ if((p = data_for_patline(pat)) != NULL){
+ if(fprintf(fp_new, "%s\n", p) == EOF)
+ err--;
+
+ fs_give((void **)&p);
+ }
+ }
+
+ if(err || fclose(fp_new) == EOF){
+ if(err)
+ (void)fclose(fp_new);
+
+ err--;
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "I/O error: \"%.200s\": %.200s",
+ tfile, error_description(errno));
+ }
+
+ if(!err && rename_file(tfile, patline->filepath) < 0){
+ err--;
+ q_status_message3(SM_ORDER | SM_DING, 3, 4,
+ _("Error renaming \"%s\" to \"%s\": %s"),
+ tfile, patline->filepath, error_description(errno));
+ dprint((2,
+ "write_pattern_file: Error renaming (%s,%s): %s\n",
+ tfile ? tfile : "?",
+ (patline && patline->filepath) ? patline->filepath : "?",
+ error_description(errno)));
+ }
+
+ if(tfile){
+ our_unlink(tfile);
+ fs_give((void **)&tfile);
+ }
+
+ if(!err)
+ patline->dirty = 0;
+
+ return(err);
+}
+
+
+/*
+ * Write literal pattern lines into lvalue (pinerc variable).
+ *
+ * Args lvalue -- Pointer to char * to fill in variable value
+ * patline --
+ *
+ * Returns 0 -- all is ok, lvalue has been filled in, file has been written
+ * else -- error, lvalue untouched, file not written
+ */
+int
+write_pattern_lit(char **lvalue, PAT_LINE_S *patline)
+{
+ char *p = NULL;
+ int err = 0;
+ PAT_S *pat;
+
+ pat = patline ? patline->first : NULL;
+
+ if(pat && lvalue && (p = data_for_patline(pat)) != NULL){
+ size_t l;
+
+ l = strlen(p) + 4;
+ *lvalue = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(*lvalue, "LIT:", l+1);
+ (*lvalue)[l] = '\0';
+ strncat(*lvalue, p, l+1-1-strlen(*lvalue));
+ (*lvalue)[l] = '\0';
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Unknown error saving pattern variable"));
+ err--;
+ }
+
+ if(p)
+ fs_give((void **)&p);
+
+ return(err);
+}
+
+
+int
+write_pattern_inherit(char **lvalue, PAT_LINE_S *patline)
+{
+ int err = 0;
+
+ if(patline && patline->type == Inherit && lvalue)
+ *lvalue = cpystr(INHERIT);
+ else
+ err--;
+
+ return(err);
+}
+
+
+
+char *
+data_for_patline(PAT_S *pat)
+{
+ char *p = NULL, *q, *to_pat = NULL,
+ *news_pat = NULL, *from_pat = NULL,
+ *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL,
+ *arb_pat = NULL, *fldr_type_pat = NULL, *fldr_pat = NULL,
+ *afrom_type_pat = NULL, *abooks_pat = NULL,
+ *alltext_pat = NULL, *scorei_pat = NULL, *recip_pat = NULL,
+ *keyword_pat = NULL, *charset_pat = NULL,
+ *bodytext_pat = NULL, *age_pat = NULL, *sentdate = NULL,
+ *size_pat = NULL,
+ *category_cmd = NULL, *category_pat = NULL,
+ *category_lim = NULL,
+ *partic_pat = NULL, *stat_new_val = NULL,
+ *stat_rec_val = NULL,
+ *stat_imp_val = NULL, *stat_del_val = NULL,
+ *stat_ans_val = NULL, *stat_8bit_val = NULL,
+ *stat_bom_val = NULL, *stat_boy_val = NULL,
+ *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL,
+ *sig_act = NULL, *nick = NULL, *templ_act = NULL,
+ *litsig_act = NULL, *cstm_act = NULL, *smtp_act = NULL,
+ *nntp_act = NULL, *comment = NULL,
+ *repl_val = NULL, *forw_val = NULL, *comp_val = NULL,
+ *incol_act = NULL, *inherit_nick = NULL,
+ *score_act = NULL, *hdrtok_act = NULL,
+ *sort_act = NULL, *iform_act = NULL, *start_act = NULL,
+ *folder_act = NULL, *filt_ifnotdel = NULL,
+ *filt_nokill = NULL, *filt_del_val = NULL,
+ *filt_imp_val = NULL, *filt_ans_val = NULL,
+ *filt_new_val = NULL, *filt_nonterm = NULL,
+ *keyword_set = NULL, *keyword_clr = NULL;
+ int to_not = 0, news_not = 0, from_not = 0,
+ sender_not = 0, cc_not = 0, subj_not = 0,
+ partic_not = 0, recip_not = 0, alltext_not, bodytext_not,
+ keyword_not = 0, charset_not = 0;
+ size_t l;
+ ACTION_S *action = NULL;
+ NAMEVAL_S *f;
+
+ if(!pat)
+ return(p);
+
+ if((pat->patgrp && pat->patgrp->bogus)
+ || (pat->action && pat->action->bogus)){
+ if(pat->raw)
+ p = cpystr(pat->raw);
+
+ return(p);
+ }
+
+ if(pat->patgrp){
+ if(pat->patgrp->nick)
+ if((nick = add_pat_escapes(pat->patgrp->nick)) && !*nick)
+ fs_give((void **) &nick);
+
+ if(pat->patgrp->comment)
+ if((comment = add_pat_escapes(pat->patgrp->comment)) && !*comment)
+ fs_give((void **) &comment);
+
+ if(pat->patgrp->to){
+ to_pat = pattern_to_config(pat->patgrp->to);
+ to_not = pat->patgrp->to->not;
+ }
+
+ if(pat->patgrp->from){
+ from_pat = pattern_to_config(pat->patgrp->from);
+ from_not = pat->patgrp->from->not;
+ }
+
+ if(pat->patgrp->sender){
+ sender_pat = pattern_to_config(pat->patgrp->sender);
+ sender_not = pat->patgrp->sender->not;
+ }
+
+ if(pat->patgrp->cc){
+ cc_pat = pattern_to_config(pat->patgrp->cc);
+ cc_not = pat->patgrp->cc->not;
+ }
+
+ if(pat->patgrp->recip){
+ recip_pat = pattern_to_config(pat->patgrp->recip);
+ recip_not = pat->patgrp->recip->not;
+ }
+
+ if(pat->patgrp->partic){
+ partic_pat = pattern_to_config(pat->patgrp->partic);
+ partic_not = pat->patgrp->partic->not;
+ }
+
+ if(pat->patgrp->news){
+ news_pat = pattern_to_config(pat->patgrp->news);
+ news_not = pat->patgrp->news->not;
+ }
+
+ if(pat->patgrp->subj){
+ subj_pat = pattern_to_config(pat->patgrp->subj);
+ subj_not = pat->patgrp->subj->not;
+ }
+
+ if(pat->patgrp->alltext){
+ alltext_pat = pattern_to_config(pat->patgrp->alltext);
+ alltext_not = pat->patgrp->alltext->not;
+ }
+
+ if(pat->patgrp->bodytext){
+ bodytext_pat = pattern_to_config(pat->patgrp->bodytext);
+ bodytext_not = pat->patgrp->bodytext->not;
+ }
+
+ if(pat->patgrp->keyword){
+ keyword_pat = pattern_to_config(pat->patgrp->keyword);
+ keyword_not = pat->patgrp->keyword->not;
+ }
+
+ if(pat->patgrp->charsets){
+ charset_pat = pattern_to_config(pat->patgrp->charsets);
+ charset_not = pat->patgrp->charsets->not;
+ }
+
+ if(pat->patgrp->arbhdr){
+ ARBHDR_S *a;
+ char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL;
+ int len = 0;
+
+ /* This is brute force dumb, but who cares? */
+ for(a = pat->patgrp->arbhdr; a; a = a->next){
+ if(a->field && a->field[0]){
+ p1 = pattern_to_string(a->p);
+ p1 = p1 ? p1 : cpystr("");
+ l = strlen(a->field)+strlen(p1)+1;
+ p2 = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(p2, l+1, "%s=%s", a->field, p1);
+ p3 = add_pat_escapes(p2);
+ l = strlen(p3)+6;
+ p4 = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(p4, l+1, "/%s%sARB%s",
+ (a->p && a->p->not) ? "!" : "",
+ a->isemptyval ? "E" : "", p3);
+ len += strlen(p4);
+
+ if(p1)
+ fs_give((void **)&p1);
+ if(p2)
+ fs_give((void **)&p2);
+ if(p3)
+ fs_give((void **)&p3);
+ if(p4)
+ fs_give((void **)&p4);
+ }
+ }
+
+ p = arb_pat = (char *)fs_get((len + 1) * sizeof(char));
+
+ for(a = pat->patgrp->arbhdr; a; a = a->next){
+ if(a->field && a->field[0]){
+ p1 = pattern_to_string(a->p);
+ p1 = p1 ? p1 : cpystr("");
+ l = strlen(a->field)+strlen(p1)+1;
+ p2 = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(p2, l+1, "%s=%s", a->field, p1);
+ p3 = add_pat_escapes(p2);
+ l = strlen(p3)+6;
+ p4 = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(p4, l+1, "/%s%sARB%s",
+ (a->p && a->p->not) ? "!" : "",
+ a->isemptyval ? "E" : "", p3);
+ sstrncpy(&p, p4, len+1-(p-arb_pat));
+
+ if(p1)
+ fs_give((void **)&p1);
+ if(p2)
+ fs_give((void **)&p2);
+ if(p3)
+ fs_give((void **)&p3);
+ if(p4)
+ fs_give((void **)&p4);
+ }
+ }
+
+ arb_pat[len] = '\0';
+ }
+
+ if(pat->patgrp->age_uses_sentdate)
+ sentdate = cpystr("/SENTDATE=1");
+
+ if(pat->patgrp->do_score){
+ p = stringform_of_intvl(pat->patgrp->score);
+ if(p){
+ scorei_pat = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(pat->patgrp->do_age){
+ p = stringform_of_intvl(pat->patgrp->age);
+ if(p){
+ age_pat = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(pat->patgrp->do_size){
+ p = stringform_of_intvl(pat->patgrp->size);
+ if(p){
+ size_pat = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(pat->patgrp->category_cmd && pat->patgrp->category_cmd[0]){
+ size_t sz;
+ char **l, *q;
+
+ /* concatenate into string with commas first */
+ sz = 0;
+ for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++)
+ sz += strlen(l[0]) + 1;
+
+ if(sz){
+ char *p;
+ int first_one = 1;
+
+ q = (char *)fs_get(sz);
+ memset(q, 0, sz);
+ p = q;
+ for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++){
+ if(!first_one)
+ sstrncpy(&p, ",", sz-(p-q));
+
+ first_one = 0;
+ sstrncpy(&p, l[0], sz-(p-q));
+ }
+
+ q[sz-1] = '\0';
+
+ category_cmd = add_pat_escapes(q);
+ fs_give((void **)&q);
+ }
+ }
+
+ if(pat->patgrp->do_cat){
+ p = stringform_of_intvl(pat->patgrp->cat);
+ if(p){
+ category_pat = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(pat->patgrp->cat_lim != -1L){
+ category_lim = (char *) fs_get(20 * sizeof(char));
+ snprintf(category_lim, 20, "%ld", pat->patgrp->cat_lim);
+ }
+
+ if((f = pat_fldr_types(pat->patgrp->fldr_type)) != NULL)
+ fldr_type_pat = f->shortname;
+
+ if(pat->patgrp->folder)
+ fldr_pat = pattern_to_config(pat->patgrp->folder);
+
+ if((f = inabook_fldr_types(pat->patgrp->inabook)) != NULL
+ && f->value != IAB_DEFL)
+ afrom_type_pat = f->shortname;
+
+ if(pat->patgrp->abooks)
+ abooks_pat = pattern_to_config(pat->patgrp->abooks);
+
+ if(pat->patgrp->stat_new != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_new)) != NULL)
+ stat_new_val = f->shortname;
+
+ if(pat->patgrp->stat_rec != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_rec)) != NULL)
+ stat_rec_val = f->shortname;
+
+ if(pat->patgrp->stat_del != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_del)) != NULL)
+ stat_del_val = f->shortname;
+
+ if(pat->patgrp->stat_ans != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_ans)) != NULL)
+ stat_ans_val = f->shortname;
+
+ if(pat->patgrp->stat_imp != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_imp)) != NULL)
+ stat_imp_val = f->shortname;
+
+ if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_8bitsubj)) != NULL)
+ stat_8bit_val = f->shortname;
+
+ if(pat->patgrp->stat_bom != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_bom)) != NULL)
+ stat_bom_val = f->shortname;
+
+ if(pat->patgrp->stat_boy != PAT_STAT_EITHER &&
+ (f = role_status_types(pat->patgrp->stat_boy)) != NULL)
+ stat_boy_val = f->shortname;
+ }
+
+ if(pat->action){
+ action = pat->action;
+
+ if(action->is_a_score){
+ if(action->scoreval != 0L &&
+ action->scoreval >= SCORE_MIN && action->scoreval <= SCORE_MAX){
+ score_act = (char *) fs_get(5 * sizeof(char));
+ snprintf(score_act, 5, "%ld", pat->action->scoreval);
+ }
+
+ if(action->scorevalhdrtok)
+ hdrtok_act = hdrtok_to_config(action->scorevalhdrtok);
+ }
+
+ if(action->is_a_role){
+ if(action->inherit_nick)
+ inherit_nick = add_pat_escapes(action->inherit_nick);
+ if(action->fcc)
+ fcc_act = add_pat_escapes(action->fcc);
+ if(action->litsig)
+ litsig_act = add_pat_escapes(action->litsig);
+ if(action->sig)
+ sig_act = add_pat_escapes(action->sig);
+ if(action->template)
+ templ_act = add_pat_escapes(action->template);
+
+ if(action->cstm){
+ size_t sz;
+ char **l, *q;
+
+ /* concatenate into string with commas first */
+ sz = 0;
+ for(l = action->cstm; l[0] && l[0][0]; l++)
+ sz += strlen(l[0]) + 1;
+
+ if(sz){
+ char *p;
+ int first_one = 1;
+
+ q = (char *)fs_get(sz);
+ memset(q, 0, sz);
+ p = q;
+ for(l = action->cstm; l[0] && l[0][0]; l++){
+ if((!struncmp(l[0], "from", 4) &&
+ (l[0][4] == ':' || l[0][4] == '\0')) ||
+ (!struncmp(l[0], "reply-to", 8) &&
+ (l[0][8] == ':' || l[0][8] == '\0')))
+ continue;
+
+ if(!first_one)
+ sstrncpy(&p, ",", sz-(p-q));
+
+ first_one = 0;
+ sstrncpy(&p, l[0], sz-(p-q));
+ }
+
+ q[sz-1] = '\0';
+
+ cstm_act = add_pat_escapes(q);
+ fs_give((void **)&q);
+ }
+ }
+
+ if(action->smtp){
+ size_t sz;
+ char **l, *q;
+
+ /* concatenate into string with commas first */
+ sz = 0;
+ for(l = action->smtp; l[0] && l[0][0]; l++)
+ sz += strlen(l[0]) + 1;
+
+ if(sz){
+ char *p;
+ int first_one = 1;
+
+ q = (char *)fs_get(sz);
+ memset(q, 0, sz);
+ p = q;
+ for(l = action->smtp; l[0] && l[0][0]; l++){
+ if(!first_one)
+ sstrncpy(&p, ",", sz-(p-q));
+
+ first_one = 0;
+ sstrncpy(&p, l[0], sz-(p-q));
+ }
+
+ q[sz-1] = '\0';
+
+ smtp_act = add_pat_escapes(q);
+ fs_give((void **)&q);
+ }
+ }
+
+ if(action->nntp){
+ size_t sz;
+ char **l, *q;
+
+ /* concatenate into string with commas first */
+ sz = 0;
+ for(l = action->nntp; l[0] && l[0][0]; l++)
+ sz += strlen(l[0]) + 1;
+
+ if(sz){
+ char *p;
+ int first_one = 1;
+
+ q = (char *)fs_get(sz);
+ memset(q, 0, sz);
+ p = q;
+ for(l = action->nntp; l[0] && l[0][0]; l++){
+ if(!first_one)
+ sstrncpy(&p, ",", sz-(p-q));
+
+ first_one = 0;
+ sstrncpy(&p, l[0], sz-(p-q));
+ }
+
+ q[sz-1] = '\0';
+
+ nntp_act = add_pat_escapes(q);
+ fs_give((void **)&q);
+ }
+ }
+
+ if((f = role_repl_types(action->repl_type)) != NULL)
+ repl_val = f->shortname;
+
+ if((f = role_forw_types(action->forw_type)) != NULL)
+ forw_val = f->shortname;
+
+ if((f = role_comp_types(action->comp_type)) != NULL)
+ comp_val = f->shortname;
+ }
+
+ if(action->is_a_incol && action->incol){
+ char *ptr, buf[256], *p1, *p2;
+
+ ptr = buf;
+ memset(buf, 0, sizeof(buf));
+ sstrncpy(&ptr, "/FG=", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, (p1=add_pat_escapes(action->incol->fg)), sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, "/BG=", sizeof(buf)-(ptr-buf));
+ sstrncpy(&ptr, (p2=add_pat_escapes(action->incol->bg)), sizeof(buf)-(ptr-buf));
+ buf[sizeof(buf)-1] = '\0';
+ /* the colors will be doubly escaped */
+ incol_act = add_pat_escapes(buf);
+ if(p1)
+ fs_give((void **)&p1);
+
+ if(p2)
+ fs_give((void **)&p2);
+ }
+
+ if(action->is_a_other){
+ char buf[256];
+
+ if(action->sort_is_set){
+ snprintf(buf, sizeof(buf), "%.50s%.50s",
+ sort_name(action->sortorder),
+ action->revsort ? "/Reverse" : "");
+ sort_act = add_pat_escapes(buf);
+ }
+
+ if(action->index_format)
+ iform_act = add_pat_escapes(action->index_format);
+
+ if(action->startup_rule != IS_NOTSET &&
+ (f = startup_rules(action->startup_rule)) != NULL)
+ start_act = S_OR_L(f);
+ }
+
+ if(action->is_a_role && action->from){
+ char *bufp;
+ size_t len;
+
+ len = est_size(action->from);
+ bufp = (char *) fs_get(len * sizeof(char));
+ p = addr_string_mult(action->from, bufp, len);
+ if(p){
+ from_act = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(action->is_a_role && action->replyto){
+ char *bufp;
+ size_t len;
+
+ len = est_size(action->replyto);
+ bufp = (char *) fs_get(len * sizeof(char));
+ p = addr_string_mult(action->replyto, bufp, len);
+ if(p){
+ replyto_act = add_pat_escapes(p);
+ fs_give((void **)&p);
+ }
+ }
+
+ if(action->is_a_filter){
+ if(action->folder){
+ if((folder_act = pattern_to_config(action->folder)) != NULL){
+ if(action->move_only_if_not_deleted)
+ filt_ifnotdel = cpystr("/NOTDEL=1");
+ }
+ }
+
+ if(action->keyword_set)
+ keyword_set = pattern_to_config(action->keyword_set);
+
+ if(action->keyword_clr)
+ keyword_clr = pattern_to_config(action->keyword_clr);
+
+ if(!action->kill)
+ filt_nokill = cpystr("/NOKILL=1");
+
+ if(action->non_terminating)
+ filt_nonterm = cpystr("/NONTERM=1");
+
+ if(action->state_setting_bits){
+ char buf[256];
+ int dval, nval, ival, aval;
+
+ buf[0] = '\0';
+ p = buf;
+
+ convert_statebits_to_vals(action->state_setting_bits,
+ &dval, &aval, &ival, &nval);
+ if(dval != ACT_STAT_LEAVE &&
+ (f = msg_state_types(dval)) != NULL)
+ filt_del_val = f->shortname;
+
+ if(aval != ACT_STAT_LEAVE &&
+ (f = msg_state_types(aval)) != NULL)
+ filt_ans_val = f->shortname;
+
+ if(ival != ACT_STAT_LEAVE &&
+ (f = msg_state_types(ival)) != NULL)
+ filt_imp_val = f->shortname;
+
+ if(nval != ACT_STAT_LEAVE &&
+ (f = msg_state_types(nval)) != NULL)
+ filt_new_val = f->shortname;
+ }
+ }
+ }
+
+ l = strlen(nick ? nick : "Alternate Role") +
+ strlen(comment ? comment : "") +
+ strlen(to_pat ? to_pat : "") +
+ strlen(from_pat ? from_pat : "") +
+ strlen(sender_pat ? sender_pat : "") +
+ strlen(cc_pat ? cc_pat : "") +
+ strlen(recip_pat ? recip_pat : "") +
+ strlen(partic_pat ? partic_pat : "") +
+ strlen(news_pat ? news_pat : "") +
+ strlen(subj_pat ? subj_pat : "") +
+ strlen(alltext_pat ? alltext_pat : "") +
+ strlen(bodytext_pat ? bodytext_pat : "") +
+ strlen(arb_pat ? arb_pat : "") +
+ strlen(scorei_pat ? scorei_pat : "") +
+ strlen(keyword_pat ? keyword_pat : "") +
+ strlen(charset_pat ? charset_pat : "") +
+ strlen(age_pat ? age_pat : "") +
+ strlen(size_pat ? size_pat : "") +
+ strlen(category_cmd ? category_cmd : "") +
+ strlen(category_pat ? category_pat : "") +
+ strlen(category_lim ? category_lim : "") +
+ strlen(fldr_pat ? fldr_pat : "") +
+ strlen(abooks_pat ? abooks_pat : "") +
+ strlen(sentdate ? sentdate : "") +
+ strlen(inherit_nick ? inherit_nick : "") +
+ strlen(score_act ? score_act : "") +
+ strlen(hdrtok_act ? hdrtok_act : "") +
+ strlen(from_act ? from_act : "") +
+ strlen(replyto_act ? replyto_act : "") +
+ strlen(fcc_act ? fcc_act : "") +
+ strlen(litsig_act ? litsig_act : "") +
+ strlen(cstm_act ? cstm_act : "") +
+ strlen(smtp_act ? smtp_act : "") +
+ strlen(nntp_act ? nntp_act : "") +
+ strlen(sig_act ? sig_act : "") +
+ strlen(incol_act ? incol_act : "") +
+ strlen(sort_act ? sort_act : "") +
+ strlen(iform_act ? iform_act : "") +
+ strlen(start_act ? start_act : "") +
+ strlen(filt_ifnotdel ? filt_ifnotdel : "") +
+ strlen(filt_nokill ? filt_nokill : "") +
+ strlen(filt_nonterm ? filt_nonterm : "") +
+ (folder_act ? (strlen(folder_act) + 8) : 0) +
+ strlen(keyword_set ? keyword_set : "") +
+ strlen(keyword_clr ? keyword_clr : "") +
+ strlen(templ_act ? templ_act : "") + 540;
+ /*
+ * The +540 above is larger than needed but not everything is accounted
+ * for with the strlens.
+ */
+ p = (char *) fs_get(l * sizeof(char));
+
+ q = p;
+ sstrncpy(&q, "pattern=\"/NICK=", l-(q-p));
+
+ if(nick){
+ sstrncpy(&q, nick, l-(q-p));
+ fs_give((void **) &nick);
+ }
+ else
+ sstrncpy(&q, "Alternate Role", l-(q-p));
+
+ if(comment){
+ sstrncpy(&q, "/", l-(q-p));
+ sstrncpy(&q, "COMM=", l-(q-p));
+ sstrncpy(&q, comment, l-(q-p));
+ fs_give((void **) &comment);
+ }
+
+ if(to_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(to_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "TO=", l-(q-p));
+ sstrncpy(&q, to_pat, l-(q-p));
+ fs_give((void **) &to_pat);
+ }
+
+ if(from_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(from_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "FROM=", l-(q-p));
+ sstrncpy(&q, from_pat, l-(q-p));
+ fs_give((void **) &from_pat);
+ }
+
+ if(sender_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(sender_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "SENDER=", l-(q-p));
+ sstrncpy(&q, sender_pat, l-(q-p));
+ fs_give((void **) &sender_pat);
+ }
+
+ if(cc_pat){
+ sstrncpy(&q,"/", l-(q-p));
+ if(cc_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q,"CC=", l-(q-p));
+ sstrncpy(&q, cc_pat, l-(q-p));
+ fs_give((void **) &cc_pat);
+ }
+
+ if(recip_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(recip_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "RECIP=", l-(q-p));
+ sstrncpy(&q, recip_pat, l-(q-p));
+ fs_give((void **) &recip_pat);
+ }
+
+ if(partic_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(partic_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "PARTIC=", l-(q-p));
+ sstrncpy(&q, partic_pat, l-(q-p));
+ fs_give((void **) &partic_pat);
+ }
+
+ if(news_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(news_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "NEWS=", l-(q-p));
+ sstrncpy(&q, news_pat, l-(q-p));
+ fs_give((void **) &news_pat);
+ }
+
+ if(subj_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(subj_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "SUBJ=", l-(q-p));
+ sstrncpy(&q, subj_pat, l-(q-p));
+ fs_give((void **)&subj_pat);
+ }
+
+ if(alltext_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(alltext_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "ALL=", l-(q-p));
+ sstrncpy(&q, alltext_pat, l-(q-p));
+ fs_give((void **) &alltext_pat);
+ }
+
+ if(bodytext_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(bodytext_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "BODY=", l-(q-p));
+ sstrncpy(&q, bodytext_pat, l-(q-p));
+ fs_give((void **) &bodytext_pat);
+ }
+
+ if(keyword_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(keyword_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "KEY=", l-(q-p));
+ sstrncpy(&q, keyword_pat, l-(q-p));
+ fs_give((void **) &keyword_pat);
+ }
+
+ if(charset_pat){
+ sstrncpy(&q, "/", l-(q-p));
+ if(charset_not)
+ sstrncpy(&q, "!", l-(q-p));
+
+ sstrncpy(&q, "CHAR=", l-(q-p));
+ sstrncpy(&q, charset_pat, l-(q-p));
+ fs_give((void **) &charset_pat);
+ }
+
+ if(arb_pat){
+ sstrncpy(&q, arb_pat, l-(q-p));
+ fs_give((void **)&arb_pat);
+ }
+
+ if(scorei_pat){
+ sstrncpy(&q, "/SCOREI=", l-(q-p));
+ sstrncpy(&q, scorei_pat, l-(q-p));
+ fs_give((void **) &scorei_pat);
+ }
+
+ if(age_pat){
+ sstrncpy(&q, "/AGE=", l-(q-p));
+ sstrncpy(&q, age_pat, l-(q-p));
+ fs_give((void **) &age_pat);
+ }
+
+ if(size_pat){
+ sstrncpy(&q, "/SIZE=", l-(q-p));
+ sstrncpy(&q, size_pat, l-(q-p));
+ fs_give((void **) &size_pat);
+ }
+
+ if(category_cmd){
+ sstrncpy(&q, "/CATCMD=", l-(q-p));
+ sstrncpy(&q, category_cmd, l-(q-p));
+ fs_give((void **) &category_cmd);
+ }
+
+ if(category_pat){
+ sstrncpy(&q, "/CATVAL=", l-(q-p));
+ sstrncpy(&q, category_pat, l-(q-p));
+ fs_give((void **) &category_pat);
+ }
+
+ if(category_lim){
+ sstrncpy(&q, "/CATLIM=", l-(q-p));
+ sstrncpy(&q, category_lim, l-(q-p));
+ fs_give((void **) &category_lim);
+ }
+
+ if(sentdate){
+ sstrncpy(&q, sentdate, l-(q-p));
+ fs_give((void **) &sentdate);
+ }
+
+ if(fldr_type_pat){
+ sstrncpy(&q, "/FLDTYPE=", l-(q-p));
+ sstrncpy(&q, fldr_type_pat, l-(q-p));
+ }
+
+ if(fldr_pat){
+ sstrncpy(&q, "/FOLDER=", l-(q-p));
+ sstrncpy(&q, fldr_pat, l-(q-p));
+ fs_give((void **) &fldr_pat);
+ }
+
+ if(afrom_type_pat){
+ sstrncpy(&q, "/AFROM=", l-(q-p));
+ sstrncpy(&q, afrom_type_pat, l-(q-p));
+
+ /*
+ * Add address types. If it is From or Reply-to
+ * leave this out so it will still work with pine.
+ */
+ if((pat->patgrp->inabook & IAB_FROM
+ && pat->patgrp->inabook & IAB_REPLYTO
+ && !(pat->patgrp->inabook & IAB_SENDER)
+ && !(pat->patgrp->inabook & IAB_TO)
+ && !(pat->patgrp->inabook & IAB_CC))
+ ||
+ (!(pat->patgrp->inabook & IAB_FROM)
+ && !(pat->patgrp->inabook & IAB_REPLYTO)
+ && !(pat->patgrp->inabook & IAB_SENDER)
+ && !(pat->patgrp->inabook & IAB_TO)
+ && !(pat->patgrp->inabook & IAB_CC))){
+ ; /* leave it out */
+ }
+ else{
+ sstrncpy(&q, "/AFROMA=", l-(q-p));
+ if(pat->patgrp->inabook & IAB_FROM)
+ sstrncpy(&q, "F", l-(q-p));
+
+ if(pat->patgrp->inabook & IAB_REPLYTO)
+ sstrncpy(&q, "R", l-(q-p));
+
+ if(pat->patgrp->inabook & IAB_SENDER)
+ sstrncpy(&q, "S", l-(q-p));
+
+ if(pat->patgrp->inabook & IAB_TO)
+ sstrncpy(&q, "T", l-(q-p));
+
+ if(pat->patgrp->inabook & IAB_CC)
+ sstrncpy(&q, "C", l-(q-p));
+ }
+ }
+
+ if(abooks_pat){
+ sstrncpy(&q, "/ABOOKS=", l-(q-p));
+ sstrncpy(&q, abooks_pat, l-(q-p));
+ fs_give((void **) &abooks_pat);
+ }
+
+ if(stat_new_val){
+ sstrncpy(&q, "/STATN=", l-(q-p));
+ sstrncpy(&q, stat_new_val, l-(q-p));
+ }
+
+ if(stat_rec_val){
+ sstrncpy(&q, "/STATR=", l-(q-p));
+ sstrncpy(&q, stat_rec_val, l-(q-p));
+ }
+
+ if(stat_del_val){
+ sstrncpy(&q, "/STATD=", l-(q-p));
+ sstrncpy(&q, stat_del_val, l-(q-p));
+ }
+
+ if(stat_imp_val){
+ sstrncpy(&q, "/STATI=", l-(q-p));
+ sstrncpy(&q, stat_imp_val, l-(q-p));
+ }
+
+ if(stat_ans_val){
+ sstrncpy(&q, "/STATA=", l-(q-p));
+ sstrncpy(&q, stat_ans_val, l-(q-p));
+ }
+
+ if(stat_8bit_val){
+ sstrncpy(&q, "/8BITS=", l-(q-p));
+ sstrncpy(&q, stat_8bit_val, l-(q-p));
+ }
+
+ if(stat_bom_val){
+ sstrncpy(&q, "/BOM=", l-(q-p));
+ sstrncpy(&q, stat_bom_val, l-(q-p));
+ }
+
+ if(stat_boy_val){
+ sstrncpy(&q, "/BOY=", l-(q-p));
+ sstrncpy(&q, stat_boy_val, l-(q-p));
+ }
+
+ sstrncpy(&q, "\" action=\"", l-(q-p));
+
+ if(inherit_nick && *inherit_nick){
+ sstrncpy(&q, "/INICK=", l-(q-p));
+ sstrncpy(&q, inherit_nick, l-(q-p));
+ fs_give((void **)&inherit_nick);
+ }
+
+ if(action){
+ if(action->is_a_role)
+ sstrncpy(&q, "/ROLE=1", l-(q-p));
+
+ if(action->is_a_incol)
+ sstrncpy(&q, "/ISINCOL=1", l-(q-p));
+
+ if(action->is_a_srch)
+ sstrncpy(&q, "/ISSRCH=1", l-(q-p));
+
+ if(action->is_a_score)
+ sstrncpy(&q, "/ISSCORE=1", l-(q-p));
+
+ if(action->is_a_filter){
+ /*
+ * Older pine will interpret a filter that has no folder
+ * as a Delete, even if we set it up here to be a Just Set
+ * State filter. Disable the filter for older versions in that
+ * case. If kill is set then Delete is what is supposed to
+ * happen, so that's ok. If folder is set then Move is what is
+ * supposed to happen, so ok.
+ */
+ if(!action->kill && !action->folder)
+ sstrncpy(&q, "/FILTER=2", l-(q-p));
+ else
+ sstrncpy(&q, "/FILTER=1", l-(q-p));
+ }
+
+ if(action->is_a_other)
+ sstrncpy(&q, "/OTHER=1", l-(q-p));
+ }
+
+ if(score_act){
+ sstrncpy(&q, "/SCORE=", l-(q-p));
+ sstrncpy(&q, score_act, l-(q-p));
+ fs_give((void **)&score_act);
+ }
+
+ if(hdrtok_act){
+ sstrncpy(&q, "/SCOREHDRTOK=", l-(q-p));
+ sstrncpy(&q, hdrtok_act, l-(q-p));
+ fs_give((void **)&hdrtok_act);
+ }
+
+ if(from_act){
+ sstrncpy(&q, "/FROM=", l-(q-p));
+ sstrncpy(&q, from_act, l-(q-p));
+ fs_give((void **) &from_act);
+ }
+
+ if(replyto_act){
+ sstrncpy(&q, "/REPL=", l-(q-p));
+ sstrncpy(&q, replyto_act, l-(q-p));
+ fs_give((void **)&replyto_act);
+ }
+
+ if(fcc_act){
+ sstrncpy(&q, "/FCC=", l-(q-p));
+ sstrncpy(&q, fcc_act, l-(q-p));
+ fs_give((void **)&fcc_act);
+ }
+
+ if(litsig_act){
+ sstrncpy(&q, "/LSIG=", l-(q-p));
+ sstrncpy(&q, litsig_act, l-(q-p));
+ fs_give((void **)&litsig_act);
+ }
+
+ if(sig_act){
+ sstrncpy(&q, "/SIG=", l-(q-p));
+ sstrncpy(&q, sig_act, l-(q-p));
+ fs_give((void **)&sig_act);
+ }
+
+ if(templ_act){
+ sstrncpy(&q, "/TEMPLATE=", l-(q-p));
+ sstrncpy(&q, templ_act, l-(q-p));
+ fs_give((void **)&templ_act);
+ }
+
+ if(cstm_act){
+ sstrncpy(&q, "/CSTM=", l-(q-p));
+ sstrncpy(&q, cstm_act, l-(q-p));
+ fs_give((void **)&cstm_act);
+ }
+
+ if(smtp_act){
+ sstrncpy(&q, "/SMTP=", l-(q-p));
+ sstrncpy(&q, smtp_act, l-(q-p));
+ fs_give((void **)&smtp_act);
+ }
+
+ if(nntp_act){
+ sstrncpy(&q, "/NNTP=", l-(q-p));
+ sstrncpy(&q, nntp_act, l-(q-p));
+ fs_give((void **)&nntp_act);
+ }
+
+ if(repl_val){
+ sstrncpy(&q, "/RTYPE=", l-(q-p));
+ sstrncpy(&q, repl_val, l-(q-p));
+ }
+
+ if(forw_val){
+ sstrncpy(&q, "/FTYPE=", l-(q-p));
+ sstrncpy(&q, forw_val, l-(q-p));
+ }
+
+ if(comp_val){
+ sstrncpy(&q, "/CTYPE=", l-(q-p));
+ sstrncpy(&q, comp_val, l-(q-p));
+ }
+
+ if(incol_act){
+ sstrncpy(&q, "/INCOL=", l-(q-p));
+ sstrncpy(&q, incol_act, l-(q-p));
+ fs_give((void **)&incol_act);
+ }
+
+ if(sort_act){
+ sstrncpy(&q, "/SORT=", l-(q-p));
+ sstrncpy(&q, sort_act, l-(q-p));
+ fs_give((void **)&sort_act);
+ }
+
+ if(iform_act){
+ sstrncpy(&q, "/IFORM=", l-(q-p));
+ sstrncpy(&q, iform_act, l-(q-p));
+ fs_give((void **)&iform_act);
+ }
+
+ if(start_act){
+ sstrncpy(&q, "/START=", l-(q-p));
+ sstrncpy(&q, start_act, l-(q-p));
+ }
+
+ if(folder_act){
+ sstrncpy(&q, "/FOLDER=", l-(q-p));
+ sstrncpy(&q, folder_act, l-(q-p));
+ fs_give((void **) &folder_act);
+ }
+
+ if(filt_ifnotdel){
+ sstrncpy(&q, filt_ifnotdel, l-(q-p));
+ fs_give((void **) &filt_ifnotdel);
+ }
+
+ if(filt_nonterm){
+ sstrncpy(&q, filt_nonterm, l-(q-p));
+ fs_give((void **) &filt_nonterm);
+ }
+
+ if(filt_nokill){
+ sstrncpy(&q, filt_nokill, l-(q-p));
+ fs_give((void **) &filt_nokill);
+ }
+
+ if(filt_new_val){
+ sstrncpy(&q, "/STATN=", l-(q-p));
+ sstrncpy(&q, filt_new_val, l-(q-p));
+ }
+
+ if(filt_del_val){
+ sstrncpy(&q, "/STATD=", l-(q-p));
+ sstrncpy(&q, filt_del_val, l-(q-p));
+ }
+
+ if(filt_imp_val){
+ sstrncpy(&q, "/STATI=", l-(q-p));
+ sstrncpy(&q, filt_imp_val, l-(q-p));
+ }
+
+ if(filt_ans_val){
+ sstrncpy(&q, "/STATA=", l-(q-p));
+ sstrncpy(&q, filt_ans_val, l-(q-p));
+ }
+
+ if(keyword_set){
+ sstrncpy(&q, "/KEYSET=", l-(q-p));
+ sstrncpy(&q, keyword_set, l-(q-p));
+ fs_give((void **) &keyword_set);
+ }
+
+ if(keyword_clr){
+ sstrncpy(&q, "/KEYCLR=", l-(q-p));
+ sstrncpy(&q, keyword_clr, l-(q-p));
+ fs_give((void **) &keyword_clr);
+ }
+
+ if(q-p < l)
+ *q++ = '\"';
+
+ if(q-p < l)
+ *q = '\0';
+
+ p[l-1] = '\0';
+
+ return(p);
+}
+
+
+void
+convert_statebits_to_vals(long int bits, int *dval, int *aval, int *ival, int *nval)
+{
+ if(dval)
+ *dval = ACT_STAT_LEAVE;
+ if(aval)
+ *aval = ACT_STAT_LEAVE;
+ if(ival)
+ *ival = ACT_STAT_LEAVE;
+ if(nval)
+ *nval = ACT_STAT_LEAVE;
+
+ if(ival){
+ if(bits & F_FLAG)
+ *ival = ACT_STAT_SET;
+ else if(bits & F_UNFLAG)
+ *ival = ACT_STAT_CLEAR;
+ }
+
+ if(aval){
+ if(bits & F_ANS)
+ *aval = ACT_STAT_SET;
+ else if(bits & F_UNANS)
+ *aval = ACT_STAT_CLEAR;
+ }
+
+ if(dval){
+ if(bits & F_DEL)
+ *dval = ACT_STAT_SET;
+ else if(bits & F_UNDEL)
+ *dval = ACT_STAT_CLEAR;
+ }
+
+ if(nval){
+ if(bits & F_UNSEEN)
+ *nval = ACT_STAT_SET;
+ else if(bits & F_SEEN)
+ *nval = ACT_STAT_CLEAR;
+ }
+}
+
+
+/*
+ * The "searched" bit will be set for each message which matches.
+ *
+ * Args: patgrp -- Pattern to search with
+ * stream --
+ * searchset -- Restrict search to this set
+ * section -- Searching a section of the message, not the whole thing
+ * get_score -- Function to return the score for a message
+ * flags -- Most of these are flags to mail_search_full. However, we
+ * overload the flags namespace and pass some flags of our
+ * own in here that we pick off before calling mail_search.
+ * Danger, danger, don't overlap with flag values defined
+ * for c-client (that we want to use). Flags that we will
+ * use here are:
+ * MP_IN_CCLIENT_CB
+ * If this is set we are in a callback from c-client
+ * because some imap data arrived. We don't want to
+ * call c-client again because it isn't re-entrant safe.
+ * This is only a problem if we need to get the text of
+ * a message to do the search, the envelope is cached
+ * already.
+ * MP_NOT
+ * We want a ! of the patgrp in the search.
+ * We also throw in SE_FREE for free, since we create
+ * the search program here.
+ *
+ * Returns: 1 if any message in the searchset matches this pattern
+ * 0 if no matches
+ * -1 if couldn't perform search because of no_fetch restriction
+ */
+int
+match_pattern(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset,
+ char *section, long int (*get_score)(MAILSTREAM *, long int),
+ long int flags)
+{
+ SEARCHPGM *pgm;
+ SEARCHSET *s;
+ MESSAGECACHE *mc;
+ long i, msgno = 0L;
+ int in_client_callback = 0, not = 0;
+
+ dprint((7, "match_pattern\n"));
+
+ /*
+ * Is the current folder the right type and possibly the right specific
+ * folder for a match?
+ */
+ if(!(patgrp && !patgrp->bogus && match_pattern_folder(patgrp, stream)))
+ return(0);
+
+ /*
+ * NULL searchset means that there is no message to compare against.
+ * This is a match if the folder type matches above (that gets
+ * us here), and there are no patterns to match against.
+ *
+ * It is not totally clear what should be done in the case of an empty
+ * search set. If there is search criteria, and someone does something
+ * that is not specific to any messages (composing from scratch,
+ * forwarding an attachment), then we can't be sure what a user would
+ * expect. The original way was to just use the role, which we'll
+ * preserve here.
+ */
+ if(!searchset)
+ return(1);
+
+ /*
+ * change by sderr : match_pattern_folder will sometimes
+ * accept NULL streams, but if we are not in a folder-type-only
+ * match test, we don't
+ */
+ if(!stream)
+ return(0);
+
+ if(flags & MP_IN_CCLIENT_CB){
+ in_client_callback++;
+ flags &= ~MP_IN_CCLIENT_CB;
+ }
+
+ if(flags & MP_NOT){
+ not++;
+ flags &= ~MP_NOT;
+ }
+
+ flags |= SE_FREE;
+
+ if(patgrp->stat_bom != PAT_STAT_EITHER){
+ if(patgrp->stat_bom == PAT_STAT_YES){
+ if(!ps_global->beginning_of_month){
+ return(0);
+ }
+ }
+ else if(patgrp->stat_bom == PAT_STAT_NO){
+ if(ps_global->beginning_of_month){
+ return(0);
+ }
+ }
+ }
+
+ if(patgrp->stat_boy != PAT_STAT_EITHER){
+ if(patgrp->stat_boy == PAT_STAT_YES){
+ if(!ps_global->beginning_of_year){
+ return(0);
+ }
+ }
+ else if(patgrp->stat_boy == PAT_STAT_NO){
+ if(ps_global->beginning_of_year){
+ return(0);
+ }
+ }
+ }
+
+ if(in_client_callback && is_imap_stream(stream)
+ && (patgrp->alltext || patgrp->bodytext))
+ return(-1);
+
+ pgm = match_pattern_srchpgm(patgrp, stream, searchset);
+ if(not && !(is_imap_stream(stream) && !modern_imap_stream(stream))){
+ SEARCHPGM *srchpgm;
+
+ srchpgm = pgm;
+ pgm = mail_newsearchpgm();
+ pgm->not = mail_newsearchpgmlist();
+ pgm->not->pgm = srchpgm;
+ }
+
+ if((patgrp->alltext || patgrp->bodytext)
+ && (!is_imap_stream(stream) || modern_imap_stream(stream)))
+ /*
+ * Cache isn't going to work. Search on server.
+ * Except that is likely to not work on an old imap server because
+ * the OR criteria won't work and we are likely to have some ORs.
+ * So turn off the NOSERVER flag (and search on server if remote)
+ * unless the server is an old server. It doesn't matter if we
+ * turn if off if it's not an imap stream, but we do it anyway.
+ */
+ flags &= ~SE_NOSERVER;
+
+ if(section){
+ /*
+ * Mail_search_full only searches the top-level msg. We want to
+ * search an attached msg instead. First do the stuff
+ * that mail_search_full would have done before calling
+ * mail_search_msg, then call mail_search_msg with a section number.
+ * Mail_search_msg does take a section number even though
+ * mail_search_full doesn't.
+ */
+
+ /*
+ * We'll only ever set section if the searchset is a single message.
+ */
+ if(pgm->msgno->next == NULL && pgm->msgno->first == pgm->msgno->last)
+ msgno = pgm->msgno->first;
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = NIL;
+
+ if(mail_search_msg(stream,msgno,section,pgm)
+ && msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)))
+ mc->searched = T;
+
+ if(flags & SE_FREE)
+ mail_free_searchpgm(&pgm);
+ }
+ else{
+ /*
+ * Here we could be checking on the return value to see if
+ * the search was "successful" or not. It may be the case
+ * that we'd want to stop trying filtering if we got some
+ * sort of error, but for now we would just continue on
+ * to the next filter.
+ */
+ pine_mail_search_full(stream, "UTF-8", pgm, flags);
+ }
+
+ /* we searched without the not, reverse it */
+ if(not && is_imap_stream(stream) && !modern_imap_stream(stream)){
+ for(msgno = 1L; msgno < mn_get_total(sp_msgmap(stream)); msgno++)
+ if(stream && msgno && msgno <= stream->nmsgs
+ && (mc=mail_elt(stream,msgno)) && mc->searched)
+ mc->searched = NIL;
+ else
+ mc->searched = T;
+ }
+
+ /* check scores */
+ if(get_score && scores_are_used(SCOREUSE_GET) && patgrp->do_score){
+ char *savebits;
+ SEARCHSET *ss;
+
+ /*
+ * Get_score may call build_header_line recursively (we may
+ * be in build_header_line now) so we have to preserve and
+ * restore the sequence bits.
+ */
+ savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char));
+
+ for(i = 1L; i <= stream->nmsgs; i++){
+ if((mc = mail_elt(stream, i)) != NULL){
+ savebits[i] = mc->sequence;
+ mc->sequence = 0;
+ }
+ }
+
+ /*
+ * Build a searchset which will get all the scores that we
+ * need but not more.
+ */
+ for(s = searchset; s; s = s->next)
+ for(msgno = s->first; msgno <= s->last; msgno++)
+ if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)) && mc->searched
+ && get_msg_score(stream, msgno) == SCORE_UNDEF)
+ mc->sequence = 1;
+
+ if((ss = build_searchset(stream)) != NULL){
+ (void)calculate_some_scores(stream, ss, in_client_callback);
+ mail_free_searchset(&ss);
+ }
+
+ /*
+ * Now check the scores versus the score intervals to see if
+ * any of the messages which have matched up to this point can
+ * be tossed because they don't match the score interval.
+ */
+ for(s = searchset; s; s = s->next)
+ for(msgno = s->first; msgno <= s->last; msgno++)
+ if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)) && mc->searched){
+ long score;
+
+ score = (*get_score)(stream, msgno);
+
+ /*
+ * If the score is outside all of the intervals,
+ * turn off the searched bit.
+ * So that means we check each interval and if
+ * it is inside any interval we stop and leave
+ * the bit set. If it is outside we keep checking.
+ */
+ if(score != SCORE_UNDEF){
+ INTVL_S *iv;
+
+ for(iv = patgrp->score; iv; iv = iv->next)
+ if(score >= iv->imin && score <= iv->imax)
+ break;
+
+ if(!iv)
+ mc->searched = NIL;
+ }
+ }
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = savebits[i];
+
+ fs_give((void **)&savebits);
+ }
+
+ /* if there are still matches, check for 8bit subject match */
+ if(patgrp->stat_8bitsubj != PAT_STAT_EITHER)
+ find_8bitsubj_in_messages(stream, searchset, patgrp->stat_8bitsubj, 1);
+
+ /* if there are still matches, check for charset matches */
+ if(patgrp->charsets)
+ find_charsets_in_messages(stream, searchset, patgrp, 1);
+
+ /* Still matches, check addrbook */
+ if(patgrp->inabook != IAB_EITHER)
+ address_in_abook(stream, searchset, patgrp->inabook, patgrp->abooks);
+
+ /* Still matches? Run the categorization command on each msg. */
+ if(pith_opt_filter_pattern_cmd)
+ (*pith_opt_filter_pattern_cmd)(patgrp->category_cmd, searchset, stream, patgrp->cat_lim, patgrp->cat);
+
+ for(s = searchset; s; s = s->next)
+ for(msgno = s->first; msgno > 0L && msgno <= s->last; msgno++)
+ if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)) && mc->searched)
+ return(1);
+
+ return(0);
+}
+
+
+/*
+ * Look through messages in searchset to see if they contain 8bit
+ * characters in their subjects. All of the messages in
+ * searchset should initially have the searched bit set. Turn off the
+ * searched bit where appropriate.
+ */
+void
+find_8bitsubj_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
+ int stat_8bitsubj, int saveseqbits)
+{
+ char *savebits = NULL;
+ SEARCHSET *s, *ss = NULL;
+ MESSAGECACHE *mc;
+ long count = 0L;
+ unsigned long msgno;
+
+ /*
+ * If we are being called while in build_header_line we may
+ * call build_header_line recursively. So save and restore the
+ * sequence bits.
+ */
+ if(saveseqbits)
+ savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
+
+ for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
+ if((mc = mail_elt(stream, msgno)) != NULL){
+ if(savebits)
+ savebits[msgno] = mc->sequence;
+
+ mc->sequence = 0;
+ }
+ }
+
+ /*
+ * Build a searchset so we can look at all the envelopes
+ * we need to look at but only those we need to look at.
+ * Everything with the searched bit set is still a
+ * possibility, so restrict to that set.
+ */
+
+ for(s = searchset; s; s = s->next)
+ for(msgno = s->first; msgno <= s->last; msgno++)
+ if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)) && mc->searched){
+ mc->sequence = 1;
+ count++;
+ }
+
+ ss = build_searchset(stream);
+
+ if(count){
+ SEARCHSET **sset;
+
+ mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
+
+ /*
+ * This causes the lookahead to fetch precisely
+ * the messages we want (in the searchset) instead
+ * of just fetching the next 20 sequential
+ * messages. If the searching so far has caused
+ * a sparse searchset in a large mailbox, the
+ * difference can be substantial.
+ * This resets automatically after the first fetch.
+ */
+ sset = (SEARCHSET **) mail_parameters(stream,
+ GET_FETCHLOOKAHEAD,
+ (void *) stream);
+ if(sset)
+ *sset = ss;
+ }
+
+ for(s = ss; s; s = s->next){
+ for(msgno = s->first; msgno <= s->last; msgno++){
+ ENVELOPE *e;
+
+ if(!stream || msgno <= 0L || msgno > stream->nmsgs)
+ continue;
+
+ e = pine_mail_fetchenvelope(stream, msgno);
+ if(stat_8bitsubj == PAT_STAT_YES){
+ if(e && e->subject){
+ char *p;
+
+ for(p = e->subject; *p; p++)
+ if(*p & 0x80)
+ break;
+
+ if(!*p && msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)))
+ mc->searched = NIL;
+ }
+ else if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)))
+ mc->searched = NIL;
+ }
+ else if(stat_8bitsubj == PAT_STAT_NO){
+ if(e && e->subject){
+ char *p;
+
+ for(p = e->subject; *p; p++)
+ if(*p & 0x80)
+ break;
+
+ if(*p && msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)))
+ mc->searched = NIL;
+ }
+ }
+ }
+ }
+
+ if(savebits){
+ for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
+ if((mc = mail_elt(stream, msgno)) != NULL)
+ mc->sequence = savebits[msgno];
+
+ fs_give((void **) &savebits);
+ }
+
+ if(ss)
+ mail_free_searchset(&ss);
+}
+
+
+/*
+ * Look through messages in searchset to see if they contain any of the
+ * charsets or scripts listed in charsets pattern. All of the messages in
+ * searchset should initially have the searched bit set. Turn off the
+ * searched bit where appropriate.
+ */
+void
+find_charsets_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
+ PATGRP_S *patgrp, int saveseqbits)
+{
+ char *savebits = NULL;
+ unsigned long msgno;
+ long count = 0L;
+ MESSAGECACHE *mc;
+ SEARCHSET *s, *ss;
+
+ if(!stream || !patgrp)
+ return;
+
+ /*
+ * When we actually want to use charsets, we convert it into a list
+ * of charsets instead of the mixed list of scripts and charsets and
+ * we eliminate duplicates. This is more efficient when we actually
+ * do the lookups and compares.
+ */
+ if(!patgrp->charsets_list){
+ PATTERN_S *cs;
+ const CHARSET *cset;
+ STRLIST_S *sl = NULL, *newsl;
+ unsigned long scripts = 0L;
+ SCRIPT *script;
+
+ for(cs = patgrp->charsets; cs; cs = cs->next){
+ /*
+ * Run through the charsets pattern looking for
+ * scripts and set the corresponding script bits.
+ * If it isn't a script, it is a character set.
+ */
+ if(cs->substring && (script = utf8_script(cs->substring)))
+ scripts |= script->script;
+ else{
+ /* add it to list as a specific character set */
+ newsl = new_strlist(cs->substring);
+ if(compare_strlists_for_match(sl, newsl)) /* already in list */
+ free_strlist(&newsl);
+ else{
+ newsl->next = sl;
+ sl = newsl;
+ }
+ }
+ }
+
+ /*
+ * Now scripts has a bit set for each script the user
+ * specified in the charsets pattern. Go through all of
+ * the known charsets and include ones in these scripts.
+ */
+ if(scripts){
+ for(cset = utf8_charset(NIL); cset && cset->name; cset++){
+ if(cset->script & scripts){
+
+ /* filter this out of each script, not very useful */
+ if(!strucmp("ISO-2022-JP-2", cset->name)
+ || !strucmp("UTF-7", cset->name)
+ || !strucmp("UTF-8", cset->name))
+ continue;
+
+ /* add cset->name to the list */
+ newsl = new_strlist(cset->name);
+ if(compare_strlists_for_match(sl, newsl))
+ free_strlist(&newsl);
+ else{
+ newsl->next = sl;
+ sl = newsl;
+ }
+ }
+ }
+ }
+
+ patgrp->charsets_list = sl;
+ }
+
+ /*
+ * This may call build_header_line recursively because we may be in
+ * build_header_line now. So we have to preserve and restore the
+ * sequence bits since we want to use them here.
+ */
+ if(saveseqbits)
+ savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
+
+ for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
+ if((mc = mail_elt(stream, msgno)) != NULL){
+ if(savebits)
+ savebits[msgno] = mc->sequence;
+
+ mc->sequence = 0;
+ }
+ }
+
+
+ /*
+ * Build a searchset so we can look at all the bodies
+ * we need to look at but only those we need to look at.
+ * Everything with the searched bit set is still a
+ * possibility, so restrict to that set.
+ */
+
+ for(s = searchset; s; s = s->next)
+ for(msgno = s->first; msgno <= s->last; msgno++)
+ if(msgno > 0L && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)) && mc->searched){
+ mc->sequence = 1;
+ count++;
+ }
+
+ ss = build_searchset(stream);
+
+ if(count){
+ SEARCHSET **sset;
+
+ mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
+
+ /*
+ * This causes the lookahead to fetch precisely
+ * the messages we want (in the searchset) instead
+ * of just fetching the next 20 sequential
+ * messages. If the searching so far has caused
+ * a sparse searchset in a large mailbox, the
+ * difference can be substantial.
+ * This resets automatically after the first fetch.
+ */
+ sset = (SEARCHSET **) mail_parameters(stream,
+ GET_FETCHLOOKAHEAD,
+ (void *) stream);
+ if(sset)
+ *sset = ss;
+ }
+
+ for(s = ss; s; s = s->next){
+ for(msgno = s->first; msgno <= s->last; msgno++){
+
+ if(msgno <= 0L || msgno > stream->nmsgs)
+ continue;
+
+ if(patgrp->charsets_list
+ && charsets_present_in_msg(stream,msgno,patgrp->charsets_list)){
+ if(patgrp->charsets->not){
+ if((mc = mail_elt(stream, msgno)))
+ mc->searched = NIL;
+ }
+ /* else leave it */
+ }
+ else{ /* charset isn't in message */
+ if(!patgrp->charsets->not){
+ if((mc = mail_elt(stream, msgno)))
+ mc->searched = NIL;
+ }
+ /* else leave it */
+ }
+ }
+ }
+
+ if(savebits){
+ for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
+ if((mc = mail_elt(stream, msgno)) != NULL)
+ mc->sequence = savebits[msgno];
+
+ fs_give((void **) &savebits);
+ }
+
+ if(ss)
+ mail_free_searchset(&ss);
+}
+
+
+/*
+ * Look for any of the charsets in this particular message.
+ *
+ * Returns 1 if there is a match, 0 otherwise.
+ */
+int
+charsets_present_in_msg(MAILSTREAM *stream, long unsigned int rawmsgno, STRLIST_S *charsets)
+{
+ BODY *body = NULL;
+ ENVELOPE *env = NULL;
+ STRLIST_S *msg_charsets = NULL;
+ int ret = 0;
+
+ if(charsets && stream && rawmsgno > 0L && rawmsgno <= stream->nmsgs){
+ env = pine_mail_fetchstructure(stream, rawmsgno, &body);
+ collect_charsets_from_subj(env, &msg_charsets);
+ collect_charsets_from_body(body, &msg_charsets);
+ if(msg_charsets){
+ ret = compare_strlists_for_match(msg_charsets, charsets);
+ free_strlist(&msg_charsets);
+ }
+ }
+
+ return(ret);
+}
+
+
+void
+collect_charsets_from_subj(ENVELOPE *env, STRLIST_S **listptr)
+{
+ STRLIST_S *newsl;
+ char *text, *e;
+
+ if(listptr && env && env->subject){
+ /* find encoded word */
+ for(text = env->subject; *text; text++){
+ if((*text == '=') && (text[1] == '?') && isalpha(text[2]) &&
+ (e = strchr(text+2,'?'))){
+ *e = '\0'; /* tie off charset name */
+
+ newsl = new_strlist(text+2);
+ *e = '?';
+
+ if(compare_strlists_for_match(*listptr, newsl))
+ free_strlist(&newsl);
+ else{
+ newsl->next = *listptr;
+ *listptr = newsl;
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Check for any of the charsets in any of the charset params in
+ * any of the text parts of the body of a message. Put them in the list
+ * pointed to by listptr.
+ */
+void
+collect_charsets_from_body(struct mail_bodystruct *body, STRLIST_S **listptr)
+{
+ PART *part;
+ char *cset;
+
+ if(listptr && body){
+ switch(body->type){
+ case TYPEMULTIPART:
+ for(part = body->nested.part; part; part = part->next)
+ collect_charsets_from_body(&part->body, listptr);
+
+ break;
+
+ case TYPEMESSAGE:
+ if(!strucmp(body->subtype, "RFC822")){
+ collect_charsets_from_subj(body->nested.msg->env, listptr);
+ collect_charsets_from_body(body->nested.msg->body, listptr);
+ break;
+ }
+ /* else fall through to text case */
+
+ case TYPETEXT:
+ cset = parameter_val(body->parameter, "charset");
+ if(cset){
+ STRLIST_S *newsl;
+
+ newsl = new_strlist(cset);
+
+ if(compare_strlists_for_match(*listptr, newsl))
+ free_strlist(&newsl);
+ else{
+ newsl->next = *listptr;
+ *listptr = newsl;
+ }
+
+ fs_give((void **) &cset);
+ }
+
+ break;
+
+ default: /* non-text terminal mode */
+ break;
+ }
+ }
+}
+
+
+/*
+ * If any of the names in list1 is the same as any of the names in list2
+ * then return 1, else return 0. Comparison is case independent.
+ */
+int
+compare_strlists_for_match(STRLIST_S *list1, STRLIST_S *list2)
+{
+ int ret = 0;
+ STRLIST_S *cs1, *cs2;
+
+ for(cs1 = list1; !ret && cs1; cs1 = cs1->next)
+ for(cs2 = list2; !ret && cs2; cs2 = cs2->next)
+ if(cs1->name && cs2->name && !strucmp(cs1->name, cs2->name))
+ ret = 1;
+
+ return(ret);
+}
+
+
+int
+match_pattern_folder(PATGRP_S *patgrp, MAILSTREAM *stream)
+{
+ int is_news;
+
+ /* change by sderr : we match FLDR_ANY even if stream is NULL */
+ return((patgrp->fldr_type == FLDR_ANY)
+ || (stream
+ && (((is_news = IS_NEWS(stream))
+ && patgrp->fldr_type == FLDR_NEWS)
+ || (!is_news && patgrp->fldr_type == FLDR_EMAIL)
+ || (patgrp->fldr_type == FLDR_SPECIFIC
+ && match_pattern_folder_specific(patgrp->folder,
+ stream, FOR_PATTERN)))));
+}
+
+
+/*
+ * Returns positive if this stream is open on one of the folders in the
+ * folders argument, 0 otherwise.
+ *
+ * If FOR_PATTERN is set, this interprets simple names as nicknames in
+ * the incoming collection, otherwise it treats simple names as being in
+ * the primary collection.
+ * If FOR_FILT is set, the folder names are detokenized before being used.
+ */
+int
+match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags)
+{
+ PATTERN_S *p;
+ int match = 0;
+ char *patfolder, *free_this = NULL;
+
+ dprint((8, "match_pattern_folder_specific\n"));
+
+ if(!(stream && stream->mailbox && stream->mailbox[0]))
+ return(0);
+
+ /*
+ * For each of the folders in the pattern, see if we get
+ * a match. We're just looking for any match. If none match,
+ * we return 0, otherwise we fall through and check the rest
+ * of the pattern. The fact that the string is called "substring"
+ * is not meaningful. We're just using the convenient pattern
+ * structure to store a list of folder names. They aren't
+ * substrings of names, they are the whole name.
+ */
+ for(p = folders; !match && p; p = p->next){
+ free_this = NULL;
+ if(flags & FOR_FILTER)
+ patfolder = free_this = detoken_src(p->substring, FOR_FILT, NULL,
+ NULL, NULL, NULL);
+ else
+ patfolder = p->substring;
+
+ if(patfolder
+ && (!strucmp(patfolder, ps_global->inbox_name)
+ || !strcmp(patfolder, ps_global->VAR_INBOX_PATH))){
+ if(sp_flagged(stream, SP_INBOX))
+ match++;
+ }
+ else{
+ char *fname;
+ char *t, *streamfolder;
+ char tmp1[MAILTMPLEN], tmp2[MAX(MAILTMPLEN,NETMAXMBX)];
+ CONTEXT_S *cntxt = NULL;
+
+ if(flags & FOR_PATTERN){
+ /*
+ * See if patfolder is a nickname in the incoming collection.
+ * If so, use its real name instead.
+ */
+ if(patfolder[0] &&
+ (ps_global->context_list->use & CNTXT_INCMNG) &&
+ (fname = (folder_is_nick(patfolder,
+ FOLDERS(ps_global->context_list),
+ 0))))
+ patfolder = fname;
+ }
+ else{
+ char *save_ref = NULL;
+
+ /*
+ * If it's an absolute pathname, we treat is as a local file
+ * instead of interpreting it in the primary context.
+ */
+ if(!is_absolute_path(patfolder)
+ && !(cntxt = default_save_context(ps_global->context_list)))
+ cntxt = ps_global->context_list;
+
+ /*
+ * Because this check is independent of where the user is
+ * in the folder hierarchy and has nothing to do with that,
+ * we want to ignore the reference field built into the
+ * context. Zero it out temporarily here.
+ */
+ if(cntxt && cntxt->dir){
+ save_ref = cntxt->dir->ref;
+ cntxt->dir->ref = NULL;
+ }
+
+ patfolder = context_apply(tmp1, cntxt, patfolder, sizeof(tmp1));
+ if(save_ref)
+ cntxt->dir->ref = save_ref;
+ }
+
+ switch(patfolder[0]){
+ case '{':
+ if(stream->mailbox[0] == '{' &&
+ same_stream(patfolder, stream) &&
+ (streamfolder = strindex(&stream->mailbox[1], '}')) &&
+ (t = strindex(&patfolder[1], '}')) &&
+ (!strcmp(t+1, streamfolder+1) ||
+ (*(t+1) == '\0' && !strcmp("INBOX", streamfolder+1))))
+ match++;
+
+ break;
+
+ case '#':
+ if(!strcmp(patfolder, stream->mailbox))
+ match++;
+
+ break;
+
+ default:
+ t = (strlen(patfolder) < (MAILTMPLEN/2))
+ ? mailboxfile(tmp2, patfolder) : NULL;
+ if(t && *t && !strcmp(t, stream->mailbox))
+ match++;
+
+ break;
+ }
+ }
+
+ if(free_this)
+ fs_give((void **) &free_this);
+ }
+
+ return(match);
+}
+
+
+/*
+ * generate a search program corresponding to the provided patgrp
+ */
+SEARCHPGM *
+match_pattern_srchpgm(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset)
+{
+ SEARCHPGM *pgm, *tmppgm;
+ SEARCHOR *or;
+ SEARCHSET **sp;
+
+ pgm = mail_newsearchpgm();
+
+ sp = &pgm->msgno;
+ /* copy the searchset */
+ while(searchset){
+ SEARCHSET *s;
+
+ s = mail_newsearchset();
+ s->first = searchset->first;
+ s->last = searchset->last;
+ searchset = searchset->next;
+ *sp = s;
+ sp = &s->next;
+ }
+
+ if(!patgrp)
+ return(pgm);
+
+ if(patgrp->subj){
+ if(patgrp->subj->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("subject", patgrp->subj, tmppgm);
+ }
+
+ if(patgrp->cc){
+ if(patgrp->cc->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("cc", patgrp->cc, tmppgm);
+ }
+
+ if(patgrp->from){
+ if(patgrp->from->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("from", patgrp->from, tmppgm);
+ }
+
+ if(patgrp->to){
+ if(patgrp->to->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("to", patgrp->to, tmppgm);
+ }
+
+ if(patgrp->sender){
+ if(patgrp->sender->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("sender", patgrp->sender, tmppgm);
+ }
+
+ if(patgrp->news){
+ if(patgrp->news->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("newsgroups", patgrp->news, tmppgm);
+ }
+
+ /* To OR Cc */
+ if(patgrp->recip){
+ if(patgrp->recip->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ or = next_or(&tmppgm->or);
+
+ set_up_search_pgm("to", patgrp->recip, or->first);
+ set_up_search_pgm("cc", patgrp->recip, or->second);
+ }
+
+ /* To OR Cc OR From */
+ if(patgrp->partic){
+ if(patgrp->partic->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ or = next_or(&tmppgm->or);
+
+ set_up_search_pgm("to", patgrp->partic, or->first);
+
+ or->second->or = mail_newsearchor();
+ set_up_search_pgm("cc", patgrp->partic, or->second->or->first);
+ set_up_search_pgm("from", patgrp->partic, or->second->or->second);
+ }
+
+ if(patgrp->arbhdr){
+ ARBHDR_S *a;
+
+ for(a = patgrp->arbhdr; a; a = a->next)
+ if(a->field && a->field[0] && a->p){
+ if(a->p->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm(a->field, a->p, tmppgm);
+ }
+ }
+
+ if(patgrp->alltext){
+ if(patgrp->alltext->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("alltext", patgrp->alltext, tmppgm);
+ }
+
+ if(patgrp->bodytext){
+ if(patgrp->bodytext->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ set_up_search_pgm("bodytext", patgrp->bodytext, tmppgm);
+ }
+
+ if(patgrp->keyword){
+ PATTERN_S *p_old, *p_new, *new_pattern = NULL, **nextp;
+ char *q;
+
+ if(patgrp->keyword->not)
+ tmppgm = next_not(pgm);
+ else
+ tmppgm = pgm;
+
+ /*
+ * The keyword entries may be nicknames instead of the actual
+ * keywords, so those need to be converted to actual keywords.
+ *
+ * If we search for keywords that are not defined for a folder
+ * we may get error messages back that we don't want instead of
+ * just no match. We will build a replacement pattern here which
+ * contains only the defined subset of the keywords.
+ */
+
+ nextp = &new_pattern;
+
+ for(p_old = patgrp->keyword; p_old; p_old = p_old->next){
+ q = nick_to_keyword(p_old->substring);
+ if(user_flag_index(stream, q) >= 0){
+ p_new = (PATTERN_S *) fs_get(sizeof(*p_new));
+ memset(p_new, 0, sizeof(*p_new));
+ p_new->substring = cpystr(q);
+ *nextp = p_new;
+ nextp = &p_new->next;
+ }
+ }
+
+ /*
+ * If there are some matching keywords that are defined in
+ * the folder, then we are ok because we will match only if
+ * we match one of those. However, if the list is empty, then
+ * we can't just leave this part of the search program empty.
+ * That would result in a match instead of not a match.
+ * We can fake our way around the problem with NOT. If the
+ * list is empty we want the opposite, so we insert a NOT in
+ * front of an empty program. We may end up with NOT NOT if
+ * this was already NOT'd, but that's ok, too. Alternatively,
+ * we could undo the first NOT instead.
+ */
+
+ if(new_pattern){
+ set_up_search_pgm("keyword", new_pattern, tmppgm);
+ free_pattern(&new_pattern);
+ }
+ else
+ (void) next_not(tmppgm); /* add NOT of something that matches,
+ so the NOT thing doesn't match */
+ }
+
+ if(patgrp->do_age && patgrp->age){
+ INTVL_S *iv;
+ SEARCHOR *or;
+
+ tmppgm = pgm;
+
+ for(iv = patgrp->age; iv; iv = iv->next){
+ if(iv->next){
+ or = next_or(&tmppgm->or);
+ set_search_by_age(iv, or->first, patgrp->age_uses_sentdate);
+ tmppgm = or->second;
+ }
+ else
+ set_search_by_age(iv, tmppgm, patgrp->age_uses_sentdate);
+ }
+ }
+
+ if(patgrp->do_size && patgrp->size){
+ INTVL_S *iv;
+ SEARCHOR *or;
+
+ tmppgm = pgm;
+
+ for(iv = patgrp->size; iv; iv = iv->next){
+ if(iv->next){
+ or = next_or(&tmppgm->or);
+ set_search_by_size(iv, or->first);
+ tmppgm = or->second;
+ }
+ else
+ set_search_by_size(iv, tmppgm);
+ }
+ }
+
+ SETPGMSTATUS(patgrp->stat_new,pgm->unseen,pgm->seen);
+ SETPGMSTATUS(patgrp->stat_rec,pgm->recent,pgm->old);
+ SETPGMSTATUS(patgrp->stat_del,pgm->deleted,pgm->undeleted);
+ SETPGMSTATUS(patgrp->stat_imp,pgm->flagged,pgm->unflagged);
+ SETPGMSTATUS(patgrp->stat_ans,pgm->answered,pgm->unanswered);
+
+ return(pgm);
+}
+
+
+SEARCHPGM *
+next_not(SEARCHPGM *pgm)
+{
+ SEARCHPGMLIST *not, **not_ptr;
+
+ if(!pgm)
+ return(NULL);
+
+ /* find next unused not slot */
+ for(not = pgm->not; not && not->next; not = not->next)
+ ;
+
+ if(not)
+ not_ptr = &not->next;
+ else
+ not_ptr = &pgm->not;
+
+ /* allocate */
+ *not_ptr = mail_newsearchpgmlist();
+
+ return((*not_ptr)->pgm);
+}
+
+
+SEARCHOR *
+next_or(struct search_or **startingor)
+{
+ SEARCHOR *or, **or_ptr;
+
+ /* find next unused or slot */
+ for(or = (*startingor); or && or->next; or = or->next)
+ ;
+
+ if(or)
+ or_ptr = &or->next;
+ else
+ or_ptr = startingor;
+
+ /* allocate */
+ *or_ptr = mail_newsearchor();
+
+ return(*or_ptr);
+}
+
+
+void
+set_up_search_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
+{
+ SEARCHOR *or;
+
+ if(field && pattern && pgm){
+
+ /*
+ * To is special because we want to use the ReSent-To header instead
+ * of the To header if it exists. We set up something like:
+ *
+ * if((resent-to matches pat1 or pat2...)
+ * OR
+ * (<resent-to doesn't exist> AND (to matches pat1 or pat2...)))
+ *
+ * Some servers (Exchange, apparently) seem to have trouble with
+ * the search for the empty string to decide if the header exists
+ * or not. So, we will search for either the empty string OR the
+ * header with a SPACE in it. Some still have trouble with this
+ * so we are changing it to be off by default.
+ */
+ if(!strucmp(field, "to") && F_ON(F_USE_RESENTTO, ps_global)){
+ or = next_or(&pgm->or);
+
+ add_type_to_pgm("resent-to", pattern, or->first);
+
+ /* check for resent-to doesn't exist */
+ or->second->not = mail_newsearchpgmlist();
+
+ or->second->not->pgm->or = mail_newsearchor();
+ set_srch("resent-to", " ", or->second->not->pgm->or->first);
+ set_srch("resent-to", "", or->second->not->pgm->or->second);
+
+ /* now add the real To search to second */
+ add_type_to_pgm(field, pattern, or->second);
+ }
+ else
+ add_type_to_pgm(field, pattern, pgm);
+ }
+}
+
+
+void
+add_type_to_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
+{
+ PATTERN_S *p;
+ SEARCHOR *or;
+ SEARCHPGM *notpgm, *tpgm;
+ int cnt = 0;
+
+ if(field && pattern && pgm){
+ /*
+ * Here is a weird bit of logic. What we want here is simply
+ * A or B or C or D
+ * for all of the elements of pattern. Ors are a bit complicated.
+ * The list of ORs in the SEARCHPGM structure are ANDed together,
+ * not ORd together. It's for things like
+ * Subject A or B AND From C or D
+ * The Subject part would be one member of the OR list and the From
+ * part would be another member of the OR list. Instead we want
+ * a big OR which may have more than two members (first and second)
+ * but the structure just has two members. So we have to build an
+ * OR tree and we build it by going down one branch of the tree
+ * instead of by balancing the branches.
+ *
+ * or
+ * / \
+ * first==A second
+ * / \
+ * first==B second
+ * / \
+ * first==C second==D
+ *
+ * There is an additional problem. Some servers don't like deeply
+ * nested logic in the SEARCH command. The tree above produces a
+ * fairly deeply nested command if the user wants to match on
+ * several different From addresses or Subjects...
+ * We use the tried and true equation
+ *
+ * (A or B) == !(!A and !B)
+ *
+ * to change the deeply nested OR tree into ANDs which aren't nested.
+ * Right now we're only doing that if the nesting is fairly deep.
+ * We can think of some reasons to do that. First, we know that the
+ * OR thing works, that's what we've been using for a while and the
+ * only problem is the deep nesting. 2nd, it is easier to understand.
+ * 3rd, it looks dumb to use NOT NOT A instead of A.
+ * It is probably dumb to mix the two, but what the heck.
+ * Hubert 2003-04-02
+ */
+ for(p = pattern; p; p = p->next)
+ cnt++;
+
+ if(cnt < 10){ /* use ORs if count is low */
+ for(p = pattern; p; p = p->next){
+ if(p->next){
+ or = next_or(&pgm->or);
+
+ set_srch(field, p->substring ? p->substring : "", or->first);
+ pgm = or->second;
+ }
+ else
+ set_srch(field, p->substring ? p->substring : "", pgm);
+ }
+ }
+ else{ /* else use ANDs */
+ /* ( A or B or C ) <=> ! ( !A and !B and !C ) */
+
+ /* first, NOT of the whole thing */
+ notpgm = next_not(pgm);
+
+ /* then the not list is ANDed together */
+ for(p = pattern; p; p = p->next){
+ tpgm = next_not(notpgm);
+ set_srch(field, p->substring ? p->substring : "", tpgm);
+ }
+ }
+ }
+}
+
+
+void
+set_srch(char *field, char *value, SEARCHPGM *pgm)
+{
+ char *decoded;
+ STRINGLIST **list;
+
+ if(!(field && value && pgm))
+ return;
+
+ if(!strucmp(field, "subject"))
+ list = &pgm->subject;
+ else if(!strucmp(field, "from"))
+ list = &pgm->from;
+ else if(!strucmp(field, "to"))
+ list = &pgm->to;
+ else if(!strucmp(field, "cc"))
+ list = &pgm->cc;
+ else if(!strucmp(field, "sender"))
+ list = &pgm->sender;
+ else if(!strucmp(field, "reply-to"))
+ list = &pgm->reply_to;
+ else if(!strucmp(field, "in-reply-to"))
+ list = &pgm->in_reply_to;
+ else if(!strucmp(field, "message-id"))
+ list = &pgm->message_id;
+ else if(!strucmp(field, "newsgroups"))
+ list = &pgm->newsgroups;
+ else if(!strucmp(field, "followup-to"))
+ list = &pgm->followup_to;
+ else if(!strucmp(field, "alltext"))
+ list = &pgm->text;
+ else if(!strucmp(field, "bodytext"))
+ list = &pgm->body;
+ else if(!strucmp(field, "keyword"))
+ list = &pgm->keyword;
+ else{
+ set_srch_hdr(field, value, pgm);
+ return;
+ }
+
+ if(!list)
+ return;
+
+ *list = mail_newstringlist();
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, value);
+
+ (*list)->text.data = (unsigned char *)cpystr(decoded);
+ (*list)->text.size = strlen(decoded);
+}
+
+
+void
+set_srch_hdr(char *field, char *value, SEARCHPGM *pgm)
+{
+ char *decoded;
+ SEARCHHEADER **hdr;
+
+ if(!(field && value && pgm))
+ return;
+
+ hdr = &pgm->header;
+ if(!hdr)
+ return;
+
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, value);
+ while(*hdr && (*hdr)->next)
+ *hdr = (*hdr)->next;
+
+ if(*hdr)
+ (*hdr)->next = mail_newsearchheader(field, decoded);
+ else
+ *hdr = mail_newsearchheader(field, decoded);
+}
+
+
+void
+set_search_by_age(INTVL_S *age, SEARCHPGM *pgm, int age_uses_sentdate)
+{
+ time_t now, comparetime;
+ struct tm *tm;
+ unsigned short i;
+
+ if(!(age && pgm))
+ return;
+
+ now = time(0);
+
+ if(age->imin >= 0L && age->imin == age->imax){
+ comparetime = now;
+ comparetime -= (age->imin * 86400L);
+ tm = localtime(&comparetime);
+ if(tm && tm->tm_year >= 70){
+ i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
+ tm->tm_mday);
+ if(age_uses_sentdate)
+ pgm->senton = i;
+ else
+ pgm->on = i;
+ }
+ }
+ else{
+ /*
+ * The 20000's are just protecting against overflows.
+ * That's back past the start of email time, anyway.
+ */
+ if(age->imin > 0L && age->imin < 20000L){
+ comparetime = now;
+ comparetime -= ((age->imin - 1L) * 86400L);
+ tm = localtime(&comparetime);
+ if(tm && tm->tm_year >= 70){
+ i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
+ tm->tm_mday);
+ if(age_uses_sentdate)
+ pgm->sentbefore = i;
+ else
+ pgm->before = i;
+ }
+ }
+
+ if(age->imax >= 0L && age->imax < 20000L){
+ comparetime = now;
+ comparetime -= (age->imax * 86400L);
+ tm = localtime(&comparetime);
+ if(tm && tm->tm_year >= 70){
+ i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
+ tm->tm_mday);
+ if(age_uses_sentdate)
+ pgm->sentsince = i;
+ else
+ pgm->since = i;
+ }
+ }
+ }
+}
+
+
+void
+set_search_by_size(INTVL_S *size, SEARCHPGM *pgm)
+{
+ if(!(size && pgm))
+ return;
+
+ /*
+ * INTVL_S intervals include the endpoints, pgm larger and smaller
+ * do not include the endpoints.
+ */
+ if(size->imin != INTVL_UNDEF && size->imin > 0L)
+ pgm->larger = size->imin - 1L;
+
+ if(size->imax != INTVL_UNDEF && size->imax >= 0L && size->imax != INTVL_INF)
+ pgm->smaller = size->imax + 1L;
+}
+
+
+static char *extra_hdrs;
+
+/*
+ * Run through the patterns and note which headers we'll need to ask for
+ * which aren't normally asked for and so won't be cached.
+ */
+void
+calc_extra_hdrs(void)
+{
+ PAT_S *pat = NULL;
+ int alloced_size;
+ long type = (ROLE_INCOL | ROLE_SCORE);
+ ARBHDR_S *a;
+ PAT_STATE pstate;
+ char *q, *p = NULL, *hdrs[MLCMD_COUNT + 1], **pp;
+ INDEX_COL_S *cdesc;
+#define INITIALSIZE 1000
+
+ q = (char *)fs_get((INITIALSIZE+1) * sizeof(char));
+ q[0] = '\0';
+ alloced_size = INITIALSIZE;
+ p = q;
+
+ /*
+ * *ALWAYS* make sure Resent-To is in the set of
+ * extra headers getting fetched.
+ *
+ * This is because we *will* reference it when we're
+ * building header lines and thus want it fetched with
+ * the standard envelope data. Worse, in the IMAP case
+ * we're called back from c-client with the envelope data
+ * so we can format and display the index lines as they
+ * arrive, so we have to ensure the resent-to field
+ * is in the cache so we don't reenter c-client
+ * to look for it from the callback. Yeouch.
+ */
+ add_eh(&q, &p, "resent-to", &alloced_size);
+ add_eh(&q, &p, "resent-date", &alloced_size);
+ add_eh(&q, &p, "resent-from", &alloced_size);
+ add_eh(&q, &p, "resent-cc", &alloced_size);
+ add_eh(&q, &p, "resent-subject", &alloced_size);
+
+ /*
+ * Sniff at viewer-hdrs too so we can include them
+ * if there are any...
+ */
+ for(pp = ps_global->VAR_VIEW_HEADERS; pp && *pp; pp++)
+ if(non_eh(*pp))
+ add_eh(&q, &p, *pp, &alloced_size);
+
+ /*
+ * Be sure to ask for List management headers too
+ * since we'll offer their use in the message view
+ */
+ for(pp = rfc2369_hdrs(hdrs); *pp; pp++)
+ add_eh(&q, &p, *pp, &alloced_size);
+
+ if(nonempty_patterns(type, &pstate))
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ /*
+ * This section wouldn't be necessary if sender was retreived
+ * from the envelope. But if not, we do need to add it.
+ */
+ if(pat->patgrp && pat->patgrp->sender)
+ add_eh(&q, &p, "sender", &alloced_size);
+
+ if(pat->patgrp && pat->patgrp->arbhdr)
+ for(a = pat->patgrp->arbhdr; a; a = a->next)
+ if(a->field && a->field[0] && a->p && non_eh(a->field))
+ add_eh(&q, &p, a->field, &alloced_size);
+ }
+
+ /*
+ * Check for use of HEADER or X-Priority in index-format.
+ */
+ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){
+ if(cdesc->ctype == iHeader && cdesc->hdrtok && cdesc->hdrtok->hdrname
+ && cdesc->hdrtok->hdrname[0] && non_eh(cdesc->hdrtok->hdrname))
+ add_eh(&q, &p, cdesc->hdrtok->hdrname, &alloced_size);
+ else if(cdesc->ctype == iPrio
+ || cdesc->ctype == iPrioAlpha
+ || cdesc->ctype == iPrioBang)
+ add_eh(&q, &p, PRIORITYNAME, &alloced_size);
+ }
+
+ /*
+ * Check for use of scorevalhdrtok in scoring patterns.
+ */
+ type = ROLE_SCORE;
+ if(nonempty_patterns(type, &pstate))
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ /*
+ * This section wouldn't be necessary if sender was retreived
+ * from the envelope. But if not, we do need to add it.
+ */
+ if(pat->action && pat->action->scorevalhdrtok
+ && pat->action->scorevalhdrtok->hdrname
+ && pat->action->scorevalhdrtok->hdrname[0]
+ && non_eh(pat->action->scorevalhdrtok->hdrname))
+ add_eh(&q, &p, pat->action->scorevalhdrtok->hdrname, &alloced_size);
+ }
+
+ set_extra_hdrs(q);
+ if(q)
+ fs_give((void **)&q);
+}
+
+
+int
+non_eh(char *field)
+{
+ char **t;
+ static char *existing[] = {"subject", "from", "to", "cc", "sender",
+ "reply-to", "in-reply-to", "message-id",
+ "path", "newsgroups", "followup-to",
+ "references", NULL};
+
+ /*
+ * If it is one of these, we should already have it
+ * from the envelope or from the extra headers c-client
+ * already adds to the list (hdrheader and hdrtrailer
+ * in imap4r1.c, Aug 99, slh).
+ */
+ for(t = existing; *t; t++)
+ if(!strucmp(field, *t))
+ return(FALSE);
+
+ return(TRUE);
+}
+
+
+/*
+ * Add field to extra headers string if not already there.
+ */
+void
+add_eh(char **start, char **ptr, char *field, int *asize)
+{
+ char *s;
+
+ /* already there? */
+ for(s = *start; (s = srchstr(s, field)) != NULL; s++)
+ if(s[strlen(field)] == SPACE || s[strlen(field)] == '\0')
+ return;
+
+ /* enough space for it? */
+ while(strlen(field) + (*ptr - *start) + 1 > *asize){
+ (*asize) *= 2;
+ fs_resize((void **)start, (*asize)+1);
+ *ptr = *start + strlen(*start);
+ }
+
+ if(*ptr > *start)
+ sstrncpy(ptr, " ", *asize-(*ptr - *start));
+
+ sstrncpy(ptr, field, *asize-(*ptr - *start));
+
+ (*start)[*asize] = '\0';
+}
+
+
+void
+set_extra_hdrs(char *hdrs)
+{
+ free_extra_hdrs();
+ if(hdrs && *hdrs)
+ extra_hdrs = cpystr(hdrs);
+}
+
+
+char *
+get_extra_hdrs(void)
+{
+ return(extra_hdrs);
+}
+
+
+void
+free_extra_hdrs(void)
+{
+ if(extra_hdrs)
+ fs_give((void **)&extra_hdrs);
+}
+
+
+int
+is_ascii_string(char *str)
+{
+ if(!str)
+ return(0);
+
+ while(*str && isascii(*str))
+ str++;
+
+ return(*str == '\0');
+}
+
+
+void
+free_patline(PAT_LINE_S **patline)
+{
+ if(patline && *patline){
+ free_patline(&(*patline)->next);
+ if((*patline)->filename)
+ fs_give((void **)&(*patline)->filename);
+ if((*patline)->filepath)
+ fs_give((void **)&(*patline)->filepath);
+ free_pat(&(*patline)->first);
+ fs_give((void **)patline);
+ }
+}
+
+
+void
+free_pat(PAT_S **pat)
+{
+ if(pat && *pat){
+ free_pat(&(*pat)->next);
+ free_patgrp(&(*pat)->patgrp);
+ free_action(&(*pat)->action);
+ if((*pat)->raw)
+ fs_give((void **)&(*pat)->raw);
+
+ fs_give((void **)pat);
+ }
+}
+
+
+void
+free_patgrp(PATGRP_S **patgrp)
+{
+ if(patgrp && *patgrp){
+ if((*patgrp)->nick)
+ fs_give((void **) &(*patgrp)->nick);
+
+ if((*patgrp)->comment)
+ fs_give((void **) &(*patgrp)->comment);
+
+ if((*patgrp)->category_cmd)
+ free_list_array(&(*patgrp)->category_cmd);
+
+ if((*patgrp)->charsets_list)
+ free_strlist(&(*patgrp)->charsets_list);
+
+ free_pattern(&(*patgrp)->to);
+ free_pattern(&(*patgrp)->cc);
+ free_pattern(&(*patgrp)->recip);
+ free_pattern(&(*patgrp)->partic);
+ free_pattern(&(*patgrp)->from);
+ free_pattern(&(*patgrp)->sender);
+ free_pattern(&(*patgrp)->news);
+ free_pattern(&(*patgrp)->subj);
+ free_pattern(&(*patgrp)->alltext);
+ free_pattern(&(*patgrp)->bodytext);
+ free_pattern(&(*patgrp)->keyword);
+ free_pattern(&(*patgrp)->charsets);
+ free_pattern(&(*patgrp)->folder);
+ free_arbhdr(&(*patgrp)->arbhdr);
+ free_intvl(&(*patgrp)->score);
+ free_intvl(&(*patgrp)->age);
+ fs_give((void **) patgrp);
+ }
+}
+
+
+void
+free_pattern(PATTERN_S **pattern)
+{
+ if(pattern && *pattern){
+ free_pattern(&(*pattern)->next);
+ if((*pattern)->substring)
+ fs_give((void **)&(*pattern)->substring);
+ fs_give((void **)pattern);
+ }
+}
+
+
+void
+free_arbhdr(ARBHDR_S **arbhdr)
+{
+ if(arbhdr && *arbhdr){
+ free_arbhdr(&(*arbhdr)->next);
+ if((*arbhdr)->field)
+ fs_give((void **)&(*arbhdr)->field);
+ free_pattern(&(*arbhdr)->p);
+ fs_give((void **)arbhdr);
+ }
+}
+
+
+void
+free_intvl(INTVL_S **intvl)
+{
+ if(intvl && *intvl){
+ free_intvl(&(*intvl)->next);
+ fs_give((void **) intvl);
+ }
+}
+
+
+void
+free_action(ACTION_S **action)
+{
+ if(action && *action){
+ if((*action)->from)
+ mail_free_address(&(*action)->from);
+ if((*action)->replyto)
+ mail_free_address(&(*action)->replyto);
+ if((*action)->fcc)
+ fs_give((void **)&(*action)->fcc);
+ if((*action)->litsig)
+ fs_give((void **)&(*action)->litsig);
+ if((*action)->sig)
+ fs_give((void **)&(*action)->sig);
+ if((*action)->template)
+ fs_give((void **)&(*action)->template);
+ if((*action)->scorevalhdrtok)
+ free_hdrtok(&(*action)->scorevalhdrtok);
+ if((*action)->cstm)
+ free_list_array(&(*action)->cstm);
+ if((*action)->smtp)
+ free_list_array(&(*action)->smtp);
+ if((*action)->nntp)
+ free_list_array(&(*action)->nntp);
+ if((*action)->nick)
+ fs_give((void **)&(*action)->nick);
+ if((*action)->inherit_nick)
+ fs_give((void **)&(*action)->inherit_nick);
+ if((*action)->incol)
+ free_color_pair(&(*action)->incol);
+ if((*action)->folder)
+ free_pattern(&(*action)->folder);
+ if((*action)->index_format)
+ fs_give((void **)&(*action)->index_format);
+ if((*action)->keyword_set)
+ free_pattern(&(*action)->keyword_set);
+ if((*action)->keyword_clr)
+ free_pattern(&(*action)->keyword_clr);
+
+ fs_give((void **)action);
+ }
+}
+
+
+/*
+ * Returns an allocated copy of the pat.
+ *
+ * Args pat -- the source pat
+ *
+ * Returns a copy of pat.
+ */
+PAT_S *
+copy_pat(PAT_S *pat)
+{
+ PAT_S *new_pat = NULL;
+
+ if(pat){
+ new_pat = (PAT_S *)fs_get(sizeof(*new_pat));
+ memset((void *)new_pat, 0, sizeof(*new_pat));
+
+ new_pat->patgrp = copy_patgrp(pat->patgrp);
+ new_pat->action = copy_action(pat->action);
+ }
+
+ return(new_pat);
+}
+
+
+/*
+ * Returns an allocated copy of the patgrp.
+ *
+ * Args patgrp -- the source patgrp
+ *
+ * Returns a copy of patgrp.
+ */
+PATGRP_S *
+copy_patgrp(PATGRP_S *patgrp)
+{
+ char *p;
+ PATGRP_S *new_patgrp = NULL;
+
+ if(patgrp){
+ new_patgrp = (PATGRP_S *)fs_get(sizeof(*new_patgrp));
+ memset((void *)new_patgrp, 0, sizeof(*new_patgrp));
+
+ if(patgrp->nick)
+ new_patgrp->nick = cpystr(patgrp->nick);
+
+ if(patgrp->comment)
+ new_patgrp->comment = cpystr(patgrp->comment);
+
+ if(patgrp->to){
+ p = pattern_to_string(patgrp->to);
+ new_patgrp->to = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->to->not = patgrp->to->not;
+ }
+
+ if(patgrp->from){
+ p = pattern_to_string(patgrp->from);
+ new_patgrp->from = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->from->not = patgrp->from->not;
+ }
+
+ if(patgrp->sender){
+ p = pattern_to_string(patgrp->sender);
+ new_patgrp->sender = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->sender->not = patgrp->sender->not;
+ }
+
+ if(patgrp->cc){
+ p = pattern_to_string(patgrp->cc);
+ new_patgrp->cc = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->cc->not = patgrp->cc->not;
+ }
+
+ if(patgrp->recip){
+ p = pattern_to_string(patgrp->recip);
+ new_patgrp->recip = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->recip->not = patgrp->recip->not;
+ }
+
+ if(patgrp->partic){
+ p = pattern_to_string(patgrp->partic);
+ new_patgrp->partic = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->partic->not = patgrp->partic->not;
+ }
+
+ if(patgrp->news){
+ p = pattern_to_string(patgrp->news);
+ new_patgrp->news = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->news->not = patgrp->news->not;
+ }
+
+ if(patgrp->subj){
+ p = pattern_to_string(patgrp->subj);
+ new_patgrp->subj = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->subj->not = patgrp->subj->not;
+ }
+
+ if(patgrp->alltext){
+ p = pattern_to_string(patgrp->alltext);
+ new_patgrp->alltext = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->alltext->not = patgrp->alltext->not;
+ }
+
+ if(patgrp->bodytext){
+ p = pattern_to_string(patgrp->bodytext);
+ new_patgrp->bodytext = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->bodytext->not = patgrp->bodytext->not;
+ }
+
+ if(patgrp->keyword){
+ p = pattern_to_string(patgrp->keyword);
+ new_patgrp->keyword = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->keyword->not = patgrp->keyword->not;
+ }
+
+ if(patgrp->charsets){
+ p = pattern_to_string(patgrp->charsets);
+ new_patgrp->charsets = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_patgrp->charsets->not = patgrp->charsets->not;
+ }
+
+ if(patgrp->charsets_list)
+ new_patgrp->charsets_list = copy_strlist(patgrp->charsets_list);
+
+ if(patgrp->arbhdr){
+ ARBHDR_S *aa, *a, *new_a;
+
+ aa = NULL;
+ for(a = patgrp->arbhdr; a; a = a->next){
+ new_a = (ARBHDR_S *)fs_get(sizeof(*new_a));
+ memset((void *)new_a, 0, sizeof(*new_a));
+
+ if(a->field)
+ new_a->field = cpystr(a->field);
+
+ if(a->p){
+ p = pattern_to_string(a->p);
+ new_a->p = string_to_pattern(p);
+ fs_give((void **)&p);
+ new_a->p->not = a->p->not;
+ }
+
+ new_a->isemptyval = a->isemptyval;
+
+ if(aa){
+ aa->next = new_a;
+ aa = aa->next;
+ }
+ else{
+ new_patgrp->arbhdr = new_a;
+ aa = new_patgrp->arbhdr;
+ }
+ }
+ }
+
+ new_patgrp->fldr_type = patgrp->fldr_type;
+
+ if(patgrp->folder){
+ p = pattern_to_string(patgrp->folder);
+ new_patgrp->folder = string_to_pattern(p);
+ fs_give((void **)&p);
+ }
+
+ new_patgrp->inabook = patgrp->inabook;
+
+ if(patgrp->abooks){
+ p = pattern_to_string(patgrp->abooks);
+ new_patgrp->abooks = string_to_pattern(p);
+ fs_give((void **)&p);
+ }
+
+ new_patgrp->do_score = patgrp->do_score;
+ if(patgrp->score){
+ INTVL_S *intvl, *iv, *new_iv;
+
+ intvl = NULL;
+ for(iv = patgrp->score; iv; iv = iv->next){
+ new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
+ memset((void *) new_iv, 0, sizeof(*new_iv));
+
+ new_iv->imin = iv->imin;
+ new_iv->imax = iv->imax;
+
+ if(intvl){
+ intvl->next = new_iv;
+ intvl = intvl->next;
+ }
+ else{
+ new_patgrp->score = new_iv;
+ intvl = new_patgrp->score;
+ }
+ }
+ }
+
+ new_patgrp->do_age = patgrp->do_age;
+ if(patgrp->age){
+ INTVL_S *intvl, *iv, *new_iv;
+
+ intvl = NULL;
+ for(iv = patgrp->age; iv; iv = iv->next){
+ new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
+ memset((void *) new_iv, 0, sizeof(*new_iv));
+
+ new_iv->imin = iv->imin;
+ new_iv->imax = iv->imax;
+
+ if(intvl){
+ intvl->next = new_iv;
+ intvl = intvl->next;
+ }
+ else{
+ new_patgrp->age = new_iv;
+ intvl = new_patgrp->age;
+ }
+ }
+ }
+
+ new_patgrp->age_uses_sentdate = patgrp->age_uses_sentdate;
+
+ new_patgrp->do_size = patgrp->do_size;
+ if(patgrp->size){
+ INTVL_S *intvl, *iv, *new_iv;
+
+ intvl = NULL;
+ for(iv = patgrp->size; iv; iv = iv->next){
+ new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
+ memset((void *) new_iv, 0, sizeof(*new_iv));
+
+ new_iv->imin = iv->imin;
+ new_iv->imax = iv->imax;
+
+ if(intvl){
+ intvl->next = new_iv;
+ intvl = intvl->next;
+ }
+ else{
+ new_patgrp->size = new_iv;
+ intvl = new_patgrp->size;
+ }
+ }
+ }
+
+ new_patgrp->stat_new = patgrp->stat_new;
+ new_patgrp->stat_rec = patgrp->stat_rec;
+ new_patgrp->stat_del = patgrp->stat_del;
+ new_patgrp->stat_imp = patgrp->stat_imp;
+ new_patgrp->stat_ans = patgrp->stat_ans;
+
+ new_patgrp->stat_8bitsubj = patgrp->stat_8bitsubj;
+ new_patgrp->stat_bom = patgrp->stat_bom;
+ new_patgrp->stat_boy = patgrp->stat_boy;
+
+ new_patgrp->do_cat = patgrp->do_cat;
+ if(patgrp->cat){
+ INTVL_S *intvl, *iv, *new_iv;
+
+ intvl = NULL;
+ for(iv = patgrp->cat; iv; iv = iv->next){
+ new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
+ memset((void *) new_iv, 0, sizeof(*new_iv));
+
+ new_iv->imin = iv->imin;
+ new_iv->imax = iv->imax;
+
+ if(intvl){
+ intvl->next = new_iv;
+ intvl = intvl->next;
+ }
+ else{
+ new_patgrp->cat = new_iv;
+ intvl = new_patgrp->cat;
+ }
+ }
+ }
+
+ if(patgrp->category_cmd)
+ new_patgrp->category_cmd = copy_list_array(patgrp->category_cmd);
+ }
+
+ return(new_patgrp);
+}
+
+
+/*
+ * Returns an allocated copy of the action.
+ *
+ * Args action -- the source action
+ *
+ * Returns a copy of action.
+ */
+ACTION_S *
+copy_action(ACTION_S *action)
+{
+ ACTION_S *newaction = NULL;
+ char *p;
+
+ if(action){
+ newaction = (ACTION_S *)fs_get(sizeof(*newaction));
+ memset((void *)newaction, 0, sizeof(*newaction));
+
+ newaction->is_a_role = action->is_a_role;
+ newaction->is_a_incol = action->is_a_incol;
+ newaction->is_a_score = action->is_a_score;
+ newaction->is_a_filter = action->is_a_filter;
+ newaction->is_a_other = action->is_a_other;
+ newaction->is_a_srch = action->is_a_srch;
+ newaction->repl_type = action->repl_type;
+ newaction->forw_type = action->forw_type;
+ newaction->comp_type = action->comp_type;
+ newaction->scoreval = action->scoreval;
+ newaction->kill = action->kill;
+ newaction->state_setting_bits = action->state_setting_bits;
+ newaction->move_only_if_not_deleted = action->move_only_if_not_deleted;
+ newaction->non_terminating = action->non_terminating;
+ newaction->sort_is_set = action->sort_is_set;
+ newaction->sortorder = action->sortorder;
+ newaction->revsort = action->revsort;
+ newaction->startup_rule = action->startup_rule;
+
+ if(action->from)
+ newaction->from = copyaddrlist(action->from);
+ if(action->replyto)
+ newaction->replyto = copyaddrlist(action->replyto);
+ if(action->cstm)
+ newaction->cstm = copy_list_array(action->cstm);
+ if(action->smtp)
+ newaction->smtp = copy_list_array(action->smtp);
+ if(action->nntp)
+ newaction->nntp = copy_list_array(action->nntp);
+ if(action->fcc)
+ newaction->fcc = cpystr(action->fcc);
+ if(action->litsig)
+ newaction->litsig = cpystr(action->litsig);
+ if(action->sig)
+ newaction->sig = cpystr(action->sig);
+ if(action->template)
+ newaction->template = cpystr(action->template);
+ if(action->nick)
+ newaction->nick = cpystr(action->nick);
+ if(action->inherit_nick)
+ newaction->inherit_nick = cpystr(action->inherit_nick);
+ if(action->incol)
+ newaction->incol = new_color_pair(action->incol->fg,
+ action->incol->bg);
+ if(action->scorevalhdrtok){
+ newaction->scorevalhdrtok = new_hdrtok(action->scorevalhdrtok->hdrname);
+ if(action->scorevalhdrtok && action->scorevalhdrtok->fieldseps){
+ if(newaction->scorevalhdrtok->fieldseps)
+ fs_give((void **) &newaction->scorevalhdrtok->fieldseps);
+
+ newaction->scorevalhdrtok->fieldseps = cpystr(action->scorevalhdrtok->fieldseps);
+ }
+ }
+
+ if(action->folder){
+ p = pattern_to_string(action->folder);
+ newaction->folder = string_to_pattern(p);
+ fs_give((void **) &p);
+ }
+
+ if(action->keyword_set){
+ p = pattern_to_string(action->keyword_set);
+ newaction->keyword_set = string_to_pattern(p);
+ fs_give((void **) &p);
+ }
+
+ if(action->keyword_clr){
+ p = pattern_to_string(action->keyword_clr);
+ newaction->keyword_clr = string_to_pattern(p);
+ fs_give((void **) &p);
+ }
+
+ if(action->index_format)
+ newaction->index_format = cpystr(action->index_format);
+ }
+
+ return(newaction);
+}
+
+
+/*
+ * Given a role, return an allocated role. If this role inherits from
+ * another role, then do the correct inheriting so that the result is
+ * the role we want to use. The inheriting that is done is just the set
+ * of set- actions. This is for role stuff, no inheriting happens for scores
+ * or for colors.
+ *
+ * Args role -- The source role
+ *
+ * Returns a role.
+ */
+ACTION_S *
+combine_inherited_role(ACTION_S *role)
+{
+ PAT_STATE pstate;
+ PAT_S *pat;
+
+ /*
+ * Protect against loops in the role inheritance.
+ */
+ if(role && role->is_a_role && nonempty_patterns(ROLE_DO_ROLES, &pstate))
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
+ if(pat->action){
+ if(pat->action == role)
+ pat->action->been_here_before = 1;
+ else
+ pat->action->been_here_before = 0;
+ }
+
+ return(combine_inherited_role_guts(role));
+}
+
+
+ACTION_S *
+combine_inherited_role_guts(ACTION_S *role)
+{
+ ACTION_S *newrole = NULL, *inherit_role = NULL;
+ PAT_STATE pstate;
+
+ if(role && role->is_a_role){
+ newrole = (ACTION_S *)fs_get(sizeof(*newrole));
+ memset((void *)newrole, 0, sizeof(*newrole));
+
+ newrole->repl_type = role->repl_type;
+ newrole->forw_type = role->forw_type;
+ newrole->comp_type = role->comp_type;
+ newrole->is_a_role = role->is_a_role;
+
+ if(role->inherit_nick && role->inherit_nick[0] &&
+ nonempty_patterns(ROLE_DO_ROLES, &pstate)){
+ PAT_S *pat;
+
+ /* find the inherit_nick pattern */
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patgrp &&
+ pat->patgrp->nick &&
+ !strucmp(role->inherit_nick, pat->patgrp->nick)){
+ /* found it, if it has a role, use it */
+ if(!pat->action->been_here_before){
+ pat->action->been_here_before = 1;
+ inherit_role = pat->action;
+ }
+
+ break;
+ }
+ }
+
+ /*
+ * inherit_role might inherit further from other roles.
+ * In any case, we copy it so that we'll consistently have
+ * an allocated copy.
+ */
+ if(inherit_role){
+ if(inherit_role->inherit_nick && inherit_role->inherit_nick[0])
+ inherit_role = combine_inherited_role_guts(inherit_role);
+ else
+ inherit_role = copy_action(inherit_role);
+ }
+ }
+
+ if(role->from)
+ newrole->from = copyaddrlist(role->from);
+ else if(inherit_role && inherit_role->from)
+ newrole->from = copyaddrlist(inherit_role->from);
+
+ if(role->replyto)
+ newrole->replyto = copyaddrlist(role->replyto);
+ else if(inherit_role && inherit_role->replyto)
+ newrole->replyto = copyaddrlist(inherit_role->replyto);
+
+ if(role->fcc)
+ newrole->fcc = cpystr(role->fcc);
+ else if(inherit_role && inherit_role->fcc)
+ newrole->fcc = cpystr(inherit_role->fcc);
+
+ if(role->litsig)
+ newrole->litsig = cpystr(role->litsig);
+ else if(inherit_role && inherit_role->litsig)
+ newrole->litsig = cpystr(inherit_role->litsig);
+
+ if(role->sig)
+ newrole->sig = cpystr(role->sig);
+ else if(inherit_role && inherit_role->sig)
+ newrole->sig = cpystr(inherit_role->sig);
+
+ if(role->template)
+ newrole->template = cpystr(role->template);
+ else if(inherit_role && inherit_role->template)
+ newrole->template = cpystr(inherit_role->template);
+
+ if(role->cstm)
+ newrole->cstm = copy_list_array(role->cstm);
+ else if(inherit_role && inherit_role->cstm)
+ newrole->cstm = copy_list_array(inherit_role->cstm);
+
+ if(role->smtp)
+ newrole->smtp = copy_list_array(role->smtp);
+ else if(inherit_role && inherit_role->smtp)
+ newrole->smtp = copy_list_array(inherit_role->smtp);
+
+ if(role->nntp)
+ newrole->nntp = copy_list_array(role->nntp);
+ else if(inherit_role && inherit_role->nntp)
+ newrole->nntp = copy_list_array(inherit_role->nntp);
+
+ if(role->nick)
+ newrole->nick = cpystr(role->nick);
+
+ if(inherit_role)
+ free_action(&inherit_role);
+ }
+
+ return(newrole);
+}
+
+
+void
+mail_expunge_prefilter(MAILSTREAM *stream, int flags)
+{
+ int sfdo_state = 0, /* Some Filter Depends On or Sets State */
+ sfdo_scores = 0, /* Some Filter Depends On Scores */
+ ssdo_state = 0; /* Some Score Depends On State */
+
+ if(!stream || !sp_flagged(stream, SP_LOCKED))
+ return;
+
+ /*
+ * An Expunge causes a re-examination of the filters to
+ * see if any state changes have caused new matches.
+ */
+
+ sfdo_scores = (scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS);
+ if(sfdo_scores)
+ ssdo_state = (scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP);
+
+ if(!(sfdo_scores && ssdo_state))
+ sfdo_state = some_filter_depends_on_active_state();
+
+
+ if(sfdo_state || (sfdo_scores && ssdo_state)){
+ if(sfdo_scores && ssdo_state)
+ clear_folder_scores(stream);
+
+ reprocess_filter_patterns(stream, sp_msgmap(stream),
+ (flags & MI_CLOSING) |
+ MI_REFILTERING | MI_STATECHGONLY);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Dispatch messages matching FILTER patterns.
+
+ Args:
+ stream -- mail stream serving messages
+ msgmap -- sequence to msgno mapping table
+ recent -- number of recent messages to check (but really only its
+ nonzeroness is used)
+
+ When we're done, any filtered messages are filtered and the message
+ mapping table has any filtered messages removed.
+ ---*/
+void
+process_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, long int recent)
+{
+ long i, n, raw;
+ imapuid_t uid;
+ int we_cancel = 0, any_msgs = 0, any_to_filter = 0;
+ int exbits, nt = 0, pending_actions = 0, for_debugging = 0;
+ int cleared_index_cache = 0;
+ long rflags = ROLE_DO_FILTER;
+ char *nick = NULL;
+ char busymsg[80];
+ MSGNO_S *tmpmap = NULL;
+ MESSAGECACHE *mc;
+ PAT_S *pat, *nextpat = NULL;
+ SEARCHPGM *pgm = NULL;
+ SEARCHSET *srchset = NULL;
+ long flags = (SE_NOPREFETCH|SE_FREE);
+ PAT_STATE pstate;
+
+ dprint((5, "process_filter_patterns(stream=%s, recent=%ld)\n",
+ !stream ? "<null>" :
+ sp_flagged(stream, SP_INBOX) ? "inbox" :
+ stream->original_mailbox ? stream->original_mailbox :
+ stream->mailbox ? stream->mailbox :
+ "?",
+ recent));
+
+ if(!msgmap || !stream)
+ return;
+
+ if(!recent)
+ sp_set_flags(stream, sp_flags(stream) | SP_FILTERED);
+
+ while(stream && stream->nmsgs && nonempty_patterns(rflags, &pstate)){
+
+ for_debugging++;
+ pending_actions = 0;
+ nextpat = NULL;
+
+ uid = mail_uid(stream, stream->nmsgs);
+
+ /*
+ * Some of the search stuff won't work on old servers so we
+ * get the data and search locally. Big performance hit.
+ */
+ if(is_imap_stream(stream) && !modern_imap_stream(stream))
+ flags |= SE_NOSERVER;
+
+ /*
+ * ignore all previously filtered messages
+ * and, if requested, anything not a recent
+ * arrival...
+ *
+ * Here we're using spare6 (MN_STMP), meaning we'll only
+ * search the ones with spare6 marked, new messages coming
+ * in will not be considered. There used to be orig_nmsgs,
+ * which kept track of this, but if a message gets expunged,
+ * then a new message could be lower than orig_nmsgs.
+ */
+ for(i = 1; i <= stream->nmsgs; i++)
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_FILTERED){
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->spare6 = 0;
+ }
+ else if(!recent || !(exbits & MSG_EX_TESTED)){
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->spare6 = 1;
+
+ any_to_filter++;
+ }
+ else if((mc = mail_elt(stream, i)) != NULL)
+ mc->spare6 = 0;
+ }
+ else{
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->spare6 = !recent;
+
+ any_to_filter += !recent;
+ }
+
+ if(!any_to_filter){
+ dprint((5, "No messages need filtering\n"));
+ }
+
+ /* Then start searching */
+ for(pat = first_pattern(&pstate); any_to_filter && pat; pat = nextpat){
+ nextpat = next_pattern(&pstate);
+ dprint((5,
+ "Trying filter \"%s\"\n",
+ (pat->patgrp && pat->patgrp->nick)
+ ? pat->patgrp->nick : "?"));
+ if(pat->patgrp && !pat->patgrp->bogus
+ && pat->action && !pat->action->bogus
+ && !trivial_patgrp(pat->patgrp)
+ && match_pattern_folder(pat->patgrp, stream)
+ && !match_pattern_folder_specific(pat->action->folder,
+ stream, FOR_FILTER)){
+
+ /*
+ * We could just keep track of spare6 accurately when
+ * we change the msgno_exceptions flags, but...
+ */
+ for(i = 1; i <= stream->nmsgs; i++){
+ if((mc=mail_elt(stream, i)) && mc->spare6){
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_FILTERED)
+ mc->sequence = 0;
+ else if(!recent || !(exbits & MSG_EX_TESTED))
+ mc->sequence = 1;
+ else
+ mc->sequence = 0;
+ }
+ else
+ mc->sequence = !recent;
+ }
+ else
+ mc->sequence = 0;
+ }
+
+ if(!(srchset = build_searchset(stream))){
+ dprint((5, "Empty searchset\n"));
+ continue; /* nothing to search, move on */
+ }
+
+#ifdef DEBUG
+ {SEARCHSET *s;
+ dprint((5, "searchset="));
+ for(s = srchset; s; s = s->next){
+ if(s->first == s->last || s->last == 0L){
+ dprint((5, " %ld", s->first));
+ }
+ else{
+ dprint((5, " %ld-%ld", s->first, s->last));
+ }
+ }
+ dprint((5, "\n"));
+ }
+#endif
+ nick = (pat && pat->patgrp && pat->patgrp->nick
+ && pat->patgrp->nick[0]) ? pat->patgrp->nick : NULL;
+ snprintf(busymsg, sizeof(busymsg), _("Processing filter \"%s\""),
+ nick ? nick : "?");
+
+ /*
+ * The strange last argument is so that the busy message
+ * won't come out until after a second if the user sets
+ * the feature to quell "filtering done". That's because
+ * they are presumably interested in the filtering actions
+ * themselves more than what is happening, so they'd
+ * rather see the action messages instead of the processing
+ * message. That's my theory anyway.
+ */
+ if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
+ any_msgs = we_cancel = busy_cue(busymsg, NULL,
+ F_ON(F_QUELL_FILTER_DONE_MSG, ps_global)
+ ? 1 : 0);
+
+ if(pat->patgrp->stat_bom != PAT_STAT_EITHER){
+ if(pat->patgrp->stat_bom == PAT_STAT_YES){
+ if(!ps_global->beginning_of_month){
+ dprint((5,
+ "Filter %s wants beginning of month and it isn't bom\n",
+ nick ? nick : "?"));
+ continue;
+ }
+ }
+ else if(pat->patgrp->stat_bom == PAT_STAT_NO){
+ if(ps_global->beginning_of_month){
+ dprint((5,
+ "Filter %s does not want beginning of month and it is bom\n",
+ nick ? nick : "?"));
+ continue;
+ }
+ }
+ }
+
+ if(pat->patgrp->stat_boy != PAT_STAT_EITHER){
+ if(pat->patgrp->stat_boy == PAT_STAT_YES){
+ if(!ps_global->beginning_of_year){
+ dprint((5,
+ "Filter %s wants beginning of year and it isn't boy\n",
+ nick ? nick : "?"));
+ continue;
+ }
+ }
+ else if(pat->patgrp->stat_boy == PAT_STAT_NO){
+ if(ps_global->beginning_of_year){
+ dprint((5,
+ "Filter %s does not want beginning of year and it is boy\n",
+ nick ? nick : "?"));
+ continue;
+ }
+ }
+ }
+
+ pgm = match_pattern_srchpgm(pat->patgrp, stream, srchset);
+
+ pine_mail_search_full(stream, "UTF-8", pgm, flags);
+
+ /* check scores */
+ if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS &&
+ pat->patgrp->do_score){
+ SEARCHSET *s, *ss;
+
+ /*
+ * Build a searchset so we can get all the scores we
+ * need and only the scores we need efficiently.
+ */
+
+ for(i = 1; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ for(s = srchset; s; s = s->next)
+ for(i = s->first; i <= s->last; i++)
+ if(i > 0L && stream && i <= stream->nmsgs
+ && (mc=mail_elt(stream, i)) && mc->searched &&
+ get_msg_score(stream, i) == SCORE_UNDEF)
+ mc->sequence = 1;
+
+ if((ss = build_searchset(stream)) != NULL){
+ (void)calculate_some_scores(stream, ss, 0);
+ mail_free_searchset(&ss);
+ }
+
+ /*
+ * Now check the patterns which have matched so far
+ * to see if their score is in the score interval.
+ */
+ for(s = srchset; s; s = s->next)
+ for(i = s->first; i <= s->last; i++)
+ if(i > 0L && stream && i <= stream->nmsgs
+ && (mc=mail_elt(stream, i)) && mc->searched){
+ long score;
+
+ score = get_msg_score(stream, i);
+
+ /*
+ * If the score is outside all of the intervals,
+ * turn off the searched bit.
+ * So that means we check each interval and if
+ * it is inside any interval we stop and leave
+ * the bit set. If it is outside we keep checking.
+ */
+ if(score != SCORE_UNDEF){
+ INTVL_S *iv;
+
+ for(iv = pat->patgrp->score; iv; iv = iv->next)
+ if(score >= iv->imin && score <= iv->imax)
+ break;
+
+ if(!iv)
+ mc->searched = NIL;
+ }
+ }
+ }
+
+ /* check for 8bit subject match or not */
+ if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER)
+ find_8bitsubj_in_messages(stream, srchset,
+ pat->patgrp->stat_8bitsubj, 0);
+
+ /* if there are still matches, check for charset matches */
+ if(pat->patgrp->charsets)
+ find_charsets_in_messages(stream, srchset, pat->patgrp, 0);
+
+ if(pat->patgrp->inabook != IAB_EITHER)
+ address_in_abook(stream, srchset, pat->patgrp->inabook, pat->patgrp->abooks);
+
+ /* Still matches? Run the categorization command on each msg. */
+ if(pith_opt_filter_pattern_cmd)
+ (*pith_opt_filter_pattern_cmd)(pat->patgrp->category_cmd, srchset, stream, pat->patgrp->cat_lim, pat->patgrp->cat);
+
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ nt = pat->action->non_terminating;
+ pending_actions = MAX(nt, pending_actions);
+
+ /*
+ * Change some state bits.
+ * This used to only happen if kill was not set, but
+ * it can be useful to Delete a message even if killing.
+ * That way, it will show up in another pine that isn't
+ * running the same filter as Deleted, so the user won't
+ * bother looking at it. Hubert 2004-11-16
+ */
+ if(pat->action->state_setting_bits
+ || pat->action->keyword_set
+ || pat->action->keyword_clr){
+ tmpmap = NULL;
+ mn_init(&tmpmap, stream->nmsgs);
+
+ for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) && mc->searched
+ && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
+ && (exbits & MSG_EX_FILTERED))){
+ if(!n++){
+ mn_set_cur(tmpmap, i);
+ }
+ else{
+ mn_add_cur(tmpmap, i);
+ }
+ }
+
+ if(n){
+ long flagbits;
+ char **keywords_to_set = NULL,
+ **keywords_to_clr = NULL;
+ PATTERN_S *pp;
+ int cnt;
+
+ flagbits = pat->action->state_setting_bits;
+
+ if(pat->action->keyword_set){
+ for(cnt = 0, pp = pat->action->keyword_set;
+ pp; pp = pp->next)
+ cnt++;
+
+ keywords_to_set = (char **) fs_get((cnt+1) *
+ sizeof(*keywords_to_set));
+ memset(keywords_to_set, 0,
+ (cnt+1) * sizeof(*keywords_to_set));
+ for(cnt = 0, pp = pat->action->keyword_set;
+ pp; pp = pp->next){
+ char *q;
+
+ q = nick_to_keyword(pp->substring);
+ if(q && q[0])
+ keywords_to_set[cnt++] = cpystr(q);
+ }
+
+ flagbits |= F_KEYWORD;
+ }
+
+ if(pat->action->keyword_clr){
+ for(cnt = 0, pp = pat->action->keyword_clr;
+ pp; pp = pp->next)
+ cnt++;
+
+ keywords_to_clr = (char **) fs_get((cnt+1) *
+ sizeof(*keywords_to_clr));
+ memset(keywords_to_clr, 0,
+ (cnt+1) * sizeof(*keywords_to_clr));
+ for(cnt = 0, pp = pat->action->keyword_clr;
+ pp; pp = pp->next){
+ char *q;
+
+ q = nick_to_keyword(pp->substring);
+ if(q && q[0])
+ keywords_to_clr[cnt++] = cpystr(q);
+ }
+
+ flagbits |= F_UNKEYWORD;
+ }
+
+ set_some_flags(stream, tmpmap, flagbits,
+ keywords_to_set, keywords_to_clr, 1,
+ nick);
+ }
+
+ mn_give(&tmpmap);
+ }
+
+ /*
+ * The two halves of the if-else are almost the same and
+ * could probably be combined cleverly. The if clause
+ * is simply setting the MSG_EX_FILTERED bit, and leaving
+ * n set to zero. The msgno_exclude is not done in this case.
+ * The else clause excludes each message (because it is
+ * either filtered into nothing or moved to folder). The
+ * exclude messes with the msgmap and that changes max_msgno,
+ * so the loop control is a little tricky.
+ */
+ if(!(pat->action->kill || pat->action->folder)){
+ n = 0L;
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ if((raw = mn_m2raw(msgmap, i)) > 0L
+ && stream && raw <= stream->nmsgs
+ && (mc = mail_elt(stream, raw)) && mc->searched){
+ dprint((5,
+ "FILTER matching \"%s\": msg %ld%s\n",
+ nick ? nick : "unnamed",
+ raw, nt ? " (dont stop)" : ""));
+ if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
+ exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
+ else
+ exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
+
+ /*
+ * If this matched an earlier non-terminating rule
+ * we've been keeping track of that so that we can
+ * turn it into a permanent match at the end.
+ * However, now we've matched another rule that is
+ * terminating so we don't have to worry about it
+ * anymore. Turn off the flag.
+ */
+ if(!nt && exbits & MSG_EX_FILTONCE)
+ exbits ^= MSG_EX_FILTONCE;
+
+ exbits &= ~MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, raw, "0", &exbits, TRUE);
+ }
+ }
+ else{
+ for(i = 1L, n = 0L; i <= mn_get_total(msgmap); )
+ if((raw = mn_m2raw(msgmap, i))
+ && raw > 0L && stream && raw <= stream->nmsgs
+ && (mc = mail_elt(stream, raw)) && mc->searched){
+ dprint((5,
+ "FILTER matching \"%s\": msg %ld %s%s\n",
+ nick ? nick : "unnamed",
+ raw, pat->action->folder ? "filed" : "killed",
+ nt ? " (dont stop)" : ""));
+ if(nt)
+ i++;
+ else{
+ if(!cleared_index_cache
+ && stream == ps_global->mail_stream){
+ cleared_index_cache = 1;
+ clear_index_cache(stream, 0);
+ }
+
+ msgno_exclude(stream, msgmap, i, 1);
+ /*
+ * If this message is new, decrement
+ * new_mail_count. Previously, the caller would
+ * do this by counting MN_EXCLUDE before and after,
+ * but the results weren't accurate in the case
+ * where new messages arrived while filtering,
+ * or the filtered message could have gotten
+ * expunged.
+ */
+ if(msgno_exceptions(stream, raw, "0", &exbits,
+ FALSE)
+ && (exbits & MSG_EX_RECENT)){
+ long l, ll;
+
+ l = sp_new_mail_count(stream);
+ ll = sp_recent_since_visited(stream);
+ dprint((5, "New message being filtered, decrement new_mail_count: %ld -> %ld\n", l, l-1L));
+ if(l > 0L)
+ sp_set_new_mail_count(stream, l-1L);
+ if(ll > 0L)
+ sp_set_recent_since_visited(stream, ll-1L);
+ }
+ }
+
+ if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
+ exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
+ else
+ exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
+
+ /* set pending exclusion for later */
+ if(nt)
+ exbits |= MSG_EX_PEND_EXLD;
+
+ /*
+ * If this matched an earlier non-terminating rule
+ * we've been keeping track of that so that we can
+ * turn it into a permanent match at the end.
+ * However, now we've matched another rule that is
+ * terminating so we don't have to worry about it
+ * anymore. Turn off the flags.
+ */
+ if(!nt && exbits & MSG_EX_FILTONCE){
+ exbits ^= MSG_EX_FILTONCE;
+
+ /* we've already excluded it, too */
+ if(exbits & MSG_EX_PEND_EXLD)
+ exbits ^= MSG_EX_PEND_EXLD;
+ }
+
+ exbits &= ~MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, raw, "0", &exbits, TRUE);
+ n++;
+ }
+ else
+ i++;
+ }
+
+ if(n && pat->action->folder){
+ PATTERN_S *p;
+ int err = 0;
+
+ tmpmap = NULL;
+ mn_init(&tmpmap, stream->nmsgs);
+
+ /*
+ * For everything matching msg that hasn't
+ * already been saved somewhere, do it...
+ */
+ for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) && mc->searched
+ && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
+ && (exbits & MSG_EX_FILED))){
+ if(!n++){
+ mn_set_cur(tmpmap, i);
+ }
+ else{
+ mn_add_cur(tmpmap, i);
+ }
+ }
+
+ /*
+ * Remove already deleted messages from the tmp
+ * message map.
+ * There is a bug with this. If a filter moves a
+ * message to another folder _and_ sets the deleted
+ * status, then the setting of the deleted status
+ * will already have happened above in set_some_flags.
+ * So if the move_only_if_not_deleted bit is set that
+ * message will never be moved. A workaround for the
+ * user is to not set the move-only-if-not-deleted
+ * option.
+ */
+ if(n && pat->action->move_only_if_not_deleted){
+ char *seq;
+ MSGNO_S *tmpmap2 = NULL;
+ long nn = 0L;
+ MESSAGECACHE *mc;
+
+ mn_init(&tmpmap2, stream->nmsgs);
+
+ /*
+ * First, make sure elts are valid for all the
+ * interesting messages.
+ */
+ if((seq = invalid_elt_sequence(stream, tmpmap)) != NULL){
+ pine_mail_fetch_flags(stream, seq, NIL);
+ fs_give((void **) &seq);
+ }
+
+ for(i = mn_first_cur(tmpmap); i > 0L;
+ i = mn_next_cur(tmpmap)){
+ mc = ((raw = mn_m2raw(tmpmap, i)) > 0L
+ && stream && raw <= stream->nmsgs)
+ ? mail_elt(stream, raw) : NULL;
+ if(mc && !mc->deleted){
+ if(!nn++){
+ mn_set_cur(tmpmap2, i);
+ }
+ else{
+ mn_add_cur(tmpmap2, i);
+ }
+ }
+ }
+
+ mn_give(&tmpmap);
+ tmpmap = tmpmap2;
+ n = nn;
+ }
+
+ if(n){
+ for(p = pat->action->folder; p; p = p->next){
+ int dval;
+ int flags_for_save;
+ char *processed_name;
+
+ /* does this filter set delete bit? ... */
+ convert_statebits_to_vals(pat->action->state_setting_bits, &dval, NULL, NULL, NULL);
+ /* ... if so, tell save not to fix it before copy */
+ flags_for_save = SV_FOR_FILT | SV_INBOXWOCNTXT |
+ (nt ? 0 : SV_DELETE) |
+ ((dval != ACT_STAT_SET) ? SV_FIX_DELS : 0);
+ processed_name = detoken_src(p->substring,
+ FOR_FILT, NULL,
+ NULL, NULL, NULL);
+ if(move_filtered_msgs(stream, tmpmap,
+ (processed_name && *processed_name)
+ ? processed_name : p->substring,
+ flags_for_save, nick)){
+ /*
+ * If we filtered into the current
+ * folder, chuck a ping down the
+ * stream so the user can notice it
+ * before the next new mail check...
+ */
+ if(ps_global->mail_stream
+ && ps_global->mail_stream != stream
+ && match_pattern_folder_specific(
+ pat->action->folder,
+ ps_global->mail_stream,
+ FOR_FILTER)){
+ (void) pine_mail_ping(ps_global->mail_stream);
+ }
+ }
+ else{
+ err = 1;
+ break;
+ }
+
+ if(processed_name)
+ fs_give((void **) &processed_name);
+ }
+
+ if(!err)
+ for(n = mn_first_cur(tmpmap);
+ n > 0L;
+ n = mn_next_cur(tmpmap)){
+
+ if(msgno_exceptions(stream, mn_m2raw(tmpmap, n),
+ "0", &exbits, FALSE))
+ exbits |= (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
+ else
+ exbits = (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
+
+ exbits &= ~MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, mn_m2raw(tmpmap, n),
+ "0", &exbits, TRUE);
+ }
+ }
+
+ mn_give(&tmpmap);
+ }
+
+ mail_free_searchset(&srchset);
+ }
+
+ /*
+ * If this is the last rule,
+ * we make sure we delete messages that we delayed deleting
+ * in the save. We delayed so that the deletion wouldn't have
+ * an effect on later rules. We convert any temporary
+ * FILED (FILEONCE) and FILTERED (FILTONCE) flags
+ * (which were set by an earlier non-terminating rule)
+ * to permanent. We also exclude some messages from the view.
+ */
+ if(pending_actions && !nextpat){
+
+ pending_actions = 0;
+ tmpmap = NULL;
+ mn_init(&tmpmap, stream->nmsgs);
+
+ for(i = 1L, n = 0L; i <= mn_get_total(msgmap); i++){
+
+ raw = mn_m2raw(msgmap, i);
+ if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_FILEONCE){
+ if(!n++){
+ mn_set_cur(tmpmap, raw);
+ }
+ else{
+ mn_add_cur(tmpmap, raw);
+ }
+ }
+ }
+ }
+
+ if(n)
+ set_some_flags(stream, tmpmap, F_DEL, NULL, NULL, 0, NULL);
+
+ mn_give(&tmpmap);
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++){
+ raw = mn_m2raw(msgmap, i);
+ if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_PEND_EXLD){
+ if(!cleared_index_cache
+ && stream == ps_global->mail_stream){
+ cleared_index_cache = 1;
+ clear_index_cache(stream, 0);
+ }
+
+ msgno_exclude(stream, msgmap, i, 1);
+ if(msgno_exceptions(stream, raw, "0",
+ &exbits, FALSE)
+ && (exbits & MSG_EX_RECENT)){
+ long l, ll;
+
+ /*
+ * If this message is new, decrement
+ * new_mail_count. See the above
+ * call to msgno_exclude.
+ */
+ l = sp_new_mail_count(stream);
+ ll = sp_recent_since_visited(stream);
+ dprint((5, "New message being filtered. Decrement new_mail_count: %ld -> %ld\n", l, l-1L));
+ if(l > 0L)
+ sp_set_new_mail_count(stream, l - 1L);
+ if(ll > 0L)
+ sp_set_recent_since_visited(stream, ll - 1L);
+ }
+
+ i--; /* to compensate for loop's i++ */
+ }
+
+ /* get rid of temporary flags */
+ if(exbits & (MSG_EX_FILTONCE | MSG_EX_FILEONCE |
+ MSG_EX_PEND_EXLD)){
+ if(exbits & MSG_EX_FILTONCE){
+ /* convert to permament */
+ exbits ^= MSG_EX_FILTONCE;
+ exbits |= MSG_EX_FILTERED;
+ }
+
+ /* convert to permament */
+ if(exbits & MSG_EX_FILEONCE){
+ exbits ^= MSG_EX_FILEONCE;
+ exbits |= MSG_EX_FILED;
+ }
+
+ if(exbits & MSG_EX_PEND_EXLD)
+ exbits ^= MSG_EX_PEND_EXLD;
+
+ exbits &= ~MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, raw, "0", &exbits,TRUE);
+ }
+ }
+ }
+ }
+ }
+
+ /* New mail arrival means start over */
+ if(mail_uid(stream, stream->nmsgs) == uid)
+ break;
+ /* else, go again */
+
+ recent = 1; /* only check recent ones now */
+ }
+
+ if(!recent){
+ /* clear status change flags */
+ for(i = 1; i <= stream->nmsgs; i++){
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_STATECHG){
+ exbits &= ~MSG_EX_STATECHG;
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+ }
+ }
+ }
+
+ /* clear any private "recent" flags and add TESTED flag */
+ for(i = 1; i <= stream->nmsgs; i++){
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_RECENT
+ || !(exbits & MSG_EX_TESTED)
+ || (!recent && exbits & MSG_EX_STATECHG)){
+ exbits &= ~MSG_EX_RECENT;
+ exbits |= MSG_EX_TESTED;
+ if(!recent)
+ exbits &= ~MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+ }
+ else{
+ exbits = MSG_EX_TESTED;
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+
+ /* clear any stmp flags just in case */
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->spare6 = 0;
+ }
+
+ msgmap->flagged_stmp = 0L;
+
+ if(any_msgs && F_OFF(F_QUELL_FILTER_MSGS, ps_global)
+ && F_OFF(F_QUELL_FILTER_DONE_MSG, ps_global)){
+ q_status_message(SM_ORDER, 0, 1, _("filtering done"));
+ display_message('x');
+ }
+}
+
+
+/*
+ * Re-check the filters for matches because a change of message state may
+ * have changed the results.
+ */
+void
+reprocess_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, int flags)
+{
+ if(stream){
+ long i;
+ int exbits;
+
+ if(msgno_include(stream, msgmap, flags)
+ && stream == ps_global->mail_stream
+ && !(flags & MI_CLOSING)){
+ clear_index_cache(stream, 0);
+ refresh_sort(stream, msgmap, SRT_NON);
+ ps_global->mangled_header = 1;
+ }
+
+ /*
+ * Passing 1 in the last argument causes it to only look at the
+ * messages we included above, which should be only the ones we
+ * need to look at.
+ */
+ process_filter_patterns(stream, msgmap,
+ (flags & MI_STATECHGONLY) ? 1L : 0);
+
+ /* clear status change flags */
+ for(i = 1; i <= stream->nmsgs; i++){
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
+ if(exbits & MSG_EX_STATECHG){
+ exbits &= ~MSG_EX_STATECHG;
+ msgno_exceptions(stream, i, "0", &exbits, TRUE);
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * When killing or filtering we don't want to match by mistake. So if
+ * a pattern has nothing set except the Current Folder Type (which is always
+ * set to something) we'll consider it to be trivial and not a match.
+ * match_pattern uses this to determine if there is a match, where it is
+ * just triggered on the Current Folder Type.
+ */
+int
+trivial_patgrp(PATGRP_S *patgrp)
+{
+ int ret = 1;
+
+ if(patgrp){
+ if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to ||
+ patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic ||
+ patgrp->alltext || patgrp->bodytext)
+ ret = 0;
+
+ if(ret && patgrp->do_age)
+ ret = 0;
+
+ if(ret && patgrp->do_size)
+ ret = 0;
+
+ if(ret && patgrp->do_score)
+ ret = 0;
+
+ if(ret && patgrp->category_cmd && patgrp->category_cmd[0])
+ ret = 0;
+
+ if(ret && patgrp_depends_on_state(patgrp))
+ ret = 0;
+
+ if(ret && patgrp->stat_8bitsubj != PAT_STAT_EITHER)
+ ret = 0;
+
+ if(ret && patgrp->charsets)
+ ret = 0;
+
+ if(ret && patgrp->stat_bom != PAT_STAT_EITHER)
+ ret = 0;
+
+ if(ret && patgrp->stat_boy != PAT_STAT_EITHER)
+ ret = 0;
+
+ if(ret && patgrp->inabook != IAB_EITHER)
+ ret = 0;
+
+ if(ret && patgrp->arbhdr){
+ ARBHDR_S *a;
+
+ for(a = patgrp->arbhdr; a && ret; a = a->next)
+ if(a->field && a->field[0] && a->p)
+ ret = 0;
+ }
+ }
+
+ return(ret);
+}
+
+
+int
+some_filter_depends_on_active_state(void)
+{
+ long rflags = ROLE_DO_FILTER;
+ PAT_S *pat;
+ PAT_STATE pstate;
+ int ret = 0;
+
+ if(nonempty_patterns(rflags, &pstate)){
+
+ for(pat = first_pattern(&pstate);
+ pat && !ret;
+ pat = next_pattern(&pstate))
+ if(patgrp_depends_on_active_state(pat->patgrp))
+ ret++;
+ }
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Move all messages with sequence bit lit to dstfldr
+
+ Args: stream -- stream to use
+ msgmap -- map of messages to be moved
+ dstfldr -- folder to receive moved messages
+ flags_for_save
+
+ Returns: nonzero on success or on readonly stream
+ ----*/
+int
+move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr,
+ int flags_for_save, char *nick)
+{
+ long n;
+ int we_cancel = 0, width;
+ CONTEXT_S *save_context = NULL;
+ char buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1];
+ char *save_ref = NULL;
+#define FILTMSG_MAX 30
+
+ if(!stream)
+ return 0;
+
+ if(READONLY_FOLDER(stream)){
+ dprint((1,
+ "Can't delete messages in readonly folder \"%s\"\n",
+ STREAMNAME(stream)));
+ q_status_message1(SM_ORDER, 1, 3,
+ _("Can't delete messages in readonly folder \"%s\""),
+ STREAMNAME(stream));
+ return 1;
+ }
+
+ buf[0] = '\0';
+
+ width = MAX(10, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80);
+ snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"\"",
+ nick ? nick : "", nick ? ": " : "",
+ comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)));
+ /* 2 is for brackets, 5 is for " DONE" in busy alarm */
+ width -= (strlen(buf) + 2 + 5);
+ snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"%s\"",
+ nick ? nick : "", nick ? ": " : "",
+ comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)),
+ short_str(dstfldr, sbuf, sizeof(sbuf), width, FrontDots));
+
+ dprint((5, "%s\n", buf));
+
+ if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
+ we_cancel = busy_cue(buf, NULL, 0);
+
+ if(!is_absolute_path(dstfldr)
+ && !(save_context = default_save_context(ps_global->context_list)))
+ save_context = ps_global->context_list;
+
+ /*
+ * Because this save is happening independent of where the user is
+ * in the folder hierarchy and has nothing to do with that, we want
+ * to ignore the reference field built into the context. Zero it out
+ * temporarily here so it won't affect the results of context_apply
+ * in save.
+ *
+ * This might be a problem elsewhere, as well. The same thing as this
+ * is also done in match_pattern_folder_specific, which is also only
+ * called from within process_filter_patterns. But there could be
+ * others. We could have a separate function, something like
+ * copy_default_save_context(), that automatically zeroes out the
+ * reference field in the copy. However, some of the uses of
+ * default_save_context() require that a pointer into the actual
+ * context list is returned, so this would have to be done carefully.
+ * Besides, we don't know of any other problems so we'll just change
+ * these known cases for now.
+ */
+ if(save_context && save_context->dir){
+ save_ref = save_context->dir->ref;
+ save_context->dir->ref = NULL;
+ }
+
+ n = save(ps_global, stream, save_context, dstfldr, msgmap, flags_for_save);
+
+ if(save_ref)
+ save_context->dir->ref = save_ref;
+
+ if(n != mn_total_cur(msgmap)){
+ int exbits;
+ long x;
+
+ buf[0] = '\0';
+
+ /* Clear "filtered" flags for failed messages */
+ for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap))
+ if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x),
+ "0", &exbits, FALSE)){
+ exbits &= ~(MSG_EX_FILTONCE | MSG_EX_FILEONCE |
+ MSG_EX_FILTERED | MSG_EX_FILED);
+ msgno_exceptions(stream, mn_m2raw(msgmap, x),
+ "0", &exbits, TRUE);
+ }
+
+ /* then re-incorporate them into folder they belong */
+ (void) msgno_include(stream, sp_msgmap(stream), MI_NONE);
+ clear_index_cache(stream, 0);
+ refresh_sort(stream, sp_msgmap(stream), SRT_NON);
+ ps_global->mangled_header = 1;
+ }
+ else{
+ snprintf(buf, sizeof(buf), _("Filtered all %s message to \"%s\""),
+ comatose(n), dstfldr);
+ dprint((5, "%s\n", buf));
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(buf[0] ? 0 : -1);
+
+ return(buf[0] != '\0');
+}
+
+
+/*----------------------------------------------------------------------
+ Move all messages with sequence bit lit to dstfldr
+
+ Args: stream -- stream to use
+ msgmap -- which messages to set
+ flagbits -- which flags to set or clear
+ kw_on -- keywords to set
+ kw_off -- keywords to clear
+ verbose -- 1 => busy alarm after 1 second
+ 2 => forced busy alarm
+ ----*/
+void
+set_some_flags(MAILSTREAM *stream, MSGNO_S *msgmap, long int flagbits,
+ char **kw_on, char **kw_off, int verbose, char *nick)
+{
+ long count = 0L, flipped_flags;
+ int we_cancel = 0;
+ char buf[150], *seq;
+
+ if(!stream)
+ return;
+
+ if(READONLY_FOLDER(stream)){
+ dprint((1, "Can't set flags in readonly folder \"%s\"\n",
+ STREAMNAME(stream)));
+ q_status_message1(SM_ORDER, 1, 3,
+ _("Can't set flags in readonly folder \"%s\""),
+ STREAMNAME(stream));
+ return;
+ }
+
+ /* use this to determine if anything needs to be done */
+ flipped_flags = ((flagbits & F_ANS) ? F_UNANS : 0) |
+ ((flagbits & F_UNANS) ? F_ANS : 0) |
+ ((flagbits & F_FLAG) ? F_UNFLAG : 0) |
+ ((flagbits & F_UNFLAG) ? F_FLAG : 0) |
+ ((flagbits & F_DEL) ? F_UNDEL : 0) |
+ ((flagbits & F_UNDEL) ? F_DEL : 0) |
+ ((flagbits & F_SEEN) ? F_UNSEEN : 0) |
+ ((flagbits & F_UNSEEN) ? F_SEEN : 0) |
+ ((flagbits & F_KEYWORD) ? F_UNKEYWORD : 0) |
+ ((flagbits & F_UNKEYWORD) ? F_KEYWORD : 0);
+ if((seq = currentf_sequence(stream, msgmap, flipped_flags, &count, 0,
+ kw_off, kw_on)) != NULL){
+ char *sets = NULL, *clears = NULL;
+ char *ps, *pc, **t;
+ size_t clen, slen;
+
+ /* allocate enough space for mail_flags arguments */
+ for(slen=100, t = kw_on; t && *t; t++)
+ slen += (strlen(*t) + 1);
+
+ sets = (char *) fs_get(slen * sizeof(*sets));
+
+ for(clen=100, t = kw_off; t && *t; t++)
+ clen += (strlen(*t) + 1);
+
+ clears = (char *) fs_get(clen * sizeof(*clears));
+
+ sets[0] = clears[0] = '\0';
+ ps = sets;
+ pc = clears;
+
+ snprintf(buf, sizeof(buf), "%.30s%.2sSetting flags in %.10s message%.10s",
+ nick ? nick : "", nick ? ": " : "",
+ comatose(count), plural(count));
+
+ if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
+ we_cancel = busy_cue(buf, NULL, verbose ? 0 : 1);
+
+ /*
+ * What's going on here? If we want to set more than one flag
+ * we can do it with a single roundtrip by combining the arguments
+ * into a single call and separating them with spaces.
+ */
+ if(flagbits & F_ANS)
+ sstrncpy(&ps, "\\ANSWERED", slen-(ps-sets));
+ if(flagbits & F_FLAG){
+ if(ps > sets)
+ sstrncpy(&ps, " ", slen-(ps-sets));
+
+ sstrncpy(&ps, "\\FLAGGED", slen-(ps-sets));
+ }
+ if(flagbits & F_DEL){
+ if(ps > sets)
+ sstrncpy(&ps, " ", slen-(ps-sets));
+
+ sstrncpy(&ps, "\\DELETED", slen-(ps-sets));
+ }
+ if(flagbits & F_SEEN){
+ if(ps > sets)
+ sstrncpy(&ps, " ", slen-(ps-sets));
+
+ sstrncpy(&ps, "\\SEEN", slen-(ps-sets));
+ }
+ if(flagbits & F_KEYWORD){
+ for(t = kw_on; t && *t; t++){
+ int i;
+
+ /*
+ * We may be able to tell that this will fail before
+ * we actually try it.
+ */
+ if(stream->kwd_create ||
+ (((i=user_flag_index(stream, *t)) >= 0) && i < NUSERFLAGS)){
+ if(ps > sets)
+ sstrncpy(&ps, " ", slen-(ps-sets));
+
+ sstrncpy(&ps, *t, slen-(ps-sets));
+ }
+ else{
+ int some_defined = 0;
+ static int msg_delivered = 0;
+
+ some_defined = some_user_flags_defined(stream);
+
+ if(msg_delivered++ < 2){
+ char b[200], c[200], *p;
+ int w;
+
+ if(some_defined){
+ snprintf(b, sizeof(b), "Can't set \"%.30s\". No more keywords in ", keyword_to_nick(*t));
+ w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
+ p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
+ q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
+ }
+ else{
+ snprintf(b, sizeof(b), "Can't set \"%.30s\". Can't add keywords in ", keyword_to_nick(*t));
+ w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
+ p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
+ q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
+ }
+ }
+
+ if(some_defined){
+ dprint((1, "Can't set keyword \"%s\". No more keywords allowed in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
+ }
+ else{
+ dprint((1, "Can't set keyword \"%s\". Can't add keywords in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
+ }
+ }
+ }
+ }
+
+ /* need a separate call for the clears */
+ if(flagbits & F_UNANS)
+ sstrncpy(&pc, "\\ANSWERED", clen-(pc-clears));
+ if(flagbits & F_UNFLAG){
+ if(pc > clears)
+ sstrncpy(&pc, " ", clen-(pc-clears));
+
+ sstrncpy(&pc, "\\FLAGGED", clen-(pc-clears));
+ }
+ if(flagbits & F_UNDEL){
+ if(pc > clears)
+ sstrncpy(&pc, " ", clen-(pc-clears));
+
+ sstrncpy(&pc, "\\DELETED", clen-(pc-clears));
+ }
+ if(flagbits & F_UNSEEN){
+ if(pc > clears)
+ sstrncpy(&pc, " ", clen-(pc-clears));
+
+ sstrncpy(&pc, "\\SEEN", clen-(pc-clears));
+ }
+ if(flagbits & F_UNKEYWORD){
+ for(t = kw_off; t && *t; t++){
+ if(pc > clears)
+ sstrncpy(&pc, " ", clen-(pc-clears));
+
+ sstrncpy(&pc, *t, clen-(pc-clears));
+ }
+ }
+
+
+ if(sets[0])
+ mail_flag(stream, seq, sets, ST_SET);
+
+ if(clears[0])
+ mail_flag(stream, seq, clears, 0L);
+
+ fs_give((void **) &sets);
+ fs_give((void **) &clears);
+ fs_give((void **) &seq);
+
+ if(we_cancel)
+ cancel_busy_cue(buf[0] ? 0 : -1);
+ }
+}
+
+
+/*
+ * Delete messages which are marked FILTERED and excluded.
+ * Messages which are FILTERED but not excluded are those that have had
+ * their state set by a filter pattern, but are to remain in the same
+ * folder.
+ */
+void
+delete_filtered_msgs(MAILSTREAM *stream)
+{
+ int exbits;
+ long i;
+ char *seq;
+ MESSAGECACHE *mc;
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if(msgno_exceptions(stream, i, "0", &exbits, FALSE)
+ && (exbits & MSG_EX_FILTERED)
+ && get_lflag(stream, NULL, i, MN_EXLD)){
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 1;
+ }
+ else if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ if((seq = build_sequence(stream, NULL, NULL)) != NULL){
+ mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT);
+ fs_give((void **) &seq);
+ }
+}
diff --git a/pith/pattern.h b/pith/pattern.h
new file mode 100644
index 00000000..4b222d3e
--- /dev/null
+++ b/pith/pattern.h
@@ -0,0 +1,417 @@
+/*
+ * $Id: pattern.h 942 2008-03-04 18:21:33Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_PATTERN_INCLUDED
+#define PITH_PATTERN_INCLUDED
+
+
+#include "../pith/msgno.h"
+#include "../pith/sorttype.h"
+#include "../pith/string.h"
+#include "../pith/indxtype.h"
+
+
+/*
+ * This structure is used to contain strings which are matched against
+ * header fields. The match is a simple substring match. The match is
+ * an OR of all the patterns in the PATTERN_S list. That is,
+ * substring1_matches OR substring2_matches OR substring3_matches.
+ * If not is set in the _head_ of the PATTERN_S, it is a NOT of the
+ * whole pattern, that is,
+ * NOT (substring1_matches OR substring2_matches OR substring3_matches).
+ * The not variable is not meaningful except in the head member of the
+ * PATTERN_S list.
+ */
+typedef struct pattern_s {
+ int not; /* NOT of whole pattern */
+ char *substring;
+ struct pattern_s *next;
+} PATTERN_S;
+
+/*
+ * List of these is a list of arbitrary freetext headers and patterns.
+ * This may be part of a pattern group.
+ * The isemptyval bit is to keep track of the difference between an arb
+ * header with no value set and one with the empty value "" set. For the
+ * other builtin headers this difference is kept track of by whether or
+ * not the header is in the config file at all or not. Here we want to
+ * be able to add a header to the config file without necessarily giving
+ * it a value.
+ */
+typedef struct arbhdr_s {
+ char *field;
+ PATTERN_S *p;
+ int isemptyval;
+ struct arbhdr_s *next;
+} ARBHDR_S;
+
+/*
+ * A list of intervals of integers.
+ */
+typedef struct intvl_s {
+ long imin, imax;
+ struct intvl_s *next;
+} INTVL_S;
+
+/*
+ * A Pattern group gives characteristics of an envelope to match against. Any of
+ * the characteristics (to, from, ...) which is non-null must match for the
+ * whole thing to be considered a match. That is, it is an AND of all the
+ * non-null members.
+ */
+typedef struct patgrp_s {
+ char *nick;
+ char *comment; /* for user, not used for anything */
+ PATTERN_S *to,
+ *from,
+ *sender,
+ *cc,
+ *recip,
+ *partic,
+ *news,
+ *subj,
+ *alltext,
+ *bodytext,
+ *keyword,
+ *charsets;
+ STRLIST_S *charsets_list; /* used for efficiency, computed from charset */
+ ARBHDR_S *arbhdr; /* list of arbitrary hdrnames and patterns */
+ int fldr_type; /* see FLDR_* below */
+ PATTERN_S *folder; /* folder if type FLDR_SPECIFIC */
+ int inabook; /* see IAB_* below */
+ PATTERN_S *abooks;
+ int do_score;
+ INTVL_S *score;
+ int do_age;
+ INTVL_S *age; /* ages are in days */
+ int do_size;
+ INTVL_S *size;
+ int age_uses_sentdate; /* on or off */
+ int do_cat;
+ char **category_cmd;
+ INTVL_S *cat;
+ long cat_lim; /* -1 no limit 0 only headers */
+ int bogus; /* patgrp contains unknown stuff */
+ int stat_new, /* msg status is New (Unseen) */
+ stat_rec, /* msg status is Recent */
+ stat_del, /* msg status is Deleted */
+ stat_imp, /* msg is flagged Important */
+ stat_ans, /* msg is flagged Answered */
+ stat_8bitsubj, /* subject contains 8bit chars */
+ stat_bom, /* this is first pine run of the month */
+ stat_boy; /* this is first pine run of the year */
+} PATGRP_S;
+
+#define FLDR_ANY 0
+#define FLDR_NEWS 1
+#define FLDR_EMAIL 2
+#define FLDR_SPECIFIC 3
+
+#define FLDR_DEFL FLDR_EMAIL
+
+
+#define IAB_EITHER 0x0 /* don't care if in or not */
+
+#define IAB_TYPE_MASK 0xf
+#define IAB_YES 0x1 /* addresses in any abook */
+#define IAB_NO 0x2 /* " not " */
+#define IAB_SPEC_YES 0x3 /* addresses in specific abooks */
+#define IAB_SPEC_NO 0x4
+
+/*
+ * Warning about reply-to. We're using the c-client envelope reply-to which
+ * means if there isn't a real reply-to it uses the From!
+ */
+#define IAB_ADDR_MASK 0xff0
+#define IAB_FROM 0x10 /* from address included in list */
+#define IAB_REPLYTO 0x20 /* reply-to address included in list */
+#define IAB_SENDER 0x40 /* sender address included in list */
+#define IAB_TO 0x80 /* to address included in list */
+#define IAB_CC 0x100 /* cc address included in list */
+
+#define IAB_DEFL IAB_EITHER
+
+
+#define FILTER_STATE 0
+#define FILTER_KILL 1
+#define FILTER_FOLDER 2
+
+/*
+ * For the Status parts of a PATGRP_S. For example, stat_del is Deleted
+ * status. User sets EITHER means they don't care, it always matches.
+ * YES means it must be deleted to match. NO means it must not be deleted.
+ */
+#define PAT_STAT_EITHER 0 /* we don't care which, yes or no */
+#define PAT_STAT_YES 1 /* yes, this status is true */
+#define PAT_STAT_NO 2 /* no, this status is not true */
+
+/*
+ * For the State setting part of a filter action
+ */
+#define ACT_STAT_LEAVE 0 /* leave msg state alone */
+#define ACT_STAT_SET 1 /* set this part of msg state */
+#define ACT_STAT_CLEAR 2 /* clear this part of msg state */
+
+typedef struct action_s {
+ unsigned is_a_role:1; /* this is a role action */
+ unsigned is_a_incol:1; /* this is an index color action */
+ unsigned is_a_score:1; /* this is a score setting action */
+ unsigned is_a_filter:1; /* this is a filter action */
+ unsigned is_a_other:1; /* this is a miscellaneous action */
+ unsigned is_a_srch:1; /* this is for Select cmd, no action */
+ unsigned bogus:1; /* action contains unknown stuff */
+ unsigned been_here_before:1; /* inheritance loop prevention */
+ /* --- These are for roles --- */
+ ADDRESS *from; /* value to set for From */
+ ADDRESS *replyto; /* value to set for Reply-To */
+ char **cstm; /* custom headers */
+ char **smtp; /* custom SMTP server for this role */
+ char **nntp; /* custom NNTP server for this role */
+ char *fcc; /* value to set for Fcc */
+ char *litsig; /* value to set Literal Signature */
+ char *sig; /* value to set for Sig File */
+ char *template; /* value to set for Template */
+ char *nick; /* value to set for Nickname */
+ int repl_type; /* see ROLE_REPL_* below */
+ int forw_type; /* see ROLE_FORW_* below */
+ int comp_type; /* see ROLE_COMP_* below */
+ char *inherit_nick; /* pattern we inherit actions from */
+ /* --- This is for indexcoloring --- */
+ COLOR_PAIR *incol; /* colors for index line */
+ /* --- This is for scoring --- */
+ long scoreval;
+ HEADER_TOK_S *scorevalhdrtok;
+ /* --- These are for filtering --- */
+ int kill;
+ long state_setting_bits;
+ PATTERN_S *keyword_set; /* set these keywords */
+ PATTERN_S *keyword_clr; /* clear these keywords */
+ PATTERN_S *folder; /* folders to recv. filtered mail */
+ int move_only_if_not_deleted; /* on or off */
+ int non_terminating; /* on or off */
+ /* --- These are for other --- */
+ /* sort order of folder */
+ unsigned sort_is_set:1;
+ SortOrder sortorder; /* sorting order */
+ int revsort; /* whether or not to reverse sort */
+ /* Index format of folder */
+ char *index_format;
+ unsigned startup_rule;
+} ACTION_S;
+
+/* flags for first_pattern..., set_role_from_msg, and confirm_role() */
+#define PAT_CLOSED 0x00000000 /* closed */
+#define PAT_OPENED 0x00000001 /* opened successfully */
+#define PAT_OPEN_FAILED 0x00000002
+#define PAT_USE_CURRENT 0x00000010 /* use current_val to set up pattern */
+#define PAT_USE_CHANGED 0x00000020 /* use changed_val to set up pattern */
+#define PAT_USE_MAIN 0x00000040 /* use main_user_val */
+#define PAT_USE_POST 0x00000080 /* use post_user_val */
+#define ROLE_COMPOSE 0x00000100 /* roles with compose value != NO */
+#define ROLE_REPLY 0x00000200 /* roles with reply value != NO */
+#define ROLE_FORWARD 0x00000400 /* roles with forward value != NO */
+#define ROLE_INCOL 0x00000800 /* patterns with non-Normal colors */
+#define ROLE_SCORE 0x00001000 /* patterns with non-zero scorevals */
+#define ROLE_DO_ROLES 0x00010000 /* role patterns */
+#define ROLE_DO_INCOLS 0x00020000 /* index line color patterns */
+#define ROLE_DO_SCORES 0x00040000 /* set score patterns */
+#define ROLE_DO_FILTER 0x00080000 /* filter patterns */
+#define ROLE_DO_OTHER 0x00100000 /* miscellaneous patterns */
+#define ROLE_DO_SRCH 0x00200000 /* index line color patterns */
+#define ROLE_OLD_PAT 0x00400000 /* old patterns variable */
+#define ROLE_OLD_FILT 0x00800000 /* old patterns-filters variable */
+#define ROLE_OLD_SCORE 0x01000000 /* old patterns-scores variable */
+#define ROLE_CHANGES 0x02000000 /* start editing with changes
+ already registered */
+
+#define PAT_OPEN_MASK 0x0000000f
+#define PAT_USE_MASK 0x000000f0
+#define ROLE_MASK 0x00ffff00
+
+#define ROLE_REPL_NO 0 /* never use for reply */
+#define ROLE_REPL_YES 1 /* use for reply with confirmation */
+#define ROLE_REPL_NOCONF 2 /* use for reply without confirmation */
+#define ROLE_FORW_NO 0 /* ... forward ... */
+#define ROLE_FORW_YES 1
+#define ROLE_FORW_NOCONF 2
+#define ROLE_COMP_NO 0 /* ... compose ... */
+#define ROLE_COMP_YES 1
+#define ROLE_COMP_NOCONF 2
+
+#define ROLE_REPL_DEFL ROLE_REPL_YES /* default reply value */
+#define ROLE_FORW_DEFL ROLE_FORW_YES /* default forward value */
+#define ROLE_COMP_DEFL ROLE_COMP_NO /* default compose value */
+#define ROLE_NOTAROLE_DEFL ROLE_COMP_NO
+
+#define INTVL_INF (2147483646L)
+#define INTVL_UNDEF (INTVL_INF + 1L)
+#define SCORE_UNDEF INTVL_UNDEF
+#define SCORE_MIN (-100)
+#define SCORE_MAX (100)
+#define SCOREUSE_GET 0x000
+#define SCOREUSE_INVALID 0x001 /* will recalculate scores_in_use next time */
+#define SCOREUSE_ROLES 0x010 /* scores are used for roles */
+#define SCOREUSE_INCOLS 0x020 /* scores are used for index line colors */
+#define SCOREUSE_FILTERS 0x040 /* scores are used for filters */
+#define SCOREUSE_OTHER 0x080 /* scores are used for miscellaneous stuff */
+#define SCOREUSE_INDEX 0x100 /* scores are used in index-format */
+#define SCOREUSE_STATEDEP 0x200 /* scores depend on message state */
+
+/*
+ * A message is compared with a pattern group to see if it matches.
+ * If it does match, then there are actions which are taken.
+ */
+typedef struct pat_s {
+ PATGRP_S *patgrp;
+ ACTION_S *action;
+ struct pat_line_s *patline; /* pat_line that goes with this pat */
+ char *raw;
+ unsigned inherit:1;
+ struct pat_s *next;
+ struct pat_s *prev;
+} PAT_S;
+
+typedef enum {TypeNotSet = 0, Literal, File, Inherit} PAT_TYPE;
+
+/*
+ * There's one of these for each line in the pinerc variable.
+ * Normal type=Literal patterns have a patline with both first and last
+ * pointing to the pattern. Type File has one patline for the file and first
+ * and last point to the first and last patterns in the file.
+ * The patterns aren't linked into one giant list, the patlines are.
+ * To traverse all the patterns you have to go through the patline list
+ * and then for each patline go from first to last through the patterns.
+ * That's what next_pattern and friends do.
+ */
+typedef struct pat_line_s {
+ PAT_TYPE type;
+ PAT_S *first; /* 1st pattern in list belonging to this line */
+ PAT_S *last;
+ char *filename; /* If type File, the filename */
+ char *filepath;
+ unsigned readonly:1;
+ unsigned dirty:1; /* needs to be written back to storage */
+ struct pat_line_s *next;
+ struct pat_line_s *prev;
+} PAT_LINE_S;
+
+typedef struct pat_handle {
+ PAT_LINE_S *patlinehead; /* list of in-core, parsed pat lines */
+ unsigned dirtypinerc:1; /* needs to be written */
+} PAT_HANDLE;
+
+typedef struct pat_state {
+ long rflags;
+ int cur_rflag_num;
+ PAT_LINE_S *patlinecurrent;
+ PAT_S *patcurrent; /* current pat within patline */
+} PAT_STATE;
+
+#define PATTERN_MAGIC "P#Pats"
+#define PATTERN_FILE_VERS "01"
+
+
+/*
+ * This is a little dangerous. We're passing flags to match_pattern and
+ * peeling some of them off for our own use while passing the rest on
+ * to mail_search_full. So we need to define ours so they don't overlap
+ * with the c-client flags that can be passed to mail_search_full.
+ * We could formalize it with mrc.
+ */
+#define MP_IN_CCLIENT_CB 0x10000 /* we're in a c-client callback! */
+#define MP_NOT 0x20000 /* use ! of patgrp for search */
+
+
+/* match_pattern_folder_specific flags */
+#define FOR_PATTERN 0x01
+#define FOR_FILTER 0x02
+#define FOR_OPTIONSCREEN 0x04
+
+
+extern PAT_HANDLE **cur_pat_h;
+
+
+/* exported protoypes */
+void role_process_filters(void);
+int add_to_pattern(PAT_S *, long);
+char *add_pat_escapes(char *);
+char *remove_pat_escapes(char *);
+char *add_roletake_escapes(char *);
+char *add_comma_escapes(char *);
+void set_pathandle(long);
+void close_every_pattern(void);
+void close_patterns(long);
+int nonempty_patterns(long, PAT_STATE *);
+int any_patterns(long, PAT_STATE *);
+int edit_pattern(PAT_S *, int, long);
+int add_pattern(PAT_S *, long);
+int delete_pattern(int, long);
+int shuffle_pattern(int, int, long);
+PAT_LINE_S *parse_pat_file(char *);
+INTVL_S *parse_intvl(char *);
+char *stringform_of_intvl(INTVL_S *);
+char *hdrtok_to_stringform(HEADER_TOK_S *);
+HEADER_TOK_S *stringform_to_hdrtok(char *);
+char *hdrtok_to_config(HEADER_TOK_S *);
+HEADER_TOK_S *config_to_hdrtok(char *);
+int scores_are_used(int);
+int patgrp_depends_on_state(PATGRP_S *);
+int patgrp_depends_on_active_state(PATGRP_S *);
+PATTERN_S *parse_pattern(char *, char *, int);
+PATTERN_S *string_to_pattern(char *);
+char *pattern_to_string(PATTERN_S *);
+char *pattern_to_config(PATTERN_S *);
+PATTERN_S *config_to_pattern(char *);
+PATTERN_S *editlist_to_pattern(char **);
+char **pattern_to_editlist(PATTERN_S *);
+PATGRP_S *nick_to_patgrp(char *, int);
+PAT_S *first_pattern(PAT_STATE *);
+PAT_S *last_pattern(PAT_STATE *);
+PAT_S *prev_pattern(PAT_STATE *);
+PAT_S *next_pattern(PAT_STATE *);
+int write_patterns(long);
+void convert_statebits_to_vals(long, int *, int *, int *, int *);
+int match_pattern(PATGRP_S *, MAILSTREAM *, SEARCHSET *,char *,
+ long (*)(MAILSTREAM *, long), long);
+void find_8bitsubj_in_messages(MAILSTREAM *, SEARCHSET *, int, int);
+void find_charsets_in_messages(MAILSTREAM *, SEARCHSET *, PATGRP_S *, int);
+int compare_strlists_for_match(STRLIST_S *, STRLIST_S *);
+int match_pattern_folder(PATGRP_S *, MAILSTREAM *);
+int match_pattern_folder_specific(PATTERN_S *, MAILSTREAM *, int);
+SEARCHPGM *match_pattern_srchpgm(PATGRP_S *, MAILSTREAM *, SEARCHSET *);
+void calc_extra_hdrs(void);
+char *get_extra_hdrs(void);
+void free_extra_hdrs(void);
+void free_pat(PAT_S **);
+void free_pattern(PATTERN_S **);
+void free_action(ACTION_S **);
+PAT_S *copy_pat(PAT_S *);
+PATGRP_S *copy_patgrp(PATGRP_S *);
+ACTION_S *copy_action(ACTION_S *);
+ACTION_S *combine_inherited_role(ACTION_S *);
+void mail_expunge_prefilter(MAILSTREAM *, int);
+void process_filter_patterns(MAILSTREAM *, MSGNO_S *, long);
+void reprocess_filter_patterns(MAILSTREAM *, MSGNO_S *, int);
+int trivial_patgrp(PATGRP_S *);
+void free_patgrp(PATGRP_S **);
+void free_patline(PAT_LINE_S **patline);
+int some_filter_depends_on_active_state(void);
+void delete_filtered_msgs(MAILSTREAM *);
+void free_intvl(INTVL_S **);
+void free_arbhdr(ARBHDR_S **);
+
+
+#endif /* PITH_PATTERN_INCLUDED */
diff --git a/pith/pine.hlp b/pith/pine.hlp
new file mode 100644
index 00000000..3af1062e
--- /dev/null
+++ b/pith/pine.hlp
@@ -0,0 +1,35307 @@
+# $Id: pine.hlp 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $
+#
+# T H E A L P I N E M E S S A G E S Y S T E M
+#
+#/* ========================================================================
+# * Copyright 2006-2008 University of Washington
+# * Copyright 2013 Eduardo Chappa
+# *
+# * Licensed under the Apache License, Version 2.0 (the "License");
+# * you may not use this file except in compliance with the License.
+# * You may obtain a copy of the License at
+# *
+# * http://www.apache.org/licenses/LICENSE-2.0
+# *
+# * ========================================================================
+# */
+#
+ Help text for the Alpine mailer
+
+This file is in a format created to be turned into text strings in a C
+program.
+
+There are two shell scripts that run on this. Cmplhelp.sh is the
+first and turns this into a C file (helptext.c) of text strings that
+are compiled and linked. The other program, cmplhlp2.sh, turns this
+into a .h file (helptext.h) with extern string definitions of the
+strings in the .c file. The code that actually processes these files
+while alpine is running is in help.c
+
+The lines with "===== xxxx ====" divide the different help screens. The
+xxx is the name of the variable that strings will be put in, which are
+also declared in helptext.h.
+
+# is a comment
+
+Help text screen text can be either plain text OR HTML. The latter is
+denoted by the first line starting with "<HTML>". The former is simply
+displayed as it's formatted here.
+
+HTML is limited to simple formatting ala HTML 2.0. No forms, or tables.
+In addition a small set of tools are are available to customize the HTML
+screen's text:
+
+1a) Default and function key bindings are separated like this:
+
+<!--chtml if pinemode="function_key"-->
+ Function key bindings here
+<!--chtml else-->
+ Default key bindings here
+<!--chtml endif-->
+
+
+1b) A way to distinguish HTML text that is to be displayed when
+ pine is running vs. when the text is served up outside Alpine
+ (someday) can be done via:
+
+<!--chtml if pinemode="running"-->
+ Text displayed when viewed within a running pine session
+<!--chtml else-->
+ Text displayed when HTML viewed outside pine (using chtml aware server)
+<!--chtml endif-->
+
+1c) A way to distinguish HTML text that is to be displayed under
+ PC-Alpine vs. not is available via:
+
+<!--chtml if pinemode="os_windows"-->
+ Text displayed under PC-Alpine
+<!--chtml else-->
+ Text displayed otherwise
+<!--chtml endif-->
+
+WARNING ABOUT CHTML "if-else-endif" CLAUSES: They don't nest.
+
+2a) Several "server side include" commented elements are supported:
+
+<!--#include file="textfile"-->
+
+The file "textfile" will be inserted into the HTML text directly.
+Alpine does no formatting of the text. At some point we might want to
+look at the first line for <HTML> but not today.
+
+2b) Various bits of Alpine's running state can be inserted into the
+HTML text as well using the special comment:
+
+<!--#echo var="variable"-->
+
+Where "variable" is one of either:
+
+ ALPINE_VERSION
+ ALPINE_REVISION
+ ALPINE_COMPILE_DATE
+ ALPINE_TODAYS_DATE
+ C_CLIENT_VERSION
+ _LOCAL_FULLNAME_
+ _LOCAL_ADDRESS_
+ _BUGS_FULLNAME_
+ _BUGS_ADDRESS_
+ CURRENT_DIR
+ HOME_DIR
+ PINE_CONF_PATH
+ PINE_CONF_FIXED_PATH
+ PINE_INFO_PATH
+ MAIL_SPOOL_PATH
+ MAIL_SPOOL_LOCK_PATH
+ VAR_<VARNAME> - where <VARNAME> is config variable name
+ FEAT_<FEATURENAME> - where <FEATURENAME> is config feature name
+
+3) The URL scheme "X-Alpine-Gripe:" is available to insert links to
+ pine's composer such that various debugging data can be attached to the
+ message. Aside from normal email addresses, this can be set to
+ either "_LOCAL_ADDRESS_" for the configured local help address, or
+ "_BUGS_ADDRESS_" for the configured local bug reporting address.
+ Aside from the special tokens above, the default behavior only differs
+ from "mailto:" by the insertion of a special Subject: prefix that
+ consists of a randomly-generated token for tracking purposes.
+ Several optional parameters can be included to control what is
+ attached or offered for attachment to the message:
+
+ ?config -- Automatically attaches the user's configuration
+ information to the trouble report
+ ?keys -- Automatically attaches the user's most recent
+ keystrokes
+ ?curmsg -- Causes the user to get an offer to attach the
+ current message to the trouble report
+ ?local -- Automatically attaches the result of the script
+ defined by VAR_BUGS_EXTRAS
+
+For HTML-format sections, the help screen dividers "===== xxxx ====" must
+contain one and only one space after the first and before the second set of
+equal signs.
+
+Note to authors of this file: to mark sections that need further revision,
+please use the text string on the following line consistently so that it is
+easy to find those places again in this file:
+*revision needed*
+
+NOTE: Several sections of help text which weren't being used were removed
+at RCS version number 4.122. In particular, there were some placeholders
+with help text for the config screen and the composer that didn't have any
+reasonable place to be called from.
+Dummy change to get revision in pine.hlp
+
+============= h_revision =================
+$Id: pine.hlp 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $
+============= h_news =================
+<HTML>
+<HEAD>
+<TITLE>RELEASE NOTES for Alpine</TITLE>
+</HEAD>
+<BODY>
+<H1>Alpine Release Notes</H1>
+<DIV ALIGN=CENTER>
+Version <!--#echo var="ALPINE_VERSION"-->(<!--#echo var="ALPINE_REVISION"-->)
+<BR>
+<!--chtml if pinemode="running"-->
+(built <!--#echo var=ALPINE_COMPILE_DATE-->)
+<!--chtml endif-->
+<BR>Copyright 2006-2008 University of Washington
+<BR>Copyright 2013 Eduardo Chappa
+</DIV>
+
+<P>
+Alpine is an &quot;Alternatively Licensed Program for Internet
+News and Email&quot; produced until 2008 by the University of Washington.
+It is intended to be an easy-to-use program for
+sending, receiving, and filing Internet electronic mail messages and
+bulletin board (Netnews) messages. Alpine is designed to run on a wide
+variety of Unix&reg; operating systems. A version for Microsoft Windows&reg;
+is available as is a world wide web based version designed to run under the
+Apache web server.
+
+
+<H2>New in Alpine <!--#echo var="ALPINE_VERSION"-->(<!--#echo var="ALPINE_REVISION"-->)</H2>
+
+Version <!--#echo var="ALPINE_VERSION"-->(<!--#echo var="ALPINE_REVISION"-->)
+addresses bugs found in previous releases and has a few additions as well.
+
+<P>
+Additions include:
+<P>
+
+<UL>
+ <LI> Quota report for IMAP folders that support it (press the &quot;@&quot; command in the index screen of such folder).
+ <LI> Search a folder for the content of any header with the &quot;;&quot; command.
+ <LI> Foreign characters are decoded correctly in IMAP folders.
+ <LI> Question about breaking connection to slow servers includes their name.
+ <LI> Internal x-alpine-help: resource locator for sending links to internal help.
+ <LI> OpenSuse: Alpine find location of OpenSSL certificates.
+ <LI> Cygwin: Alpine builds without need of patch.
+ <LI> Recognition of proper mime type for docx, xlsx, and pptx files.
+ <LI> When composing a message, Alpine will create a new thread when the subject is erased.
+ <LI> Add support for strong encryption of password file when S/MIME is built in.
+</UL>
+
+<P>
+Bugs that have been addressed include:
+<P>
+
+<UL>
+ <LI> Alpine will close a folder after confirming with user their intention and not reopen it.
+ <LI> Double allocation of memory in Pico.
+ <LI> Alpine does not give warning of message sent and posted upon receipt by email of message posted in newsgroup.
+ <LI> Handling of STYLE html parameter may make Alpine not display the content of a message.
+ <LI> Not recognition of environment variables in some options.
+ <LI> Not display of login prompt during initial keystrokes.
+ <LI> justification of long urls breaks them.
+ <LI> Incorrect New Mail message when envelope is not available.
+ <LI> Inorrect display of PREFDATE, PREFDATETIME and PREFTIME tokens.
+ <LI> Crash when resizing the screen after display of LDAP search.
+ <LI> Crash when redrawing screen while opening a remote folder collection.
+ <LI> Infinite loop in scrolltool function during notification of new mail.
+ <LI> No repaint of the screen was done when the SMARTDATE token is used in the index screen after midnight.
+ <LI> No display of signed and encrypted S/MIME messages.
+ <LI> Alpine will not build with OpenSSL.
+ <LI> Crash for double locking in calls to c-client.
+ <LI> Bad recognition of mime-encoded text may make Alpine not print the subject of a message.
+ <LI> Ignore the references header when threading messages
+ <LI> No update of colors in index screen after update to addressbook.
+</UL>
+
+<P>
+Version 2.01 addresses bugs found in previous releases and has a few
+additions as well.
+
+<P>
+Additions include:
+<P>
+
+<UL>
+ <LI> Fixed non-ASCII web alpine handling
+ <LI> Added web alpine help.
+ <LI> Allow web alpine inbox on second IMAP server.
+ <LI> Allow web alpine config reset after bad inbox-path gets set.
+ <LI> Added web alpine ability to create group contact from contact list members.
+ <LI> Backed out web alpine coercing of default sort-key of arrival to date/reverse.
+ <LI> Tidied up web alpine script layout.
+ <LI> Fixed web alpine status message ordering
+ <LI> Added web alpine Fcc setting via Contacts in Compose
+ <LI> Fixed web alpine autodraft attachment issues
+ <LI> Fixed web alpine problems with recent count maintenance
+ <LI> Fixed web alpine newmail arrival display in message list
+ <LI> Added web alpine confirmation to folder create for Move/Copy
+ <LI> Added web alpine user-domain support
+ <LI> Fixed web alpine to support INBOX-within-a-collection deletion
+</UL>
+
+<P>
+Bugs that have been addressed include:
+<P>
+
+<UL>
+ <LI> In web alpine fixed delete all selected within a search result to reorient
+ correctly to whole-mailbox message list.
+ <LI> Fixed web alpine delete in view page to be sensitive to sort
+ <LI> Fixed web alpine open of folder within directory from folder manager page.
+ <LI> Fixed web alpine open of folder within directory from left column's recent cache.
+ <LI> Fixed web alpine problems with spaces in special folder names like Drafts
+ <LI> Fixed web alpine adding contacts from message list and view
+ <LI> Fixed web alpine create of non-existent fcc
+ <LI> Remove mistakenly left debugger statement in web alpine javascript.
+ <LI> Some UNIX alpine build problems fixed
+ <LI> Crash in pico and pilot when nl_langinfo returned something unrecognizable
+ or NULL. Add recognition of "646" to nl_langinfo wrapper. This is returned
+ by locale charmap call on some Solaris 8 systems.
+ <LI> MacOS Keychain logins were not working correctly with generic host names, like
+ imap.gmail.com, as opposed to specific instances like rx-in-209.google.com, causing
+ new password requests when not needed
+ <LI> Possible crash in WhereIs command while in FOLDER LIST when cursor is located on the
+ last folder in the list
+ <LI> Change to S/MIME get_x509_subject_email so that X509v3 alternative names are
+ looked for along with the email address
+ <LI> Changes to configure to get spellcheck options with work with arguments.
+ <LI> Add change from Mark Crispin of panda.com to at least minimally handle non-ascii hostname
+ returned by gethostname (iPhone can do this)
+ <LI> Fixed a bug that prevents a filter that moves a message into a local folder
+ from also setting the DELETE flag in that moved message. Fix from Eduardo Chappa.
+ <LI> Changed size of shellpath in open_system_pipe from 32 to MAXPATH. Fix from
+ Jake Scott of marganstanley.com.
+ <LI> Buffer overflow bug in c-client's tmail/dmail, fix from Mark Crispin. This
+ is not used in alpine.
+ <LI> Imapd server crash from unguarded fs_give in IDLE code, fix from Crispin.
+ Apparently this causes RIM Blackberry BIS service problems. This is not
+ used in alpine.
+ <LI> Tmail uninitialized pointer fix from Neil Hoggarth. Not used in alpine.
+ <LI> Buffer overflow possibility in RFC822BUFFER routines in c-client library.
+ Fix from Ludwig Nussel of SUSE and from Crispin.
+ <LI> Include whole filename in export filename history
+ <LI> Fix display bug in pico when Replace command is canceled. Fix from Eduardo Chappa.
+</UL>
+
+<P>
+Version 2.00
+addressed bugs found in previous releases and had a few additions as well.
+<P>
+Additions included:
+<P>
+
+<UL>
+ <LI> Redesigned Web Alpine interface
+ <LI> Experimental <A HREF="h_mainhelp_smime">S/MIME support</A> added
+ in UNIX versions of Alpine
+ <LI> Attempt to include the attachment filename as part of the name of the
+ temporary file used when viewing an attachment with an external program.
+ Add some randomness to that filename to make it hard to predict the filename.
+ Add a filename extension, usually derived from the type/subtype, to the
+ temporary filename. This was previously done only on Windows and MacOS X.
+ <LI> Enhance address completion in the composer (TAB command) so that it looks
+ through nicknames, fullnames, and addresses from the address book; addresses
+ from the message being viewed, if any; and the results from
+ <A HREF="h_direct_config">LDAP Directory Server</A>
+ lookups for all of the defined directory servers that have the
+ <A HREF="h_config_ldap_opts_impl">&quot;Use-Implicitly-From-Composer&quot;</A>
+ feature set.
+ <LI> Make the default character set setting more liberal in what it will accept
+ from the UNIX nl_langinfo call and the various values of LANG seen in the wild
+ <LI> Remove the Alpine revision number from the titlebar in released versions
+ while leaving it there in snapshot versions
+ <LI> Add a <A HREF="h_config_quell_asterisks">feature</A> to suppress the
+ display of asterisks when you type a password for Alpine
+ <LI> Add line wrapping when displaying <EM>PRE</EM> formatted sections of HTML
+ <LI> When the
+ <A HREF="h_config_dates_to_local"><!--#echo var="FEAT_convert-dates-to-localtime"--></A>
+ feature is turned on convert not only the dates in the index screen but also
+ the dates in the MESSAGE VIEW
+</UL>
+
+<P>
+Bugs addressed in the 2.00 release included:
+<P>
+
+<UL>
+ <LI> Crash when using tab-completion for selecting a Save filename
+ <LI> Make Web Alpine help text images relative for more portability
+ <LI> Fixed attach save of html parts in Web Alpine
+ <LI> Viewing, printing, exporting, replying, and bouncing of message
+ digests was broken. Replying and bouncing should not have been
+ allowed at all for digests. It would be nice to have a more standard
+ index-like view of a message digest but that has not been addressed
+ with this minor bug fix.
+ <LI> Adjust wrapping of HTML messages so that the margins specified by
+ <A HREF="h_config_viewer_margin_left"><!--#echo var="VAR_viewer-margin-left"--></A> and
+ <A HREF="h_config_viewer_margin_right"><!--#echo var="VAR_viewer-margin-right"--></A>
+ are observed correctly
+ <LI> Interrupt of WhereIs command in index was broken
+ <LI> The <A HREF="h_config_unk_char_set"><!--#echo var="VAR_unknown-character-set"--></A>
+ option did not work correctly interpreting unknown characters in message headers
+ <LI> Long address lines could cause blank continuation lines
+ <LI> Save to a local default INBOX failed if the primary collection was also local,
+ which it is by default. The save went to ~/mail/inbox instead.
+ <LI> Make a default save folder of &quot;inbox&quot; always mean the real
+ inbox, not the inbox in the primary collection
+ <LI> Address book entries with lots of addresses would be truncated when
+ entered in the composer with a screen size wider than 270 or so charcters
+ <LI> Some fields in the index screen were truncated when the screen width was
+ wider than 256 characters
+ <LI> Crash when TABing to next folder, the next folder with new mail is a POP
+ folder, and there is a more than 10 minute pause between typing the TAB
+ and typing the Yes
+</UL>
+
+<P>
+Version 1.10(962)
+addressed bugs found in previous releases and had a few additions as well.
+<P>
+Additions included:
+<P>
+
+<UL>
+ <LI> Add the possibility of setting a default role
+ (see <A HREF="h_role_select">Roles Screen</A>)
+ which may be convenient if your work flow involves acting in one
+ role for a while then switching to another role and staying in the
+ new role for another period of time
+ <LI> When Saving and the IMAP server problem &quot;Message to save shrank!&quot;
+ is encountered, ask the user if he or she wants to continue with the
+ risky Save anyway instead of aborting. This may be helpful if your
+ IMAP server is broken in this way but be aware that it is possible there
+ was a real error instead of just a broken server implementation.
+ <LI> Some configure adjustments for Kerberos detection and
+ for SCO OpenServer 5 support
+ <LI> Hide INBOX in a collection if it also appears as an
+ <A HREF="h_config_enable_incoming">Incoming Folder</A>
+ <LI> Show asterisks for feedback when the user is typing a password
+ <LI> Performance improvement for threading of large folders
+ <LI> Previously, the search used to find
+ Pattern matches containing To patterns searched for both To
+ and Resent-To headers. The relatively complicated search this
+ produces causes problems when using some deficient IMAP servers.
+ Now the default is to look only for To headers and ignore the
+ presence of Resent-To. The previous behavior may be restored
+ with the <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> feature.
+ <LI> Add an
+ <A HREF="h_config_unk_char_set"><!--#echo var="VAR_unknown-character-set"--></A>
+ to help with reading malformed unlabeled messages
+ <LI><A HREF="h_config_suppress_user_agent"><!--#echo var="FEAT_suppress-user-agent-when-sending"--></A> option added
+ <LI> Map some Shift-LeftArrow escape sequences to LeftArrow
+ <LI> Add feature <A HREF="h_config_warn_if_fcc_blank"><!--#echo var="FEAT_warn-if-blank-fcc"--></A>
+</UL>
+
+<P>
+Bugs addressed in the 1.10(962) release included:
+<P>
+
+<UL>
+ <LI> Crash when encountering certain errors from an SMTP server
+ <LI> Crash in composer caused by overflow in replace_pat()
+ <LI> Hang when authenticating to an SMTP server that fails with a
+ &quot;connection disconnected&quot; error
+ <LI> Bug in handling of trailing tab character in flowed text
+ <LI> Security enhancement for mailcap parameter substitution
+ <LI> <A HREF="h_config_strip_sigdashes"><!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></A>
+ did not work if the message being replied to was not flowed text
+ and <A HREF="h_config_quell_flowed_text"><!--#echo var="FEAT_quell-flowed-text"--></A>
+ was not turned on
+ <LI> Don't allow printer to be changed through hidden config screen
+ if system administrator didn't want it to be allowed
+ <LI> Attempts are sometimes made to set the Forwarded flag when alpine
+ should know that it won't work, causing error messages to appear
+ <LI> A <A HREF="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></A>
+ of double-quote double-quote didn't work right
+ <LI> Quoting wasn't being done to protect special characters from the
+ MacOS X shell when
+ <A HREF="h_config_browser"><!--#echo var="VAR_url-viewers"--></A>
+ was not defined
+ <LI> On MacOS X message attachments should be shown internally instead of
+ being shown using the Mail application
+ <LI> When replying to a message with a charset of X-UNKNOWN Alpine would
+ sometimes set the outgoing charset to X-UNKNOWN, making the result
+ unreadable
+ <LI> When the sending of a message failed lines with leading spaces had one
+ additional space inserted in each of those lines when the user
+ was returned to the composer
+ <LI> The <A HREF="h_index_cmd_whereis">WhereIs</A> command missed some index lines
+ that contained non-ascii characters because it was truncating the
+ line being searched so that it was shorter than what was visible on
+ the screen
+ <LI> When composing, an attachment with a long name that causes wrapping in
+ just the wrong place would generate an error and cause the send
+ of the attachment to fail
+ <LI> After calling the file browser to attach a file in the composer, a resize
+ of the window when back in the composer would redraw the last screen that
+ had been shown in the browser instead of the current composer screen
+ <LI> Possible crash in index screen when encountering unparseable addresses
+ or when using one of the PRIORITY tokens or the HEADER token in the
+ <a href="h_config_index_format"><!--#echo var="VAR_index-format"--></a>
+ <LI> Problems with Header Color editing if the configuration option
+ <a href="h_config_customhdr_color"><!--#echo var="VAR_viewer-hdr-colors"--></a>
+ was inadvertently changed to the Empty Value in the hidden config screen
+ <LI> When resuming the final postponed message from an Exchange server the user
+ could get a certificate validation failure because alpine was trying
+ to validate the canonical name of the folder instead of the name the
+ user used
+ <LI> Windows line endings in a mimetypes file on a Unix system cause a
+ failure to find a match
+ <LI> Make matching of extension names case independent in mimetypes files
+ <LI> Windows dialog boxes for entering text were not working correctly
+ <LI> Replying to or Forwarding multipart/alternative messages which had a
+ single text/html part did not work well
+ <LI> Printing the print confirmation page caused a crash
+ <LI> A To line with a long, quoted personal name could display incorrectly
+ if it was close to the same width as the screen
+ <LI> When <A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+ and <A HREF="h_config_incoming_checking_total"><!--#echo var="FEAT_incoming-checking-includes-total"--></A>
+ are turned on hide (0/0) when the folder is empty
+ <LI> Folder completion while Saving didn't work if the collection being
+ saved to was the local home directory
+</UL>
+
+<P>
+Version 1.00
+was an evolutionary release based on
+<A HREF="http://www.washington.edu/pine/">Pine</A>, which was also
+developed at the University of Washington.
+It is upwards-compatible for existing Pine users.
+
+<P>
+Changes included:
+<P>
+<UL>
+ <LI> Ground-up reorganization of source code around addition
+ of "pith/" core routine library.
+ <LI> Fundamental improvement in Alpine's internal text handling, which
+ is now based exclusively on Unicode. This allows displaying incoming
+ messages and producing outgoing messages in many different languages.
+ <LI> Ground-up reorganization of build and install procedures
+ based on GNU Build System's autotools. NOTE, the included IMAP library
+ build is not based on autotools, so some features will not work. However,
+ it should get built automatically during the Alpine build process.
+ <LI> Web-based version included built on TCL designed to run under
+ a suitable CGI-supporting web server such as Apache.
+</UL>
+
+<P>
+
+Details on changes in previous (prerelease) versions of Alpine
+may be found at the following URL:
+<P>
+<CENTER><SAMP><A HREF="http://www.washington.edu/alpine/changes.html">http://www.washington.edu/alpine/changes.html</A></SAMP></CENTER>
+<P>
+
+<HR WIDTH="75%"><P>
+
+<H2>Getting Help</H2>
+<DL>
+<DT>Online Help</DT>
+<DD>
+Every Alpine screen and command has associated help text
+accessible via the &quot;?&quot; key (or Ctrl-G in text-input contexts).
+</DD>
+
+<DT>Web Help</DT>
+<DD>
+The most current source of information about Alpine,
+including new version availability, is the web page at
+<P>
+<CENTER><SAMP><A HREF="http://www.washington.edu/alpine/">http://www.washington.edu/alpine/</A></SAMP></CENTER>
+</DD>
+</DL>
+
+Frequently Asked Questions (and answers) may be found at the following
+URL:
+<P>
+<CENTER><SAMP><A HREF="http://www.washington.edu/alpine/faq/">http://www.washington.edu/alpine/faq/</A></SAMP></CENTER>
+<P>
+
+<HR WIDTH="75%"><P>
+
+<H2>Additional Information</H2>
+
+General Alpine configuration information can be found
+<A HREF="h_news_config">here</A>.
+<P>
+This is revision (<!--#echo var="ALPINE_REVISION"-->) of the Alpine software.
+Alpine mailbox and <A HREF="http://www.washington.edu/imap/">IMAP</A> server
+access is provided by the IMAP Toolkit Environment (c-client library)
+version <!--#echo var="C_CLIENT_VERSION"-->.
+<P>
+Alpine was developed by the Office of Computing &amp; Communications at
+the University of Washington in Seattle. A more complete list of
+principal players and key contributors can be found on the credits Web
+page at
+
+<P>
+<CENTER><A HREF="http://www.washington.edu/alpine/credits.html">http://www.washington.edu/alpine/credits</A></CENTER>
+
+<P>
+Alpine Copyright 2006-2008 University of Washington,
+Copyright 2013 Eduardo Chappa.
+
+<P>
+Additional legal notices can be found <A HREF="h_news_legal">here</A>
+or at the web URL:
+
+<P>
+<CENTER><A HREF="http://www.washington.edu/alpine/overview/legal.html">http://www.washington.edu/alpine/overview/legal</A></CENTER>
+
+<P>
+&lt;End of Release Notes&gt;
+</BODY>
+</HTML>
+====== h_tls_failure_details ======
+<HTML>
+<HEAD>
+<TITLE>Certificate Validation Details</TITLE>
+</HEAD>
+<BODY>
+<H1>Certificate Validation Details</H1>
+
+This screen gives details as to why the certificate validation failed: the
+name of the desired server system; the reason for failure; and the name on
+the certificate. This is primarily of interest to experts.
+
+<P>
+&lt;End of help&gt;
+</BODY>
+</HTML>
+====== h_tls_failure ======
+<HTML>
+<HEAD>
+<TITLE>TLS or SSL Failure</TITLE>
+</HEAD>
+<BODY>
+<H1>TLS or SSL Failure</H1>
+
+An attempt was made to establish a secure, encrypted connection to the
+server system using either Transport Layer Security (TLS) or the older
+Secure Sockets Layer (SSL). This attempt failed.
+
+<P>
+You should contact your server system management for more assistance.
+The problem is probably at the server system and not in Alpine or your local
+system. The text in this screen may be helpful for the server system
+management in debugging the problem,
+
+<P>
+&lt;End of help&gt;
+</BODY>
+</HTML>
+====== h_tls_validation_failure ======
+<HTML>
+<HEAD>
+<TITLE>TLS and SSL Certificate Validation Failures</TITLE>
+</HEAD>
+<BODY>
+<H1>TLS and SSL Certificate Validation Failures</H1>
+
+An attempt was made to establish a secure, encrypted connection to the
+server system using either Transport Layer Security (TLS) or the older
+Secure Sockets Layer (SSL).
+
+<P>
+An important part of this procedure is server certificate validation. A
+server certificate is an &quot;electronic identification card&quot; for the server
+system that is signed by a well-known certificate authority (CA). Alpine
+compares the server system identity in the server certificate with the
+name of the server system to which it is trying to connect. Alpine also
+verifies that the CA signature is authentic.
+
+<P>
+Some common failure conditions are:
+<P>
+
+<UL>
+ <LI> [UNIX Alpine] Self signed certificate. This means that the server system
+signed its own certificate. This does not necessarily indicate anything
+bad; the server operators may simply have elected to not purchase a
+signed certificate from a certificate authority.
+
+ <LI> [UNIX Alpine] Unable to get local issuer certificate. This means that
+the signature on the server system is from an unknown certificate authority.
+It can also mean that no certificate authority certificates have been
+installed on the local UNIX system.
+
+ <LI> [PC Alpine] Self-signed certificate or untrusted authority. This is
+the same as either of the above two conditions in UNIX Alpine. Note that
+Windows systems typically have a full set of certificate authority
+certificates installed, so it is more likely to be a self-signed
+certificate than an unknown certificate authority.
+
+ <LI> Server name does not match certificate. This means that the server
+presented a proper signed certificate for a name other than the desired
+name.
+</UL>
+
+<P>
+Any of these conditions can indicate that you are being attacked and have
+been directed to an imposter server that will record your password and
+your private mail messages. It can also mean something innocuous.
+
+<P>
+If you are certain that the problem is innocuous, you can append the
+option
+
+<P>
+<CENTER><SAMP>/novalidate-cert</SAMP></CENTER>
+<P>
+
+to the server system name where it appears in your configuration (e.g. the
+<A HREF="h_config_inbox_path"><!--#echo var="VAR_inbox-path"--></A>,
+a folder-collection, or a news or SMTP server). This will
+disable certificate validation. On the other hand, if you are attacked,
+you will get no warning if you do this.
+
+<P>
+&lt;End of Cert Validation Failures help&gt;
+</BODY>
+</HTML>
+====== h_release_tlscerts ======
+<HTML>
+<HEAD>
+<TITLE>TLS and SSL usage note</TITLE>
+</HEAD>
+<BODY>
+<H1>TLS and SSL usage note</H1>
+
+<P>
+When using Alpine from Unix or Windows 2000,
+server certificates must be signed by a trusted certificate authority.
+You may relax this requirement (at the cost of some security) by using
+the
+<A HREF="h_folder_server_syntax">NoValidate-Cert</A>
+modifier in the mailbox name.
+
+<P>
+<CENTER><SAMP>{foo.example.com/novalidate-cert}INBOX</SAMP></CENTER>
+<P>
+
+The fully-qualified host name of the server should be used
+so that it matches the host name in the server certificate.
+<P>
+Here is an example of a host specification that directs Alpine to use
+the SSL port (993) and an encrypted data stream.
+<P>
+<CENTER><SAMP>{foo.example.com/ssl}INBOX</SAMP></CENTER>
+<P>
+&lt;End of TLS usage help&gt;
+</BODY>
+</HTML>
+====== h_news_config ======
+<HTML>
+<HEAD>
+<TITLE>Alpine Configuration</TITLE>
+</HEAD>
+<BODY>
+<H1>Alpine Configuration</H1>
+
+<H2>Using Environment Variables</H2>
+
+The values of Alpine configuration options may include environment variables
+that are replaced by the value of the variable at the time Alpine is run
+(and also at the time the config option is changed).
+The syntax to use environment variables is a subset of the common Unix
+shell dollar-syntax.
+For example, if
+
+<P><CENTER><SAMP>$VAR</SAMP></CENTER><P>
+
+appears in the value of an Alpine configuration option it is looked up in the
+environent (using getenv(&quot;VAR&quot;)) and its
+looked-up value replaces the <SAMP>$VAR</SAMP> part of the option value.
+To include a literal dollar sign you may precede the dollar sign with another
+dollar sign.
+In other words, if the text
+
+<P><CENTER><SAMP>$$text</SAMP></CENTER><P>
+
+is the value of a configuration option, it will be expanded to
+
+<P><CENTER><SAMP>$text</SAMP></CENTER><P>
+
+and no environment lookup will be done.
+For Unix Alpine it will also work to use a backslash character to
+escape the special meaning of the dollar sign, but $$ is preferable since
+it works for both PC-Alpine and Unix Alpine, allowing the configuration option
+to be in a shared configuration file.
+<P>
+
+This all sounds more complicated than it actually is.
+An example may make it clearer.
+Unfortunately, the way in which environment variables are set is OS-dependent
+and command shell-dependent.
+In some Unix command shells you may use
+
+<P><CENTER><SAMP>PERSNAME="Fred Flintstone"</SAMP></CENTER><P>
+ <CENTER><SAMP>export PERSNAME</SAMP></CENTER><P>
+
+Now, if you use Alpine's Setup/Config screen to set
+
+<P><CENTER><SAMP><!--#echo var="VAR_personal-name"-->=$PERSNAME</SAMP></CENTER><P>
+
+the <SAMP>$PERSNAME</SAMP> would be replaced by <SAMP>Fred Flintstone</SAMP>
+so that this would be equivalent to
+
+<P><CENTER><SAMP><!--#echo var="VAR_personal-name"-->=Fred Flintstone</SAMP></CENTER><P>
+
+Note, environment variable substitution happens after configuration
+options that are lists are split into the separate elements of the list,
+so a single environment variable can't contain a list of values.
+
+<P>
+The environment variable doesn't have to be the only thing
+after the equal sign.
+However, if the name of the variable is not at the end of the line or
+followed by a space (so that you can tell where the variable name ends),
+it must be enclosed in curly braces like
+
+<P><CENTER><SAMP>${VAR}</SAMP></CENTER><P>
+
+It is always ok to use the braces even if you don't need to.
+<P>
+It is also possible to set a default value for an environment variable.
+This default value will be used if the environment variable is not
+set (that is, if getenv(&quot;VAR&quot;) returns NULL).
+The syntax used to set a default value is
+
+<P><CENTER><SAMP>${VAR:-default value}</SAMP></CENTER><P>
+
+If the config file contains
+
+<P><CENTER><SAMP>personal-name=${VAR:-Fred Flintstone}</SAMP></CENTER><P>
+
+then when Alpine is run <SAMP>VAR</SAMP> will be looked up in the environment.
+If <SAMP>VAR</SAMP> is found then <SAMP>personal-name</SAMP> will have
+the value that <SAMP>VAR</SAMP> was set to, otherwise,
+<SAMP>personal-name</SAMP> will be set to <SAMP>Fred Flintstone</SAMP>,
+the default value.
+(Note that the variable is called &quot;personal-name&quot; in the config
+file but is displayed in the config screen as
+&quot;<!--#echo var="VAR_personal-name"-->&quot;.
+In general, the value that goes into a config file is never exactly the
+same as the value you see on the screen.)
+
+<P>
+An example where an environment variable might be useful is the
+variable <SAMP>Inbox-Path</SAMP> in the global configuration file.
+Suppose most users used the server
+
+<P><CENTER><SAMP>imapserver.example.com</SAMP></CENTER><P>
+
+but that there were some exceptions who used
+
+<P><CENTER><SAMP>altimapserver.example.com</SAMP></CENTER><P>
+
+In this case, the system manager might include the following line in
+the systemwide default Alpine configuration file
+
+<P><CENTER><SAMP>Inbox-Path=${IMAPSERVER:-imapserver.example.com}</SAMP></CENTER><P>
+
+For the exceptional users adding
+
+<P><CENTER><SAMP>IMAPSERVER=altimapserver.example.com</SAMP></CENTER><P>
+
+to their environment should work.
+<P>
+Another example might be the case where a user has to use a different
+SMTP server from work and from home.
+The setup might be something as simple as
+
+<P><CENTER><SAMP>smtp-server=$SMTP</SAMP></CENTER><P>
+
+or perhaps a default value could be given.
+Note that, as mentioned above, the variable <SAMP>SMTP</SAMP> cannot contain
+a list of SMTP servers.
+<P>
+
+<H2>Configuration precedence</H2>
+
+There are several levels of Alpine configuration. Configuration values at
+a given level override corresponding values at lower levels. In order of
+increasing precedence:
+<P>
+<UL>
+ <LI> built-in defaults
+ <LI> system-wide
+<!--chtml if pinemode="os_windows"-->
+ config file from command line or provided
+ by "PINECONF" environment variable
+<!--chtml else-->
+ pine.conf file
+<!--chtml endif-->
+ <LI> personal configuration file
+ <LI> personal exceptions configuration file
+ <LI> command-line options
+ <!--chtml if pinemode="os_windows"--><!--chtml else-->
+ <LI> system-wide pine.conf.fixed file<!--chtml endif-->
+</UL>
+<P>
+The values in both the personal configuration file and the
+<A HREF="h_config_exceptions">exceptions</A>
+configuration file may be set using the Setup command.
+Setup/Config is the command to change most of the personal configuration
+options.
+The other Setup subcommands are also used to change the configuration,
+for example, Setup/AddressBook, Setup/Rules, and so on.
+Changing the personal exceptions configuration is very similar.
+To change a value in the Config screen you would use the command
+Setup/eXceptions/Config.
+Likewise for the other Setup subcommands (Setup/eXceptions/Rules and so on).
+<P>
+There are a couple exceptions to the rule that configuration values are replaced
+by the value of the same option in a higher-precedence file.
+The Feature-List variable has values that are additive, but can be
+negated by prepending &quot;no-&quot; in front of an individual feature name.
+So for features, each individual feature's value is replaced by the value
+of the same feature in a higher-precedence file.
+Note that this is done automatically for you when you change these values via
+the Setup/Config command.
+The other exception to the <EM>replace</EM> semantics happens when you
+use <A HREF="h_config_inheritance">configuration inheritance</A>
+for option lists.
+<P>
+
+<H2>File name defaults</H2>
+
+Notes:<P>
+
+<BR> &lt;exe dir&gt; = directory where pine.exe found.
+<BR> &lt;pinerc dir&gt; = directory where pinerc found.
+<BR> # = default file name is overridable in pinerc.
+<BR> $HOME, if not explicitly set, defaults to root of the current drive.
+<BR> $MAILCAPS, if set, is used in lieu of the default mailcap search paths.
+<BR> + between the mailcap paths implies that the two files are combined.
+<BR> ; between other default paths implies that the first one found is used.
+</P>
+Alpine looks for most support files in the same directory it finds its
+personal configuration file (pinerc). The -p command-line flag may be
+used to specify a particular path name for the pinerc file. If a
+pinerc file does not exist, it will be created (if directory permissions
+allow). In PC-Alpine, if -p or $PINERC are not defined, Alpine will look
+in $HOME&#92;PINE and the directory containing the PINE.EXE. If a PINERC
+file does not exist in either one, it will create one in the first of those
+two directories that is writable. In detail:
+<PRE>
+
+PC-Alpine:
+
+ executable &lt;DOS search path&gt;&#92;pine.exe
+ help index &lt;exe dir&gt;&#92;pine.ndx
+ help text &lt;exe dir&gt;&#92;pine.hlp
+
+ pers config $PINERC ; $HOME&#92;pine&#92;PINERC ; &lt;exe dir&gt;&#92;PINERC
+ except config $PINERCEX ; $HOME&#92;pine&#92;PINERCEX ; &lt;exe dir&gt;&#92;PINERCEX
+ global cfg $PINECONF
+
+ debug &lt;pinerc dir&gt;&#92;pinedebg.txtN
+ crash &lt;pinerc dir&gt;&#92;pinecrsh.txt
+ signature# &lt;pinerc dir&gt;&#92;pine.sig
+ addressbook# &lt;pinerc dir&gt;&#92;addrbook
+ mailcap# &lt;pinerc dir&gt;&#92;mailcap + &lt;exe dir&gt;&#92;mailcap
+ mimetypes# &lt;pinerc dir&gt;&#92;mimetype + &lt;exe dir&gt;&#92;mimetype
+ newsrc# $HOME&#92;newsrc (if exists, else) &lt;pinerc dir&gt;&#92;newsrc
+ sentmail# $HOME&#92;mail&#92;sentmail.mtx
+ postponed# $HOME&#92;mail&#92;postpond.mtx
+ interrupted $HOME&#92;mail&#92;intruptd
+
+Unix Alpine:
+
+ executable &lt;Unix search path&gt;/pine
+ persnl cfg ~/.pinerc
+ except cfg ~/.pinercex
+ global cfg <!--#echo var="PINE_CONF_PATH"-->
+ fixed cfg <!--#echo var="PINE_CONF_FIXED_PATH"-->
+ local help <!--#echo var="PINE_INFO_PATH"-->
+
+ interrupted ~/.pine-interrupted-mail
+ debug ~/.pine-debugN
+ crash ~/.pine-crash
+ newsrc# ~/.newsrc
+ signature# &lt;pinerc dir&gt;/.signature
+ addressbook# &lt;pinerc dir&gt;/.addressbook
+ postponed# ~/mail/postponed-msgs
+ sentmail# ~/mail/sent-mail
+ mailcap# ~/.mailcap + /etc/mailcap
+ + /usr/etc/mailcap + /usr/local/etc/mailcap
+ mimetypes# ~/.mime.types + /etc/mime.types + /usr/local/lib/mime.types
+
+ news-spool varies across Unix flavors, e.g. /var/spool/news or /usr/spool/news
+ active-news varies across Unix flavors, e.g. /usr/lib/news/active
+ lock files /tmp/.<!--#echo var="MAIL_SPOOL_LOCK_PATH"-->
+ inbox <!--#echo var="MAIL_SPOOL_PATH"-->
+ password /etc/passwd
+
+Unix Alpine and PC-Alpine:
+
+ .ab* remote addressbook support files
+ a[1-9]* temporary (while Alpine is running) addressbook files
+
+</PRE>
+<P>
+
+<H2>Mailcap files</H2>
+
+Alpine honors the mailcap configuration system for specifying external
+programs for handling attachments. The mailcap file maps MIME attachment
+types to the external programs loaded on your system that can display
+and/or print the file. A sample mailcap file comes bundled with the Alpine
+distribution. It includes comments that explain the syntax you need to
+use for mailcap. With the mailcap file, any program (mail readers,
+newsreaders, WWW clients) can use the same configuration for handling
+MIME-encoded data.
+<P>
+
+<H2>MIME-Types files</H2>
+
+Alpine uses mime-types files (.mime.types or MIMETYPE) to determine
+what Content-Type to use for labeling an attached file, based on
+the file extension. That is, this file provides a mapping between
+filename extensions and MIME content-types.
+<P>
+
+<H2>Environment variables</H2>
+
+PC-Alpine uses the following environment variables:
+<DL>
+<DT>PINERC</DT>
+<DD>Optional path to pinerc file.</DD>
+<DT>PINERCEX</DT>
+<DD>Optional path to personal exceptions configuration file.</DD>
+<DT>PINECONF</DT>
+<DD>Optional path to global pine config file.</DD>
+<DT>HOME</DT>
+<DT>TMPDIR, TMP, or TEMP</DT>
+<DT>COMSPEC</DT>
+<DT>MAILCAPS</DT>
+<DD>A <B>semicolon</B> delimited list of path names to mailcap files.</DD>
+<DT>USER_DICTIONARY</DT>
+<DD>Used to specify the file to contain the user's spell check
+dictionary. The default is <SAMP>DICT.U</SAMP> in the same
+directory as the <SAMP>SPELL32.DLL</SAMP></DD>
+</DL>
+
+Unix Alpine uses the following environment variables:
+<DL>
+<DT>TERM</DT>
+<DD>Tells Alpine what kind of terminal is being used.</DD>
+<DT>DISPLAY</DT>
+<DD>Determines if Alpine will try to display IMAGE attachments.</DD>
+<DT>SHELL</DT>
+<DD>If not set, default is &quot;/bin/sh&quot;.</DD>
+<DT>TMPDIR, TMP, or TEMP</DT>
+<DT>MAILCAPS</DT>
+<DD>A <B>colon</B> delimited list of path names to mailcap files.</DD>
+</DL>
+<!--chtml if pinemode="os_windows"-->
+<P>
+<H2>Common PC-Alpine Configuration Problems</H2>
+
+<H3>Configuration settings aren't being saved</H3>
+
+<P>This problem can happen if you run pine from one directory and
+then decide to move your pine directory to another location. PC-Alpine
+stores certain variables, including the configuration location, in the
+Windows Registry (which you shouldn't ever need to manually edit). There
+are a couple of ways to go about removing or resetting the values in the
+registry.
+
+<P>
+1) Run PC-Alpine's registry value deletion command. This can be done by
+running: &quot;&lt;your&nbsp;pine&nbsp;directory&gt;&#92;pine.exe&nbsp;-registry&nbsp;clear&quot; from the DOS
+prompt. You could create a shortcut to pine.exe and change the &quot;Target&quot;
+value to the above command.
+
+<P>
+2) Tell PC-Alpine where to look for the configuration file. Configuration
+information is stored in a file called the PINERC. With the &quot;-p&nbsp;PINERC&quot;
+option, you can tell PC-Alpine the location of your pinerc. An example of
+this would be to run: &quot;&lt;your&nbsp;pine&nbsp;directory&gt;&#92;pine.exe&nbsp;-p&nbsp;C:&#92;pine&#92;mypinerc&quot;.
+Again, you can use the DOS prompt or the shortcut method explained in (1).
+
+<P>
+Additionally, there is the &quot;-registry&nbsp;set&quot; option, which will actively
+set registry values to the current setting, and is therefore useful with
+the &quot;-p&nbsp;PINERC&quot; option.
+
+<!--chtml endif-->
+<P>
+&lt;End of Configuration Notes&gt;
+</BODY>
+</HTML>
+====== h_news_legal ======
+<html>
+<head>
+<TITLE>Alpine Legal Notices</TITLE>
+</head>
+<body>
+
+<H1>Alpine Legal Notices</H1>
+
+Alpine and its constituent programs are covered by the Apache License Version 2.0.
+
+
+<P>
+&lt;End of Alpine Legal Notices&gt;
+</BODY>
+</HTML>
+===== h_info_on_mbox =====
+<HTML>
+<HEAD>
+<TITLE>Information on mbox driver</TITLE>
+</HEAD>
+<BODY>
+<H1>Information on &quot;Missing Mail&quot; and the &quot;mbox&quot; driver</H1>
+
+Beginning with Pine 4.00 (Pine came before Alpine)
+a new INBOX access method is
+available as part of the standard configuration. It is called the
+&quot;mbox&quot; driver and it works like this:<P>
+
+<P>
+<BLOCKQUOTE>
+If the file &quot;mbox&quot; exists in the user's home directory, and
+is in Unix mailbox format, then when INBOX is opened this file will be
+selected as INBOX instead of the mail spool file. Messages will be
+automatically transferred from the mail spool file into the mbox
+file.
+</BLOCKQUOTE>
+
+<P>
+The advantage of this method is that, after new mail has been copied
+from the system mail spool, all subsequent access is confined to the
+user's home directory, which is desirable on some systems. However, a
+possible disadvantage is that mail tools other than those from the
+University of Washington will not know to look for mail in the user's
+mbox file. For example, POP or IMAP servers other than those from the
+University of Washington, and many &quot;new mail notification&quot;
+programs may not work as expected with this driver enabled.<P>
+
+To disable this behavior, either remove/rename the &quot;mbox&quot;
+file or find the <A HREF="h_config_disable_drivers"><!--#echo var="VAR_disable-these-drivers"--></A>
+option in Setup/Config
+and add &quot;mbox&quot; to it:
+<P>
+<CENTER><SAMP><!--#echo var="VAR_disable-these-drivers"-->=mbox</SAMP></CENTER>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_info_on_locking =====
+<HTML>
+<HEAD>
+<TITLE>FAQs on Alpine Locking</TITLE>
+</HEAD>
+<BODY>
+<H1>What Systems Managers Need to Know about Alpine File Locking</H1>
+
+There is an extensive section on locking in the Alpine technical notes;
+this information is intended to provide answers to some common questions:<P>
+<OL>
+<LI> Why did locking change in Pine 4.00?<BR>
+The actual locking mechanisms did not change in 4.00.
+What changed is that when one particular locking mechanism used by Alpine
+fails, Alpine now issues a warning message. Prior to Pine 4.00, the locking
+failure would occur, but no warning was issued.<P>
+
+<LI> Is this what the &quot;Mailbox vulnerable&quot; message is about?<BR>
+Yes. It means that Alpine was unable to create a lockfile in the
+spool directory, generally because of overly restrictive protections on the
+spool directory. The correct permissions on the spool directory for
+running Alpine are 1777, i.e. read-write-execute permission for everyone,
+with the sticky-bit set, so only owners of a file can delete them.<P>
+
+<LI> Why does Alpine require that the mail spool directory have 1777
+ protections?<BR>
+Alpine was designed to run without special privileges. This means that in
+order to create a lockfile in the spool directory, it is necessary to have
+the spool directory permissions be world-writable.<P>
+
+<LI> Can't you create the lockfile somewhere else?<BR>
+No. The lockfile in question must be in the mail spool directory, because
+that's where the mail delivery program expects to find it, and the purpose
+of the file is to coordinate access between the mail client (Alpine) and the
+mail delivery program.<P>
+
+<LI> Isn't having the spool directory world-writable a big security risk?<BR>
+No. Remember that the individual mail files in the spool directory are
+NOT world-writable, only the containing directory. Setting the &quot;sticky
+bit&quot; -- indicated by the &quot;1&quot; before the &quot;777&quot; mode
+-- means that only the owner of the file (or root) can delete files in the
+directory. So the only bad behavior that is invited by the 1777 mode is that
+anyone could
+create a random file in the spool directory. If the spool directory is
+under quota control along with home directories, there is little incentive
+for anyone to do this, and even without quotas a periodic scan for
+non-mail files usually takes care of the problem. <P>
+
+<LI> Why not run Alpine as setgid mail?<BR>
+Alpine was never designed to run with privileges, and to do so introduces a
+significant security vulnerability. For example, if a user suspends Alpine,
+the resulting shell will have group privileges. This is one example of
+why we strongly recommend against running Alpine as a privileged program.
+In addition, a &quot;privileged mailer &quot paradigm would mean that normal
+users
+could not test Alpine versions or other mailers that had not been installed
+by the system administrators.<P>
+
+
+<LI> Are there any alternatives to creating .lock files in the spool dir?<BR>
+There are, but they all have different sets of tradeoffs, and not all will
+work on all systems. Some examples:<UL>
+ <LI> Use lock system calls. Works fine on a few systems, provided mail
+ spool is local. Doesn't work reliably if NFS is used.
+ Doesn't work unless <B>all</B> the mail programs accessing the spool dir
+ use the same calls.
+ <LI> Deliver mail to user's home directory. An excellent solution, highly
+ recommended -- but one which is incompatible with some &quot;legacy&quot;
+mail tools that always look in the spool directory for the mail.
+</UL><P>
+
+<LI> Are these spool directory lock files the only kinds of locks used by
+ Alpine?<BR>
+No. Alpine also creates lockfiles in the /tmp directory. For normal Unix
+mailbox format folders, these are used to coordinate access between
+multiple Alpine sessions. <P>
+
+<LI> What about the
+<A HREF="h_config_quell_lock_failure_warnings">&quot;<!--#echo var="FEAT_quell-lock-failure-warnings"-->&quot;</A> feature added in Pine 4.01?<BR>
+This is for people who are content to live dangerously, or who have
+specific knowledge that the spool directory lockfiles are superfluous on
+their system (because both Alpine and the mail delivery program are using
+system call file locking in a context that works reliably, e.g. not NFS.)<P>
+
+<LI> Where can I find more details on how Alpine locking works?<BR>
+See the Alpine Technical Notes.<P>
+
+</OL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_finding_help ====
+<HTML>
+<HEAD>
+<TITLE>Finding more information and requesting help</TITLE>
+</HEAD>
+<BODY>
+<H1>Places to Look for More Answers</H1>
+If you have questions about or problems with Alpine that you cannot resolve
+after consulting the program's internal, context-sensitive help screens, here
+are additional information resources that you may find helpful:
+<P>
+<UL>
+ <LI> Alpine's top-level <A HREF="main_menu_tx">MAIN MENU HELP</A>.<P>
+
+ <LI> Alpine's <A HREF="h_help_index">Help Index</A>.<P>
+
+ <LI> Alpine's internal <A HREF="h_news">Release Notes</A>. They contain a
+listing of changes in Alpine <!--#echo var="ALPINE_VERSION"-->
+ since the last version, which may be useful for you to be aware of,
+<B>especially</B> if a &quot;problem&quot; you are encountering is actually
+a change in the way an aspect of Alpine works. There, you will also find notes
+on Alpine configuration.<P>
+
+ <LI> The Alpine Information Center (maintained by the University of
+ Washington) World Wide Web site contains, among other things
+ <UL>
+ <LI>a collection of Frequently Asked Questions (and answers!) about Alpine
+ <LI>an overview of the basics for beginning Alpine users
+ <LI>Technical Notes for systems administrators
+ <LI>archives (including a searchable index) of the alpine-info
+mailing list, on which matters of interest to systems/email administrators,
+developers, trainers, user support personnel, and others involved with Alpine
+messaging on a &quot;technical&quot; level are discussed.
+</UL>
+ The Alpine Information Center can be accessed with a WWW browser at:<P>
+ <CENTER><A HREF="http://www.washington.edu/alpine/">http://www.washington.edu/alpine/</A></CENTER>
+</UL>
+<P><HR WIDTH="75%">
+<H1>Requesting help</H1>
+If the internal help, the Release Notes, the Alpine Information Center, and your
+local online and print resources do not help you resolve a problem, please
+start by contacting your local computer support staff and asking for help.
+<p>
+This is especially true if:
+<ul>
+ <li>You suddenly have trouble sending or receiving mail.
+ <li>You receive a "disk quota exceeded" message.
+ <li>You have forgotten your password.
+ <li>You think your account may have been compromised.
+ <li>You need help viewing an attachment.
+ <li>You need to know how to configure your:
+ <A HREF="h_config_nntp_server">NNTP (news) server</A>,
+ <A HREF="h_config_smtp_server">SMTP (sending mail) server</A>,
+ <A HREF="h_config_ldap_server">LDAP (directory lookup) server</A>, or
+ <A HREF="h_config_inbox_path">INBOX (incoming mail) path</A>.
+ <li>You want to know what alternative editors or spellcheckers you may be able to use.
+ <li>You want to block email from a particular person.
+ <li>You're going on vacation and need to autorespond to incoming mail.
+ <li>You want to automatically file or filter incoming messages.
+</ul>
+
+In all of these cases,
+you should contact <B>your</B> support staff, because <B>only they</B>
+will be able to assist you effectively. Your support staff may be, depending on who
+provides you with the email account you use Alpine with, for example:<UL>
+<LI> the computing help desk of (a department of) your university, school,
+employer, ... ; or
+<LI> the customer service center of your Internet Service Provider; or
+<LI> the friendly volunteer helpers of your Freenet; or
+<LI> the person who setup your computer and internet connection.
+</UL>
+
+Due to the large number of Alpine installations worldwide, and because we
+receive no funding for it, the Alpine development team <B>cannot provide
+individual support services outside the University of Washington</B>.
+<P>
+If you have no local computing support to turn to, the worldwide <b>comp.mail.pine</b>
+newsgroup can be a valuable source of information and assistance for Alpine
+user issues.
+<P>
+For systems/email administrators, developers, trainers, user support
+personnel, and others involved with Alpine messaging on a &quot;technical&quot;
+level, the mailing list alpine-info is available; for information on
+subscribing and posting to it, see
+<P>
+<CENTER><A HREF="http://www.washington.edu/alpine/alpine-info/subscribing.html">http://www.washington.edu/alpine/alpine-info/subscribing.html</A></CENTER>
+<P>
+
+Regardless of whom you are asking for help with Alpine, remember
+to provide as much detail as you can about the
+nature of any problem you are encountering, such as
+<UL>
+<LI>when it first occurred;
+<LI>what, if anything, happened that might have brought it about;
+<LI>whether it still persists;
+<LI>whether it is reproducible, and if so, how;
+<LI>what, if anything, you already tried to solve it.
+</UL>
+It may also be helpful if you specify what version of Alpine you are using
+<!--chtml if pinemode="running"-->
+-- this is <!--#echo var="ALPINE_VERSION"--> --
+<!--chtml endif-->
+and on what system, and when the copy of Alpine you are using was created
+<!--chtml if pinemode="running"-->
+-- for this copy: <!--#echo var=ALPINE_COMPILE_DATE-->
+<!--chtml endif-->
+
+<!--chtml if pinemode="running"-->
+<P>
+When the Alpine program you are currently using was installed, a support
+contact email address may have been set up; in that case, you can simply select
+this link now to send a message to it:<BR>
+<A HREF="X-Alpine-Gripe:_LOCAL_ADDRESS_?local"><!--#echo var="_LOCAL_FULLNAME_"--></A><P>
+<!--chtml endif-->
+<!--chtml if [ -r PINE_INFO_PATH ]-->
+<HR WIDTH="75%">Local Support Contacts:<P>
+<!--#include file="PINE_INFO_PATH"-->
+<HR WIDTH="75%">
+<!--chtml endif-->
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== new_user_greeting ======
+<HTML>
+<HEAD>
+<TITLE>NEW USER GREETING</TITLE>
+</HEAD>
+<BODY>
+<CENTER>&lt;&lt;&lt;This message will appear only once&gt;&gt;&gt;</CENTER>
+<BR>
+<H1>Welcome to Alpine ... a Program for Internet News and Email</H1>
+We hope you will explore Alpine's many capabilities. From the MAIN MENU,
+select Setup/Config to see many of the options available to you. Also note
+that all screens have context-sensitive help text available.<P>
+<!--chtml if pinemode="phone_home"-->
+SPECIAL REQUEST:
+This software is made available as a public service of the
+University of Washington in Seattle. We are no longer actively developing
+the software, but it is still helpful to us to have an idea of how many
+people are using Alpine. Are you willing to be counted as an Alpine user? Pressing
+<A HREF="X-Alpine-Phone-Home:">Return</A>
+will send an anonymous (meaning, your real email address will not be revealed)
+message to the Alpine team at the University of Washington for purposes of tallying.
+<P>
+<!--To Exit this screen and continue your Alpine session press "E".-->
+<!--chtml else-->
+To Exit this screen and continue your Alpine session press "Return".
+<!--chtml endif-->
+</BODY>
+</HTML>
+===== new_alpine_user_greeting ======
+<HTML>
+<HEAD>
+<TITLE>NEW ALPINE USER GREETING</TITLE>
+</HEAD>
+<BODY>
+<CENTER>&lt;&lt;&lt;This message will appear only once&gt;&gt;&gt;</CENTER>
+<BR>
+<H1>Welcome to Alpine ... a Program for Internet News and Email</H1>
+Your Alpine configuration file indicates that you may have used Pine before
+but not Alpine.
+If you are familiar with the way Pine works, you should be comfortable
+using Alpine.
+Your Pine configuration file is automatically used for Alpine.
+The Release Notes may be viewed by pressing
+&quot;R&quot; now or while in the MAIN MENU.
+<P>
+<!--chtml if pinemode="phone_home"-->
+SPECIAL REQUEST:
+This software is made available as a public service of the
+University of Washington in Seattle. We are no longer actively developing
+the software, but it is still helpful to us to have an idea of how many
+people are using Alpine. Are you willing to be counted as an Alpine user? Pressing
+<A HREF="X-Alpine-Phone-Home:">Return</A>
+will send an anonymous (meaning, your real email address will not be revealed)
+message to the Alpine team at the University of Washington for purposes of tallying.
+<P>
+<!--To Exit this screen and continue your Alpine session press "E".-->
+<!--chtml else-->
+To Exit this screen and continue your Alpine session press "Return".
+<!--chtml endif-->
+</BODY>
+</HTML>
+===== new_version_greeting ======
+<HTML>
+<HEAD>
+<TITLE>NEW VERSION GREETING</TITLE>
+</HEAD>
+<BODY>
+<CENTER>&lt;&lt;&lt;This message will appear only once&gt;&gt;&gt;</CENTER>
+<BR>
+<H1>Welcome to Alpine version <!--#echo var="ALPINE_VERSION"-->!</H1>
+Your Alpine configuration file indicates that you may not have used
+this version of Alpine before. This version's significant changes are
+documented in the Release Notes, which may be viewed by pressing
+&quot;R&quot; now or while in the MAIN MENU.
+<P>
+<!--chtml if pinemode="phone_home"-->
+SPECIAL REQUEST:
+This software is made available as a public service of the
+University of Washington in Seattle. We are no longer actively developing
+the software, but it is still helpful to us to have an idea of how many
+people are using Alpine. Are you willing to be counted as an Alpine user? Pressing
+<A HREF="X-Alpine-Phone-Home:">Return</A>
+will send an anonymous (meaning, your real email address will not be revealed)
+message to the Alpine team at the University of Washington for purposes of tallying.
+
+<!--To Exit this screen and continue your Alpine session press "E".-->
+<!--chtml else-->
+To Exit this screen and continue your Alpine session press "Return".
+<!--chtml endif-->
+</BODY>
+</HTML>
+
+===== main_menu_tx ======
+<HTML>
+<HEAD>
+<TITLE>GENERAL INFORMATION ON THE ALPINE MESSAGE SYSTEM</TITLE>
+</HEAD>
+<BODY>
+<H1>GENERAL INFORMATION ON THE ALPINE MESSAGE SYSTEM</H1>
+<DIV ALIGN=CENTER>
+Version <!--#echo var="ALPINE_VERSION"-->
+<!--chtml if pinemode="running"-->
+<BR>(built <!--#echo var=ALPINE_COMPILE_DATE-->)
+<!--chtml endif-->
+</DIV>
+<CENTER>Copyright 2006-2008 University of Washington
+<BR> Copyright 2013 Eduardo Chappa
+</CENTER>
+
+<P>
+When you are viewing a help screen, there may be links to
+other topics highlighted (in Reverse video) in the text.
+Here is an example.
+The word &quot;Introduction&quot; in the TABLE OF CONTENTS below should be
+highlighted.
+If you type carriage return (or V for View Link, see the commands at the
+bottom of the screen) you will be taken to a new help screen to view the
+Introduction.
+The commands at the bottom of the screen should then include
+&quot;P Prev Help&quot;.
+If you type &quot;P&quot; you will end up back here.
+If you type &quot;E&quot; for Exit, you will be back out of help and returned
+to the place you were in Alpine when you entered Help.
+In this case, you would go back to the MAIN MENU.
+There are also other links that are highlighted in bold (or the color used
+by your terminal to display bold).
+The items after the Introduction in the TABLE OF CONTENTS are all examples
+of such links.
+In order to view those links, you first have to make the link you want
+to view the current link.
+The &quot;NextLink&quot; and &quot;PrevLink&quot; commands
+(see bottom of screen) can do that for you.
+<P>
+
+<H2>TABLE OF CONTENTS</H2>
+<OL>
+ <LI> <A HREF="h_mainhelp_intro">Introduction</A>
+ <LI> <A HREF="h_mainhelp_pinehelp">Alpine Help</A>
+<!--chtml if [ -r PINE_INFO_PATH ]-->
+ <LI> <A HREF="h_mainhelp_localsupport">Local Support Contacts</A>
+<!--chtml endif-->
+ <LI> <A HREF="h_mainhelp_cmds">Giving Commands in Alpine</A>
+ <LI> <A HREF="h_mainhelp_config">Alpine Configuration</A>
+ <LI> <A HREF="h_mainhelp_status">Titlebar Line</A>
+ <LI> <A HREF="h_mainhelp_mainmenu">Main Menu</A>
+ <LI> <A HREF="h_mainhelp_index">Index of Messages</A>
+ <LI> <A HREF="h_mainhelp_reading">Reading Messages</A>
+ <LI> <A HREF="h_mainhelp_composing">Composing Messages</A>
+ <LI> <A HREF="h_mainhelp_readingnews">Reading News</A>
+ <LI> <A HREF="h_mainhelp_abooks">Address Books</A>
+ <LI> <A HREF="h_mainhelp_ldap">LDAP Directories</A>
+ <LI> <A HREF="h_mainhelp_folders">Folders</A>
+ <LI> <A HREF="h_mainhelp_collections">Collection Lists</A>
+ <LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+ <LI> <A HREF="h_mainhelp_color">Color Setup</A>
+ <LI> <A HREF="h_mainhelp_filtering">Filtering</A>
+ <LI> <A HREF="h_mainhelp_roles">Roles</A>
+ <LI> <A HREF="h_mainhelp_patterns">Patterns</A>
+ <LI> <A HREF="h_mainhelp_keywords">Keywords (or Flags, or Labels)</A>
+ <LI> <A HREF="h_mainhelp_mouse">Using a Mouse</A>
+ <LI> <A HREF="h_mainhelp_cmdlineopts">Command Line Options</A>
+ <LI> <A HREF="h_mainhelp_securing">Securing Your Alpine Session</A>
+ <LI> <A HREF="h_mainhelp_smime">S/MIME</A>
+ <LI> <A HREF="h_mainhelp_problems">Reporting Problems</A>
+ <LI> <A HREF="X-Alpine-Config:">Show Supported Options in this Alpine</A>
+ <LI> <A HREF="h_help_index">Index to Alpine's Online Help</A>
+</OL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_intro ======
+<HTML>
+<HEAD>
+<TITLE>Introduction</TITLE>
+</HEAD>
+<BODY>
+<H1>Introduction</H1>
+
+Alpine is an &quot;Alternatively Licensed Program for Internet
+News and Email&quot; produced until 2008 by the University of Washington.
+It is intended to be an easy-to-use program for
+sending, receiving, and filing Internet electronic mail messages and
+bulletin board (Netnews/Usenet) messages. Alpine supports the following
+Internet protocols and specifications: SMTP (Simple Mail Transport Protocol),
+NNTP (Network News Transport Protocol), MIME (Multipurpose Internet Mail
+Extensions), IMAP (Internet Message Access Protocol), and LDAP (Lightweight
+Directory Access Protocol).<p>
+
+Although originally designed for inexperienced email users, Alpine has
+evolved to support many advanced features. There are an ever-growing
+number of configuration and personal-preference options, though which of
+them are available to you is determined by your local system managers.
+
+<H2>WHAT ALPINE DOES...</H2>
+
+Alpine is a &quot;mail user agent&quot; (MUA), which is a program that
+allows you to
+compose and read messages using Internet mail standards. (Whether you
+can correspond with others on the Internet depends on whether or not your
+computer is connected to the Internet.) Alpine also allows reading and
+posting messages on the Internet &quot;net news&quot; system, provided
+that your site operates a suitable news server.
+
+<H2>WHAT ALPINE DOES NOT DO...</H2>
+
+A &quot;mail user agent&quot; such as Alpine is just one part of a
+messaging system. Here are some things that are <B>not</B> done by Alpine,
+but require other programs:<P>
+<UL>
+ <LI> Actual relaying of email... which is done by &quot;message transfer
+agents&quot;.
+ <LI> Vacation messages... automatically responding to incoming messages
+ <LI> Anything to do with &quot;talk&quot;... which has nothing to do with
+email.
+ <LI> Anything to do with &quot;irc&quot;... which has nothing to do with email.
+ <LI> List processing... resending one message to a list of recipients.
+</UL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+
+===== h_mainhelp_pinehelp ======
+<HTML>
+<HEAD>
+<TITLE>Alpine Help</TITLE>
+</HEAD>
+<BODY>
+<H1>Alpine Help</H1>
+
+Alpine help is generally context-sensitive. In other words, each Alpine screen you
+use will have its own help text, explaining the choices available for that
+screen. This general help section, on the other hand, attempts to give an
+overall picture of what Alpine is capable of doing, as well as pointers to
+additional help sections about specific topics.<p>
+
+Much of the help text contains links to further help topics, similar to
+how the World Wide Web works.
+You may choose a link to view using the &quot;NextLink&quot; and
+&quot;PrevLink&quot; commands to change the link that is highlighted.
+The &quot;View Link&quot; command will then show you the highlighted link.
+Similar to the Back button in a web browser, the &quot;Prev Help&quot; command
+will return you to where you were before viewing the link, and &quot;Exit Help&quot
+will return you to the location in Alpine before you asked for help.
+For example, if you are reading this text in Alpine you may return to the
+help table of contents with the &quot;Prev Help&quot; command or you may view the
+Release notes link in the next paragraph and then return here with
+&quot;Prev Help&quot;.
+<P>
+
+In addition to this general help on Alpine, <A HREF="h_news">Release Notes</A>
+on the current Alpine version are also available from the MAIN MENU: Press
+<!--chtml if pinemode="function_key"-->
+&quot;F9&quot;
+<!--chtml else-->
+&quot;R&quot;
+<!--chtml endif-->
+to browse the release notes. These include changes since the last release,
+configuration information, the history of the Alpine
+project, credits, and legal notices.
+
+Alpine files and documentation are available via FTP or WWW:
+<P>
+<CENTER><SAMP><A
+HREF="ftp://ftp.cac.washington.edu/alpine/">ftp://ftp.cac.washington.edu/alpine/</A>
+</SAMP></CENTER>
+or
+<CENTER><SAMP><A
+HREF="http://www.washington.edu/alpine/">http://www.washington.edu/alpine/</A></SAMP
+></CENTER>
+<P>
+
+If you would like to print <EM>all</EM> of Alpine's internal help text
+(not recommended) for a little light bedtime reading, then press
+<!--chtml if pinemode="function_key"-->
+&quot;F12&quot;
+<!--chtml else-->
+&quot;Z&quot;
+<!--chtml endif-->
+now. (This assumes that the
+copy of Alpine you are using has been properly configured for printing
+at your site.)
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_localsupport ======
+<HTML>
+<HEAD>
+<TITLE>Local Support Contacts</TITLE>
+</HEAD>
+<BODY>
+<H1>Local Support Contacts</H1>
+
+<!--chtml if [ -r PINE_INFO_PATH ]-->
+<!--#include file="PINE_INFO_PATH"-->
+<!--chtml else-->
+No Local Support Contacts configured.
+<!--chtml endif-->
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+
+===== h_mainhelp_cmds ======
+<HTML>
+<HEAD>
+<TITLE>Giving Commands in Alpine</TITLE>
+</HEAD>
+<BODY>
+<H1>Giving Commands in Alpine</H1>
+
+Unless configured otherwise
+(<A HREF="h_config_blank_keymenu"><!--#echo var="FEAT_disable-keymenu"--></A>)
+the bottom two lines of the screen are always used to list the
+commands you can give. You press the keys that are highlighted to give
+the command. The commands for getting help and going back to the main
+menu are always present (except when viewing help as you are now).
+<!--chtml if pinemode="function_key"-->
+<!--chtml else-->
+<p>
+Pressing O (meaning &quot;Other Commands&quot;) changes the keys
+you see at the bottom of any screen. In some cases there are 3 or
+even 4 different
+sets of keys that can be seen by using the O key. <EM>All commands are
+active</EM>, even if they are not currently showing at the bottom of your
+screen. In other words, you <EM>never</EM> need to press the O key, except to
+remind yourself of the proper key to press to perform an operation.
+
+<H2>Control Key Commands</H2>
+When composing mail, and in a few other places, in Alpine you
+have to use Control keys. This means pressing the Control key (usually labeled
+&quot;Ctrl&quot;) and the
+letter indicated at the same time. Usually, this is shown with a
+&quot;^&quot; in front of the letter. On some systems, certain control
+characters are intercepted before they get to Alpine. As a work-around,
+you can press the ESCAPE key twice followed by the desired key. For
+example, if Ctrl-O (^O) does not work on your system, try typing
+&quot;ESC ESC O&quot;.
+<!--chtml endif-->
+<H2>Paging Up and Down</H2>
+The &quot;+&quot; and &quot;-&quot; keys are used for
+moving to the next or previous page. The space bar is a synonym for
+&quot;+&quot;. You may also use Ctrl-V to page down and Ctrl-Y to page
+up as you do in the message composer. On screens with a WhereIs (search)
+command, W or Ctrl-W followed by Ctrl-V will move to the bottom of the
+message or list, and W or Ctrl-W followed by Ctrl-Y will move to the top
+of the message or list.
+
+<H2>Return Key</H2>
+The return key is usually a synonym for a frequently used
+command. When viewing a message, there is currently not a default
+command, so RETURN does nothing; when in the index, it is synonymous with
+&quot;view msg&quot;. In the key menu at the bottom of the screen, whatever is
+enclosed in square brackets [] is the same as the return key.
+
+<H2>Control Keys Not Used By Alpine</H2>
+Most commands in Alpine are single letters, with -- we hope -- some mnemonic
+value, but in places where Alpine is expecting text input, e.g. in the composer or
+at prompts for file/folder names, control keys must be used for editing and
+navigation functions.
+<P>
+
+Alpine has used nearly all the control keys available. There are, however,
+certain control keys that are reserved by other programs or for technical
+reasons. Alpine does not use any of these keys:
+<DL>
+ <DT>Ctrl-S</DT> <DD>Used by Unix as &quot;stop output&quot;</DD>
+ <DT>Ctrl-Q</DT> <DD>Used by Unix as &quot;resume output&quot;</DD>
+ <DT>Ctrl-]</DT> <DD>Often used by Telnet as escape key</DD>
+</DL>
+<P>
+Note: Ctrl-S and Ctrl-Q can be subject to
+<A HREF="h_special_xon_xoff">special handling</A>.
+<P>
+In addition, while the ESC key alone is not used for command input,
+Alpine will recognize two consecutive ESC key presses followed by a letter
+key as a substitute for control key input. For example, the control key
+<SAMP>Ctrl-X</SAMP> can alternatively be entered using the
+three keystrokes: <SAMP>ESC&nbsp;ESC&nbsp;x</SAMP>.
+This is useful if the communication program you are using
+(e.g. Telnet) has its own, conflicting, idea of what certain control
+characters mean.
+
+
+<H2>Repainting the Screen</H2>
+Sometimes what is displayed on the screen will be
+incorrect due to noise on the phone line or other causes and you will want
+to repaint the whole screen to make it correct. You can use the Ctrl-L
+command to do this. It never hurts to do it when in doubt.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_status ======
+<HTML>
+<HEAD>
+<TITLE>Titlebar Line</TITLE>
+</HEAD>
+<BODY>
+<H1>Titlebar Line</H1>
+
+The top line of the screen is Alpine's titlebar line. It will always display
+the current version of Alpine and will also convey information about the
+status of the program. This is where you look to find out what
+collection, folder and message number is active and where you are in Alpine.
+<P>
+
+If the titlebar line says &quot;READONLY&quot; it means that the open folder
+(typically your INBOX) is &quot;locked&quot; by another mail session --
+most likely a more recent session of Alpine has taken the INBOX lock.
+<P>
+
+If the titlebar line says &quot;CLOSED&quot; it means that you are trying to
+access a
+folder on a remote mail server, and for some reason, communication with
+the mail server has either been lost, or never successfully established.
+This can be a result of trying to open a non-existent folder, or one
+stored on an invalid or non-operational server, or it can mean that Alpine
+has been suspended for more that 30 minutes while accessing a remote mail
+server.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_mainmenu ======
+<HTML>
+<HEAD>
+<TITLE>Main Menu</TITLE>
+</HEAD>
+<BODY>
+<H1>Main Menu</H1>
+
+The Main Menu lists Alpine's main options.
+The key or keys you must type to enter your
+choice are to the left of each option or command name.
+You can type either uppercase or lowercase letters,
+and you should not press &lt;Return&gt; after typing the
+letter (unless you are specifically asking for the default,
+highlighted command).
+<P>
+
+From the Main Menu you can choose to read online help, write (compose) and
+send a message, look at an index of your mail messages, open or maintain
+your mail folders, update your address book, configure Alpine, and quit Alpine.
+There are additional options listed at
+the bottom of the screen as well.
+
+<P>
+The Help command usually returns context-sensitive help information.
+However, in the Main Menu you get the most general help, which includes
+a Table of Contents.
+The last entry in the Table of Contents is an Index of help topics,
+so this is a good place to go if you are having trouble finding how
+to do something.
+
+<H2>Main Menu Commands</H2>
+The Alpine main menu lists the most common Alpine functions. A <a
+href="h_main_menu_commands">full list of these
+commands</a> and what they do is available.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_abooks ======
+<HTML>
+<HEAD>
+<TITLE>Address Books</TITLE>
+</HEAD>
+<BODY>
+<H1>Address Books</H1>
+
+
+As you use email, you can build a list of your regular email correspondents
+in your Alpine
+Address Book. At the Alpine MAIN MENU, press A to see the Address Book List
+screen. Your
+personal address book will be highlighted. Press &lt;Return&gt; to view it.
+You can use the address book to store email addresses for individuals or
+groups, to create easily
+remembered &quot;nicknames&quot; for these addresses, and to quickly retrieve an email
+address when you are composing a message.
+<P>
+There are two ways to add addresses to your address book: you can add them
+manually or take them from messages (by pressing T to access the Take command).
+With either method, you specify nicknames for your correspondents. A single
+address book entry (or nickname) can point to just one email address, or, it can
+point to more than one. When it points to more than one, it is called a
+distribution list. Each distribution list has a nickname, a full name, and a
+list of addresses. These
+addresses may be actual addresses, other nicknames in your address book, or
+other
+distribution lists.
+
+<P>
+Additional information is available in Alpine's online help:
+<ul>
+ <li><a href="h_abook_opened">The Alpine Address Book</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_ldap ======
+<HTML>
+<HEAD>
+<TITLE>LDAP</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP</H1>
+
+LDAP (Lightweight Directory Access Protocol) is a standard means of accessing
+an organization's shared
+directories. Essentially, using LDAP, Alpine is able to find email addresses in
+large address
+books, rather like the White Pages provided by the phone company. As an Alpine
+user, it is not
+necessary to know much about how this works, only how to use it and how to
+configure
+it.
+<P>
+More information on configuring LDAP is available in Alpine's online help:
+<ul>
+ <li><a href="h_direct_config">Setup LDAP Directory Servers</a></li>
+</ul>
+<P>
+Additional help on using LDAP in Alpine is also available:
+<ul>
+ <li><a href="h_ldap_view">LDAP Response View Explained</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_index ======
+<HTML>
+<HEAD>
+<TITLE>Index of Messages</TITLE>
+</HEAD>
+<BODY>
+<H1>Index of Messages</H1>
+
+In Alpine's message index, the selected message is highlighted. The first
+column on the left is blank, shows a &quot;+&quot; if the message was
+sent directly to you (i.e., it is not a
+copy or from a list), or a &quot;-&quot; if you were explicitly Cc'd.
+<P>
+The second column may be blank, or it may contain:
+<ul>
+ <li>"N" if the message is new (unread), </li>
+ <li>"A" if you have answered the message (using the Reply command), </li>
+ <li>"D" if you have marked the message for deletion.</li>
+</ul>
+
+<P>
+Note: If you answer a message as well as mark it deleted (in either order),
+you will only see the &quot;D&quot;.
+
+<P>
+The rest of the columns in the message line show you the message
+number, date sent, sender, size, and subject. For details, press ? (Help).
+The behavior and appearance of the Index screen is highly configurable.
+In the Setup/Config screen search (with the WhereIs command) for options
+that contain the words &quot;index&quot; or &quot;thread&quot; to see
+many of the configuration possibilities.
+In particular, the
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot;
+option may be used to configure the look of the standard MESSAGE INDEX lines
+in many different ways.
+Find <!--#echo var="VAR_index-format"--> in the Setup/Config screen and
+read the help text there for more information.
+<P>
+Most of the commands you need to handle your messages are visible at the
+bottom of the screen, and you can press O (OTHER CMDS) to see additional
+commands that are available.
+You do not need to see these &quot;other commands&quot;
+on the screen to use them. That is, you never need to press O as a prefix
+for any other command.
+
+<P>
+Additional information is available in Alpine's online help:
+<ul>
+ <li><a href="h_mail_index">Message Index Commands</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_reading ======
+<HTML>
+<HEAD>
+<TITLE>Reading Messages</TITLE>
+</HEAD>
+<BODY>
+<H1>Reading Messages</H1>
+
+The message text screen shows you the text of the message along with
+its header. If a message has attachments, those will be listed (but not
+displayed) also. The titlebar line displays information about the currently
+open message, folder and collection. You see the name of the collection
+(if there is one) in angle brackets, then the name of the folder, then the
+message number and finally the position within the current message (in
+percent). If the message is marked for deletion
+&quot;DEL&quot; will appear in the
+upper right as well.
+
+<P>
+As with every Alpine screen, the bottom two lines show you the commands
+available.
+
+<P>Additional information is available in Alpine's online help:
+<ul>
+ <li><a href="h_mail_view">Message Text Screen</a></li>
+ <li><a href="h_attachment_screen">Attachment Index Screen Explained</a></li>
+ <li><a href="h_mail_text_att_view">Attachment View Screen Explained</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_composing ======
+<HTML>
+<HEAD>
+<TITLE>Composing Messages</TITLE>
+</HEAD>
+<BODY>
+<H1>Composing Messages</H1>
+
+To write a message, press C (Compose). You see the Compose Message
+screen, which is divided into two parts: the header area and the message
+text area. The header area is where information on the recipient (the To:
+field) and the subject line go, while the message text area contains the
+actual text of the email message. Different commands are available to you
+when your cursor is in different areas on this screen. To see additional
+help on commands in either the message text or header area, type
+<Control>G (Get help).
+
+<P>
+To move around, use the arrow keys or Ctrl-N (Next line) and Ctrl-P
+(Previous line); to correct typing errors, use &lt;Backspace&gt; or &lt;Delete&gt;.
+
+<P>The following information from Alpine's online help may prove useful:
+<ul>
+ <li><a href="h_composer_to">Message Header Commands</a></li>
+ <li><a href="h_compose_richhdr">Rich Header Command</a></li>
+ <li><a href="h_composer">Composer Commands</a></li>
+ <li><a href="h_edit_nav_cmds">Composer Editing Commands</a></li>
+ <li><a href="h_config_change_your_from">Changing your From Address</a></li>
+ <li><a href="h_compose_send">Send Command</a></li>
+ <li><a href="h_compose_spell">Spell Check Command</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_collections ======
+<HTML>
+<HEAD>
+<TITLE>Collection Lists</TITLE>
+</HEAD>
+<BODY>
+<H1>Collection Lists</H1>
+
+Collection lists are Alpine's way of organizing groups of folders. Each
+"collection" can reside on a different server, for example, and contain a
+different group of mail folders.
+
+<P>
+For more information on this, see:
+<ul>
+ <li><a href="h_what_are_collections">Folder Collections Explained</a></li>
+</ul>
+<P>
+Additional information relating to collection lists is also available in
+Alpine's online
+help:
+<ul>
+ <li><a href="h_collection_maint">Setup Collection List Screen</a></li>
+ <li><a href="h_collection_screen">Collection List Screen</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_folders ======
+<HTML>
+<HEAD>
+<TITLE>Folders</TITLE>
+</HEAD>
+<BODY>
+<H1>Folders</H1>
+
+Messages can quickly accumulate in your INBOX folder. If you use email
+often, you soon could have hundreds. You need to delete messages you do
+not want, and you can use folders to organize messages you wish to save. A
+folder is a collection of one or more messages that are stored (just like
+the messages in your INBOX) so you can access and manage them.
+
+<P>
+You can organize your email messages into different folders by topic,
+correspondent, date, or any other category that is meaningful to you. You
+can create your own folders, and Alpine automatically provides three:
+<ul>
+ <li>The INBOX folder: messages sent to you are listed in this folder.
+ When you first start Alpine and go to the Message Index screen, you are
+looking at the list of messages in your INBOX folder. Every incoming
+message remains in your INBOX until you delete it or save it in another
+folder. </li>
+ <li>The sent-mail folder: copies of messages you send are stored in this
+folder. This is
+convenient if you cannot remember whether you actually sent a message and want
+to check, or
+if you want to send a message again.</li>
+ <li>The saved-messages folder: copies of messages you save are stored in this
+folder
+unless you choose to save them to other folders you create yourself.</li>
+</ul>
+
+<P>
+More information about folders is available in Alpine's online help:
+<ul>
+ <li><a href="h_folder_open">Explanation of Folder Selection</a></li>
+ <li><a href="h_folder_maint">Help for Folder List</a></li>
+ <li><a href="h_valid_folder_names">Explanation of Valid Folder Names</a></li>
+ <li><a href="h_folder_fcc">Folder Select for Fcc ("sent-mail")
+Explained</a></li>
+ <li><a href="h_folder_save">Folder Select for Save Explained</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_color ======
+<HTML>
+<HEAD>
+<TITLE>Color</TITLE>
+</HEAD>
+<BODY>
+<H1>Color</H1>
+
+If the terminal emulator you are using is capable of displaying color or if
+you are using PC-Alpine, then it is possible to set up Alpine so that various
+parts of the display will be shown in colors you configure. This is done
+using the Setup Color screen, available from the MAIN MENU by selecting
+the Setup command followed by &quot;K&quot; for Kolor (because &quot;C&quot;
+stands for Config in this context).
+
+<P>
+For example, you may color things like the titlebar, the current item,
+the keymenu, and the status messages.
+You may also color lines in the index, and headers and quoted text in the
+MESSAGE TEXT screen.
+You use the Color Setup screen for configuring most of this, but you must
+use the IndexColor setup for coloring whole index lines.
+These are available from the MAIN MENU under Setup/Kolor and Setup/Rules/IndexColor.
+
+<P>
+The following entries in Alpine's online help provide additional information
+about how to use color:
+<UL>
+ <LI> <A HREF="h_color_setup">Color Setup screen</A>
+ <LI> <A HREF="h_rules_incols">Index Line Color</A>
+ <LI> <A HREF="h_config_quote_color">quoted text</A> in message view
+ <LI> <A HREF="h_config_customhdr_pattern">text associated with user-defined headers</A> in message view
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_mouse ======
+<HTML>
+<HEAD>
+<TITLE>Using a Mouse</TITLE>
+</HEAD>
+<BODY>
+<H1>Using a Mouse</H1>
+
+If you are using PC-Alpine mouse support is turned on automatically.
+If you are using UNIX Alpine within an X terminal window or within
+a terminal emulator that supports an xterm-style mouse, then you may
+turn on support for the mouse with the feature
+<A HREF="h_config_enable_mouse"><!--#echo var="FEAT_enable-mouse-in-xterm"--></A>.
+For UNIX Alpine you will also need to set the $DISPLAY environment variable.
+<P>
+PC-Alpine offers considerable mouse support. You can view what is
+&quot;clickable&quot; by dragging your mouse over any screen; when the
+arrow cursor changes into a hand, you found something. Mouse-click
+possibilities include navigating between screens and folders and
+double-clicking on hyperlinks to open your Web browser.
+Context-sensitive pop-up menus appear with a right-click on your PC-Alpine
+screen. Examples of right-click options include &quot;copy&quot; after
+selecting text to copy and &quot;View in New Window&quot; when you click
+on a particular message in the Message Index. The menu choices available
+to you will vary based upon what screen is open, where on the screen your
+cursor is located, and even what action you have already taken.
+Within a folder, you may set the &quot;Important&quot; flag on any
+message.
+<P>
+X terminal mouse support is more limited but still quite powerful.
+As with PC-Alpine, clicking on any of the commands in the keymenu at
+the bottom of the screen will execute that command as if you typed it.
+Double-clicking on a link, for example the link to the
+<!--#echo var="FEAT_enable-mouse-in-xterm"--> feature in the paragraph above,
+will take you to that link.
+Double-clicking on an index line will view the message, and so on.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_keywords ======
+<HTML>
+<HEAD>
+<TITLE>Keywords</TITLE>
+</HEAD>
+<BODY>
+<H1>Keywords</H1>
+
+Within a folder, you may set the &quot;Important&quot; flag on any
+message.
+This doesn't have any system-defined meaning and is only called
+the Important flag because many users use it to signify that a message
+is important to them in some way.
+<P>
+You may also define your own set of keywords.
+You might know these as user defined flags or as labels.
+These are similar to the Important flag but you choose the names for yourself.
+<P>
+Alpine will only display keywords that
+have been added by you in the Flag Details screen or
+that have been configured by you using the Setup/Config option
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>.
+Keywords set by other means (for example, by another email client) will not
+show up in Alpine unless you configure Alpine to know about them.
+They will show up in the Flag Details screen, but will not show up, for example,
+in the index line.
+
+<P>
+The following entries in Alpine's online help provide additional information
+about how to use keywords:
+<ul>
+ <li><A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--> config option</A></li>
+ <li><A HREF="h_common_flag">Flag command to set keywords</A></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_roles ======
+<HTML>
+<HEAD>
+<TITLE>Roles</TITLE>
+</HEAD>
+<BODY>
+<H1>Roles</H1>
+
+You may play different roles depending on who you are replying to. For
+example, if you are replying to a message addressed to help-desk you may
+be acting as a Help Desk Worker. That role may require that you use a
+different return address and/or a different signature.
+
+<P>
+To configure roles, go to the MAIN MENU and use the Setup command
+followed by &quot;Rules&quot; and then &quot;Roles&quot;.
+The following entries in Alpine's online help provide additional information
+about how to
+use roles:
+<ul>
+ <li><a href="h_rules_roles">Setup Roles Screen</a></li>
+ <li><a href="h_role_select">Roles Screen</a></li>
+</ul>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_filtering ======
+<HTML>
+<HEAD>
+<TITLE>Filtering</TITLE>
+</HEAD>
+<BODY>
+<H1>Filtering</H1>
+
+The software that actually delivers mail (the stuff that happens
+before Alpine is involved) for you is in a better position to do mail filtering
+than Alpine itself.
+If possible, you may want to look into using that sort of mail filtering to
+deliver mail to different folders, delete it, or forward it.
+However, if you'd like Alpine to help with this, Alpine's filtering is for you.
+
+<P>
+Filtering is a way to automatically move certain messages from one folder
+to another or to automatically delete messages.
+You may also automatically set the state (Important, New, Deleted, Answered) of messages
+and set <A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> for messages.
+Alpine doesn't have the ability to forward mail to another address or
+to deliver vacation messages.
+
+<P>
+To configure filtering, go to the MAIN MENU and use the Setup command
+followed by &quot;Rules&quot; and then &quot;Filters&quot;.
+The following entries in Alpine's online help provide additional information
+about how to use filtering:
+<UL>
+ <LI> <A HREF="h_rules_filter">Filtering Setup screen</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_patterns ======
+<HTML>
+<HEAD>
+<TITLE>Patterns</TITLE>
+</HEAD>
+<BODY>
+<H1>Patterns</H1>
+
+Patterns are used with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so it may help you to understand exactly how Patterns work.
+The following entries in Alpine's online help provide information
+about using Patterns:
+<UL>
+ <LI> <A HREF="h_rule_patterns">Patterns</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_cmdlineopts ======
+<HTML>
+<HEAD>
+<TITLE>Command Line Options</TITLE>
+</HEAD>
+<BODY>
+<H1>Command Line Options</H1>
+
+Alpine accepts a number of command line arguments, allowing you, for
+example, to start Alpine and immediately access a particular folder.
+Many of these arguments overlap with options in the Alpine configuration file.
+If there is a difference, then an option set on the command line takes
+precedence.
+Alpine expects command line arguments (other than addresses) to be
+preceded by a &quot;-&quot; (dash) as normally used by UNIX programs.
+A <a href="h_command_line_options">full list</a> of command line
+possibilities is available.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_config ======
+<HTML>
+<HEAD>
+<TITLE>Alpine Configuration</TITLE>
+</HEAD>
+<BODY>
+<H1>Alpine Configuration</H1>
+
+Unless it has been administratively disabled, the Setup command on the
+MAIN MENU has several subcommands that allow you to modify Alpine's behavior.
+The possible subcommands are for general Configuration settings,
+Printer settings, Changing your Password, Signature setup,
+AddressBook setup, Collection Lists setup, Rules (including Roles, Filters,
+Scores, Search, Indexcolor, and Other rules), LDAP Directory setup,
+and Color configuration.
+In particular, the &quot;Config&quot; subcommand has many features you may
+set or unset and many other configuration variables that may be set to change
+the way Alpine works.
+Every one of the hundreds of options available in that configuration settings
+screen has help text associated with it.
+You may read that text by moving the cursor to highlight the option and then
+typing the Help command.
+<P>
+These settings are stored in your personal
+&quot;pinerc&quot; configuration file (or, optionally, they may be stored
+<A HREF="h_config_remote_config">remotely</A>),
+but on shared systems these settings
+may be over-ridden by a system-wide control file (due to local site
+security or support policies). A global pine configuration file can also
+be used to set default values for all Alpine users on a particular system.
+Power users may be interested in splitting their personal configuration
+data into two pieces, a generic piece and
+<A HREF="h_config_exceptions">exceptions</A> which apply to
+a particular platform.
+They may also be interested in <A HREF="h_config_inheritance">configuration inheritance</A>.
+General Alpine configuration information can be found
+<A HREF="h_news_config">here</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_aggops ======
+<HTML>
+<HEAD>
+<TITLE>Aggregate Operations</TITLE>
+</HEAD>
+<BODY>
+<H1>Aggregate Operations</H1>
+
+When you are in the MESSAGE INDEX, the available commands
+(for example, Delete, Undelete, Save, Reply, and so on)
+normally act on a single message.
+So, for example, if you press the Delete command, the currently highlighted
+message is marked Deleted.
+These commands that normally act on a single message may be applied to
+several messages at once instead.
+<P>
+By default this feature is turned on, but it could be administratively turned
+off to reduce complexity.
+The feature
+<A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>
+in the Setup/Config screen is used to turn it off or on.
+When this feature is turned on, the four commands &quot;Select&quot;,
+&quot;SelectCur&quot;, &quot;ZoomMode&quot;, and &quot;Apply&quot;
+are available.
+The two selection commands allow you to mark a set of
+messages as being &quot;selected&quot;.
+The &quot;ZoomMode&quot; command will toggle between
+displaying only the selected messages and displaying all the messages.
+The &quot;Apply&quot; command allows you to
+apply one of the regular MESSAGE INDEX commands to all of the selected
+messages instead of to only the highlighted message.
+<P>
+An example aggregate operation would be to catch up when reading
+a news group.
+That is, get rid of all the messages in the news group so that you can
+start fresh.
+The easiest way to do this in Alpine is to use aggregate operations.
+You want to Delete all of the messages in the group.
+You could start at the top and type &quot;D&quot; once for every message.
+A much faster method is to first Select all of the messages in the group,
+and then Delete all of them.
+This would take four keystrokes:
+<P>
+<CENTER><SAMP>; a (to select all messages)</SAMP></CENTER>
+<BR>
+<CENTER><SAMP>a d (to delete all selected messages)</SAMP></CENTER>
+<P>
+Another use of Select is to use it for searching for a particular message
+or set of messages in a large folder.
+You may know that the message was From a certain user.
+You could select all messages from that user to start, and use Zoom to
+look at only those messages.
+If there were still too many messages to look at you could Narrow the
+set of messages further by selecting from all of those messages only
+the ones that were after a certain date, or contained a particular phrase
+in the Subject, or were too a particular address, and so on.
+That may be the end of what you are doing, or you may want to use Apply to
+Save or Forward or Print all of the selected messages.
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>,
+<LI> <A HREF="h_config_auto_unselect"><!--#echo var="FEAT_auto-unselect-after-apply"--></A>.
+<LI> <A HREF="h_config_auto_unzoom"><!--#echo var="FEAT_auto-unzoom-after-apply"--></A>,
+<LI> <A HREF="h_config_auto_zoom"><!--#echo var="FEAT_auto-zoom-after-select"--></A>, and
+<LI> <A HREF="h_config_select_wo_confirm"><!--#echo var="FEAT_select-without-confirm"--></A>.
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_readingnews ======
+<HTML>
+<HEAD>
+<TITLE>Reading News</TITLE>
+</HEAD>
+<BODY>
+<H1>Reading News</H1>
+
+<H2>Background</H2>
+Alpine can read and post to Internet (or USENET) newsgroups, using the same
+commands as for mail. Similar to mailing lists but existing on a larger scale,
+Usenet newsgroups allow groups of people with common interests to discuss
+particular topics. You might find newsgroups related to your career, or you
+might wish to check out the online discussion among the fans of your favorite
+television show.
+
+<H2>Configuring Alpine for Reading News</H2>
+Alpine often arrives
+pre-configured by your system administrator to automatically access the
+newsgroups offered by your organization, Internet Service Provider, or
+school. PC-Alpine users, and those attempting to customize Unix Alpine, will
+need additional details on <a href="h_configuring_news">how to
+configure Alpine to read news</a>.
+
+<H2>Accessing Newsgroups</H2>
+The first step in reading news is to access the newsgroups collections
+screen from Alpine. If everything is configured properly, you should be able
+to do this by first typing L (folder List), then selecting the folder
+collection listed as "News." The actual name of this collection may differ
+from system to system.
+
+<H2>Subscribing to Newsgroups</H2>
+
+Once you have accessed the news collection, you need to subscribe to a
+newsgroup that interests you. Subscribing to a newsgroup means that Alpine
+will keep a record of the newsgroups in which you are interested and which
+articles in those newsgroups have been read.
+
+<H2>Using Newsgroups</H2>
+Alpine uses the similar commands to read news as to read mail. For example,
+the D command marks messages as Deleted (or "Dismissed," if you prefer),
+and the R command Replies to a news posting. Basically, Alpine allows you to
+read news as if it were mail, so you don't need to change the way you
+interact with Alpine.
+<P>
+There is also additional Alpine help available on
+<A HREF="h_reading_news">how to use Alpine to read news</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_securing ======
+<HTML>
+<HEAD>
+<TITLE>Securing your Alpine Session</TITLE>
+</HEAD>
+<BODY>
+<H1>Securing your Alpine Session</H1>
+
+By default, Alpine will attempt to connect to an IMAP server on the normal
+IMAP service port (143).
+If the Alpine you are using has been built to
+support &quot;Transport Layer Security&quot; (TLS)
+and &quot;Secure Sockets Layer&quot; (SSL)
+(check by clicking <A HREF="X-Alpine-Config:">here</A>),
+and the server offers the STARTTLS capability, then a secure (encrypted)
+session will be established.
+<P>
+When you are connected to a remote folder the titlebar will contain a plus sign
+in the far right column if the connection is encrypted using TLS or SSL.
+Similarly, when you are being prompted for a password a plus sign will appear in the prompt
+if the connection is encrypted.
+
+<H2>More Information on Alpine with SSL and TLS</H2>
+<UL>
+<LI> <A HREF="h_release_tlscerts">TLS and SSL Usage Note</A> </LI>
+<LI> <A HREF="h_folder_server_syntax">/SSL</A> option for older servers which support port 993 SSL but not TLS </LI>
+<LI> <A HREF="h_config_alt_auth"><!--#echo var="FEAT_try-alternative-authentication-driver-first"--></A> feature </LI>
+<LI> <A HREF="h_config_quell_ssl_largeblocks"><!--#echo var="FEAT_quell-ssl-largeblocks"--></A> PC-Alpine feature for working around OS SSL-problems</A> </LI>
+</UL>
+<H2>Here are some other security-related features and options</H2>
+<P>
+<UL>
+<LI> <A HREF="h_config_disable_password_caching"><!--#echo var="FEAT_disable-password-caching"--></A> feature to disable password caching </LI>
+<LI> <A HREF="h_config_mailcap_params"><!--#echo var="FEAT_enable-mailcap-param-substitution"--></A> feature </LI>
+<LI> <A HREF="h_config_disable_auths"><!--#echo var="VAR_disable-these-authenticators"--></A> option </LI>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_problems ======
+<HTML>
+<HEAD>
+<TITLE>Reporting Problems</TITLE>
+</HEAD>
+<BODY>
+<H1>Reporting Problems</H1>
+
+We ask that you first read the relevant help screens and then seek
+assistance from your own local support staff. Once you are sure that your
+difficulty is not a local configuration problem, you might look at the
+help section explaining where to look for
+<A HREF="h_finding_help">more information</A> and where to
+get assistance.
+<P>
+
+Please note: Alpine has been adapted to several other operating systems
+besides those directly supported by the University of Washington; see:
+<P>
+<CENTER><A HREF="http://www.washington.edu/alpine/overview/non-UW.html">http://www.washington.edu/alpine/overview/non-UW.html</A></CENTER>
+
+<P>
+Inquiries about these other ports (e.g., VMS and AmigaDOS) should be
+directed to the individual or group that did the adaptation.
+<P>
+
+<ADDRESS>
+ Alpine&nbsp;Development&nbsp;Team&nbsp;&lt;alpine-contact@u.washington.edu&gt;<BR>
+ Computing&nbsp;&amp;&nbsp;Communications<BR>
+ University&nbsp;of&nbsp;Washington<BR>
+ Seattle,&nbsp;WA&nbsp;98195<BR>
+</ADDRESS>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_main_menu_commands ======
+<HTML>
+<HEAD>
+<TITLE>MAIN MENU COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>MAIN MENU COMMANDS</H1>
+
+<!--chtml if pinemode="function_key"-->
+&nbsp;Available&nbsp;Commands&nbsp;--
+&nbsp;Group&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;2<BR>
+&nbsp;------------------------------
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------
+----------<BR>
+&nbsp;F1&nbsp;&nbsp;Show&nbsp;this&nbsp;help&nbsp;text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F1&nbsp;&nbsp;Show&nbsp;this&nbsp;help&nbsp;text<BR>
+&nbsp;F2&nbsp;&nbsp;Show&nbsp;all&nbsp;other&nbsp;available&nbsp;commands&nbsp;&nbsp;&nbsp;F2&nbsp;&nbsp;Show&nbsp;other&nbsp;commands<BR>
+&nbsp;F3&nbsp;&nbsp;Quit&nbsp;Alpine<BR>
+&nbsp;F4&nbsp;&nbsp;Execute&nbsp;current&nbsp;MAIN&nbsp;MENU&nbsp;command&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;<A
+HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;message<BR>
+&nbsp;F5&nbsp;&nbsp;Select&nbsp;previous&nbsp;command&nbsp;up&nbsp;on&nbsp;menu&nbsp;&nbsp;F5&nbsp;&nbsp;<A
+HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;screen<BR>
+&nbsp;F6&nbsp;&nbsp;Select&nbsp;next&nbsp;command&nbsp;down&nbsp;on&nbsp;menu&nbsp;&nbsp;&nbsp;&nbsp;F6&nbsp;&nbsp;<A
+HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;F7&nbsp;&nbsp;<A
+HREF="h_common_index">MESSAGE&nbsp;INDEX</A>&nbsp;screen<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;F8&nbsp;&nbsp;<A
+HREF="h_main_journal">Journal</A>&nbsp;of&nbsp;status&nbsp;messages<BR>
+&nbsp;F9&nbsp;&nbsp;Display&nbsp;<A
+HREF="h_main_release_notes">Release&nbsp;Notes</A>&nbsp;notes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;&nbsp;<A
+HREF="h_main_setup">SETUP</A>&nbsp;menus<BR>
+&nbsp;F10&nbsp;<A
+HREF="h_main_kblock">Lock&nbsp;Keyboard</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A
+HREF="h_main_addrbook">ADDRESS&nbsp;BOOK</A>&nbsp;screen<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;F11&nbsp;<A
+HREF="h_common_role">Compose&nbsp;message&nbsp;using&nbsp;a&nbsp;role</a><BR>
+<!--chtml else-->
+&nbsp;General&nbsp;Alpine&nbsp;Commands&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Main&nbsp;Menu&nbsp;Screen&nbsp;Commands<BR>
+&nbsp;---------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------<BR>
+&nbsp;?&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;O&nbsp;&nbsp;Show&nbsp;all&nbsp;Other&nbsp;available&nbsp;commands<BR>
+&nbsp;C&nbsp;&nbsp;<A
+HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;P&nbsp;&nbsp;Select&nbsp;Previous&nbsp;command&nbsp;up&nbsp;on&nbsp;menu<BR>
+&nbsp;I&nbsp;&nbsp;<A
+HREF="h_common_index">MESSAGE&nbsp;INDEX</A>&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;N&nbsp;&nbsp;Select&nbsp;Next&nbsp;command&nbsp;down&nbsp;on&nbsp;menu<BR>
+&nbsp;L&nbsp;&nbsp;<A
+HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;R&nbsp;&nbsp;Display&nbsp;Alpine&nbsp;<A HREF="h_main_release_notes">Release&nbsp;Notes</A><BR>
+&nbsp;A&nbsp;&nbsp;<A
+HREF="h_main_addrbook">ADDRESS&nbsp;BOOK</A>&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;K&nbsp;&nbsp;<A
+HREF="h_main_kblock">Lock&nbsp;Keyboard</A><BR>
+&nbsp;S&nbsp;&nbsp;<A
+HREF="h_main_setup">SETUP</A>&nbsp;functions&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;G&nbsp;&nbsp;<A
+HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+&nbsp;Q&nbsp;&nbsp;Quit&nbsp;Alpine&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;J&nbsp;&nbsp;<A HREF="h_main_journal">Journal</A>&nbsp;of&nbsp;status&nbsp;messages<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;&nbsp;<A
+HREF="h_common_role">Compose&nbsp;message&nbsp;using&nbsp;a&nbsp;role</a><BR>
+<!--chtml endif-->
+
+<P>
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI> The availability of certain commands (e.g. some of the options under
+SETUP) is determined by Alpine configuration files and system capabilities.
+At some sites, certain commands may not be available due to security or
+support concerns.
+</OL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+
+===== h_command_line_options ======
+<HTML>
+<HEAD>
+<TITLE>COMMAND LINE OPTIONS</TITLE>
+</HEAD>
+<BODY>
+<H1>COMMAND LINE OPTIONS</H1>
+Possible starting arguments for Alpine:
+
+<DL COMPACT>
+
+<DT> <EM>[addresses]</EM>
+
+<DD> Send-to: If you give <EM>Alpine</EM> an argument or arguments which
+do not begin with a dash, <EM>Alpine</EM> treats them as email addresses.
+<EM>Alpine</EM> will startup in
+the composer with a message started to the addresses specified.
+Once the message is sent, the <EM>Alpine</EM> session closes.
+Standard input redirection is allowed.
+Separate multiple addresses with a space between them.
+Addresses are placed in the &quot;To&quot; field only.
+<P>
+
+<DT> &lt; <EM>file</EM>
+
+<DD> <EM>Alpine</EM> will startup in the composer with <EM>file</EM> read
+into the body of the message.
+Once the message is sent, the <EM>Alpine</EM> session closes.
+<P>
+
+<DT> -attach <EM>file</EM>
+
+<DD> Go directly into composer with given file attached.
+<P>
+
+<DT> -attachlist <EM>file-list</EM>
+
+<DD> Go directly into composer with given files attached.
+This must be the last option on the command line.
+<P>
+
+<DT> -attach_and_delete <EM>file</EM>
+
+<DD> Go directly into composer with given file attached, delete when finished.
+<P>
+
+<DT> -aux <EM>local_directory</EM>
+
+<DD> <EM>PC-Alpine</EM> only.
+This tells <EM>PC-Alpine</EM> the local directory to use for storing auxiliary
+files, like debug files, address books, and signature files. The pinerc may
+be local or remote.
+<P>
+
+<DT> -bail
+
+<DD> If the personal configuration file doesn't already exist, exit.
+This might be useful if the configuration file is accessed using some
+remote filesystem protocol. If the remote mount is missing this will cause
+<EM>Alpine</EM> to quit instead of creating a new pinerc.
+<P>
+
+<DT> -c <EM>n</EM>
+
+<DD> When used with the <CODE>-f</CODE> option, apply the <EM>n</EM>th context.
+This is used when there are multiple folder collections (contexts) and you
+want to open a folder not in the primary collection.
+<P>
+
+<DT> -conf
+
+<DD> Configuration: Prints a sample system configuration file to the
+screen or standard output. To generate an initial system configuration
+file, execute
+
+<PRE><CODE>
+ pine -conf > <!--#echo var="PINE_CONF_PATH"-->
+</CODE></PRE>
+<P>
+
+To generate a system configuration file using settings from an old
+system configuration file, execute
+
+<PRE><CODE>
+ pine -P old-pine.conf -conf > <!--#echo var="PINE_CONF_PATH"-->
+</CODE></PRE>
+<P>
+A system configuration file is not required.
+<P>
+
+<DT> -copy_abook &lt;<EM>local_abook_file</EM>&gt; &lt;<EM>remote_abook_folder</EM>&gt;
+
+<DD> Copy an address book file to a remote address book folder.
+If the remote folder doesn't exist, it will be created.
+If it exists but the first message in the folder isn't a remote address
+book header message, the copy will be aborted.
+This flag will not usually be used by a user.
+Instead, the user will create a remote address book from within <EM>Alpine</EM>
+and copy entries from the local address book by using aggregate Save in
+the address book screen.
+<P>
+
+<DT> -copy_pinerc &lt;<EM>local_pinerc_file</EM>&gt; &lt;<EM>remote_pinerc_folder</EM>&gt;
+
+<DD> Copy a pinerc configuration file to a remote pinerc folder.
+If the remote folder doesn't exist, it will be created.
+If it exists but the first message in the folder isn't a remote pinerc
+header message, the copy will be aborted.
+This flag may be useful to users who already have a local pinerc file and
+would like to convert it to a remote pinerc folder and use that instead.
+This gives a way to bootstrap that conversion without having to manually
+reset all of the variables in the remote pinerc folder.
+<P>
+
+<DT> -d <EM>debug-level</EM>
+
+<DD> Debug Level: Sets the level of debugging information written by
+<EM>Alpine</EM>.
+<EM>debug-level</EM> can be set to any integer 0-9.
+A debug level of 0 turns off debugging for the session.
+(Actually there are some levels higher than 9, but you probably don't
+want to see them.)
+<P>
+
+<DT> -d <EM>keywords</EM>
+
+<DD> You may use a more detailed version of the debugging flag to set
+the debug level in separate parts of <EM>Alpine</EM>.
+The possibilities are flush, timestamp, imap=0..4, tcp, numfiles=0..31, and
+verbose=0..9.
+<EM>Flush</EM> causes debugging information to be flushed immediately to
+the debug file as it is written.
+<EM>Verbose</EM> is the general debugging verbosity level.
+<EM>Timestamp</EM> causes timestamps to be added to the debug file, which
+is useful when you are trying to figure out what is responsible for delays.
+<EM>Numfiles</EM> sets the number of debug files saved.
+<EM>Imap</EM> sets the debug level for the debugging statements related
+to the conversation with the IMAP server, and more generally, for the
+debugging related to <EM>Alpine</EM>'s interaction with the C-Client library.
+<EM>Tcp</EM> turns on some TCP/IP debugging.
+<P>
+
+<DT> -f <EM>folder</EM>
+
+<DD> Startup folder: <EM>Alpine</EM> will open this folder in place
+of the standard INBOX.
+<P>
+
+<DT> -F <EM>file</EM>
+
+<DD> Open named text file for viewing and forwarding.
+<P>
+
+<DT> -h
+
+<DD> Help: Prints the list of available command-line arguments to the
+screen.
+<P>
+
+<DT> -i
+
+<DD> <EM>Alpine</EM> will start up in the FOLDER INDEX
+screen instead of the MAIN MENU.
+<P>
+
+Configuration equivalent: <EM><!--#echo var="VAR_initial-keystroke-list"-->=i</EM>.
+<P>
+
+<DT> -I <EM>a,b,c,...</EM>
+
+<DD> Initial Keystrokes: <EM>Alpine</EM> will execute this comma-separated
+sequence of commands upon startup.
+This allows users to get <EM>Alpine</EM> to start in any
+of its menus/screens.
+You cannot include any input to the composer in the initial keystrokes.
+The key &lt;Return&gt; is represented by a ``CR'' in
+the keystroke list; the spacebar is designated by the letters ``SPACE''.
+Control keys are two character sequences beginning with ``^'', such as
+``^I''.
+A tab character is ``TAB''.
+Function keys are ``F1'' - ``F12'' and the arrow keys are ``UP'',
+``DOWN'', ``LEFT'', and ``RIGHT''.
+A restriction is that you can't mix function keys and character keys in this
+list even though you can, in some cases, mix them when running <EM>Alpine</EM>.
+A user can always use only <EM>character</EM> keys in the startup list even
+if he or she is using <EM>function</EM> keys normally, or vice versa.
+If an element in this list is a string of characters surrounded by double
+quotes (&quot;) then it will be expanded into the individual characters in
+the string, excluding the double quotes.
+<P>
+
+Configuration equivalent: <EM><!--#echo var="VAR_initial-keystroke-list"--></EM>
+<P>
+
+<DT> -install
+
+<DD> For <EM>PC-Alpine</EM> only, this option prompts the user for
+some basic information to help with getting properly set up.
+<P>
+
+<DT> -k
+
+<DD> Function-Key Mode: When invoked in this way, <EM>Alpine</EM> expects
+the input of commands to be function-keys.
+Otherwise, commands are linked to the regular character keys.
+<P>
+
+Configuration equivalent: <EM><!--#echo var="FEAT_use-function-keys"--></EM> included in
+<EM>Feature-List</EM>.
+<P>
+
+<DT> -n <EM>n</EM>
+
+<DD> Message-Number: When specified, <EM>Alpine</EM> starts up in the
+FOLDER INDEX screen with the current message being the specified
+message number.
+<P>
+
+<DT> -nosplash
+
+<DD> <EM>PC-Alpine</EM> only.
+This tells <EM>PC-Alpine</EM> not to display the splash screen upon startup.
+This may be helpful for certain troubleshooting or terminal server scenarios.
+<P>
+
+<DT> -o <EM>folder</EM>
+
+<DD> Opens the INBOX (or a folder specified via the -f argument) ReadOnly.
+<P>
+
+<DT> -p <EM>pinerc</EM>
+
+<DD> Uses the named file as the personal configuration file instead of
+<EM>~/.pinerc</EM> or the default PINERC search sequence <EM>PC-Alpine</EM> uses.
+Alpinerc may be either a local file or a remote configuration folder.
+<P>
+
+<DT> -P <EM>pinerc</EM>
+
+<DD> Uses the named file as the system wide configuration file instead of
+<EM><!--#echo var="PINE_CONF_PATH"--></EM> on UNIX, or nothing on <EM>PC-Alpine</EM>.
+Alpinerc may be either a local file or a remote configuration folder.
+<P>
+
+<DT> -passfile <EM>passfile</EM>
+
+<DD> This tells <EM>Alpine</EM> what file should be used as the password file.
+This should be a fully-qualified filename.
+<P>
+
+<DT> -pinerc <EM>file</EM>
+
+<DD> Output fresh pinerc configuration to <EM>file</EM>, preserving the
+settings of variables that the user has made.
+Use <EM>file</EM> set to ``-'' to make output go to standard out.
+<P>
+
+<DT> -r
+
+<DD> Restricted Mode: For UNIX <EM>Alpine</EM> only.
+<EM>Alpine</EM> in restricted mode can only send email to itself.
+Save and export are limited.
+<P>
+
+<DT> -registry <EM>cmd</EM>
+
+<DD> For <EM>PC-Alpine</EM> only, this option affects the values of
+<EM>Alpine</EM>'s registry entries.
+Possible values for <EM>cmd</EM> are set, clear, and dump.
+<EM>Set</EM> will always reset <EM>Alpine</EM>'s registry
+entries according to its current settings.
+<EM>Clear</EM> will clear the registry values.
+<EM>Clearsilent</EM> will clear the registry values without any dialogs.
+<EM>Dump</EM> will display the values of current registry settings.
+Note that the dump command is currently disabled.
+Without the -registry option, <EM>PC-Alpine</EM> will write values into
+the registry only if there currently aren't any values set.
+<P>
+
+<DT> -sort <EM>key</EM>
+
+<DD> Sort-Key: Specifies the order messages will be displayed in for the
+FOLDER INDEX screen.
+<EM>Key</EM> can have the following values:
+arrival, date, subject, orderedsubj, thread, from, size, score, to, cc,
+arrival/reverse, date/reverse, subject/reverse, orderedsubj/reverse, thread/reverse,
+from/reverse, size/reverse, score/reverse, to/reverse, and cc/reverse.
+The default value is &quot;arrival&quot;.
+The <EM>key</EM> value reverse is equivalent to arrival/reverse.
+<P>
+
+Configuration equivalent: <EM><!--#echo var="VAR_sort-key"--></EM>.
+<P>
+
+<DT> -uninstall
+
+<DD> For <EM>PC-Alpine</EM> only, this option removes references to Alpine
+in Windows settings. The registry settings are removed and
+the password cache is cleared.
+<P>
+
+<DT> -url <EM>url</EM>
+
+<DD> Open the given URL.
+<P>
+
+<DT> -v
+
+<DD> Version: Print version information to the screen.
+<P>
+
+<DT> -x <EM>exceptions_config</EM>
+
+<DD> Configuration settings in the exceptions configuration override your normal
+default settings.
+<EM>Exceptions_config</EM> may be either a local file or a remote Alpine configuration folder.
+<P>
+
+<DT> -z
+
+<DD> Enable Suspend: When run with this flag, the key sequence ctrl-z
+will suspend the <EM>Alpine</EM> session.
+<P>
+
+Configuration equivalent: <EM><!--#echo var="FEAT_enable-suspend"--></EM> included in
+<EM>Feature-List</EM>.
+<P>
+
+<DT> -<EM>option</EM>=<EM>value</EM>
+
+<DD> Assign <EM>value</EM> to the config option <EM>option</EM>.
+For example, <EM>-signature-file=sig1</EM> or
+<EM>-Feature-List=signature-at-bottom</EM>.
+Note: Feature-List values are
+additive and features may be preceded with no- to turn them off.
+Also, as a special case, the &quot;Feature-List=&quot; part of that may be
+omitted. For example, <EM>-signature-at-bottom</EM> is equivalent to
+<EM>-Feature-List=signature-at-bottom</EM>.
+<P>
+
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_configuring_news ======
+<HTML>
+<HEAD>
+<TITLE>CONFIGURING NEWS</TITLE>
+</HEAD>
+<BODY>
+<H1>CONFIGURING NEWS</H1>
+Alpine can access news folders in any one of three different ways:
+<DL>
+<DT>REMOTE NNTP</DT>
+<DD>Using the Network News Transport Protocol (NNTP) to
+access news on a remote news server. In this case the newsrc file is
+stored on the machine where Alpine is running.
+
+<P>
+To specify a remote news-collection accessed via NNTP use the
+SETUP/collectionList screen's &quot;Add&quot; command. Set the
+Server: value to the NNTP server's hostname appended with the
+communication method &quot;/service=NNTP&quot;, and set the Path:
+value to the &quot;#news.&quot; namespace (without the quotes). See
+the &quot;<A HREF="h_composer_cntxt_server">Server:</A>&quot; field's
+help text for a more complete explanation of access method, and the
+&quot;<A HREF="h_composer_cntxt_path">Path:</A>&quot; field's help
+text for a more complete explanation of &quot;namespace&quot;.
+<P>
+Instead of specifying a news-collection, you may simply set the
+<A HREF="h_config_nntp_server">NNTP Server</A>
+option, which will cause Alpine to create a default news-collection for you.
+Another NNTP option that may be of interest is
+<A HREF="h_config_nntprange"><!--#echo var="VAR_nntp-range"--></A>.
+
+<DT>REMOTE IMAP</DT>
+<DD>Using the Internet Message Access Protocol (IMAP) to
+access news on a remote news server. In this case, your newsrc file is
+stored on the news server, in your home directory, so you must have an
+account on the news server, but you would be running Alpine on a different
+machine. The news server must be running an IMAPd server process.
+
+<P>
+To specify a remote news-collection accessed via IMAP use the
+SETUP/collectionList screen's &quot;Add&quot; command. Set the
+Server: value to the IMAP server's hostname, and set the Path: value
+to the &quot;#news.&quot; namespace (without the quotes). See the
+&quot;<A HREF="h_composer_cntxt_path">Path:</A>&quot; field's help
+text for a more complete explanation of &quot;namespace&quot;.
+
+</DD>
+
+<DT>LOCAL</DT>
+<DD>Using local file access to the news database. In this
+case, your newsrc file is stored on the news server, in your home
+directory, so you must have an account on the news server, and you would
+be running Alpine on the same machine.
+
+<P>
+To specify a local news-collection use the SETUP/collectionList
+screen's &quot;Add&quot; command. Leave the Server: value blank, and
+set the Path: value to the &quot;#news.&quot; namespace (without the
+quotes). See the &quot;<A HREF="h_composer_cntxt_path">Path:</A>&quot;
+field's help text for a more complete explanation of &quot;namespace&quot;.
+
+</DD>
+</DL>
+
+<P>
+
+NOTE: Should no news-collection be defined as above, Alpine will
+automatically create one using the Setup/Config screen's
+&quot;<!--#echo var="VAR_nntp-server"-->&quot; variable's value if defined. The collection
+will be created as a &quot;Remote NNTP&quot; as described above.
+
+<P>
+
+If you are a PC-Alpine user, either option 1 (NNTP) or option 2 (IMAP) is
+possible. If you don't have an account on the news server, or if the news
+server is not running an IMAP daemon, then you must use NNTP. (If you are not
+sure, ask your service provider, university, or company for help.) In
+this case, your Unix .newsrc file can be transferred to your PC. A good
+place to put it would be in the same directory as your PINERC file, under
+the name NEWSRC, but you can
+<A HREF="h_config_newsrc_path">specify a different location</A>
+via Alpine's Setup/Config screen.
+
+<P>
+Other configuration features related to news are
+<A HREF="h_config_8bit_nntp"><!--#echo var="FEAT_enable-8bit-nntp-posting"--></A>.
+<A HREF="h_config_compose_news_wo_conf"><!--#echo var="FEAT_compose-sets-newsgroup-without-confirm"--></A>,
+<A HREF="h_config_news_uses_recent"><!--#echo var="FEAT_news-approximates-new-status"--></A>,
+<A HREF="h_config_news_cross_deletes"><!--#echo var="FEAT_news-deletes-across-groups"--></A>,
+<A HREF="h_config_news_catchup"><!--#echo var="FEAT_news-offers-catchup-on-close"--></A>,
+<A HREF="h_config_post_wo_validation"><!--#echo var="FEAT_news-post-without-validation"--></A>,
+<A HREF="h_config_read_in_newsrc_order"><!--#echo var="FEAT_news-read-in-newsrc-order"--></A>, and
+<A HREF="h_config_quell_post_prompt"><!--#echo var="FEAT_quell-extra-post-prompt"--></A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_reading_news ======
+<HTML>
+<HEAD>
+<TITLE>READING NEWS</TITLE>
+</HEAD>
+<BODY>
+<H1>READING NEWS</H1>
+
+Alpine uses almost the same commands for manipulating news folders as for
+mail folders. This means, for example, that when you are done with a
+message, you would use &quot;D&quot; to mark it as Deleted (or Dismissed,
+if you prefer.) This &quot;mail-like&quot; behavior differs from that of
+most newsreaders, wherein a message is implicitly dismissed after you have
+looked at it once. We strongly believe that Alpine should offer as much
+consistency as possible between mail and news, so the mail paradigm --
+wherein a message does not magically disappear without explicit action by
+the user -- is used for news as well. <P>
+
+If you answer a message in a news folder, the index view will show the
+&quot;A&quot; flag as usual; but the industry standard file Alpine uses to
+keep track of what news as been read has no way of storing this flag, so
+it will not be preserved across sessions. The Deleted flag is the only
+one that is preserved when you leave and then return to a newsgroup. As an
+additional note on replies, when you Reply to a newsgroup message and say
+you want to reply to all recipients, Alpine will ask if you want to post the
+message to all the newsgroups listed in the original message. <P>
+
+If you would like Alpine to mark more-or-less recent news messages as
+&quot;New&quot;, then set the
+<A HREF="h_config_news_uses_recent">&quot;<!--#echo var="FEAT_news-approximates-new-status"-->&quot;</A>
+feature (which is set by default). This will cause messages after the last one you have marked as
+Deleted to appear with &quot;N&quot; status in the MESSAGE INDEX. The
+&quot;N&quot; status often makes it easier to distinguish later news
+articles from those you've previously seen, but not yet disposed of via
+the &quot;D&quot; key. Note that this is an approximation, not an exact
+record of which messages you have not seen.
+<P>
+
+A frequent operation in news-reading is &quot;catching up&quot; -- that
+is, getting rid of all the messages in the newsgroup so that you can
+&quot;start fresh.&quot; The easiest way to do this in Alpine is via the
+Select command. You would enter the following four keystrokes:
+<tt>;aad</tt> to select all messaged, and then apply the delete (or
+dismiss) command to all of them.
+
+<P>
+There are also additional details on
+<A HREF="h_configuring_news">configuring news</a>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+
+====== h_help_index ======
+<HTML>
+<HEAD>
+<TITLE>Help Index</TITLE>
+</HEAD>
+<BODY>
+<H1>Help Index</H1>
+<ul>
+<li><a href="h_mainhelp_abooks">Address Books</a></li>
+<li><a href="h_abook_top">ADDRESS BOOK LIST COMMANDS</a>
+<li><a href="h_main_addrbook">Address Book Command</a>
+<li><a href="h_abook_view">Address Book View Explained</a>
+<li><a href="h_compose_addrcomplete">Address Completion</a>
+<li><a href="h_abook_select_listmode">Address Listmode Selection from Composer Explained</a>
+<li><a href="h_abook_select_checks">Address Selection from Composer Explained</a>
+<li><a href="h_composer_abook_comment">Addressbook Comment Explained</a>
+<li><a href="h_composer_abook_fcc">Addressbook Fcc Explained</a>
+<li><a href="h_composer_abook_add_folder">Addressbook Folder Name Field Explained</a>
+<li><a href="h_composer_abook_full">Addressbook Fullname Explained</a>
+<li><a href="h_composer_abook_add_nick">Addressbook NickName Field Explained</a>
+<li><a href="h_composer_abook_nick">Addressbook Nickname Explained</a>
+<li><a href="h_abook_select_addr">Addressbook Selection Explained</a>
+<li><a href="h_abook_select_top">Addressbook Selection Navigation Explained</a>
+<li><a href="h_composer_abook_add_server">Addressbook Server Name Field Explained</a>
+<li><a href="h_mainhelp_aggops">Aggregate Operations</a>
+<li><a href="h_mainhelp_config">Alpine Configuration</a>
+<li><a href="h_mainhelp_pinehelp">Alpine Help</a>
+<li><a href="h_news_legal">Alpine Legal Notices</a>
+<li><a href="h_compose_alted">Alt Editor Command</a>
+<li><a href="h_index_cmd_apply">Apply Command</a>
+<li><a href="h_attachment_screen">Attachment Index Screen Explained</a>
+<li><a href="h_mail_text_att_view">Attachment View Screen Explained</a>
+<li><a href="h_mainhelp_filtering">Blocking Messages</a>
+<li><a href="h_composer_browse">BROWSER</a>
+<li><a href="h_common_bounce">Bounce Command</a>
+<li><a href="h_compose_cancel">Cancel Command</a>
+<li><a href="h_config_change_your_from">Changing your From Address</a>
+<li><a href="h_collection_screen">COLLECTION LIST screen</a>
+<li><a href="h_mainhelp_color">Color</a>
+<li><a href="h_composer_ctrl_j">COMPOSER ATTACH</a>
+<li><a href="h_composer">COMPOSER COMMANDS</a>
+<li><a href="h_mainhelp_composing">Composing Messages</a>
+<li><a href="h_mainhelp_collections">Collection Lists</a>
+<li><a href="h_composer_cntxt_nick">Collection Nickname Explained</a>
+<li><a href="h_composer_cntxt_path">Collection Path: Explained</a>
+<li><a href="h_composer_cntxt_server">Collection Server: Explained</a>
+<li><a href="h_composer_cntxt_view">Collection View: Explained</a>
+<li><a href="h_mainhelp_cmdlineopts">Command Line Options</a>
+<li><a href="h_common_compose">Compose Command</a>
+<li><a href="h_edit_nav_cmds">Composer Editing Commands Explained</a>
+<li><a href="h_common_conditional_cmds">Conditional Commands</a>
+<li><a href="h_reply_token_conditionals">Conditional Inclusion of Text for <!--#echo var="VAR_reply-leadin"-->, Signatures, and Templates</a>
+<li><a href="h_mainhelp_config">Configuration</a>
+<li><a href="h_composer_custom_free">CUSTOMIZED HEADER FIELD</a>
+<li><a href="h_config_dflt_color">Default Color</a>
+<li><a href="h_common_delete">Delete and Undelete Commands</a>
+<li><a href="h_composer_qserv_cn">Directory Query Form Explained</a>
+<li><a href="h_special_list_commands">Email List Commands Explained</a>
+<li><a href="h_composer_search">Explanation of Composer Whereis Command </a>
+<li><a href="h_folder_open">Explanation of Folder Selection</a>
+<li><a href="h_special_xon_xoff">Explanation of Alpine's XOFF/XON Handling</a>
+<li><a href="h_valid_folder_names">Explanation of Valid Folder Names</a>
+<li><a href="h_ge_export">Export File Selection</a>
+<li><a href="h_ge_allparts">Export Message File Selection</a>
+<li><a href="h_index_cmd_expunge">Expunge/Exclude Command</a>
+<li><a href="h_info_on_locking">FAQs on Alpine Locking</a>
+<li><a href="h_config_allow_chg_from">FEATURE: <!--#echo var="FEAT_allow-changing-from"--></a>
+<li><a href="h_config_allow_talk">FEATURE: <!--#echo var="FEAT_allow-talk"--></a>
+<li><a href="h_config_alt_compose_menu">FEATURE: <!--#echo var="FEAT_alternate-compose-menu"--></a>
+<li><a href="h_config_alt_role_menu">FEATURE: <!--#echo var="FEAT_alternate-role-menu"--></a>
+<li><a href="h_config_force_low_speed">FEATURE: <!--#echo var="FEAT_assume-slow-link"--></a>
+<li><a href="h_config_auto_read_msgs">FEATURE: <!--#echo var="FEAT_auto-move-read-msgs"--></a>
+<li><a href="h_config_auto_open_unread">FEATURE: <!--#echo var="FEAT_auto-open-next-unread"--></a>
+<li><a href="h_config_auto_unselect">FEATURE: <!--#echo var="FEAT_auto-unselect-after-apply"--></a>
+<li><a href="h_config_auto_unzoom">FEATURE: <!--#echo var="FEAT_auto-unzoom-after-apply"--></a>
+<li><a href="h_config_auto_zoom">FEATURE: <!--#echo var="FEAT_auto-zoom-after-select"--></a>
+<li><a href="h_config_use_boring_spinner">FEATURE: <!--#echo var="FEAT_busy-cue-spinner-only"--></a>
+<li><a href="h_config_check_mail_onquit">FEATURE: <!--#echo var="FEAT_check-newmail-when-quitting"--></a>
+<li><a href="h_config_combined_abook_display">FEATURE: <!--#echo var="FEAT_combined-addrbook-display"--></a>
+<li><a href="h_config_combined_folder_display">FEATURE: <!--#echo var="FEAT_combined-folder-display"--></a>
+<li><a href="h_config_combined_subdir_display">FEATURE: <!--#echo var="FEAT_combined-subdirectory-display"--></a>
+<li><a href="h_config_cancel_confirm">FEATURE: <!--#echo var="FEAT_compose-cancel-confirm-uses-yes"--></a>
+<li><a href="h_config_lame_list_mode">FEATURE: <!--#echo var="FEAT_enable-lame-list-mode"--></a>
+<li><a href="h_config_compose_rejects_unqual">FEATURE: <!--#echo var="FEAT_compose-rejects-unqualified-addrs"--></a>
+<li><a href="h_config_send_filter_dflt">FEATURE: <!--#echo var="FEAT_compose-send-offers-first-filter"--></a>
+<li><a href="h_config_compose_news_wo_conf">FEATURE: <!--#echo var="FEAT_compose-sets-newsgroup-without-confirm"--></a>
+<li><a href="h_config_del_from_dot">FEATURE: <!--#echo var="FEAT_compose-cut-from-cursor"--></a>
+<li><a href="h_config_compose_maps_del">FEATURE: <!--#echo var="FEAT_compose-maps-delete-key-to-ctrl-d"--></a>
+<li><a href="h_config_confirm_role">FEATURE: <!--#echo var="FEAT_confirm-role-even-for-default"--></a>
+<li><a href="h_config_tab_no_prompt">FEATURE: <!--#echo var="FEAT_continue-tab-without-confirm"--></a>
+<li><a href="h_config_dates_to_local">FEATURE: <!--#echo var="FEAT_convert-dates-to-localtime"--></a>
+<li><a href="h_config_copy_to_to_from">FEATURE: <!--#echo var="FEAT_copy-to-address-to-from-if-it-is-us"--></a>
+<li><a href="h_config_del_skips_del">FEATURE: <!--#echo var="FEAT_delete-skips-deleted"--></a>
+<li><a href="h_config_disable_config_cmd">FEATURE: <!--#echo var="FEAT_disable-config-cmd"--></a>
+<li><a href="h_config_disable_index_locale_dates">FEATURE: <!--#echo var="FEAT_disable-index-locale-dates"--></a>
+<li><a href="h_config_disable_kb_lock">FEATURE: <!--#echo var="FEAT_disable-keyboard-lock-cmd"--></a>
+<li><a href="h_config_blank_keymenu">FEATURE: <!--#echo var="FEAT_disable-keymenu"--></a>
+<li><a href="h_config_disable_password_caching">FEATURE: <!--#echo var="FEAT_disable-password-caching"--></a>
+<li><a href="h_config_disable_password_cmd">FEATURE: <!--#echo var="FEAT_disable-password-cmd"--></a>
+<li><a href="h_config_disable_pipes_in_sigs">FEATURE: <!--#echo var="FEAT_disable-pipes-in-sigs"--></a>
+<li><a href="h_config_disable_pipes_in_templates">FEATURE: <!--#echo var="FEAT_disable-pipes-in-templates"--></a>
+<li><a href="h_config_disable_regex">FEATURE: <!--#echo var="FEAT_disable-regular-expression-matching-for-alternate-addresses"--></a>
+<li><a href="h_config_disable_roles_setup">FEATURE: <!--#echo var="FEAT_disable-roles-setup-cmd"--></a>
+<li><a href="h_config_disable_roles_sigedit">FEATURE: <!--#echo var="FEAT_disable-roles-sig-edit"--></a>
+<li><a href="h_config_disable_roles_templateedit">FEATURE: <!--#echo var="FEAT_disable-roles-template-edit"--></a>
+<li><a href="h_config_input_history">FEATURE: <!--#echo var="FEAT_disable-save-input-history"--></a>
+<li><a href="h_config_disable_collate">FEATURE: <!--#echo var="FEAT_disable-setlocale-collate"--></a>
+<li><a href="h_config_disable_shared">FEATURE: <!--#echo var="FEAT_disable-shared-namespaces"--></a>
+<li><a href="h_config_disable_signature_edit">FEATURE: <!--#echo var="FEAT_disable-signature-edit-cmd"--></a>
+<li><a href="h_config_take_fullname">FEATURE: <!--#echo var="FEAT_disable-take-fullname-in-addresses"--></a>
+<li><a href="h_config_take_lastfirst">FEATURE: <!--#echo var="FEAT_disable-take-last-comma-first"--></a>
+<li><a href="h_config_disable_reset_disp">FEATURE: <!--#echo var="FEAT_disable-terminal-reset-for-display-filters"--></a>
+<li><a href="h_config_disable_sender">FEATURE: <!--#echo var="FEAT_disable-sender"--></a>
+<li><a href="h_config_quell_dead_letter">FEATURE: <!--#echo var="FEAT_quell-dead-letter-on-cancel"--></a>
+<li><a href="h_config_quell_flowed_text">FEATURE: <!--#echo var="FEAT_quell-flowed-text"--></a>
+<li><a href="h_downgrade_multipart_to_text">FEATURE: <!--#echo var="FEAT_downgrade-multipart-to-text"--></a>
+<li><a href="h_config_8bit_smtp">FEATURE: <!--#echo var="FEAT_enable-8bit-esmtp-negotiation"--></a>
+<li><a href="h_config_8bit_nntp">FEATURE: <!--#echo var="FEAT_enable-8bit-nntp-posting"--></a>
+<li><a href="h_config_enable_agg_ops">FEATURE: <!--#echo var="FEAT_enable-aggregate-command-set"--></a>
+<li><a href="h_config_enable_alt_ed">FEATURE: <!--#echo var="FEAT_enable-alternate-editor-cmd"--></a>
+<li><a href="h_config_alt_ed_now">FEATURE: <!--#echo var="FEAT_enable-alternate-editor-implicitly"--></a>
+<li><a href="h_config_arrow_nav">FEATURE: <!--#echo var="FEAT_enable-arrow-navigation"--></a>
+<li><a href="h_config_relaxed_arrow_nav">FEATURE: <!--#echo var="FEAT_enable-arrow-navigation-relaxed"--></a>
+<li><a href="h_config_compose_bg_post">FEATURE: <!--#echo var="FEAT_enable-background-sending"--></a>
+<li><a href="h_config_enable_bounce">FEATURE: <!--#echo var="FEAT_enable-bounce-cmd"--></a>
+<li><a href="h_config_cruise_mode">FEATURE: <!--#echo var="FEAT_enable-cruise-mode"--></a>
+<li><a href="h_config_cruise_mode_delete">FEATURE: <!--#echo var="FEAT_enable-cruise-mode-delete"--></a>
+<li><a href="h_config_compose_dsn">FEATURE: <!--#echo var="FEAT_enable-delivery-status-notification"--></a>
+<li><a href="h_config_enable_dot_files">FEATURE: <!--#echo var="FEAT_enable-dot-files"--></a>
+<li><a href="h_config_enable_lessthan_exit">FEATURE: <!--#echo var="FEAT_enable-exit-via-lessthan-command"--></a>
+<li><a href="h_config_fast_recent">FEATURE: <!--#echo var="FEAT_enable-fast-recent-test"--></a>
+<li><a href="h_config_enable_flag">FEATURE: <!--#echo var="FEAT_enable-flag-cmd"--></a>
+<li><a href="h_config_flag_screen_default">FEATURE: <!--#echo var="FEAT_enable-flag-screen-implicitly"--></a>
+<li><a href="h_config_flag_screen_kw_shortcut">FEATURE: <!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"--></a>
+<li><a href="h_config_enable_full_hdr_and_text">FEATURE: <!--#echo var="FEAT_enable-full-header-and-text"--></a>
+<li><a href="h_config_enable_full_hdr">FEATURE: <!--#echo var="FEAT_enable-full-header-cmd"--></a>
+<li><a href="h_config_allow_goto">FEATURE: <!--#echo var="FEAT_enable-goto-in-file-browser"--></a>
+<li><a href="h_config_enable_dot_folders">FEATURE: <!--#echo var="FEAT_enable-dot-folders"--></a>
+<li><a href="h_config_enable_incoming">FEATURE: <!--#echo var="FEAT_enable-incoming-folders"--></a>
+<li><a href="h_config_enable_incoming_checking">FEATURE: <!--#echo var="FEAT_enable-incoming-folders-checking"--></a>
+<li><a href="h_config_enable_jump">FEATURE: <!--#echo var="FEAT_enable-jump-shortcut"--></a>
+<li><a href="h_config_show_delay_cue">FEATURE: <!--#echo var="FEAT_enable-mail-check-cue"--></a>
+<li><a href="h_config_mailcap_params">FEATURE: <!--#echo var="FEAT_enable-mailcap-param-substitution"--></a>
+<li><a href="h_config_enable_mouse">FEATURE: <!--#echo var="FEAT_enable-mouse-in-xterm"--></a>
+<li><a href="h_config_enable_view_addresses">FEATURE: <!--#echo var="FEAT_enable-msg-view-addresses"--></a>
+<li><a href="h_config_enable_view_attach">FEATURE: <!--#echo var="FEAT_enable-msg-view-attachments"--></a>
+<li><a href="h_config_enable_view_arrows">FEATURE: <!--#echo var="FEAT_enable-msg-view-forced-arrows"--></a>
+<li><a href="h_config_enable_view_url">FEATURE: <!--#echo var="FEAT_enable-msg-view-urls"--></a>
+<li><a href="h_config_enable_view_web_host">FEATURE: <!--#echo var="FEAT_enable-msg-view-web-hostnames"--></a>
+<li><a href="h_config_enable_mulnewsrcs">FEATURE: <!--#echo var="FEAT_enable-multiple-newsrcs"--></a>
+<li><a href="h_config_enable_xterm_newmail">FEATURE: <!--#echo var="FEAT_enable-newmail-in-xterm-icon"--></a>
+<li><a href="h_config_enable_newmail_short_text">FEATURE: <!--#echo var="FEAT_enable-newmail-short-text-in-icon"--></a>
+<li><a href="h_config_sub_lists">FEATURE: <!--#echo var="FEAT_enable-partial-match-lists"--></a>
+<li><a href="h_config_enable_y_print">FEATURE: <!--#echo var="FEAT_enable-print-via-y-command"--></a>
+<li><a href="h_config_prefix_editing">FEATURE: <!--#echo var="FEAT_enable-reply-indent-string-editing"--></a>
+<li><a href="h_config_enable_search_and_repl">FEATURE: <!--#echo var="FEAT_enable-search-and-replace"--></a>
+<li><a href="h_config_sigdashes">FEATURE: <!--#echo var="FEAT_enable-sigdashes"--></a>
+<li><a href="h_config_new_thread_blank_subject">FEATURE: <!--#echo var="FEAT_new-thread-on-blank-subject"--></a>
+<li><a href="h_config_can_suspend">FEATURE: <!--#echo var="FEAT_enable-suspend"--></a>
+<li><a href="h_config_enable_tab_complete">FEATURE: <!--#echo var="FEAT_enable-tab-completion"--></a>
+<li><a href="h_config_enable_take_export">FEATURE: <!--#echo var="FEAT_enable-take-export"--></a>
+<li><a href="h_config_enable_role_take">FEATURE: <!--#echo var="FEAT_enable-rules-under-take"--></a>
+<li><a href="h_config_tray_icon">FEATURE: <!--#echo var="FEAT_enable-tray-icon"--></a>
+<li><a href="h_config_enable_pipe">FEATURE: <!--#echo var="FEAT_enable-unix-pipe-cmd"--></a>
+<li><a href="h_config_verbose_post">FEATURE: <!--#echo var="FEAT_enable-verbose-smtp-posting"--></a>
+<li><a href="h_config_expanded_addrbooks">FEATURE: <!--#echo var="FEAT_expanded-view-of-addressbooks"--></a>
+<li><a href="h_config_expanded_distlists">FEATURE: <!--#echo var="FEAT_expanded-view-of-distribution-lists"--></a>
+<li><a href="h_config_expanded_folders">FEATURE: <!--#echo var="FEAT_expanded-view-of-folders"--></a>
+<li><a href="h_config_expose_hidden_config">FEATURE: <!--#echo var="FEAT_expose-hidden-config"--></a>
+<li><a href="h_config_expunge_manually">FEATURE: <!--#echo var="FEAT_expunge-only-manually"--></a>
+<li><a href="h_config_auto_expunge">FEATURE: <!--#echo var="FEAT_expunge-without-confirm"--></a>
+<li><a href="h_config_full_auto_expunge">FEATURE: <!--#echo var="FEAT_expunge-without-confirm-everywhere"--></a>
+<li><a href="h_config_no_fcc_attach">FEATURE: <!--#echo var="FEAT_fcc-without-attachments"--></a>
+<li><a href="h_config_force_arrow">FEATURE: <!--#echo var="FEAT_force-arrow-cursor"--></a>
+<li><a href="h_config_forward_as_attachment">FEATURE: <!--#echo var="FEAT_forward-as-attachment"--></a>
+<li><a href="h_config_quell_empty_dirs">FEATURE: <!--#echo var="FEAT_quell-empty-directories"--></a>
+<li><a href="h_config_hide_nntp_path">FEATURE: <!--#echo var="FEAT_hide-nntp-path"--></a>
+<li><a href="h_config_attach_in_reply">FEATURE: <!--#echo var="FEAT_include-attachments-in-reply"--></a>
+<li><a href="h_config_fcc_on_bounce">FEATURE: <!--#echo var="FEAT_fcc-on-bounce"--></a>
+<li><a href="h_config_include_header">FEATURE: <!--#echo var="FEAT_include-header-in-reply"--></a>
+<li><a href="h_config_auto_include_reply">FEATURE: <!--#echo var="FEAT_include-text-in-reply"--></a>
+<li><a href="h_config_incoming_checking_total">FEATURE: <!--#echo var="FEAT_incoming-checking-includes-total"--></a>
+<li><a href="h_config_incoming_checking_recent">FEATURE: <!--#echo var="FEAT_incoming-checking-uses-recent"--></a>
+<li><a href="h_config_add_ldap">FEATURE: <!--#echo var="FEAT_ldap-result-to-addrbook-add"--></a>
+<li><a href="h_config_maildrops_preserve_state">FEATURE: <!--#echo var="FEAT_maildrops-preserve-state"--></a>
+<li><a href="h_config_mark_fcc_seen">FEATURE: <!--#echo var="FEAT_mark-fcc-seen"--></a>
+<li><a href="h_config_mark_for_cc">FEATURE: <!--#echo var="FEAT_mark-for-cc"--></a>
+<li><a href="h_config_mulnews_as_typed">FEATURE: <!--#echo var="FEAT_mult-newsrc-hostnames-as-typed"--></a>
+<li><a href="h_config_news_uses_recent">FEATURE: <!--#echo var="FEAT_news-approximates-new-status"--></a>
+<li><a href="h_config_news_cross_deletes">FEATURE: <!--#echo var="FEAT_news-deletes-across-groups"--></a>
+<li><a href="h_config_news_catchup">FEATURE: <!--#echo var="FEAT_news-offers-catchup-on-close"--></a>
+<li><a href="h_config_post_wo_validation">FEATURE: <!--#echo var="FEAT_news-post-without-validation"--></a>
+<li><a href="h_config_read_in_newsrc_order">FEATURE: <!--#echo var="FEAT_news-read-in-newsrc-order"--></a>
+<li><a href="h_config_nntp_search_uses_overview">FEATURE: <!--#echo var="FEAT_nntp-search-uses-overview"--></a>
+<li><a href="h_config_thread_sorts_by_arrival">FEATURE: <!--#echo var="FEAT_thread-sorts-by-arrival"--></a>
+<li><a href="h_config_expunge_inbox">FEATURE: <!--#echo var="FEAT_offer-expunge-of-inbox"--></a>
+<li><a href="h_config_expunge_stayopens">FEATURE: <!--#echo var="FEAT_offer-expunge-of-stayopen-folders"--></a>
+<li><a href="h_config_pass_c1_control">FEATURE: <!--#echo var="FEAT_pass-c1-control-characters-as-is"--></a>
+<li><a href="h_config_pass_control">FEATURE: <!--#echo var="FEAT_pass-control-characters-as-is"--></a>
+<li><a href="h_config_predict_nntp_server">FEATURE: <!--#echo var="FEAT_predict-nntp-server"--></a>
+<li><a href="h_config_prefer_plain_text">FEATURE: <!--#echo var="FEAT_prefer-plain-text"--></a>
+<li><a href="h_config_preopen_stayopens">FEATURE: <!--#echo var="FEAT_preopen-stayopen-folders"--></a>
+<li><a href="h_config_preserve_start_stop">FEATURE: <!--#echo var="FEAT_preserve-start-stop-characters"--></a>
+<li><a href="h_config_quell_folder_internal_msg">FEATURE: <!--#echo var="FEAT_quell-folder-internal-msg"--></a>
+<li><a href="h_config_quell_checks_comp">FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"--></a>
+<li><a href="h_config_quell_checks_comp_inbox">FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-inbox"--></a>
+<li><a href="h_config_quell_partial">FEATURE: <!--#echo var="FEAT_quell-partial-fetching"--></a>
+<li><a href="h_config_quell_local_lookup">FEATURE: <!--#echo var="FEAT_quell-user-lookup-in-passwd-file"--></a>
+<li><a href="h_config_ff_between_msgs">FEATURE: <!--#echo var="FEAT_print-formfeed-between-messages"--></a>
+<li><a href="h_config_print_from">FEATURE: <!--#echo var="FEAT_print-includes-from-line"--></a>
+<li><a href="h_config_print_index">FEATURE: <!--#echo var="FEAT_print-index-enabled"--></a>
+<li><a href="h_config_custom_print">FEATURE: <!--#echo var="FEAT_print-offers-custom-cmd-prompt"--></a>
+<li><a href="h_config_prune_uses_iso">FEATURE: <!--#echo var="FEAT_prune-uses-yyyy-mm"--></a>
+<li><a href="h_config_quell_personal_name_prompt">FEATURE: <!--#echo var="FEAT_quell-personal-name-prompt"--></a>
+<li><a href="h_config_quell_ssl_largeblocks">FEATURE: <!--#echo var="FEAT_quell-ssl-largeblocks"--></a>
+<li><a href="h_config_quell_user_id_prompt">FEATURE: <!--#echo var="FEAT_quell-user-id-prompt"--></a>
+<li><a href="h_config_quit_wo_confirm">FEATURE: <!--#echo var="FEAT_quit-without-confirm"--></a>
+<li><a href="h_config_quote_replace_noflow">FEATURE: <!--#echo var="FEAT_quote-replace-nonflowed"--></a>
+<li><a href="h_config_next_thrd_wo_confirm">FEATURE: <!--#echo var="FEAT_next-thread-without-confirm"--></a>
+<li><a href="h_config_auto_reply_to">FEATURE: <!--#echo var="FEAT_reply-always-uses-reply-to"--></a>
+<li><a href="h_config_inbox_no_confirm">FEATURE: <!--#echo var="FEAT_return-to-inbox-without-confirm"--></a>
+<li><a href="h_config_save_aggregates">FEATURE: <!--#echo var="FEAT_save-aggregates-copy-sequence"--></a>
+<li><a href="h_config_save_part_wo_confirm">FEATURE: <!--#echo var="FEAT_save-partial-msg-without-confirm"--></a>
+<li><a href="h_config_save_advances">FEATURE: <!--#echo var="FEAT_save-will-advance"--></a>
+<li><a href="h_config_save_wont_delete">FEATURE: <!--#echo var="FEAT_save-will-not-delete"--></a>
+<li><a href="h_config_quote_all_froms">FEATURE: <!--#echo var="FEAT_save-will-quote-leading-froms"--></a>
+<li><a href="h_config_scramble_message_id">FEATURE: <!--#echo var="FEAT_scramble-message-id"--></a>
+<li><a href="h_config_select_wo_confirm">FEATURE: <!--#echo var="FEAT_select-without-confirm"--></a>
+<li><a href="h_config_auto_fcc_only">FEATURE: <!--#echo var="FEAT_fcc-only-without-confirm"--></a>
+<li><a href="h_config_send_wo_confirm">FEATURE: <!--#echo var="FEAT_send-without-confirm"--></a>
+<li><a href="h_config_separate_fold_dir_view">FEATURE: <!--#echo var="FEAT_separate-folder-and-directory-entries"--></a>
+<li><a href="h_config_show_cursor">FEATURE: <!--#echo var="FEAT_show-cursor"--></a>
+<li><a href="h_config_textplain_int">FEATURE: <!--#echo var="FEAT_show-plain-text-internally"--></a>
+<li><a href="h_config_select_in_bold">FEATURE: <!--#echo var="FEAT_show-selected-in-boldface"--></a>
+<li><a href="h_config_show_sort">FEATURE: <!--#echo var="FEAT_show-sort"--></a>
+<li><a href="h_config_single_list">FEATURE: <!--#echo var="FEAT_single-column-folder-list"--></a>
+<li><a href="h_config_sig_at_bottom">FEATURE: <!--#echo var="FEAT_signature-at-bottom"--></a>
+<li><a href="h_config_slash_coll_entire">FEATURE: <!--#echo var="FEAT_slash-collapses-entire-thread"--></a>
+<li><a href="h_config_sort_fcc_alpha">FEATURE: <!--#echo var="FEAT_sort-default-fcc-alpha"--></a>
+<li><a href="h_config_sort_save_alpha">FEATURE: <!--#echo var="FEAT_sort-default-save-alpha"--></a>
+<li><a href="h_config_always_spell_check">FEATURE: <!--#echo var="FEAT_spell-check-before-sending"--></a>
+<li><a href="h_config_winpos_in_config">FEATURE: <!--#echo var="FEAT_store-window-position-in-config"--></a>
+<li><a href="h_config_strip_sigdashes">FEATURE: <!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></a>
+<li><a href="h_config_strip_ws_before_send">FEATURE: <!--#echo var="FEAT_strip-whitespace-before-send"--></a>
+<li><a href="h_config_quells_asterisks">FEATURE: <!--#echo var="FEAT_suppress-asterisks-in-password-prompt"--></a>
+<li><a href="h_config_quell_attach_ext_warn">FEATURE: <!--#echo var="FEAT_quell-attachment-extension-warn"--></a>
+<li><a href="h_config_quell_attach_extra_prompt">FEATURE: <!--#echo var="FEAT_quell-attachment-extra-prompt"--></a>
+<li><a href="h_config_no_bezerk_zone">FEATURE: <!--#echo var="FEAT_quell-berkeley-format-timezone"--></a>
+<li><a href="h_config_quell_charset_warning">FEATURE: <!--#echo var="FEAT_quell-charset-warning"--></a>
+<li><a href="h_config_quell_content_id">FEATURE: <!--#echo var="FEAT_quell-content-id"--></a>
+<li><a href="h_config_quell_post_prompt">FEATURE: <!--#echo var="FEAT_quell-extra-post-prompt"--></a>
+<li><a href="h_config_quell_filtering_done_message">FEATURE: <!--#echo var="FEAT_quell-filtering-done-message"--></a>
+<li><a href="h_config_quell_filtering_messages">FEATURE: <!--#echo var="FEAT_quell-filtering-messages"--></a>
+<li><a href="h_config_quell_full_hdr_reset">FEATURE: <!--#echo var="FEAT_quell-full-header-auto-reset"--></a>
+<li><a href="h_config_quell_imap_env">FEATURE: <!--#echo var="FEAT_quell-imap-envelope-update"--></a>
+<li><a href="h_config_quell_lock_failure_warnings">FEATURE: <!--#echo var="FEAT_quell-lock-failure-warnings"--></a>
+<li><a href="h_config_quell_domain_warn">FEATURE: <!--#echo var="FEAT_quell-maildomain-warning"--></a>
+<li><a href="h_config_quell_news_env">FEATURE: <!--#echo var="FEAT_quell-news-envelope-update"--></a>
+<li><a href="h_config_quell_host_after_url">FEATURE: <!--#echo var="FEAT_quell-server-after-link-in-html"--></a>
+<li><a href="h_config_quell_beeps">FEATURE: <!--#echo var="FEAT_quell-status-message-beeping"--></a>
+<li><a href="h_config_quell_tz_comment">FEATURE: <!--#echo var="FEAT_quell-timezone-comment-when-sending"--></a>
+<li><a href="h_config_suppress_user_agent">FEATURE: <!--#echo var="FEAT_suppress-user-agent-when-sending"--></a>
+<li><a href="h_config_tab_checks_recent">FEATURE: <!--#echo var="FEAT_tab-checks-recent"--></a>
+<li><a href="h_config_tab_uses_unseen">FEATURE: <!--#echo var="FEAT_tab-uses-unseen-for-next-folder"--></a>
+<li><a href="h_config_tab_new_only">FEATURE: <!--#echo var="FEAT_tab-visits-next-new-message-only"--></a>
+<li><a href="h_config_termcap_wins">FEATURE: <!--#echo var="FEAT_termdef-takes-precedence"--></a>
+<li><a href="h_config_color_thrd_import">FEATURE: <!--#echo var="FEAT_thread-index-shows-important-color"--></a>
+<li><a href="h_config_alt_auth">FEATURE: <!--#echo var="FEAT_try-alternative-authentication-driver-first"--></a>
+<li><a href="h_config_unsel_wont_advance">FEATURE: <!--#echo var="FEAT_unselect-will-not-advance"--></a>
+<li><a href="h_config_use_current_dir">FEATURE: <!--#echo var="FEAT_use-current-dir"--></a>
+<li><a href="h_config_use_fk">FEATURE: <!--#echo var="FEAT_use-function-keys"--></a>
+<li><a href="h_config_use_reg_start_for_stayopen">FEATURE: <!--#echo var="FEAT_use-regular-startup-rule-for-stayopen-folders"--></a>
+<li><a href="h_config_use_resentto">FEATURE: <!--#echo var="FEAT_use-resent-to-in-rules"--></a>
+<li><a href="h_config_use_sender_not_x">FEATURE: <!--#echo var="FEAT_use-sender-not-x-sender"--></a>
+<li><a href="h_config_suspend_spawns">FEATURE: <!--#echo var="FEAT_use-subshell-for-suspend"--></a>
+<li><a href="h_config_use_system_translation">FEATURE: Use System Translation</a>
+<li><a href="h_config_vertical_list">FEATURE: <!--#echo var="FEAT_vertical-folder-list"--></a>
+<li><a href="h_config_warn_if_fcc_blank">FEATURE: <!--#echo var="FEAT_warn-if-blank-fcc"--></a>
+<li><a href="h_config_warn_if_subj_blank">FEATURE: <!--#echo var="FEAT_warn-if-blank-subject"--></a>
+<li><a href="h_config_warn_if_no_to_or_cc">FEATURE: <!--#echo var="FEAT_warn-if-blank-to-and-cc-and-newsgroups"--></a>
+<li><a href="h_mainhelp_filtering">Filtering</a>
+<li><a href="h_finding_help">Finding more information and requesting help</a>
+<li><a href="h_common_flag">Flag Command</a>
+<li><a href="h_config_quell_flowed_text">Flowed Text</a>
+<li><a href="h_mainhelp_folders">Folders</a>
+<li><a href="h_what_are_collections">Folder Collections Explained</a>
+<li><a href="h_common_folders">Folder List Command</a>
+<li><a href="h_folder_fcc">Folder Select for Fcc Explained</a>
+<li><a href="h_folder_save">Folder Select for Save Explained</a>
+<li><a href="h_folder_server_syntax">Folder Server Name Syntax</a>
+<li><a href="h_config_change_your_from">From Address, Changing</a>
+<li><a href="main_menu_tx">GENERAL INFORMATION ON THE ALPINE MESSAGE SYSTEM</a>
+<li><a href="h_pine_for_windows">GETTING HELP IN ALPINE</a>
+<li><a href="h_common_goto">Goto Command</a>
+<li><a href="h_common_hdrmode">HdrMode Command</a>
+<li><a href="h_mainhelp_pinehelp">Help</a>
+<li><a href="h_special_help_nav">Help Text Navigation Explained</a>
+<li><a href="h_folder_maint">Help for Folder List</a>
+<li><a href="h_valid_folder_names">IMAP</a>
+<li><a href="h_ge_import">Import File Selection</a>
+<li><a href="h_mainhelp_index">Index of Messages</a>
+<li><a href="h_composer_ins_m">INSERT MESSAGE</a>
+<li><a href="h_composer_ins">INSERT TEXT FILE</a>
+<li><a href="h_address_format">INTERNET EMAIL ADDRESS FORMAT</a>
+<li><a href="h_info_on_mbox">Information on mbox driver</a>
+<li><a href="h_mainhelp_intro">Introduction</a>
+<li><a href="h_main_journal">Journal Command</a>
+<li><a href="h_common_jump">Jump Command</a>
+<li><a href="h_compose_justify">Justify Command</a>
+<li><a href="h_main_kblock">Keyboard Lock Command</a>
+<li><a href="h_mainhelp_keywords">Keywords (or Flags, or Labels)</a>
+<li><a href="h_mainhelp_ldap">LDAP</a>
+<li><a href="h_config_ldap_opts_tls">LDAP FEATURE: Attempt-TLS-On-Connection</a>
+<li><a href="h_config_ldap_opts_nosub">LDAP FEATURE: Disable-Ad-Hoc-Space-Substitution</a>
+<li><a href="h_config_ldap_opts_rhs">LDAP FEATURE: Lookup-Addrbook-Contents</a>
+<li><a href="h_config_ldap_opts_tlsmust">LDAP FEATURE: Require-TLS-On-Connection</a>
+<li><a href="h_config_ldap_opts_ref">LDAP FEATURE: Save-Search-Criteria-Not-Result</a>
+<li><a href="h_config_ldap_opts_impl">LDAP FEATURE: Use-Implicitly-From-Composer</a>
+<li><a href="h_config_ldap_binddn">LDAP OPTION: Bind-DN</a>
+<li><a href="h_config_ldap_cust">LDAP OPTION: Custom-Search-Filter</a>
+<li><a href="h_config_ldap_email_attr">LDAP OPTION: EmailAttribute</a>
+<li><a href="h_config_ldap_gn_attr">LDAP OPTION: GivennameAttribute</a>
+<li><a href="h_config_ldap_server">LDAP OPTION: <!--#echo var="VAR_ldap-servers"--></a>
+<li><a href="h_config_ldap_cn_attr">LDAP OPTION: NameAttribute</a>
+<li><a href="h_config_ldap_nick">LDAP OPTION: Nickname</a>
+<li><a href="h_config_ldap_port">LDAP OPTION: Port</a>
+<li><a href="h_config_ldap_base">LDAP OPTION: Search-Base</a>
+<li><a href="h_config_ldap_searchrules">LDAP OPTION: Search-Rule</a>
+<li><a href="h_config_ldap_searchtypes">LDAP OPTION: Search-Type</a>
+<li><a href="h_config_ldap_size">LDAP OPTION: Sizelimit</a>
+<li><a href="h_config_ldap_sn_attr">LDAP OPTION: SurnameAttribute</a>
+<li><a href="h_config_ldap_time">LDAP OPTION: Timelimit</a>
+<li><a href="h_ldap_view">LDAP Response View Explained</a>
+<li><a href="h_maildrop">Mail Drop: What is it?</a>
+<li><a href="h_mainhelp_mainmenu">MAIN MENU</a>
+<li><a href="h_mail_index">MESSAGE INDEX COMMANDS</a>
+<li><a href="h_mail_view">MESSAGE TEXT SCREEN</a>
+<li><a href="h_compose_markcutpaste">Mark, Cut and Paste Commands</a>
+<li><a href="h_common_index">Message Index Command</a>
+<li><a href="h_mainhelp_mouse">Mouse</a>
+<li><a href="h_mainhelp_aggops">Multiple Message Operations</a>
+<li><a href="new_user_greeting">NEW USER GREETING</a>
+<li><a href="new_version_greeting">NEW VERSION GREETING</a>
+<li><a href="h_mainhelp_readingnews">News Reading</a>
+<li><a href="h_folder_subscribe">Newsgroup Subcribe Screen explained</a>
+<li><a href="h_folder_postnews">Newsgroup selecting for Posting explained</a>
+<li><a href="h_common_nextnew">NextNew Command</a>
+<li><a href="h_abook_select_nick">Nickname Selection Explained</a>
+<li><a href="h_mainhelp_readingnews">NNTP</a>
+<li><a href="h_config_address_book">OPTION: <!--#echo var="VAR_address-book"--></a>
+<li><a href="h_config_abook_formats">OPTION: <!--#echo var="VAR_addressbook-formats"--></a>
+<li><a href="h_config_ab_sort_rule">OPTION: <!--#echo var="VAR_addrbook-sort-rule"--></a></a>
+<li><a href="h_config_alt_addresses">OPTION: <!--#echo var="VAR_alt-addresses"--></a>
+<li><a href="h_config_active_msg_interval">OPTION: <!--#echo var="VAR_busy-cue-rate"--></a>
+<li><a href="h_config_color_style">OPTION: Color Style</a>
+<li><a href="h_config_wordseps">OPTION: <!--#echo var="VAR_composer-word-separators"--></a>
+<li><a href="h_config_composer_wrap_column">OPTION: <!--#echo var="VAR_composer-wrap-column"--></a>
+<li><a href="h_config_index_color_style">OPTION: <!--#echo var="VAR_current-indexline-style"--></a>
+<li><a href="h_config_cursor_style">OPTION: Cursor Style</a>
+<li><a href="h_config_custom_hdrs">OPTION: <!--#echo var="VAR_customized-hdrs"--></a>
+<li><a href="h_config_deadlets">OPTION: <!--#echo var="VAR_dead-letter-files"--></a>
+<li><a href="h_config_comp_hdrs">OPTION: <!--#echo var="VAR_default-composer-hdrs"--></a>
+<li><a href="h_config_default_fcc">OPTION: <!--#echo var="VAR_default-fcc"--></a>
+<li><a href="h_config_def_save_folder">OPTION: <!--#echo var="VAR_default-saved-msg-folder"--></a>
+<li><a href="h_config_disable_auths">OPTION: <!--#echo var="VAR_disable-these-authenticators"--></a>
+<li><a href="h_config_disable_drivers">OPTION: <!--#echo var="VAR_disable-these-drivers"--></a>
+<li><a href="h_config_char_set">OPTION: Display Character Set</a>
+<li><a href="h_config_display_filters">OPTION: <!--#echo var="VAR_display-filters"--></a>
+<li><a href="h_config_download_cmd">OPTION: <!--#echo var="VAR_download-command"--></a>
+<li><a href="h_config_download_prefix">OPTION: <!--#echo var="VAR_download-command-prefix"--></a>
+<li><a href="h_config_editor">OPTION: <!--#echo var="VAR_editor"--></a>
+<li><a href="h_config_empty_hdr_msg">OPTION: <!--#echo var="VAR_empty-header-message"--></a>
+<li><a href="h_config_fcc_rule">OPTION: <!--#echo var="VAR_fcc-name-rule"--></a>
+<li><a href="h_config_file_dir">OPTION: File Directory</a>
+<li><a href="h_config_folder_spec">OPTION: <!--#echo var="VAR_folder-collections"--></a>
+<li><a href="h_config_reopen_rule">OPTION: <!--#echo var="VAR_folder-reopen-rule"--></a>
+<li><a href="h_config_fld_sort_rule">OPTION: <!--#echo var="VAR_folder-sort-rule"--></a>
+<li><a href="h_config_font_char_set">OPTION: Font Character Set</a>
+<li><a href="h_config_font_name">OPTION: Font Name</a>
+<li><a href="h_config_font_size">OPTION: Font Size</a>
+<li><a href="h_config_font_style">OPTION: Font Style</a>
+<li><a href="h_config_form_folder">OPTION: <!--#echo var="VAR_form-letter-folder"--></a>
+<li><a href="h_config_glob_addrbook">OPTION: <!--#echo var="VAR_global-address-book"--></a>
+<li><a href="h_config_goto_default">OPTION: <!--#echo var="VAR_goto-default-rule"--></a>
+<li><a href="h_config_header_general_color">OPTION: Header General Color</a>
+<li><a href="h_config_image_viewer">OPTION: <!--#echo var="VAR_image-viewer"--></a>
+<li><a href="h_config_inbox_path">OPTION: <!--#echo var="VAR_inbox-path"--></a>
+<li><a href="h_config_archived_folders">OPTION: <!--#echo var="VAR_incoming-archive-folders"--></a>
+<li><a href="h_config_incoming_interv">OPTION: <!--#echo var="VAR_incoming-check-interval"--></a>
+<li><a href="h_config_incoming_second_interv">OPTION: <!--#echo var="VAR_incoming-check-interval-secondary"--></a>
+<li><a href="h_config_incoming_list">OPTION: <!--#echo var="VAR_incoming-check-list"--></a>
+<li><a href="h_config_incoming_timeo">OPTION: <!--#echo var="VAR_incoming-check-timeout"--></a>
+<li><a href="h_config_incoming_folders">OPTION: <!--#echo var="VAR_incoming-folders"--></a>
+<li><a href="h_config_inc_startup">OPTION: <!--#echo var="VAR_incoming-startup-rule"--></a>
+<li><a href="h_config_incunseen_color">OPTION: Incoming Unseen Color</a>
+<li><a href="h_config_index_arrow_color">OPTION: Index Arrow Color</a>
+<li><a href="h_config_index_color">OPTION: Index Colors</a>
+<li><a href="h_config_index_format">OPTION: <!--#echo var="VAR_index-format"--></a>
+<li><a href="h_config_index_from_color">OPTION: Index From Color</a>
+<li><a href="h_config_index_opening_color">OPTION: Index Opening Color</a>
+<li><a href="h_config_index_pri_color">OPTION: Index Priority Symbol Colors</a>
+<li><a href="h_config_index_subject_color">OPTION: Index Subject Color</a>
+<li><a href="h_config_init_cmd_list">OPTION: <!--#echo var="VAR_initial-keystroke-list"--></a>
+<li><a href="h_config_key_char_set">OPTION: Keyboard Character Set</a>
+<li><a href="h_config_keylabel_color">OPTION: KeyLabel Color</a>
+<li><a href="h_config_keyname_color">OPTION: KeyName Color</a>
+<li><a href="h_config_keywords">OPTION: <!--#echo var="VAR_keywords"--></a>
+<li><a href="h_config_kw_color">OPTION: Keyword Colors</a>
+<li><a href="h_config_kw_braces">OPTION: <!--#echo var="VAR_keyword-surrounding-chars"--></a>
+<li><a href="h_config_prune_date">OPTION: <!--#echo var="VAR_last-time-prune-questioned"--></a>
+<li><a href="h_config_last_vers">OPTION: <!--#echo var="VAR_last-version-used"--></a>
+<li><a href="h_config_literal_sig">OPTION: <!--#echo var="VAR_literal-signature"--></a>
+<li><a href="h_config_mailcheck">OPTION: <!--#echo var="VAR_mail-check-interval"--></a>
+<li><a href="h_config_mailchecknoncurr">OPTION: <!--#echo var="VAR_mail-check-interval-noncurrent"--></a>
+<li><a href="h_config_mailcap_path">OPTION: <!--#echo var="VAR_mailcap-search-path"--></a>
+<li><a href="h_config_maildropcheck">OPTION: <!--#echo var="VAR_maildrop-check-minimum"--></a>
+<li><a href="h_config_maxremstream">OPTION: <!--#echo var="VAR_max-remote-connections"--></a>
+<li><a href="h_config_metamsg_color">OPTION: Meta-Message Color</a>
+<li><a href="h_config_mimetype_path">OPTION: <!--#echo var="VAR_mimetype-search-path"--></a>
+<li><a href="h_config_new_ver_quell">OPTION: <!--#echo var="VAR_new-version-threshold"--></a>
+<li><a href="h_config_fifopath">OPTION: NewMail FIFO Path</a>
+<li><a href="h_config_newmailwidth">OPTION: <!--#echo var="VAR_newmail-window-width"--></a>
+<li><a href="h_config_news_active">OPTION: <!--#echo var="VAR_news-active-file-path"--></a>
+<li><a href="h_config_news_spec">OPTION: <!--#echo var="VAR_news-collections"--></a>
+<li><a href="h_config_news_spool">OPTION: <!--#echo var="VAR_news-spool-directory"--></a>
+<li><a href="h_config_newsrc_path">OPTION: <!--#echo var="VAR_newsrc-path"--></a>
+<li><a href="h_config_nntprange">OPTION: <!--#echo var="VAR_nntp-range"--></a>
+<li><a href="h_config_nntp_server">OPTION: <!--#echo var="VAR_nntp-server"--></HEAD></a>
+<li><a href="h_config_normal_color">OPTION: Normal Color</a>
+<li><a href="h_config_opening_sep">OPTION: <!--#echo var="VAR_opening-text-separator-chars"--></a>
+<li><a href="h_config_oper_dir">OPTION: <!--#echo var="VAR_operating-dir"--></a>
+<li><a href="h_config_pat_old">OPTION: Patterns</a>
+<li><a href="h_config_pat_filts">OPTION: <!--#echo var="VAR_patterns-filters2"--></a>
+<li><a href="h_config_pat_other">OPTION: <!--#echo var="VAR_patterns-other"--></a>
+<li><a href="h_config_pat_roles">OPTION: <!--#echo var="VAR_patterns-roles"--></a>
+<li><a href="h_config_pat_scores">OPTION: <!--#echo var="VAR_patterns-scores2"--></a>
+<li><a href="h_config_pers_name">OPTION: <!--#echo var="VAR_personal-name"--></a>
+<li><a href="h_config_print_cat">OPTION: <!--#echo var="VAR_personal-print-category"--></a>
+<li><a href="h_config_print_command">OPTION: <!--#echo var="VAR_personal-print-command"--></a>
+<li><a href="h_config_post_char_set">OPTION: <!--#echo var="VAR_posting-character-set"--></a>
+<li><a href="h_config_postponed_folder">OPTION: <!--#echo var="VAR_postponed-folder"--></a>
+<li><a href="h_config_print_font_char_set">OPTION: Print-Font-Char-Set</a>
+<li><a href="h_config_print_font_name">OPTION: Print-Font-Name</a>
+<li><a href="h_config_print_font_size">OPTION: Print-Font-Size</a>
+<li><a href="h_config_print_font_style">OPTION: Print-Font-Style</a>
+<li><a href="h_config_printer">OPTION: Printer</a>
+<li><a href="h_config_prompt_color">OPTION: Prompt Color</a>
+<li><a href="h_config_pruned_folders">OPTION: <!--#echo var="VAR_pruned-folders"--></a>
+<li><a href="h_config_pruning_rule">OPTION: <!--#echo var="VAR_pruning-rule"--></a>
+<li><a href="h_config_quote_color">OPTION: Quote Colors</a>
+<li><a href="h_config_quote_replace_string">OPTION: <!--#echo var="VAR_quote-replace-string"--></a>
+<li><a href="h_config_quote_suppression">OPTION: <!--#echo var="VAR_quote-suppression-threshold"--></a>
+<li><a href="h_config_read_message_folder">OPTION: <!--#echo var="VAR_read-message-folder"--></a>
+<li><a href="h_config_remote_abook_history">OPTION: <!--#echo var="VAR_remote-abook-history"--></a>
+<li><a href="h_config_abook_metafile">OPTION: <!--#echo var="VAR_remote-abook-metafile"--></a>
+<li><a href="h_config_remote_abook_validity">OPTION: <!--#echo var="VAR_remote-abook-validity"--></a>
+<li><a href="h_config_reply_indent_string">OPTION: <!--#echo var="VAR_reply-indent-string"--></a>
+<li><a href="h_config_reply_intro">OPTION: <!--#echo var="VAR_reply-leadin"--></a>
+<li><a href="h_config_reverse_color">OPTION: Reverse Color</a>
+<li><a href="h_config_rshcmd">OPTION: <!--#echo var="VAR_rsh-command"--></a>
+<li><a href="h_config_rsh_open_timeo">OPTION: <!--#echo var="VAR_rsh-open-timeout"--></a>
+<li><a href="h_config_rshpath">OPTION: <!--#echo var="VAR_rsh-path"--></a>
+<li><a href="h_config_saved_msg_name_rule">OPTION: <!--#echo var="VAR_saved-msg-name-rule"--></a>
+<li><a href="h_config_scroll_margin">OPTION: <!--#echo var="VAR_scroll-margin"--></a>
+<li><a href="h_config_slctbl_color">OPTION: Selectable Item Color</a>
+<li><a href="h_config_sending_filter">OPTION: <!--#echo var="VAR_sending-filters"--></a>
+<li><a href="h_config_sendmail_path">OPTION: <!--#echo var="VAR_sendmail-path"--></a>
+<li><a href="h_config_signature_color">OPTION: Signature Color</a>
+<li><a href="h_config_signature_file">OPTION: <!--#echo var="VAR_signature-file"--></a>
+<li><a href="h_config_smtp_server">OPTION: <!--#echo var="VAR_smtp-server"--></a>
+<li><a href="h_config_sort_key">OPTION: <!--#echo var="VAR_sort-key"--></a>
+<li><a href="h_config_speller">OPTION: <!--#echo var="VAR_speller"--></a>
+<li><a href="h_config_sshcmd">OPTION: <!--#echo var="VAR_ssh-command"--></a>
+<li><a href="h_config_ssh_open_timeo">OPTION: <!--#echo var="VAR_ssh-open-timeout"--></a>
+<li><a href="h_config_sshpath">OPTION: <!--#echo var="VAR_ssh-path"--></a>
+<li><a href="h_config_status_color">OPTION: Status Color</a>
+<li><a href="h_config_status_msg_delay">OPTION: <!--#echo var="VAR_status-message-delay"--></a>
+<li><a href="h_config_permlocked">OPTION: <!--#echo var="VAR_stay-open-folders"--></a>
+<li><a href="h_config_tcp_open_timeo">OPTION: <!--#echo var="VAR_tcp-open-timeout"--></a>
+<li><a href="h_config_tcp_query_timeo">OPTION: <!--#echo var="VAR_tcp-query-timeout"--></a>
+<li><a href="h_config_tcp_readwarn_timeo">OPTION: <!--#echo var="VAR_tcp-read-warning-timeout"--></a>
+<li><a href="h_config_tcp_writewarn_timeo">OPTION: <!--#echo var="VAR_tcp-write-warning-timeout"--></a>
+<li><a href="h_config_thread_disp_style">OPTION: <!--#echo var="VAR_threading-display-style"--></a>
+<li><a href="h_config_thread_exp_char">OPTION: <!--#echo var="VAR_threading-expanded-character"--></a>
+<li><a href="h_config_thread_index_style">OPTION: <!--#echo var="VAR_threading-index-style"--></a>
+<li><a href="h_config_thread_indicator_char">OPTION: <!--#echo var="VAR_threading-indicator-character"--></a>
+<li><a href="h_config_thread_lastreply_char">OPTION: <!--#echo var="VAR_threading-lastreply-character"--></a>
+<li><a href="h_config_title_color">OPTION: Title Color</a>
+<li><a href="h_config_titleclosed_color">OPTION: Title Closed Color</a>
+<li><a href="h_config_titlebar_color_style">OPTION: <!--#echo var="VAR_titlebar-color-style"--></a>
+<li><a href="h_config_unk_char_set">OPTION: <!--#echo var="VAR_unknown-character-set"--></a>
+<li><a href="h_config_upload_cmd">OPTION: <!--#echo var="VAR_upload-command"--></a>
+<li><a href="h_config_upload_prefix">OPTION: <!--#echo var="VAR_upload-command-prefix"--></a>
+<li><a href="h_config_browser">OPTION: <!--#echo var="VAR_url-viewers"--></a>
+<li><a href="h_config_domain_name">OPTION: <!--#echo var="VAR_use-only-domain-name"--></a>
+<li><a href="h_config_user_dom">OPTION: <!--#echo var="VAR_user-domain"--></a>
+<li><a href="h_config_user_id">OPTION: <!--#echo var="VAR_user-id"--></a>
+<li><a href="h_config_user_input_timeo">OPTION: <!--#echo var="VAR_user-input-timeout"--></a>
+<li><a href="h_config_viewer_headers">OPTION: <!--#echo var="VAR_viewer-hdrs"--></a>
+<li><a href="h_config_customhdr_pattern">OPTION: Viewer Header Color Pattern</a>
+<li><a href="h_config_customhdr_color">OPTION: <!--#echo var="VAR_viewer-hdr-colors"--></a>
+<li><a href="h_config_viewer_margin_left">OPTION: <!--#echo var="VAR_viewer-margin-left"--></a>
+<li><a href="h_config_viewer_margin_right">OPTION: <!--#echo var="VAR_viewer-margin-right"--></a>
+<li><a href="h_config_viewer_overlap">OPTION: <!--#echo var="VAR_viewer-overlap"--></a>
+<li><a href="h_config_window_position">OPTION: Window-Position</a>
+<li><a href="h_mainhelp_patterns">Patterns</a>
+<li><a href="h_config_role_abookfrom">PATTERNS: Address in Address Book</a>
+<li><a href="h_config_role_age">PATTERNS: Age Interval</a>
+<li><a href="h_config_role_alltextpat">PATTERNS: AllText Pattern</a>
+<li><a href="h_config_role_bom">PATTERNS: Beginning of Month</a>
+<li><a href="h_config_role_boy">PATTERNS: Beginning of Year</a>
+<li><a href="h_config_role_bodytextpat">PATTERNS: BodyText Pattern</a>
+<li><a href="h_config_role_cat_cmd">PATTERNS: Categorizer Command</a>
+<li><a href="h_config_role_cat_cmd_example">PATTERNS: Categorizer Command Example</a>
+<li><a href="h_config_role_ccpat">PATTERNS: Cc Pattern</a>
+<li><a href="h_config_role_cat_limit">PATTERNS: Character Limit</a>
+<li><a href="h_config_role_charsetpat">PATTERNS: Character Set Pattern</a>
+<li><a href="h_config_role_comment">PATTERNS: Comment</a>
+<li><a href="h_config_role_fldr_type">PATTERNS: Current Folder Type</a>
+<li><a href="h_config_role_cat_status">PATTERNS: Exit Status Interval</a>
+<li><a href="h_config_role_arbpat">PATTERNS: Extra Headers Pattern</a>
+<li><a href="h_config_role_frompat">PATTERNS: From Pattern</a>
+<li><a href="h_config_role_keywordpat">PATTERNS: Keyword Pattern</a>
+<li><a href="h_config_role_stat_ans">PATTERNS: Message Answered Status</a>
+<li><a href="h_config_role_stat_del">PATTERNS: Message Deleted Status</a>
+<li><a href="h_config_role_stat_imp">PATTERNS: Message Important Status</a>
+<li><a href="h_config_role_stat_new">PATTERNS: Message New Status</a>
+<li><a href="h_config_role_stat_recent">PATTERNS: Message Recent Status</a>
+<li><a href="h_config_role_newspat">PATTERNS: News Pattern</a>
+<li><a href="h_config_role_nick">PATTERNS: Nickname</a>
+<li><a href="h_config_role_particpat">PATTERNS: Participant Pattern</a>
+<li><a href="h_config_role_stat_8bitsubj">PATTERNS: Raw 8-bit in Subject</a>
+<li><a href="h_config_role_recippat">PATTERNS: Recipient Pattern</a>
+<li><a href="h_config_role_scorei">PATTERNS: Score Interval</a>
+<li><a href="h_config_role_senderpat">PATTERNS: Sender Pattern</a>
+<li><a href="h_config_role_size">PATTERNS: Size Interval</a>
+<li><a href="h_config_role_subjpat">PATTERNS: Subject Pattern</a>
+<li><a href="h_config_role_topat">PATTERNS: To Pattern</a>
+<li><a href="h_config_filt_opts_nonterm">PATTERNS FEATURE: Dont-Stop-Even-if-Rule-Matches</a>
+<li><a href="h_config_filt_opts_notdel">PATTERNS FEATURE: Move-Only-if-Not-Deleted</a>
+<li><a href="h_config_filt_opts_sentdate">PATTERNS FEATURE: Use-Date-Header-For-Age</a>
+<li><a href="h_config_filt_rule_type">PATTERNS FILTER ACTION: Filter Action</a>
+<li><a href="h_config_filter_kw_clr">PATTERNS FILTER ACTION: Clear These Keywords</a>
+<li><a href="h_config_filt_stat_ans">PATTERNS FILTER ACTION: Set Answered Status</a>
+<li><a href="h_config_filt_stat_del">PATTERNS FILTER ACTION: Set Deleted Status</a>
+<li><a href="h_config_filt_stat_imp">PATTERNS FILTER ACTION: Set Important Status</a>
+<li><a href="h_config_filt_stat_new">PATTERNS FILTER ACTION: Set New Status</a>
+<li><a href="h_config_filter_kw_set">PATTERNS FILTER ACTION: Set These Keywords</a>
+<li><a href="h_config_incol">PATTERNS INDEXCOLOR ACTION: Index Line Color</a>
+<li><a href="h_config_set_index_format">PATTERNS OTHER ACTION: Set Index Format</a>
+<li><a href="h_config_perfolder_sort">PATTERNS OTHER ACTION: Set Sort Order</a>
+<li><a href="h_config_other_startup">PATTERNS OTHER ACTION: Set Startup Rule</a>
+<li><a href="h_config_role_inick">PATTERNS ROLE ACTION: Initialize Values From Role</a>
+<li><a href="h_config_role_setfcc">PATTERNS ROLE ACTION: Set Fcc</a>
+<li><a href="h_config_role_setfrom">PATTERNS ROLE ACTION: Set From</a>
+<li><a href="h_config_role_setlitsig">PATTERNS ROLE ACTION: Set Literal Signature</a>
+<li><a href="h_config_role_setotherhdr">PATTERNS ROLE ACTION: Set Other Headers</a>
+<li><a href="h_config_role_setreplyto">PATTERNS ROLE ACTION: Set Reply-To</a>
+<li><a href="h_config_role_setsig">PATTERNS ROLE ACTION: Set Signature</a>
+<li><a href="h_config_role_settempl">PATTERNS ROLE ACTION: Set Template</a>
+<li><a href="h_config_role_usenntp">PATTERNS ROLE ACTION: Use NNTP Server</a>
+<li><a href="h_config_role_usesmtp">PATTERNS ROLE ACTION: Use SMTP Server</a>
+<li><a href="h_config_role_scoreval">PATTERNS SCORE ACTION: Score Value</a>
+<li><a href="h_config_role_scorehdrtok">PATTERNS SCORE ACTION: Score Value From Header</a>
+<li><a href="h_config_role_composeuse">PATTERNS USE: Compose Use</a>
+<li><a href="h_config_role_forwarduse">PATTERNS USE: Forward Use</a>
+<li><a href="h_config_role_replyuse">PATTERNS USE: Reply Use</a>
+<li><a href="h_pipe_command">Pipe Command SubOptions</a>
+<li><a href="h_common_pipe">Pipe Command</a>
+<li><a href="h_valid_folder_names">POP</a>
+<li><a href="h_common_postpone">Postpone Command</a>
+<li><a href="h_common_print">Print Command</a>
+<li><a href="h_mainhelp_readingnews">Reading News</a>
+<li><a href="h_news">RELEASE NOTES for Alpine</a>
+<li><a href="h_mainhelp_roles">Roles</a>
+<li><a href="h_role_select">ROLES SCREEN</a>
+<li><a href="h_compose_readfile">Read File Command</a>
+<li><a href="h_mainhelp_reading">Reading Messages</a>
+<li><a href="h_main_release_notes">Release Notes Command</a>
+<li><a href="h_common_reply">Reply and Forward Commands</a>
+<li><a href="h_compose_richhdr">Rich Header Command</a>
+<li><a href="h_common_role">Role Command</a>
+<li><a href="h_mainhelp_smime">S/MIME</a>
+<li><a href="h_index_cmd_select">Searching for Messages</a>
+<li><a href="h_address_display">SEARCH RESULTS INDEX</a>
+<li><a href="h_address_select">SEARCH RESULTS INDEX</a>
+<li><a href="h_simple_index">SELECT POSTPONED MESSAGE</a>
+<li><a href="h_abook_config">SETUP ADDRESS BOOKS SCREEN</a>
+<li><a href="h_collection_maint">SETUP COLLECTION LIST screen</a>
+<li><a href="h_color_setup">SETUP COLOR COMMANDS</a>
+<li><a href="h_direct_config">SETUP LDAP DIRECTORY SERVERS SCREEN</a>
+<li><a href="h_rules_roles">SETUP ROLES SCREEN</a>
+<li><a href="h_rules_incols">SETUP INDEX COLORS SCREEN</a>
+<li><a href="h_rules_filter">SETUP FILTERING SCREEN</a>
+<li><a href="h_rules_score">SETUP SCORING SCREEN</a>
+<li><a href="h_common_save">Save and Export Commands</a>
+<li><a href="h_mainhelp_securing">Securing Your Alpine Session</a>
+<li><a href="h_index_cmd_select">Selecting: Select and WhereIs/Select</a>
+<li><a href="h_compose_send">Send Command</a>
+<li><a href="h_folder_server_syntax">Server Name Syntax</a>
+<li><a href="h_main_setup">Setup Command</a>
+<li><a href="X-Alpine-Config:">Show Supported Options in this Alpine</a>
+<li><a href="h_composer_sigedit">Signature Editor Commands Explained</a>
+<li><a href="h_simple_text_view">Simple Text View Screen Explained</a>
+<li><a href="h_mainhelp_smime">S/MIME</a>
+<li><a href="h_config_smime_dont_do_smime">S/MIME FEATURE: <!--#echo var="FEAT_smime-dont-do-smime"--></a>
+<li><a href="h_config_smime_encrypt_by_default">S/MIME FEATURE: <!--#echo var="FEAT_smime-encrypt-by-default"--></a>
+<li><a href="h_config_smime_remember_passphrase">S/MIME FEATURE: <!--#echo var="FEAT_smime-remember-passphrase"--></a>
+<li><a href="h_config_smime_sign_by_default">S/MIME FEATURE: <!--#echo var="FEAT_smime-sign-by-default"--></a>
+<li><a href="h_config_smime_pubcerts_in_keychain">S/MIME FEATURE: <!--#echo var="FEAT_publiccerts-in-keychain"--></a>
+<li><a href="h_config_smime_cacertcon">S/MIME OPTION: <!--#echo var="VAR_smime-cacert-container"--></a>
+<li><a href="h_config_smime_cacertdir">S/MIME OPTION: <!--#echo var="VAR_smime-cacert-directory"--></a>
+<li><a href="h_config_smime_privkeycon">S/MIME OPTION: <!--#echo var="VAR_smime-private-key-container"--></a>
+<li><a href="h_config_smime_privkeydir">S/MIME OPTION: <!--#echo var="VAR_smime-private-key-directory"--></a>
+<li><a href="h_config_smime_pubcertcon">S/MIME OPTION: <!--#echo var="VAR_smime-public-cert-container"--></a>
+<li><a href="h_config_smime_pubcertdir">S/MIME OPTION: <!--#echo var="VAR_smime-public-cert-directory"--></a>
+<li><a href="h_config_smime_transfer_cacert_to_con">S/MIME: Transfer CA Certs to Container</a>
+<li><a href="h_config_smime_transfer_cacert_to_dir">S/MIME: Transfer CA Certs to Directory</a>
+<li><a href="h_config_smime_transfer_priv_to_con">S/MIME: Transfer Private Keys to Container</a>
+<li><a href="h_config_smime_transfer_priv_to_dir">S/MIME: Transfer Private Keys to Directory</a>
+<li><a href="h_config_smime_transfer_pub_to_con">S/MIME: Transfer Public Certs to Container</a>
+<li><a href="h_config_smime_transfer_pub_to_dir">S/MIME: Transfer Public Certs to Directory</a>
+<li><a href="h_index_cmd_sort">Sort Command</a>
+<li><a href="h_compose_spell">Spell Check Command</a>
+<li><a href="h_common_suspend">Suspend Command</a>
+<li><a href="h_compose_addrcomplete">THE MESSAGE COMPOSER'S ADDRESS COMPLETION</a>
+<li><a href="h_composer_attachment">THE MESSAGE COMPOSER'S ATTCHMNT FIELD</a>
+<li><a href="h_composer_bcc">THE MESSAGE COMPOSER'S BCC FIELD</a>
+<li><a href="h_composer_cc">THE MESSAGE COMPOSER'S CC FIELD</a>
+<li><a href="h_composer_from">THE MESSAGE COMPOSER'S FROM FIELD</a>
+<li><a href="h_composer_lcc">THE MESSAGE COMPOSER'S LCC FIELD</a>
+<li><a href="h_composer_news">THE MESSAGE COMPOSER'S NEWSGRPS LINE</a>
+<li><a href="h_composer_reply_to">THE MESSAGE COMPOSER'S REPLY-TO FIELD</a>
+<li><a href="h_composer_to">THE MESSAGE COMPOSER'S TO FIELD</a>
+<li><a href="h_abook_opened">THE ALPINE ADDRESS BOOK</a>
+<li><a href="h_abook_select_nicks_take">Take Address Nickname Selection Explained</a>
+<li><a href="h_takeaddr_screen">Take Address Screen Explained</a>
+<li><a href="h_common_take">TakeAddr Command</a>
+<li><a href="h_mainhelp_status">Titlebar Line</a>
+<li><a href="h_index_tokens">Tokens for Index and Replying</a>
+<li><a href="h_config_usenone_color">Use None Color</a>
+<li><a href="h_config_usenormal_color">Use Normal Color</a>
+<li><a href="h_config_usetransparent_color">Use Transparent Color</a>
+<li><a href="h_whatis_vcard">VCARD EXPLAINED</a>
+<li><a href="h_view_cmd_hilite">View Hilite and Next item/Previous item</a>
+<li><a href="h_view_cmd_viewattch">ViewAttch Command</a>
+<li><a href="h_index_cmd_whereis">WhereIs Command</a>
+<li><a href="h_view_cmd_whereis">WhereIs Command</a>
+<li><a href="h_index_cmd_zoom">ZoomMode Command</a>
+<li><a href="h_config_browser_xterm"><!--#echo var="VAR_url-viewers"--> and X windows applications</a>
+</UL>
+
+<P>
+&lt;End of Help Index&gt;
+</BODY>
+</HTML>
+
+
+============== h_config_remote_config =============
+<HTML>
+<HEAD>
+<TITLE>Remote Configuration</TITLE>
+</HEAD>
+<BODY>
+<H1>Remote Configuration</H1>
+
+You may use the command line argument &quot;-p pinerc&quot; to tell
+Alpine to use a non-default configuration file.
+There are two types of storage for configuration information.
+<EM>Local</EM> configuration files are used by default.
+These are just regular files on the UNIX system or on the PC.
+The file &quot;<CODE>.pinerc</CODE>&quot; is the default for Unix Alpine and the
+file &quot;<CODE>PINERC</CODE>&quot; is the default for PC-Alpine.
+<EM>Remote</EM> configuration folders are stored on an IMAP server.
+The advantage of using a remote configuration is that the same information
+may be accessed from multiple platforms.
+For example, if you use one computer at work and another at home, the same
+configuration could be used from both places.
+A configuration change from one place would be seen in both places.
+To use a remote configuration you simply give a
+<A HREF="h_valid_folder_names">remote folder name</A>
+as the argument to the &quot;-p&quot; command line option.
+The command line might look something like:
+<P>
+<CENTER><SAMP>pine -p {my.imap.server}remote_pinerc</SAMP></CENTER>
+<P>
+If there are special characters in the command shell you use, you may need to
+quote the last argument (to protect the curly braces from the shell).
+The command might look like:
+<P>
+<CENTER><SAMP>pine -p &quot;{my.imap.server}remote_pinerc&quot;</SAMP></CENTER>
+<P>
+You should choose a folder name for a folder that does not yet exist.
+It will be created containing an empty configuration.
+Do not use a folder that you wish to store regular mail messages in.
+<P>
+The Setup/RemoteConfigSetup command will help you convert from a local
+configuration to a remote configuration.
+It will create a remote configuration for you and copy your current local
+configuration to it.
+It will also help you convert local address books into remote address books
+and local signature files into literal signatures contained in the
+remote configuration file.
+<P>
+If the Setup/RemoteConfigSetup command doesn't do what you want, you
+may copy a local pinerc file to a remote configuration folder by hand
+by using the command line option &quot;-copy_pinerc&quot;.
+<P>
+Another command line option, which is somewhat related to remote
+configuration, is the option &quot;-x exceptions_config&quot;.
+The configuration settings in the exceptions configuration override
+your default settings.
+It may be useful to store the default configuration (the -p argument) remotely
+and to have the exceptions configuration stored in a local file.
+You might put generic configuration information in the remote configuration
+and platform-specific configuration on each platform in the exceptions
+configuration.
+The arguments to the &quot;-p&quot; and &quot;-x&quot; options
+can be either remote folders or local files.
+<P>
+There is another command line argument that works only with PC-Alpine and
+which may prove useful when using a remote configuration.
+The option &quot;-aux local_directory&quot; allows you to tell PC-Alpine where
+to store your local auxiliary files.
+This only has an effect if your configuration file is remote.
+Some examples of auxiliary files are debug files, address book files, and
+signature files.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============== h_config_exceptions =============
+<HTML>
+<HEAD>
+<TITLE>Generic and Exceptional Configuration</TITLE>
+</HEAD>
+<BODY>
+<H1>Generic and Exceptional Configuration</H1>
+
+If you use Alpine from more than one platform it may be convenient
+to split your configuration information into two pieces, a generic piece
+and exceptions that apply to a particular platform.
+For example, suppose you use Alpine from home and from work.
+Most of your configuration settings are probably the
+same in both locations, so those settings belong in the generic settings
+configuration.
+However, you may use a different SMTP server and INBOX
+from home than you do from work.
+The
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+and
+<A HREF="h_config_inbox_path">&quot;<!--#echo var="VAR_inbox-path"-->&quot;</A>
+options could be
+part of your exceptional configuration so that they could be different in the
+two places.
+<P>
+The command line option &quot;-x exceptions_config&quot;
+may be used to split your configuration into generic and exceptional pieces.
+&quot;Exceptions_config&quot; may be either local or remote.
+The regular Alpine configuration file will contain the generic data, and
+&quot;exceptions_config&quot; will contain the exceptional data.
+<P>
+For Unix Alpine, if you don't have a &quot;-x&quot; command line option,
+Alpine will look for the file &quot;<CODE>.pinercex</CODE>&quot;
+in the same local directory that the regular config file is located in (usually
+the Unix home directory).
+If the regular config file is remote (because the command line option
+&quot;-p remote_config&quot; was used) then Unix Alpine looks in the Unix home
+directory for &quot;<CODE>.pinercex</CODE>&quot;.
+If the file does not already exist then no exceptions will be used.
+You can force exceptions to be used by using the &quot;-x&quot; option or
+by creating an empty &quot;<CODE>.pinercex</CODE>&quot; file.
+<P>
+For PC-Alpine, if you don't have a &quot;-x&quot; command line option,
+PC-Alpine will use the value of the
+environment variable <CODE>$PINERCEX</CODE>.
+If that is not set, PC-Alpine will look for
+the local file &quot;<CODE>PINERCEX</CODE>&quot;
+in the same local directory that the regular config file is located in.
+If the regular config file is remote (because the command line option
+&quot;-p remote_config&quot; was used) then PC-Alpine looks in the
+local directory specified by the &quot;-aux local_directory&quot; command
+line argument, or the directory <CODE>$HOME&#92;PINE</CODE>, or
+in the <CODE>&lt;PINE.EXE </CODE>directory<CODE>&gt;</CODE>.
+<P>
+When you have an exception configuration there is a new command
+in the Alpine Setup screen, Setup/eXceptions.
+It toggles between exceptions and the regular configuration.
+This is the usual way to make changes in your exceptional configuration data.
+For example, you would type &quot;S&quot; for Setup, &quot;X&quot; for
+eXception, then follow that with one of the Setup commands, like &quot;C&quot;
+for Config or &quot;K&quot; for Kolor.
+<P>
+For most people, splitting the configuration information into two pieces is
+going to be most useful if the generic information is accessed
+<A HREF="h_config_remote_config">remotely</A>).
+That data will be the same no matter where you access it from and if you
+change it that change will show up everywhere.
+The exceptional data will most commonly be in a local file, so that the
+contents may easily be different on each computing platform used.
+<P>
+If you already have a local configuration file with settings you like
+you may find that the command Setup/RemoteConfigSetup is useful
+in helping you convert to a remote configuration.
+The command line flag &quot;-copy_pinerc&quot;
+may also be useful.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============== h_config_inheritance =============
+<HTML>
+<HEAD>
+<TITLE>Configuration Inheritance</TITLE>
+</HEAD>
+<BODY>
+<H1>Configuration Inheritance</H1>
+
+Configuration inheritance is a power user feature.
+It is confusing and not completely supported by the configuration
+user interface.
+We start with an explanation of how configuration works in hopes of making
+it easier to describe how inheritance works.
+<P>
+Alpine uses a hierarchy of configuration values from different locations.
+There are five ways in which each configuration option (configuration
+variable) can be set.
+In increasing order of precedence they are:
+<P>
+<OL>
+<LI> the system-wide configuration file.
+
+<LI> the personal configuration file
+
+<LI> the personal exceptions file
+
+<LI> a command line argument
+
+<LI> the system-wide <EM>fixed</EM> configuration file (Unix Alpine only)
+</OL>
+<P>
+The fixed configuration file is normally
+<CODE><!--#echo var="PINE_CONF_FIXED_PATH"--></CODE>.
+<P>
+The system-wide configuration file is normally
+<CODE><!--#echo var="PINE_CONF_PATH"--></CODE> for Unix Alpine and is normally not
+set for PC-Alpine.
+For PC-Alpine, if the environment variable <EM>$PINECONF</EM> is set, that
+is used for the system-wide configuration.
+This location can be set or changed on the command line with the -P flag.
+The system-wide configuration file can be either a local file or a
+remote configuration folder.
+<P>
+For Unix Alpine, the personal configuration file is normally the file
+<CODE>.pinerc</CODE> in the user's home directory.
+This can be changed with the -p command line flag.
+For PC-Alpine, the personal configuration file is in
+<CODE>$PINERC</CODE> or <CODE>&lt;AlpineRC registry value&gt;</CODE> or
+<CODE>$HOME&#92;PINE&#92;PINERC</CODE> or
+<CODE>&lt;PINE.EXE </CODE>dir<CODE>&gt;&#92;PINERC</CODE>.
+This can be changed with the -p command line flag.
+If -p is used, the configuration data may be in a local file or a remote config
+folder.
+<P>
+For Unix Alpine, the personal exceptions configuration file is named
+<CODE>.pinercex</CODE> and is in the same directory as the personal
+configuration file, if that configuration file is not remote, and is in
+the home directory if the personal configuration file is remote.
+If the file exists, then exceptions are turned on.
+If it doesn't, then you are not using exceptions.
+Alternatively, you may change the location of the exceptions configuration
+by using the command line argument &quot;-x &lt;exceptions_config&gt;&quot;.
+Like the personal configuration data, exceptions_config may be
+either local or remote.
+<P>
+For PC-Alpine, the personal exceptions configuration file is named
+<CODE>PINERCEX</CODE> and is in the same directory as the personal
+configuration file unless the personal configuration file is remote.
+In that case, it is in the local directory specfied by the
+&quot;-aux local_directory&quot; command line argument.
+(In the case that the personal configuration is remote and there is no
+&quot;-aux&quot; command line argument, Alpine searches for
+a PINERCEX file in the directory <CODE>$HOME&#92;PINE</CODE> and
+the directory <CODE>&lt;PINE.EXE </CODE>dir<CODE>&gt;</CODE>.)
+If the file exists, then exceptions are turned on.
+If it doesn't, then you are not using exceptions.
+You may change the location of the exceptions configuration
+by using the command line argument &quot;-x &lt;exceptions_config&gt;&quot;.
+or with the
+environment variable <CODE>$PINERCEX</CODE> (if there is no &quot;-x&quot;
+option).
+<P>
+To reiterate, the value of a configuration option is taken from the
+last location in the list above in which it is set.
+Or, thinking about it slightly differently, a default value for an option
+is established in the system-wide configuration file (or internally by Alpine
+if there is no value in the system-wide file).
+That default remains in effect until and unless it is overridden by a value in a
+location further down the list, in which case a new &quot;default&quot;
+value is established.
+As we continue down the list of locations we either retain the
+value at each step or establish a new value.
+The value that is still set after going through the whole list of
+configuration locations is the one that is used.
+<P>
+So, for example, if an option is set in the system-wide configuration
+file and in the personal configuration file, but is not set in the
+exceptions, on the command line, or in the fixed file; then the value
+from the personal configuration file is the one that is used.
+Or, if it is set in the system-wide config, in the personal config, not
+in the exceptions, but is set on the command line; then the value
+on the command line is used.
+<P>
+Finally we get to inheritance.
+For configuration options that are lists, like &quot;<!--#echo var="VAR_smtp-server"-->&quot; or
+&quot;<!--#echo var="VAR_incoming-folders"-->&quot;,
+the inheritance mechanism makes it possible to <EM>combine</EM>
+the values from different locations instead of <EM>replacing</EM> the value.
+This is true of all configuration lists other than the &quot;Feature-List&quot;,
+for which you may already set whatever you want at
+any configuration location (by using the &quot;no-&quot; prefix if
+necessary).
+<P>
+To use inheritance, set the first item in a configuration list to the
+token &quot;INHERIT&quot;, without the quotes.
+If the first item is &quot;INHERIT&quot;,
+then instead of replacing the default value established so far, the rest of
+the list is appended to the default value established so far and that is
+the new value.
+<P>
+Here is an example which may make it clearer. Suppose we have:
+<P>
+<PRE>
+ System-wide config : smtp-server = smtp1.corp.com, smtp2.corp.com
+ Personal config : smtp-server = INHERIT, mysmtp.home
+ Exceptions config : smtp-server = &lt;No Value Set&gt;
+ Command line : smtp-server = &lt;No Value Set&gt;
+ Fixed config : smtp-server = &lt;No Value Set&gt;
+</PRE>
+<P>
+
+This would result in an effective smtp-server option of
+<P>
+<PRE>
+ smtp-server = smtp1.corp.com, smtp2.corp.com, mysmtp.home
+</PRE>
+<P>
+The &quot;INHERIT&quot; token can be used in any of the configuration files
+and the effect cascades.
+For example, if we change the above example to:
+<P>
+<PRE>
+ System-wide config : smtp-server = smtp1.corp.com, smtp2.corp.com
+ Personal config : smtp-server = INHERIT, mysmtp.home
+ Exceptions config : smtp-server = INHERIT, yoursmtp.org
+ Command line : smtp-server = &lt;No Value Set&gt;
+ Fixed config : smtp-server = &lt;No Value Set&gt;
+</PRE>
+<P>
+
+This would result in:
+<P>
+<PRE>
+ smtp-server = smtp1.corp.com, smtp2.corp.com, mysmtp.home, yoursmtp.org
+</PRE>
+<P>
+Unset variables are skipped over (the default value is carried forward) so
+that, for example:
+<P>
+<PRE>
+ System-wide config : smtp-server = smtp1.corp.com, smtp2.corp.com
+ Personal config : smtp-server = &lt;No Value Set&gt;
+ Exceptions config : smtp-server = INHERIT, yoursmtp.org
+ Command line : smtp-server = &lt;No Value Set&gt;
+ Fixed config : smtp-server = &lt;No Value Set&gt;
+</PRE>
+<P>
+
+produces:
+<P>
+<PRE>
+ smtp-server = smtp1.corp.com, smtp2.corp.com, yoursmtp.org
+</PRE>
+<P>
+
+If any later configuration location has a value set (for a particular list
+option) which does <EM>not</EM> begin with &quot;INHERIT&quot;,
+then that value replaces whatever value has been defined up to that point.
+In other words, that cancels out any previous inheritance.
+<P>
+<PRE>
+ System-wide config : smtp-server = smtp1.corp.com, smtp2.corp.com
+ Personal config : smtp-server = INHERIT, mysmtp.org
+ Exceptions config : smtp-server = yoursmtp.org
+ Command line : smtp-server = &lt;No Value Set&gt;
+ Fixed config : smtp-server = &lt;No Value Set&gt;
+</PRE>
+<P>
+
+results in:
+<P>
+<PRE>
+ smtp-server = yoursmtp.org
+</PRE>
+<P>
+
+For some configuration options, like &quot;<!--#echo var="VAR_viewer-hdr-colors"-->&quot; or
+&quot;<!--#echo var="VAR_patterns-roles"-->&quot;, it is
+difficult to insert the value &quot;INHERIT&quot; into the list of values
+for the option using the normal Setup tools.
+In other words, the color setting screen (for example) does not
+provide a way to input the text &quot;INHERIT&quot; as the first
+item in the <!--#echo var="VAR_viewer-hdr-colors"--> option.
+The way to do this is to either edit the pinerc file directly and manually
+insert it, or turn
+on the <A HREF="h_config_expose_hidden_config"><!--#echo var="FEAT_expose-hidden-config"--></A>
+feature and insert it using the Setup/Config screen.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============== h_special_xon_xoff =============
+<HTML>
+<HEAD>
+<TITLE>Explanation of Alpine's XOFF/XON Handling</TITLE>
+</HEAD>
+<BODY>
+<H1>XOFF/XON Handling within Alpine</H1>
+
+By default, Alpine treats Ctrl-S or Ctrl-Q (sometimes known as XOFF
+and XON) as normal characters, even though Alpine does not use them.
+However, the printer, modem, or communication software you are using may
+be configured for &quot;software flow control,&quot; which means that
+XON/XOFF must be treated as special characters by the operating system.
+If you see messages such as &quot;^S not defined for this screen&quot;,
+then your system is probably using software flow control. In this case
+you will need to set the
+<A HREF="h_config_preserve_start_stop">&quot;<!--#echo var="FEAT_preserve-start-stop-characters"-->&quot;</A>
+feature.
+<P>
+If you <EM>do</EM> set this
+feature, be advised that if you accidentally hit a Ctrl-S, Alpine will
+mysteriously freeze up with no warning. In this case, try typing a Ctrl-Q
+and see if that puts things right. Printing via the
+&quot;attached-to-ansi&quot; or
+&quot;attached-to-wyse&quot;
+option will automatically enable software
+flow-control handling for the duration of the printing.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_special_help_nav =============
+<HTML>
+<HEAD>
+<TITLE>Help Text Navigation Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Help Text Navigation Explained</H1>
+
+Alpine contains extensive context-sensitive help text. At any point,
+pressing the &quot;?&quot; key will bring up a page of help text
+explaining the options available to you. You can leave the help
+text screen and return to normal Alpine operation by pressing
+the
+<!--chtml if pinemode="function_key"-->
+F3 function
+<!--chtml else-->
+&quot;E&quot;
+<!--chtml endif-->
+key to Exit Help at any time.
+
+<P>
+Within the help screen you might find a word or phrase displayed in
+inverse text and others displayed in bold typeface. Such words and
+phrases are used to tell you Alpine has more information available on
+the topic they describe.
+The inverted text is the &quot;selected&quot; topic.
+Use the arrow keys, Ctrl-F, and Ctrl-B to change which of the phrases
+displayed in bold type
+is &quot;selected&quot;.
+Hit the Return key to display the information Alpine has available on that
+topic. While viewing such additional information, the
+<!--chtml if pinemode="function_key"-->
+F3 function
+<!--chtml else-->
+&quot;P&quot;
+<!--chtml endif-->
+key will return you to the previous help screen, and the
+<!--chtml if pinemode="function_key"-->
+F2 function
+<!--chtml else-->
+&quot;E&quot;
+<!--chtml endif-->
+key will Exit the Help system altogether.
+
+<P>
+The "N" command will tell you the internal name of the help text you are
+reading each time, so that you can send this name in the text of a message
+and create a direct link to that internal help using the x-pine-help URL
+scheme. For example, the direct link to this item is
+x-pine-help:h_special_help_nav. If you add this text to a message, then
+a person using Pine to read such message would have a direct link to this
+help text.
+
+<P>
+When you are finished reading this help text, you can press the
+<!--chtml if pinemode="function_key"-->
+F3 function
+<!--chtml else-->
+&quot;P&quot;
+<!--chtml endif-->
+key to return to the previously displayed help text.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_special_list_commands =============
+<HTML>
+<HEAD>
+<TITLE>Email List Commands Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Email List Commands Explained</H1>
+
+Electonic mail lists provide a way for like-minded users to join in
+discussions on topics of interest. The email list itself is
+represented by a
+single address that participants send messages to when they have
+something of interest to share with other members of the list. The
+receiving computer then, either automatically or after review by the
+list's owner (or moderator), sends a copy of that message to each
+member of the list.
+
+<P>
+Usually, subscribing and unsubscribing is done by sending requests in
+an email message to a special address setup to handle managing list
+membership. Often this is the name of the list followed by
+<I>-request</I>. This address is almost <EM>never</EM> the same as
+the address used to send messages to the list.
+
+<P>
+Unfortunately, email list participation commands are more a matter
+of convention than standard, and thus may vary from list to list.
+Increasingly, list management software is adding information to
+the copy of the postings as they're copied to the list members that
+explains how to do the various list management functions.
+
+<P>
+Alpine will recognize this information and offer the management commands
+they represent in a simple display. One or more of the following
+operations will be made available:
+
+<DL>
+<DT>Help</DT>
+<DD>
+A method to get help on subscribing, unsubscribing,
+an explanation of what the list is about, or special instructions
+for participation. This may be in the form of a reply in response
+to an email message, or instructions on how to connect to a Web site.
+</DD>
+
+<DT>Unsubscribe</DT>
+<DD>
+A method to get your email addressed removed from the list of
+recipients of the email list.
+</DD>
+
+<DT>Subscribe</DT>
+<DD>
+A method to get your email address added to the list of recipients
+of the email list. It may be in the form of a message sent to
+a special address or you may be connected to a web site.
+<DD>
+</DD>
+
+<DT>Post</DT>
+<DD>
+A method used to post to the email list. However, it might also
+indicate that no posting is allowed directly to the list.
+</DD>
+
+<DT>Owner</DT>
+<DD>
+A method to contact the list owner for special questions you might
+have regarding the list.
+</DD>
+
+<DT>Archive</DT>
+<DD>
+A method to view an archive of previous messages posted to the list.
+This may be in the form of a mail folder on a remote computer, an
+IMAP mailbox or even a Web site.
+</DD>
+</DL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_quota_command =============
+<HTML>
+<HEAD>
+<TITLE>Quota Screen Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Quota Screen Explained</H1>
+
+<P> This screen summarizes the quota report for this mailbox in the
+IMAP server. For each resource that you have a quota, this reports summarizes
+its use and limit.
+
+<P> Your IMAP server administrator may have set a quota based either on
+the total size of your mailbox (STORAGE), or the number of messages in
+your mailbox (MESSAGES), or some other criteria. This will be reported
+to you indicating the type of quota, its total use and its limit.
+
+<P> The report for STORAGE is reported in kibibytes (KiB). One kibibyte is
+1024 bytes. Each of the characters that you see in this help text is one
+byte, and this help text is about 1 kibibyte in size. Small messages sent
+by Alpine are normally less than 4 kibibytes in size (which includes
+headers and text). Other email programs may send messages with bigger
+sizes when they send messages, since they send plain text and an
+alternative part in HTML.
+
+<P> A convenient way to save space for the STORAGE type of quota is by
+deleting attachments. This is done on each individual message by pressing
+the &quot;V&quot; command while reading the message text, then moving the cursor
+to the position of the attachment that is to be deleted, then pressing
+&quot;D&quot; to delete such attachment, going back to reading the
+message with the &quot;&lt;&quot; command and pressing &quot;S&quot; to
+save the message in the same folder you are reading from. The saved
+message will not have the attachment that was marked deleted. Now you
+can delete and expunge the message with the unwanted attachment.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_mail_thread_index =============
+<HTML>
+<HEAD>
+<TITLE>THREAD INDEX COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>THREAD INDEX COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+Available&nbsp;&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;2<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------------------<BR>
+F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text<BR>
+F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands<BR>
+F3&nbsp;&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F3&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen<BR>
+F4&nbsp;&nbsp;View&nbsp;current&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;Quit&nbsp;Alpine<BR>
+F5&nbsp;&nbsp;Move&nbsp;to&nbsp;previous&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F5&nbsp;&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;message<BR>
+F6&nbsp;&nbsp;Move&nbsp;to&nbsp;next&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F6&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+F7&nbsp;&nbsp;Show&nbsp;previous&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F7&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;thread<BR>
+F8&nbsp;&nbsp;Show&nbsp;next&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F8&nbsp;&nbsp;<A HREF="h_index_cmd_whereis">Whereis</A><BR>
+F9&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;thread&nbsp;for&nbsp;deletion</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;&nbsp;<A HREF="h_common_print">Print</A>&nbsp;index<BR>
+F10&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;delete&nbsp;mark)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;address&nbsp;book<BR>
+F11&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F11&nbsp;<A HREF="h_common_save">Save</A>&nbsp;messages&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+F12&nbsp;<A HREF="h_common_reply">Forward</A>&nbsp;messages&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F12&nbsp;<A HREF="h_common_save">Export</A>&nbsp;messages&nbsp;into&nbsp;a&nbsp;plain&nbsp;file<BR>
+<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;3<BR>
+-----------------------------<BR>
+F3&nbsp;&nbsp;<A HREF="h_index_cmd_expunge">Expunge</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F7&nbsp;<A HREF="h_index_cmd_sort">Sort</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A HREF="h_common_bounce">Bounce</A>&nbsp;(remail)&nbsp;msg<BR>
+F5&nbsp;&nbsp;<A HREF="h_index_cmd_select">Select</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F8&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F11&nbsp;<A HREF="h_common_flag">Flag</A>&nbsp;messages&nbsp;as&nbsp;important<BR>
+F6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;<A HREF="h_common_hdrmode">Full&nbsp;Header&nbsp;Mode</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F12&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;command<BR>
+<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;4<BR>
+-----------------------------<BR>
+F3&nbsp;&nbsp;Select&nbsp;Current&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;<A HREF="h_index_cmd_zoom">Zoom</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F5&nbsp;&nbsp;COLLECTION&nbsp;LIST&nbsp;Screen
+F6&nbsp;&nbsp;<A HREF="h_common_role">Compose&nbsp;using&nbsp;a&nbsp;role</A><BR>
+<BR>
+<!--chtml else-->
+Navigating&nbsp;the&nbsp;List&nbsp;of&nbsp;Threads&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Operations&nbsp;on&nbsp;the&nbsp;Current&nbsp;Thread<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------------------<BR>
+&nbsp;P&nbsp;&nbsp;&nbsp;Move&nbsp;to&nbsp;the&nbsp;previous&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&nbsp;&nbsp;View&nbsp;Thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%&nbsp;&nbsp;<A HREF="h_common_print">Print</A><BR>
+&nbsp;N&nbsp;&nbsp;&nbsp;Move&nbsp;to&nbsp;the&nbsp;next&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;R&nbsp;&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F&nbsp;&nbsp;<A HREF="h_common_reply">Forward</A><BR>
+&nbsp;-&nbsp;&nbsp;&nbsp;Show&nbsp;previous&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;D&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;thread&nbsp;for&nbsp;deletion</A><BR>
+Spc&nbsp;&nbsp;(space&nbsp;bar)&nbsp;Show&nbsp;next&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;U&nbsp;&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;deletion&nbsp;mark)<BR>
+&nbsp;J&nbsp;&nbsp;&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;a&nbsp;specific&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;T&nbsp;&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;Address&nbsp;Book<BR>
+&nbsp;W&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_whereis">Whereis</A>&nbsp;--&nbsp;search&nbsp;for&nbsp;a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;S&nbsp;&nbsp;<A HREF="h_common_save">Save</A>&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;specific&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;E&nbsp;&nbsp;<A HREF="h_common_save">Export</A>&nbsp;as&nbsp;a&nbsp;plain&nbsp;text&nbsp;file<BR>
+Tab&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;B&nbsp;&nbsp;<A HREF="h_common_bounce">Bounce</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;<A HREF="h_common_flag">Flag</A><BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;Command<BR>
+<BR>
+Miscellaneous&nbsp;Operations&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;General&nbsp;Alpine&nbsp;Commands<BR>
+------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------<BR>
+&nbsp;G&nbsp;&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;O&nbsp;&nbsp;Show&nbsp;all&nbsp;other&nbsp;available&nbsp;commands<BR>
+&nbsp;$&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_sort">Sort</A>&nbsp;order&nbsp;of&nbsp;index&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;?&nbsp;&nbsp;Show&nbsp;Helptext&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Q&nbsp;Quit&nbsp;Alpine<BR>
+&nbsp;H&nbsp;&nbsp;&nbsp;<A HREF="h_common_hdrmode">Full&nbsp;header&nbsp;mode</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;M&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen&nbsp;&nbsp;&nbsp;&lt;&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen<BR>
+&nbsp;X&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_expunge">Expunge/Exclude</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;C&nbsp;&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;new&nbsp;message<BR>
+&nbsp;Z&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_zoom">Zoom</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;&nbsp;<A HREF="h_common_role">Compose&nbsp;using&nbsp;a&nbsp;role</A><BR>
+&nbsp;;&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_select">Select</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A&nbsp;<A HREF="h_index_cmd_apply">Apply</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;L&nbsp;&nbsp;COLLECTION&nbsp;LIST&nbsp;Screen<BR>
+&nbsp;:&nbsp;&nbsp;&nbsp;Select&nbsp;Messages&nbsp;in&nbsp;Current&nbsp;Thread<BR>
+<!--chtml endif-->
+<P>
+
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI>Availability of certain commands depends on <A HREF="h_common_conditional_cmds">feature settings</A>.
+</OL>
+
+<H2>Description of the THREAD INDEX Screen</H2>
+
+The THREAD INDEX displays summary information from each
+thread (conversation) in the current folder.
+This is useful if you want to quickly
+scan new threads, or find a particular thread without having to go
+through the text of each message, or to quickly get rid of junk
+threads, etc.
+The current thread is always highlighted.
+Each line of the THREAD INDEX contains the following columns: <P>
+<DL>
+ <DT>STATUS:</DT>
+ <DD> The markings on the left side of the thread tell you about its
+status. You may see one or more of the following codes on any given
+thread:
+<UL>
+ <LI> &quot;D&quot; for Deleted. All of the messages in this thread are marked for deletion but not yet eXpunged from the folder.
+ <LI> &quot;A&quot; for Answered. All of the messages in this thread are marked answered.
+ <LI> &quot;N&quot; for New. At least one message in the thread is New (you haven't looked at it yet).
+ <LI> &quot;+&quot; for direct-to-you. The &quot;+&quot; indicates that a message in the thread was sent directly to your account, your copy is not part of a cc: or a mailing list.
+ <LI> &quot;-&quot; for cc-to-you. The &quot;-&quot; indicates that a
+ message in the thread was sent to you as a cc:. This symbol will only show up if
+ the feature
+ &quot;<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>&quot; is turned on (which is the default).
+ <LI> &quot;X&quot; for selected. You have selected at least one message in the thread by using the
+ &quot;select&quot; command. (Some systems may optionally allow selected
+ messages to be denoted by the index line being displayed in bold
+ type instead.)
+ <LI> &quot;*&quot; for Important. You have previously used the &quot;Flag&quot; command
+ to mark at least one message in this thread as &quot;important&quot;.
+</UL></DD><P>
+
+ <DT>THREAD NUMBER:</DT>
+ <DD>Threads in a folder are numbered, from one through the number
+of threads in the folder, to help you know where you are in the folder.
+</DD><P>
+
+ <DT>DATE STARTED:</DT>
+ <DD>The date the thread was started. This is actually from the Date header
+of the first message in the thread. It doesn't take different time zones
+into account.</DD><P>
+
+ <DT>WHO STARTED THE THREAD:</DT>
+ <DD>This is usually the name of the sender of the first message in the thread, taken from
+the From header of the message.
+If there is no personal name given in that
+address, then the email address is used instead.
+If the message is from you (or from one of your
+<A HREF="h_config_alt_addresses">alternate addresses</A>),
+then the recipient's name is shown here instead, with the characters
+&quot;To: &quot; inserted before the name.
+(The idea of this is that if you started the thread you would rather see who
+the mail was sent to instead of that the mail was from you.)
+In Newsgroups, if you are
+the sender and there are no email recipients, the newsgroup name will be
+listed after the &quot;To: &quot;.
+</DD><P>
+
+ <DT>SIZE:</DT>
+ <DD>The number in parentheses is the number of messages in the thread.</DD><P>
+
+ <DT>SUBJECT:</DT>
+ <DD>As much of the thread's subject line as will fit on the screen.
+This is the subject of the first message in the thread.</DD>
+</DL>
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_mail_index =============
+<HTML>
+<HEAD>
+<TITLE>MESSAGE INDEX COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>MESSAGE INDEX COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+Available&nbsp;&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;2<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------------------<BR>
+F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text<BR>
+F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands<BR>
+F3&nbsp;&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F3&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen<BR>
+F4&nbsp;&nbsp;View&nbsp;current&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;Quit&nbsp;Alpine<BR>
+F5&nbsp;&nbsp;Move&nbsp;to&nbsp;previous&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F5&nbsp;&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;message<BR>
+F6&nbsp;&nbsp;Move&nbsp;to&nbsp;next&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F6&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+F7&nbsp;&nbsp;Show&nbsp;previous&nbsp;screen&nbsp;of&nbsp;messages&nbsp;&nbsp;&nbsp;F7&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;message<BR>
+F8&nbsp;&nbsp;Show&nbsp;next&nbsp;screen&nbsp;of&nbsp;messages&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F8&nbsp;&nbsp;<A HREF="h_index_cmd_whereis">Whereis</A><BR>
+F9&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;message&nbsp;for&nbsp;deletion</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;&nbsp;<A HREF="h_common_print">Print</A>&nbsp;message<BR>
+F10&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;delete&nbsp;mark)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;address&nbsp;book<BR>
+F11&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F11&nbsp;<A HREF="h_common_save">Save</A>&nbsp;message&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+F12&nbsp;<A HREF="h_common_reply">Forward</A>&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F12&nbsp;<A HREF="h_common_save">Export</A>&nbsp;message&nbsp;into&nbsp;a&nbsp;plain&nbsp;file<BR>
+<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;3<BR>
+-----------------------------<BR>
+F3&nbsp;&nbsp;<A HREF="h_index_cmd_expunge">Expunge/Exclude</A>&nbsp;&nbsp;&nbsp;&nbsp;F7&nbsp;<A HREF="h_index_cmd_sort">Sort</A>&nbsp;order&nbsp;of&nbsp;index&nbsp;&nbsp;F10&nbsp;<A HREF="h_common_bounce">Bounce</A>&nbsp;(remail)&nbsp;msg<BR>
+F5&nbsp;&nbsp;<A HREF="h_index_cmd_select">Select</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F8&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F11&nbsp;<A HREF="h_common_flag">Flag</A>&nbsp;message&nbsp;as&nbsp;important<BR>
+F6&nbsp;&nbsp;<A HREF="h_index_cmd_apply">Apply</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;<A HREF="h_common_hdrmode">Full&nbsp;Header&nbsp;Mode</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F12&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;command<BR>
+<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;4<BR>
+-----------------------------<BR>
+F3&nbsp;&nbsp;Select&nbsp;Current&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;<A HREF="h_index_cmd_zoom">Zoom</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F5&nbsp;&nbsp;COLLECTION&nbsp;LIST&nbsp;Screen
+F6&nbsp;&nbsp;<A HREF="h_common_role">Compose using a role</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A HREF="h_index_collapse_expand">Collapse/Expand</A>&nbsp;Thread<BR>
+<BR>
+<!--chtml else-->
+Navigating&nbsp;the&nbsp;List&nbsp;of&nbsp;Messages&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Operations&nbsp;on&nbsp;the&nbsp;Current&nbsp;Message<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------------------<BR>
+&nbsp;P&nbsp;&nbsp;&nbsp;Move&nbsp;to&nbsp;the&nbsp;previous&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&nbsp;&nbsp;View&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%&nbsp;&nbsp;<A HREF="h_common_print">Print</A><BR>
+&nbsp;N&nbsp;&nbsp;&nbsp;Move&nbsp;to&nbsp;the&nbsp;next&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;R&nbsp;&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F&nbsp;&nbsp;<A HREF="h_common_reply">Forward</A><BR>
+&nbsp;-&nbsp;&nbsp;&nbsp;Show&nbsp;previous&nbsp;screen&nbsp;of&nbsp;messages&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;D&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;for&nbsp;deletion</A><BR>
+Spc&nbsp;&nbsp;(space&nbsp;bar)&nbsp;Show&nbsp;next&nbsp;screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;U&nbsp;&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;deletion&nbsp;mark)<BR>
+&nbsp;J&nbsp;&nbsp;&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;a&nbsp;specific&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;T&nbsp;&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;Address&nbsp;Book<BR>
+&nbsp;W&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_whereis">Whereis</A>&nbsp;--&nbsp;search&nbsp;for&nbsp;a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;S&nbsp;&nbsp;<A HREF="h_common_save">Save</A>&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;specific&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;E&nbsp;&nbsp;<A HREF="h_common_save">Export</A>&nbsp;as&nbsp;a&nbsp;plain&nbsp;text&nbsp;file<BR>
+Tab&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;B&nbsp;&nbsp;<A HREF="h_common_bounce">Bounce</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;<A HREF="h_common_flag">Flag</A><BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;Command<BR>
+Miscellaneous&nbsp;Operations<BR>
+------------------------<BR>
+&nbsp;G&nbsp;&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;General&nbsp;Alpine&nbsp;Commands<BR>
+&nbsp;$&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_sort">Sort</A>&nbsp;order&nbsp;of&nbsp;index&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------<BR>
+&nbsp;H&nbsp;&nbsp;&nbsp;<A HREF="h_common_hdrmode">Full&nbsp;header&nbsp;mode</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;O&nbsp;&nbsp;Show&nbsp;all&nbsp;other&nbsp;available&nbsp;commands<BR>
+&nbsp;X&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_expunge">Expunge/Exclude</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;?&nbsp;&nbsp;Show&nbsp;Help&nbsp;text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Q&nbsp;Quit&nbsp;Alpine<BR>
+&nbsp;Z&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_zoom">Zoom</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;M&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen&nbsp;&nbsp;&nbsp;&lt;&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen<BR>
+&nbsp;;&nbsp;&nbsp;&nbsp;<A HREF="h_index_cmd_select">Select</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A&nbsp;<A HREF="h_index_cmd_apply">Apply</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;C&nbsp;&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;new&nbsp;message<BR>
+&nbsp;:&nbsp;&nbsp;&nbsp;Select&nbsp;Current&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;&nbsp;<A HREF="h_common_role">Compose using a role</A><BR>
+&nbsp;/&nbsp;&nbsp;&nbsp;<A HREF="h_index_collapse_expand">Collapse/Expand</A>&nbsp;Thread&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;L&nbsp;&nbsp;COLLECTION&nbsp;LIST&nbsp;Screen<BR>
+<!--chtml endif-->
+<P>
+
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI>Availability of certain commands depends on <A HREF="h_common_conditional_cmds">feature settings</A>.
+</OL>
+
+<H2>Description of the MESSAGE INDEX Screen</H2>
+
+The MESSAGE INDEX displays summary information from each
+message in the current folder.
+This is useful if you want to quickly
+scan new messages, or find a particular message without having to go
+through the text of each message, or to quickly get rid of junk
+messages, etc.
+<P>
+The current message is always highlighted
+and many commands operate on the current message.
+For example, the Delete command will delete the current message.
+If the folder is sorted by either Threads or OrderedSubject, then, depending
+on some of your configuration settings, a single line in the index may
+refer to an entire thread or to a subthread.
+If that is the case, then the commands that normally operate on the current
+message will operate on the thread or subthread instead.
+For example, the Delete command will delete the whole collapsed thread
+instead of just a single message.
+<P>
+Each line of the MESSAGE INDEX contains the following columns (by default --
+you can change this with the
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot; option
+in the SETUP CONFIGURATION screen): <P>
+<DL>
+ <DT>STATUS:</DT>
+ <DD> The markings on the left side of the message tell you about its
+status. You may see one or more of the following codes on any given
+message:
+<UL>
+ <LI> &quot;D&quot; for Deleted. You have marked this message for deletion but not
+ yet eXpunged the folder.
+ <LI> &quot;N&quot; for New. You have not looked at the text of the message yet.
+ <LI> &quot;A&quot; for Answered. Any time you reply to a message it is considered
+ to be answered.
+ <LI> &quot;F&quot; for Forwarded. Similar to Answered, this is set whenever you
+ forward a message.
+ <LI> &quot;+&quot; for direct-to-you. The &quot;+&quot; indicates that a message was sent
+ directly to your account, your copy is not part of a cc: or a
+ mailing list.
+ <LI> &quot;-&quot; for cc-to-you. The &quot;-&quot; indicates that a
+ message was sent to you as a cc:. This symbol will only show up if
+ the feature
+ &quot;<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>&quot; is turned on (which is the default).
+ <LI> &quot;X&quot; for selected. You have selected the message by using the
+ &quot;select&quot; command. (Some systems may optionally allow selected
+ messages to be denoted by the index line being displayed in bold
+ type.)
+ <LI> &quot;*&quot; for Important. You have previously used the &quot;Flag&quot; command
+ to mark this message as &quot;important&quot;.
+</UL></DD><P>
+
+ <DT>MESSAGE NUMBER:</DT>
+ <DD>Messages in a folder are numbered, from one through the number
+of messages in the folder, to help you know where you are in the folder.
+These numbers are always in increasing order, even if you sort the folder
+in a different way.</DD><P>
+
+ <DT>DATE SENT:</DT>
+ <DD>The date the message was sent. By default, messages are
+ordered by arrival time, not by date sent. Most of the time, arrival time
+and date sent (effectively departure time) are similar. Sometimes,
+however, the index will appear to be out of order because a message took a
+long time in delivery or because the sender is in a different time
+zone than you are. This date is just the date from the Date header
+field in the message.</DD><P>
+
+ <DT>WHO SENT THE MESSAGE:</DT>
+ <DD>This is usually the name of the sender of the message, taken from
+the From header of the message.
+If there is no personal name given in that
+address, then the email address is used instead.
+If the message is from you (or from one of your
+<A HREF="h_config_alt_addresses">alternate addresses</A>),
+then the recipient's name is shown here instead, with the characters
+&quot;To: &quot; inserted before the name.
+(The idea of this is that if you sent the mail you would rather see who
+the mail was sent to instead of that the mail was from you.
+This behavior may be changed by modifying the <!--#echo var="VAR_index-format"--> option mentioned
+above.
+In particular, use the FROM token or the FROMORTONOTNEWS token
+in place of the FROMORTO token.)
+In Newsgroups, if you are
+the sender and there are no email recipients, the newsgroup name will be
+listed after the &quot;To: &quot;. </DD><P>
+
+ <DT>SIZE:</DT>
+ <DD>The number in parentheses is the number of characters in the message.
+It may have a suffix of K, M, or G which means the number should be
+multiplied by one thousand, one million, or one billion to get the
+size of the message.</DD><P>
+
+ <DT>SUBJECT:</DT>
+ <DD>As much of the message's subject line as will fit on the screen.</DD>
+</DL>
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_mail_view ========================
+<HTML>
+<HEAD>
+<TITLE>MESSAGE TEXT SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>MESSAGE TEXT SCREEN</H1>
+<!--chtml if pinemode="function_key"-->
+Available&nbsp;&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;2<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;------------------------------<BR>
+F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text<BR>
+F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands<BR>
+F3&nbsp;&nbsp;<A HREF="h_common_index">MESSAGE&nbsp;INDEX</A>&nbsp;Screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F3&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen<BR>
+F4&nbsp;&nbsp;<A HREF="h_view_cmd_viewattch">View&nbsp;attachment</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F4&nbsp;&nbsp;Quit&nbsp;Alpine<BR>
+F5&nbsp;&nbsp;Display&nbsp;previous&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F5&nbsp;&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen<BR>
+F6&nbsp;&nbsp;Display&nbsp;next&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F6&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+F7&nbsp;&nbsp;Previous&nbsp;screen&nbsp;of&nbsp;this&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;F7&nbsp;&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;message<BR>
+F8&nbsp;&nbsp;Next&nbsp;screen&nbsp;of&nbsp;this&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F8&nbsp;&nbsp;<A HREF="h_view_cmd_whereis">Whereis</A><BR>
+F9&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;message&nbsp;for&nbsp;deletion</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F9&nbsp;&nbsp;<A HREF="h_common_print">Print</A>&nbsp;message<BR>
+F10&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;delete&nbsp;mark)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F10&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;address&nbsp;book<BR>
+F11&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F11&nbsp;<A HREF="h_common_save">Save</A>&nbsp;message&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+F12&nbsp;<A HREF="h_common_reply">Forward</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F12&nbsp;<A HREF="h_common_save">Export</A>&nbsp;message&nbsp;into&nbsp;a&nbsp;plain&nbsp;file<BR>
+<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;3<BR>
+------------------------------<BR>
+F1&nbsp;&nbsp;Show&nbsp;Help&nbsp;Text<BR>
+F2&nbsp;&nbsp;Toggle&nbsp;to&nbsp;see&nbsp;more&nbsp;commands<BR>
+F3&nbsp;&nbsp;<A HREF="h_view_cmd_hilite">View&nbsp;hilited</A><BR>
+F4&nbsp;&nbsp;Select&nbsp;current&nbsp;message<BR>
+F5&nbsp;&nbsp;Previous&nbsp;selectable&nbsp;item<BR>
+F6&nbsp;&nbsp;Next&nbsp;selectable&nbsp;item<BR>
+F7&nbsp;&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;message&nbsp;number<BR>
+F8&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;message<BR>
+F9&nbsp;&nbsp;<A HREF="h_common_hdrmode">Display&nbsp;full&nbsp;headers</A><BR>
+F10&nbsp;<A HREF="h_common_bounce">Bounce</A>&nbsp;message<BR>
+F11&nbsp;<A HREF="h_common_flag">Flag</A>&nbsp;message<BR>
+F12&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;command<BR>
+Available&nbsp;Commands&nbsp;--&nbsp;Group&nbsp;4<BR>
+F5&nbsp;&nbsp;<A HREF="h_common_role">Compose using a role</A><BR>
+<!--chtml else-->
+Operations&nbsp;on&nbsp;the&nbsp;Current&nbsp;Message<BR>
+---------------------------------<BR>
+<BR>
+&nbsp;-&nbsp;&nbsp;&nbsp;Show&nbsp;previous&nbsp;page&nbsp;of&nbsp;this&nbsp;msg&nbsp;&nbsp;&nbsp;&nbsp;S&nbsp;&nbsp;<A HREF="h_common_save">Save</A>&nbsp;into&nbsp;an&nbsp;email&nbsp;folder<BR>
+Spc&nbsp;&nbsp;(space&nbsp;bar)&nbsp;Show&nbsp;next&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;E&nbsp;&nbsp;<A HREF="h_common_save">Export</A>&nbsp;as&nbsp;a&nbsp;plain&nbsp;text&nbsp;file<BR>
+&nbsp;&gt;&nbsp;&nbsp;&nbsp;<A HREF="h_view_cmd_viewattch">View&nbsp;attachment</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;B&nbsp;&nbsp;<A HREF="h_common_bounce">Bounce</A><BR>
+&nbsp;R&nbsp;&nbsp;&nbsp;<A HREF="h_common_reply">Reply</A>&nbsp;to&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;F&nbsp;&nbsp;<A HREF="h_common_reply">Forward</A>&nbsp;message<BR>
+&nbsp;D&nbsp;&nbsp;&nbsp;<A HREF="h_common_delete">Mark&nbsp;for&nbsp;deletion</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ret&nbsp;View&nbsp;<A HREF="h_view_cmd_hilite">hilited</A>&nbsp;item<BR>
+&nbsp;U&nbsp;&nbsp;&nbsp;<A HREF="h_common_delete">Undelete</A>&nbsp;(remove&nbsp;deletion&nbsp;mark)&nbsp;&nbsp;^F&nbsp;&nbsp;Select&nbsp;next&nbsp;<A HREF="h_view_cmd_hilite">hilited</A>&nbsp;item&nbsp;in&nbsp;message<BR>
+&nbsp;T&nbsp;&nbsp;&nbsp;<A HREF="h_common_take">Take&nbsp;Address</A>&nbsp;into&nbsp;Address&nbsp;Book&nbsp;&nbsp;&nbsp;^B&nbsp;&nbsp;Select&nbsp;previous&nbsp;<A HREF="h_view_cmd_hilite">hilited</A>&nbsp;item<BR>
+&nbsp;%&nbsp;&nbsp;&nbsp;<A HREF="h_common_print">Print</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;<A HREF="h_common_flag">Flag</A>&nbsp;message<BR>
+&nbsp;W&nbsp;&nbsp;&nbsp;<A HREF="h_view_cmd_whereis">Whereis</A>:&nbsp;search&nbsp;for&nbsp;text&nbsp;in&nbsp;msg&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;<A HREF="h_common_pipe">Pipe</A>&nbsp;to&nbsp;a&nbsp;Unix&nbsp;command<BR>
+<BR>
+Navigating&nbsp;the&nbsp;List&nbsp;of&nbsp;Messages&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Other&nbsp;Commands<BR>
+-------------------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;----------------------------<BR>
+&nbsp;P&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Display&nbsp;previous&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;G&nbsp;&nbsp;&nbsp;<A HREF="h_common_goto">Goto</A>&nbsp;a&nbsp;specified&nbsp;folder<BR>
+&nbsp;N&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Display&nbsp;next&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;H&nbsp;&nbsp;&nbsp;<A HREF="h_common_hdrmode">Full&nbsp;header&nbsp;mode</A>&nbsp;on/off<BR>
+&nbsp;J&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_common_jump">Jump</A>&nbsp;to&nbsp;a&nbsp;specific&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;&nbsp;&nbsp;Select&nbsp;Current&nbsp;message<BR>
+Tab&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_common_nextnew">Next&nbsp;new</A>&nbsp;message&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A&nbsp;&nbsp;&nbsp;<A HREF="h_config_prefer_plain_text">Toggle&nbsp;Prefer&nbsp;Plain&nbsp;Text</A><BR>
+<BR>
+General&nbsp;Alpine&nbsp;Commands<BR>
+---------------------<BR>
+&nbsp;O&nbsp;&nbsp;Show&nbsp;all&nbsp;other&nbsp;available&nbsp;commands<BR>
+&nbsp;?&nbsp;&nbsp;Show&nbsp;Help&nbsp;text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Q&nbsp;Quit&nbsp;Alpine<BR>
+&nbsp;M&nbsp;&nbsp;MAIN&nbsp;MENU&nbsp;Screen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;L&nbsp;<A HREF="h_common_folders">FOLDER&nbsp;LIST</A>&nbsp;Screen&nbsp;(or&nbsp;COLLECTION&nbsp;LIST&nbsp;Screen)<BR>
+&nbsp;&lt;&nbsp;&nbsp;<A HREF="h_common_index">MESSAGE&nbsp;INDEX</A>&nbsp;Screen&nbsp;&nbsp;C&nbsp;<A HREF="h_common_compose">Compose</A>&nbsp;a&nbsp;new&nbsp;message<BR>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;<A HREF="h_common_role">Compose&nbsp;using&nbsp;a&nbsp;role</A><BR>
+<!--chtml endif-->
+<P>
+
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI>Availability of certain commands depends on <A HREF="h_common_conditional_cmds">feature settings</A>.
+</OL>
+
+<H2>Description of the MESSAGE TEXT Screen</H2>
+
+The top line of the view message screen displays status
+information about the currently open collection and folder and about the
+current message. It shows the name of the collection in angle brackets
+and then the name of the folder. The line also displays the number
+of messages in the folder, the number of the current message and the
+percentage of the current message that has been displayed on the screen.
+If the message is marked for deletion &quot;DEL&quot; will appear in the upper
+right corner.
+If the message has been answered (but not deleted) &quot;ANS&quot; will show
+in the corner.
+<P>
+
+NOTE: to rapidly move to the end of a message, hit the
+<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->
+(or Ctrl-W) key followed
+by Ctrl-V. Similarly,
+<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->
+followed by Ctrl-Y will take you to the beginning of
+a message.
+
+<H2>Explanation of Alternate Character Sets</H2>
+
+Alpine attempts to stay out of the way so that it won't prevent you from
+viewing mail in any character set. It will simply send the message to
+your display device. If the device is capable of displaying the
+message as it was written it will do so. If not, the display may be
+partially or totally incorrect.
+If the message contains characters that are not representable in your
+<A HREF="h_config_char_set">&quot;Display Character Set&quot;</A>
+variable in your configuration, then a warning message will be printed
+to your screen at the beginning of the message display.
+It is probably best to use UNIX Alpine in a terminal emulator
+capable of displaying UTF-8 characters.
+See <A HREF="h_config_char_set">Display Character Set</A> for a little
+more information about character set settings.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_select =======
+<HTML>
+<HEAD>
+<TITLE>Selecting: Select and WhereIs/Select</TITLE>
+</HEAD>
+<BODY>
+<H1>Selecting: Select and WhereIs/Select</H1>
+
+Aggregate operations give you the ability to process a group of messages
+at once. Acting on multiple messages requires two steps: (1) selecting a
+set of messages and then; (2) applying a command to that set. The first
+part is handled by the select command. Select allows you to
+select messages based on their status (read, answered, etc.), contents,
+date, size, or keywords.
+You may also select based on one of your Rules or based on threads,
+and there are quick options to select a specific message or range of messages,
+to select the current message, or to select all messages.
+<P>
+
+We describe the various selection criteria briefly:
+<P>
+
+<DL>
+<DT>select All</DT>
+<DD> Marks all the messages in the folder as selected.
+</DD>
+
+<DT>select Cur</DT>
+<DD> Selects the currently highlighted message or currently highlighted
+set of messages if in a threaded view.
+</DD>
+
+<DT>select by Number</DT>
+<DD> Select by message number. This may be a comma-separated list instead or
+a single entry.
+Each element in the list may be either a single message number or a range
+of numbers with a dash between the lowest and highest member of the range.
+Some examples are 7 to select only message number 7; 2-5 to select messages
+2 through 5; and 2-5,7-9,11 to select messages 2, 3, 4, 5, 7, 8, 9, and 11.
+The word &quot;end&quot; may be used as a substitute for the highest numbered
+message in the folder.
+If in a separate thread index where the numbers refer to threads instead of
+to messages, then you will be selecting all of the messages in the
+referenced threads instead of selecting by message number.
+</DD>
+
+<DT>select by Date</DT>
+<DD> Select by either the date stored in the Date headers of each message,
+or by the date when the messages arrived.
+This does not adjust for different time zones, but just checks to see what
+day the message was sent on.
+You may type in a date. If you do, the date should be in the form
+<P><SAMP><CENTER>DD-Mon-YYYY</CENTER></SAMP><P>
+For example,
+<P><SAMP><CENTER>24-Nov-2004</CENTER></SAMP><P>
+or
+<P><SAMP><CENTER>09-Nov-2004</CENTER></SAMP><P>
+If the date you want is close to the current date, it is probably
+easier to use the &quot;^P&nbsp;Prev Day&quot; or &quot;^N&nbsp;Next Day&quot; commands to change the default date that
+is displayed, instead of typing in the date yourself.
+Or, the &quot;^X&nbsp;Cur Msg&quot; command may be used to fill in
+the date of the currently highlighted message.
+<P>
+There are six possible settings that are selected using the
+&quot;^W&nbsp;Toggle When&quot; command.
+Three of them select messages based on the Date headers.
+They are &quot;SENT SINCE&quot;, &quot;SENT BEFORE&quot;,
+and &quot;SENT ON&quot;.
+SINCE is all messages with the selected date or later.
+BEFORE is all messages earlier than the selected date (not including the day
+itself).
+ON is all messages sent on the selected date.
+The other three select messages in the same way but they use the arrival
+times of the messages instead of the Date headers included in the messages.
+Those three are &quot;ARRIVED SINCE&quot;, &quot;ARRIVED BEFORE&quot;,
+and &quot;ARRIVED ON&quot;.
+When you save a message from one folder to another the arrival time is
+preserved.
+</DD>
+
+<DT>select by Text</DT>
+<DD> Selects messages based on the message contents.
+This allows you to select a set of messages based on whether or not the
+message headers or message body contain specified text.
+You may look for text in the Subject, the From header,
+the To header, or the Cc header.
+You may also choose Recipient, which searches for the text in
+either the To or the Cc header;
+or Participant, which means To or Cc or From.
+Besides those specific header searches, you may also search the entire
+header and text of the message with &quot;All Text&quot;, or just the
+body of the message.
+<P>
+To search for the absence of text, first type the &quot;! Not&quot; command
+before typing the specific type of text search.
+For example, you could type &quot;!&quot; followed by &quot;S&quot; to
+search for all messages that do not contain a particular word in their
+Subjects.
+<P>
+If you choose a Subject search, you may use the subject from the current
+message by typing the &quot;^X Cur Subject&quot; command.
+You may then edit it further if you wish.
+For example, you might select the subject of a reply and edit the
+&quot;Re:&nbsp;&quot; off of the front of it in order to search for
+the original message being replied to.
+All of the other header searches allow you to use addresses from the
+headers of the current message if you want to.
+You may use the &quot;^T Cur To&quot;, &quot;^R Cur From&quot;, or
+&quot;^W Cur Cc&quot;.
+In each case, if there is more than one address, only the first is offered.
+</DD>
+
+<DT>select by Status</DT>
+<DD> Selects messages based on their status.
+You may select all New, Important, Deleted, Answered, Recent, or Unseen
+messages.
+Or, if you first type the &quot;! Not&quot; command, you get not New,
+or not Important, and so on.
+If you select Deleted messages, you will get all messages with their
+Deleted flag set.
+Likewise for Important messages, all messages that you have flagged as
+being Important with the
+<A HREF="h_common_flag">Flag</A> command.
+The &quot;New&quot; and &quot;Answered&quot; choices are a little bit odd
+because they try to match what you see on the screen by default.
+&quot;New&quot; is a shorthand for messages that are Unseen, Undeleted,
+and Unanswered.
+If you have looked at the message, or deleted it, or answered it; then it
+is not considered &quot;New &quot;.
+&quot;!&nbsp;New&quot; is the opposite of &quot;New&quot;.
+<P>
+&quot;Answered&quot; is another one that is a little different.
+It means that the message has been Answered <EM>and</EM> is not deleted.
+And to make it even more confusing, &quot;!&nbsp;Answered&quot; is not
+the opposite of &quot;Answered&quot;!
+Instead, &quot;!&nbsp;Answered&quot; stands for messages that are
+both Unanswered <EM>and</EM> not deleted.
+<P>
+The other two types were added later because the special nature of the
+New flag was not what was wanted by all users.
+New does match what you see in the index by default, but if you use
+the IMAPSTATUS or SHORTIMAPSTATUS token in the
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot; it may not
+be exactly what you want.
+&quot;Unseen&quot; simply selects all unseen messages, whether or not
+they are deleted or answered, and
+&quot;Recent&quot; selects all of the messages that have been added to
+the folder since you started this Alpine session.
+(That's not technically quite true. If there are multiple mail clients
+reading an IMAP mailbox, each message will be marked as Recent in only
+one of the client's sessions.)
+</DD>
+
+<DT>select by siZe</DT>
+<DD> Selects messages based on their size being smaller than or larger
+than the size you specify.
+The size is the number of bytes.
+You may use the suffix &quot;K&quot; or &quot;k&quot; to mean 1,000 times
+the number.
+For example, 7K is the same as 7000.
+The suffix &quot;M&quot; or &quot;m&quot; means 1,000,000 times the number,
+and the suffix &quot;G&quot; or &quot;g&quot; means 1,000,000,000 times.
+Use the &quot;^W&quot; command to toggle back and forth between Smaller
+and Larger.
+</DD>
+
+<DT>select by Keyword</DT>
+<DD> Selects messages that either have or do not have
+(using the &quot;!&nbsp;Not&quot; command)
+a particular <A HREF="h_config_keywords">Keyword</A> set.
+One way to select a keyword is to use the &quot;^T&nbsp;To&nbsp;List&quot;
+command to select one from your list of keywords.
+The
+<A HREF="h_config_flag_screen_kw_shortcut"><!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"--></A> option allows selecting by Keyword initials if set.
+</DD>
+
+<DT>select by Rule</DT>
+<DD> Selects messages that either match or don't match
+(using the &quot;!&nbsp;Not&quot; command)
+one of the Rules you have defined.
+The most likely method of filling in the Rule is to use the
+&quot;^T&nbsp;To&nbsp;List&quot;
+command to select one of your Rules.
+All of the Rules you have defined will be in the list, including
+Rules for Searching, Indexcolors, Filtering, Roles, Score setting, and Other.
+They may not all make sense for this purpose, but they are all there for
+flexibility.
+You might find it useful to define some rules solely for the purpose
+of being used by the Select command.
+There is a special category for such Rules. They are called Search Rules.
+<P>
+Unfortunately, Alpine does not allow all possible Rules to be defined.
+For example, there is no logical OR operation.
+OR is accomplished in the Filter Rules or the other types of Rules by
+simply defining two rules, one that matches the first part of the OR
+and another that matches the second part.
+But you can't do that here, since you only have a single Rule to work with.
+Likewise, the order of Rules is usually important.
+For example, if the first Filter Rule (or Indexcolor rule or ...) matches
+a message, then that stops the search for a further match.
+This means that you may be confused if you try to use Select by Rule to
+check your Filter rules because the order is important when filtering but
+is not considered here.
+</DD>
+
+<DT>select by tHread</DT>
+<DD> Selects all of the messages in the current thread.
+</DD>
+</DL>
+
+After you have an initial selection, the next and subsequent selection
+commands modify the selection.
+The select command changes. It first gives
+you selection &quot;alteration&quot; options: &quot;unselect All&quot;,
+&quot;unselect Current&quot;,
+&quot;Broaden selection&quot; (implements a logical OR), and
+&quot;Narrow selection&quot; (implements a logical AND).
+After you choose either Broaden or Narrow, you then choose one of the
+selection criteria listed above (by Text or Number or ...).
+You may use select as many times as you wish to get the selected set right.
+<P>
+
+The WhereIs command has a feature (Ctrl-X) to
+select all the messages that match the WhereIs search. WhereIs searches
+through just the text that appears on the MESSAGE INDEX screen.
+This method is often slower than using the select command itself, unless the
+line you are looking for is not too far away in the index.
+<P>
+
+The availability of the aggregate operations commands is determined by the
+<A HREF="h_config_enable_agg_ops">&quot;<!--#echo var="FEAT_enable-aggregate-command-set"-->&quot;</A>
+Feature-List option in your Alpine
+configuration, which defaults to set.
+The features
+<A HREF="h_config_auto_zoom">&quot;<!--#echo var="FEAT_auto-zoom-after-select"-->&quot;</A>
+and
+<A HREF="h_config_select_wo_confirm">&quot;<!--#echo var="FEAT_select-without-confirm"-->&quot;</A>
+affect the behavior of the Select command.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_select_rule =======
+<HTML>
+<HEAD>
+<TITLE>Select: Rule</TITLE>
+</HEAD>
+<BODY>
+<H1>Select: Rule</H1>
+
+You are selecting messages that either match or don't match
+one of the Rules you have defined.
+You may either type the nickname of the Rule at the prompt, or use the
+&quot;^T&nbsp;To&nbsp;List&quot;
+command to select one of your Rules.
+All of the Rules you have defined will be in the list, including
+Rules for Indexcolors, Filtering, Roles, Score setting, and Other.
+They may not all make sense for this purpose, but they are all there for
+flexibility.
+Rules may be added by using the Setup/Rules screen off of the main Alpine
+menu.
+<P>
+Unfortunately, Alpine does not allow all possible Rules to be defined.
+For example, there is no logical OR operation.
+OR is accomplished in the Filter Rules or the other types of Rules by
+simply defining two rules, one that matches the first part of the OR
+and another that matches the second part.
+But you can't do that here, since you only have a single Rule to work with.
+Likewise, the order of Rules is usually important.
+For example, if the first Filter Rule (or Indexcolor rule or ...) matches
+a message, then that stops the search for a further match.
+This means that you may be confused if you try to use Select by Rule to
+check your Filter rules because the order is important when filtering but
+is not considered here.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_select_text =======
+<HTML>
+<HEAD>
+<TITLE>Select: Text</TITLE>
+</HEAD>
+<BODY>
+<H1>Select: Text</H1>
+
+You are selecting messages based on the contents of the message.
+This allows you to select a set of messages based on whether or not the
+message headers or message body contain specified text.
+You may look for text in the Subject, the From header,
+the To header, or the Cc header.
+You may also choose Recipient, which searches for the text in
+either the To or the Cc header;
+or Participant, which means either the To header, or the Cc header,
+or the From header.
+Besides those specific header searches, you may also search the entire
+header and text of the message with &quot;All Text&quot;, or just the
+body of the message with &quot;Body&quot;.
+<P>
+To search for the absence of text, first type the &quot;!&nbsp;Not&quot; command
+before typing the specific type of text search.
+For example, you could type &quot;!&quot; followed by &quot;S&quot; to
+search for all messages that do not contain a particular word in their
+Subjects.
+<P>
+If you choose a Subject search, you may use the subject from the current
+message by typing the &quot;^X Cur Subject&quot; command.
+You may then edit it further if you wish.
+For example, you might select the subject of a reply and edit the
+&quot;Re:&nbsp;&quot; off of the front of it in order to search for
+the original message being replied to.
+All of the other header searches allow you to use addresses from the
+headers of the current message if you want to.
+You may use the &quot;^T Cur To&quot;, &quot;^R Cur From&quot;, or
+&quot;^W Cur Cc&quot;.
+In each case, if there is more than one address, only the first is offered.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_select_status =======
+<HTML>
+<HEAD>
+<TITLE>Select: Status</TITLE>
+</HEAD>
+<BODY>
+<H1>Select: Status</H1>
+
+You are selecting messages based on the status of the message.
+For example, whether or not the message has been marked Deleted or Important,
+or whether or not it has been Answered or is New.
+If you first type the &quot;!&nbsp;Not&quot; command, you will get the
+opposite: not Deleted, not Important, and so on.
+<P>
+If you select Deleted messages, you will get all messages with their
+Deleted flag set.
+Likewise for Important messages, all messages that you have flagged as
+being Important with the
+<A HREF="h_common_flag">Flag</A> command.
+The &quot;New&quot; and &quot;Answered&quot; choices are a little bit odd
+because they try to match what you see on the screen by default.
+&quot;New&quot; is a shorthand for messages that are Unseen, Undeleted,
+and Unanswered.
+If you have looked at the message, or deleted it, or answered it; then it
+is not considered &quot;New &quot;.
+&quot;!&nbsp;New&quot; is the opposite of &quot;New&quot;.
+<P>
+&quot;Answered&quot; is another one that is a little different.
+It means that the message has been Answered <EM>and</EM> is not deleted.
+And to make it even more confusing, &quot;!&nbsp;Answered&quot; is not
+the opposite of &quot;Answered&quot;!
+Instead, &quot;!&nbsp;Answered&quot; stands for messages that are
+both Unanswered <EM>and</EM> not deleted.
+<P>
+(The New and Answered options may seem counter-intuitive.
+The reason it is done this way is
+because, by default, a Deleted message will show up with the &quot;D&quot;
+symbol in the MAIL INDEX screen even if it is New or Answered.
+The Delete symbol overrides the New and Answered symbols, because you
+usually don't care about the message anymore once you've deleted it.
+Similarly, you usually only care about whether a message is Answered or
+not if it is not Deleted.
+Once it is Deleted you've put it out of your mind.)
+<P>
+The other two options were added later because the special nature of the
+New flag was not what was wanted by all users.
+New does match what you see in the index by default, but if you use
+the IMAPSTATUS or SHORTIMAPSTATUS token in the
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot; it may not
+be exactly what you expect.
+&quot;Unseen&quot; simply selects all unseen messages, whether or not
+they are deleted or answered, and
+&quot;Recent&quot; selects all of the messages that have been added to
+the folder since you started this Alpine session.
+(That's not technically quite true. If there are multiple mail clients
+reading an IMAP mailbox, each message will be marked as Recent in only
+one of the client's sessions.
+That behavior can be convenienent for some purposes, like filtering, but
+it isn't usually what you expect when selecting.)
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_apply =======
+<HTML>
+<HEAD>
+<TITLE>Apply Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Apply Command</H1>
+
+Apply
+(<!--chtml if pinemode="function_key"-->F6<!--chtml else-->A<!--chtml endif-->)
+is the second step of most aggregate operations. Apply
+becomes active any time there is a defined set of selected messages. The
+following commands can be applied to a selected message set: delete,
+undelete, reply, forward,
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+pipe,
+<!--chtml endif-->
+print, take address, save, export, bounce, and flag.
+<P>
+
+The behavior of some of these commands in an aggregate sense is not easy to
+explain. Try them out to see what they do.
+The feature
+<A HREF="h_config_auto_unzoom">&quot;<!--#echo var="FEAT_auto-unzoom-after-apply"-->&quot;</A>
+affects the behavior of the Apply command, as does the feature
+<A HREF="h_config_auto_unselect">&quot;<!--#echo var="FEAT_auto-unselect-after-apply"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_zoom =======
+<HTML>
+<HEAD>
+<TITLE>ZoomMode Command</TITLE>
+</HEAD>
+<BODY>
+<H1>ZoomMode Command</H1>
+
+Another action you might want to take on a set of selected messages is to
+zoom in on them. Like apply, zoom only becomes active when messages have
+been selected.
+ZoomMode
+(<!--chtml if pinemode="function_key"-->F3<!--chtml else-->Z<!--chtml endif-->)
+is a toggle command that allows you to
+zoom-in (and only see the selected messages) and zoom-out (to see all
+messages in the folder). Neither apply nor zoom removes the markings that
+define the selected set; you need to use a select command in order
+to do that.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_collapse_expand =======
+<HTML>
+<HEAD>
+<TITLE>Collapse/Expand Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Collapse/Expand Command</H1>
+
+The Collapse/Expand command is only available from the MESSAGE INDEX screen when
+the folder is sorted by either Threads or OrderedSubject, and the
+<A HREF="h_config_thread_disp_style"><!--#echo var="VAR_threading-display-style"--></A>
+is set to something other than &quot;none&quot;.
+By default, this command collapses or expands the subthread that starts at
+the currently highlighted message, if any.
+If the subthread is already collapsed, then this command expands it.
+If the subthread is expanded, then this command collapses it.
+If there are no more messages below the current message in the
+thread tree (that is, there are no replies to the current message) then
+this command does nothing.
+
+<P>
+The behavior of this command is affected by the option
+<A HREF="h_config_slash_coll_entire">&quot;<!--#echo var="FEAT_slash-collapses-entire-thread"-->&quot;</A>.
+Normally, this command Collapses or Expands the subthread that
+starts at the currently highlighted message.
+If the above option is set, then this command Collapses or Expands the
+entire current thread instead of just the subthread.
+The current thread is simply the top-level thread that contains the
+current message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_sort =======
+<HTML>
+<HEAD>
+<TITLE>Sort Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Sort Command</H1>
+
+In Alpine's generic configuration, messages are presented in the order in
+which they arrive. This default can be changed in the SETUP CONFIGURATION
+with the &quot;<A HREF="h_config_sort_key"><!--#echo var="VAR_sort-key"--></A>&quot; option.
+You can also re-sort the folder on demand with the sort
+(<!--chtml if pinemode="function_key"-->F7<!--chtml else-->$<!--chtml endif-->)
+command.
+Your sorting options are:
+<P>
+<UL>
+ <LI> <A HREF="h_index_sort_arrival">A</A>rrival
+ <LI> <A HREF="h_index_sort_date">D</A>ate
+ <LI> <A HREF="h_index_sort_subj">S</A>ubject
+ <LI> <A HREF="h_index_sort_ordsubj">O</A>rderedSubject
+ <LI> t<A HREF="h_index_sort_thread">H</A>read
+ <LI> <A HREF="h_index_sort_from">F</A>rom
+ <LI> si<A HREF="h_index_sort_size">Z</A>e
+ <LI> scor<A HREF="h_index_sort_score">E</A>,
+ <LI> <A HREF="h_index_sort_to">T</A>o
+ <LI> <A HREF="h_index_sort_cc">C</A>c
+</UL>
+
+<P>
+The Reverse option will toggle the order the index is currently
+sorted by, but will not change the relative sort order.
+
+<P>
+Sorting a folder does not actually rearrange the way the folder is saved,
+it just re-arranges how the messages are presented to you. This means
+that Alpine has to do the work of sorting every time you change sort order.
+Sometimes, especially with PC-Alpine or with large folders, this could take
+a while.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_default =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Default</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Default</H1>
+
+The <EM>Default</EM> sort option just means to use the default sort order
+set in the
+<li><a href="h_config_sort_key"><!--#echo var="VAR_sort-key"--></a>
+option in Setup/Config.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_arrival =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Arrival</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Arrival</H1>
+
+The <EM>Arrival</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+in the order that they exist in the folder. This is usually the same as the
+order in which they arrived. This option is comparable to not sorting
+the messages at all.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_date =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Date</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Date</H1>
+
+The <EM>Date</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+according to the date and time they were
+sent.
+
+<P>
+On a folder like INBOX, sorting by &quot;Date&quot; should be almost
+identical to sorting by &quot;Arrival&quot;.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_subj =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Subject</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Subject</H1>
+
+The <EM>Subject</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+by subject.
+
+<P>
+Messages with the same subject are
+first grouped together, and then the groups of like-subject messages
+are arranged alphabetically.
+
+<P>
+Alpine ignores leading &quot;Re:&quot; and
+&quot;re:&quot; and trailing &quot;(fwd)&quot; when determining the
+likeness and alphabetical order of subject lines.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_ordsubj =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: OrderedSubject</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: OrderedSubject</H1>
+
+The <EM>OrderedSubject</EM> sort option arranges messages in the
+MESSAGE&nbsp;INDEX by grouping all messages with the same subject
+together, similar to sort by <A HREF="h_index_sort_subj">S</A>ubject.
+
+<P>
+However, <EM>OrderedSubj</EM> then arranges the groups of like-subject
+messages by the date of the oldest message in the group.
+
+<P>
+This sort method provides for pseudo threading of conversations within
+a folder.
+You may want to try sorting by Thread instead.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_thread =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Thread</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Thread</H1>
+
+The <EM>Thread</EM> sort option arranges messages in the
+MESSAGE&nbsp;INDEX by grouping all messages that indicate
+they are part of a conversation (discussion thread) taking
+place within a mailbox or newsgroup. This indication is
+based on information in the message's header -- specifically
+its <tt>References:</tt>, <tt>Message-ID:</tt>, and <tt>Subject:</tt> fields.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_from =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: From</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: From</H1>
+
+The <EM>From</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+by the name of the author of the message.
+
+<P>
+Messages with the same author are grouped together. Groups of
+messages are then put into alphabetical order according to message
+author.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_size =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Size</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Size</H1>
+
+The <EM>Size</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+by their relative sizes.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_score =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Score</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Score</H1>
+
+The <EM>Score</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+by their scores.
+
+<P>
+Messages with the same score are sorted in arrival order.
+Scores are something you create using the
+<A HREF="h_rules_score">&quot;SETUP SCORING&quot;</A> screen.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_to =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: To</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: To</H1>
+
+The <EM>To</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX
+by the names of the recipients of the message.
+
+<P>
+Messages with the same recipients are grouped together. Groups of
+messages are then put into alphabetical order according to message
+recipients.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_sort_cc =======
+<HTML>
+<HEAD>
+<TITLE>SORT OPTION: Cc</TITLE>
+</HEAD>
+<BODY>
+<H1>SORT OPTION: Cc</H1>
+
+The <EM>Cc</EM> sort option arranges messages in the MESSAGE&nbsp;INDEX by
+the names of the carbon copy addresses of the message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_whereis =======
+<HTML>
+<HEAD>
+<TITLE>WhereIs Command</TITLE>
+</HEAD>
+<BODY>
+<H1>WhereIs Command</H1>
+
+The WhereIs
+(<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->)
+command lets you search the MESSAGE INDEX for a word.
+It scans through whatever you see, usually the name of the author
+and the Subject line.
+WhereIs has special subcommands to let you find the beginning of the
+index (Ctrl-Y -- first message)
+or the end of the index (Ctrl-V -- last message).
+<P>
+Note that WhereIs only searches through the visible text on the screen.
+For example, if only part of the Subject of a message is shown because it
+is long, then only the visible portion of the Subject is searched.
+Also note that WhereIs does not &quot;see&quot; the
+&quot;X&quot; in column one of Index entries for selected messages
+so it can't be used to search for
+selected messages (use &quot;Zoom&quot; instead).
+<P>
+If the feature
+<A HREF="h_config_enable_agg_ops">&quot;<!--#echo var="FEAT_enable-aggregate-command-set"-->&quot;</A>
+is turned on,
+WhereIs can also be used as a quick way to select messages that match the
+string being searched for.
+Instead of typing carriage return to search for the next match, type
+Ctrl-X to select all matches.
+Once again, this only selects matches that are (or would be if the right
+index line was on the screen) visible.
+Truncated From lines or Subjects will cause matches to be missed.
+Although WhereIs is sometimes convenient for quick matching, the Select
+command is usually more powerful and usually faster.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_view_cmd_whereis =======
+<HTML>
+<HEAD>
+<TITLE>WhereIs Command</TITLE>
+</HEAD>
+<BODY>
+<H1>WhereIs Command</H1>
+
+The WhereIs
+(<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->)
+command does a &quot;find in current message&quot; operation. You
+type in text and Alpine will try to find it in the message you are
+reading. WhereIs also has subcommands to jump to the beginning (Ctrl-Y)
+or end (Ctrl-V) of the message.
+That is, to rapidly move to the end of a message, hit the
+<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->
+(or Ctrl-W) key followed
+by Ctrl-V. Similarly,
+<!--chtml if pinemode="function_key"-->F8<!--chtml else-->W<!--chtml endif-->
+followed by Ctrl-Y will take you to the beginning of a message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_view_cmd_hilite =======
+<HTML>
+<HEAD>
+<TITLE>View Hilite and Next item/Previous item</TITLE>
+</HEAD>
+<BODY>
+<H1>View Hilite and Next item/Previous item</H1>
+
+Sometimes messages may be in the form of formatted HTML text
+or they may contain URLs or Web server hostnames.
+When any of the features
+<A HREF="h_config_enable_view_url">&quot;<!--#echo var="FEAT_enable-msg-view-urls"-->&quot;</A>,
+<A HREF="h_config_enable_view_web_host">&quot;<!--#echo var="FEAT_enable-msg-view-web-hostnames"-->&quot;</A>,
+<A HREF="h_config_enable_view_attach">&quot;<!--#echo var="FEAT_enable-msg-view-attachments"-->&quot;</A>,
+or
+<A HREF="h_config_enable_view_addresses">&quot;<!--#echo var="FEAT_enable-msg-view-addresses"-->&quot;</A>
+are enabled, Alpine will represent such selectable items in the text
+in bold typeface. One of the selectable items will be displayed in
+inverse video (highlighted). This is the &quot;currently selected&quot; item.
+Press the Return key to view the currently selected item.
+<P>
+
+The Up and Down Arrows keys can be used to change the selected item
+(also see the feature
+<A HREF="h_config_enable_view_arrows">&quot;<!--#echo var="FEAT_enable-msg-view-forced-arrows"-->&quot;</A>).
+If there are no selectable items in the direction of the arrow you
+pressed, Alpine will scroll the display in that direction until one
+becomes visible. To &quot;jump&quot; forwards/backwards among selectable
+items in the message text, use the Previous and Next item commands,
+<!--chtml if pinemode="function_key"-->F5 and F6
+<!--chtml else-->^B and ^F<!--chtml endif-->.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_view_cmd_viewattch =======
+<HTML>
+<HEAD>
+<TITLE>ViewAttch Command</TITLE>
+</HEAD>
+<BODY>
+<H1>ViewAttch Command</H1>
+
+
+The View/Save Attachment
+(<!--chtml if pinemode="function_key"-->F4<!--chtml else-->V<!--chtml endif-->)
+command allows you to handle MIME attachments to a message you have
+received. Alpine shows you a list of the message attachments -- you just
+choose the attachment you want. You may either view or save the
+selected attachment.
+
+<P>
+Because many attachments require external programs for display, there
+is some system configuration that has to happen before you can
+actually display attachments. Hopefully much of that will have been
+done already by your system administrator. MIME configuration is
+handled with the &quot;mailcap&quot; configuration file. (See the section
+on configuration in the
+<A HREF="h_news">release notes</A> for more information.)
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_cmd_expunge =======
+<HTML>
+<HEAD>
+<TITLE>Expunge/Exclude Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Expunge/Exclude Command</H1>
+
+Expunge/Exclude
+(<!--chtml if pinemode="function_key"-->F3<!--chtml else-->X<!--chtml endif-->)
+is the command Alpine uses to actually remove all messages
+marked for deletion. With regular email files, expunge literally deletes
+the text from the current folder. With newsgroups or shared mailboxes,
+you don't have permission to actually remove the message, so it is an
+exclude -- Alpine removes the message from your view of the folder even
+though it is not technically gone.
+<P>
+
+The configuration features
+<A HREF="h_config_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm"-->&quot;</A>
+and
+<A HREF="h_config_full_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm-everywhere"-->&quot;</A>
+affect the behavior of the Expunge command.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_compose =======
+<HTML>
+<HEAD>
+<TITLE>Compose Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Compose Command</H1>
+
+The Compose command takes you into the Alpine message composer where you
+can start a new message for sending. This is where you type in the
+message's text and specify its recipient list (the &quot;To:&quot;
+address), where copies should be directed (e.g., &quot;Fcc&quot;,
+&quot;Cc:&quot; or &quot;Bcc:&quot;), and which files, if any, should
+be attached to the message.
+
+<P>
+When you type this command, Alpine will also automatically check for any
+interrupted (i.e., a message that was being composed when your modem
+or network connection was broken) or previously postponed messages and
+offer you a chance to continue working on those.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_index =======
+<HTML>
+<HEAD>
+<TITLE>Message Index Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Message Index Command</H1>
+
+The Index command takes you to the MESSAGE INDEX screen that displays a
+summary caption for each message in the currently-open folder. One
+message will be highlighted; this is the &quot;Current&quot; message.
+The message commands available from this screen (e.g. View, Reply,
+Forward, Delete, Print, Save, etc) apply to the current message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_folders =======
+<HTML>
+<HEAD>
+<TITLE>Folder List Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Folder List Command</H1>
+
+This Folder List command takes you to the FOLDER LIST screen that displays
+the names of all your message folders and allows you to view, rename,
+delete, and add folders. You can open (view) a different folder than the
+one currently open by highlighting the desired one (using the arrow keys
+or their control-key equivalents) and pressing RETURN.
+
+<P>
+If you have multiple folder collections defined (see the Help text for
+the FOLDER LIST screen to learn more about Collections), you may need
+to press Return to expand the collection and display all of the
+folders in it.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_main_addrbook =======
+<HTML>
+<HEAD>
+<TITLE>Address Book Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Address Book Command</H1>
+
+This command, available only from the MAIN MENU, takes you
+to the ADDRESS BOOK management screen. From here, your personal address
+book(s) may be updated.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_main_setup =======
+<HTML>
+<HEAD>
+<TITLE>Setup Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Setup Command</H1>
+
+The Setup command, available only from the MAIN MENU, prompts you for
+one of several configuration screens, including the SETUP CONFIGURATION
+screen, by which you may activate optional Alpine features.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_main_release_notes =======
+<HTML>
+<HEAD>
+<TITLE>Release Notes Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Release Notes Command</H1>
+
+This command displays information about Alpine <!--#echo var="ALPINE_VERSION"-->,
+as well as pointers to further information such as history and legal notes.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_main_kblock =======
+<HTML>
+<HEAD>
+<TITLE>Keyboard Lock Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Keyboard Lock Command</H1>
+
+This command allows your Alpine session to be protected
+during a temporary absence from your terminal.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_main_journal =======
+<HTML>
+<HEAD>
+<TITLE>Journal Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Journal Command</H1>
+
+This command displays a list of all the status messages Alpine has
+displayed (on the third line from the bottom of the screen). This may
+be useful if a message disappeared before you had a chance to read it.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_role =======
+<HTML>
+<HEAD>
+<TITLE>Role Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Role Command</H1>
+
+The Role command is similar to the Compose command except that it starts
+off by letting you select a <A HREF="h_rules_roles">role</A>
+to be used for the composition.
+You may set up alternate roles by using Setup/Rules/Roles.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_conditional_cmds =======
+<HTML>
+<HEAD>
+<TITLE>Conditional Commands</TITLE>
+</HEAD>
+<BODY>
+<H1>Conditional Commands</H1>
+
+The presence or absence of certain commands, particularly in the
+MESSAGE INDEX and MESSAGE TEXT screens, is determined by
+whether or not specific features are set in your Alpine configuration.
+(You can access the SETUP CONFIGURATION screen, where they are found, from
+Alpine's MAIN MENU.) To see if a desired command's availability is
+conditioned on a feature setting, see the command's help text (highlight
+the phrase associated with the command and hit Return).
+
+<P>
+Also note that some
+commands may be administratively disabled by your system manager;
+if they don't work, please check with your local help desk.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_pipe =======
+<HTML>
+<HEAD>
+<TITLE>Pipe Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Pipe Command</H1>
+
+Pipe
+(<!--chtml if pinemode="function_key"-->F12<!--chtml else-->|<!--chtml endif-->)
+allows you to send a message to a specified Unix command for external
+processing.
+This command's availability is controlled by the
+<A HREF="h_config_enable_pipe">&quot;<!--#echo var="FEAT_enable-unix-pipe-cmd"-->&quot;</A>
+feature.
+By default, the processed text of the message is sent to the command
+you specify and the output is captured by Alpine and shown to you.
+When you run the pipe command, there are some sub-commands which may be
+used to alter this behavior.
+These sub-commands are described <A HREF="h_pipe_command">here</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_goto =======
+<HTML>
+<HEAD>
+<TITLE>Goto Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Goto Command</H1>
+
+Goto
+(<!--chtml if pinemode="function_key"-->F6<!--chtml else-->G<!--chtml endif-->)
+is the command that lets you bypass Alpine's folder selection screens
+and jump directly to a new folder. You can select any folder in the
+world: one in your current collection, one in a different collection or
+one in a collection you've never even used before.
+<P>
+
+Alpine will help you as much as possible to narrow in on the folder you want.
+However, if the folder is outside of your defined collections, you are
+going to have to enter the exact folder location using the correct
+<A HREF="h_valid_folder_names">syntax</A>
+for a remote folder and/or fully-qualified path name.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_nextnew =======
+<HTML>
+<HEAD>
+<TITLE>NextNew Command</TITLE>
+</HEAD>
+<BODY>
+<H1>NextNew Command</H1>
+
+When you press the TAB key, Alpine advances to the next
+&quot;interesting&quot; message.
+This will be the next message you have not seen before, or the next message
+you have flagged Important, whichever comes first.
+Unread messages that have been deleted are not considered interesting.
+(A note about reading news. Alpine expects you to &quot;Delete&quot; news
+articles after you have read them if you want to remove them from future
+consideration. See <A HREF="h_mainhelp_readingnews">Reading News</A> for
+more information.)
+
+<P>
+The NextNew command is affected by the feature
+<A HREF="h_config_tab_new_only">&quot;<!--#echo var="FEAT_tab-visits-next-new-message-only"-->&quot;</A>,
+which causes Alpine to only consider Unread messages interesting, not messages
+flagged Important.
+
+<P>
+This command behaves a little differently when it finds there are no more
+interesting messages left in the current folder.
+If the current folder is one of your Incoming Message Folders
+(<A HREF="h_config_enable_incoming">&quot;<!--#echo var="FEAT_enable-incoming-folders"-->&quot;</A>)
+or it is a newsgroup, then Alpine will try to find the next folder or
+newsgroup that contains <EM>Recent</EM> messages and will ask you
+if you want to open that folder.
+This behavior may be modified by using the
+<A HREF="h_config_tab_uses_unseen">&quot;<!--#echo var="FEAT_tab-uses-unseen-for-next-folder"-->&quot;</A>
+feature that causes Alpine to look for Unseen messages instead of Recent
+messages.
+The NextNew command's behavior is also affected by the configuration features
+<A HREF="h_config_auto_open_unread">&quot;<!--#echo var="FEAT_auto-open-next-unread"-->&quot;</A>,
+and
+<A HREF="h_config_tab_no_prompt">&quot;<!--#echo var="FEAT_continue-tab-without-confirm"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_jump =======
+<HTML>
+<HEAD>
+<TITLE>Jump Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Jump Command</H1>
+
+This is Alpine's way of allowing you to go straight to a specific message.
+Just press &quot;J&quot; and then enter the message number. By default, Alpine is also
+configured such that typing in any number automatically jumps you to that
+message
+(<A HREF="h_config_enable_jump">&quot;<!--#echo var="FEAT_enable-jump-shortcut"-->&quot;</A>
+in the SETUP CONFIGURATION).
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_flag =======
+<HTML>
+<HEAD>
+<TITLE>Flag Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Flag Command</H1>
+
+Flag
+(<!--chtml if pinemode="function_key"-->F11<!--chtml else-->*<!--chtml endif-->)
+is the command that allows users to manipulate the status flags that
+appear on the left side of the MESSAGE INDEX screen. The most common
+use of this is to mark a message as important. This is something of a
+note to yourself to get back to that message. You may also use the
+flag command to set (or unset) the flags that indicate that a message
+is new, answered, deleted, or forwarded.<P>
+
+Provided the mail server supports it,
+you may also manipulate user-defined keywords
+for a message using the flag command.
+These keywords will be available if you use the Flag Details screen that you
+can get to after typing the
+Flag (<!--chtml if pinemode="function_key"-->F11<!--chtml else-->*<!--chtml endif-->)
+command.
+They will be listed after the Important, New, Answered, Deleted , and Forwarded flags,
+which are always present.
+You may add new keywords by using the Add KW command from the Flag Details screen
+or by defining them in the <A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen.
+
+The availability of the flag command is determined by the
+<A HREF="h_config_enable_flag">&quot;<!--#echo var="FEAT_enable-flag-cmd"-->&quot;</A>
+feature in your Alpine configuration. Also, it is possible that Flag could be
+administratively disabled by your system manager; if it doesn't work,
+please check with your local help desk before reporting a bug.
+The behavior of the flag command may be modified by the
+<A HREF="h_config_flag_screen_default">&quot;<!--#echo var="FEAT_enable-flag-screen-implicitly"-->&quot;</A> option or the
+<A HREF="h_config_flag_screen_kw_shortcut">&quot;<!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"-->&quot;</A> option.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_hdrmode =======
+<HTML>
+<HEAD>
+<TITLE>HdrMode Command</TITLE>
+</HEAD>
+<BODY>
+<H1>HdrMode Command</H1>
+
+Every email message comes with some header lines that you normally
+don't see (and don't want to see).
+These include anywhere from 3-20 lines (or more) added by the
+Internet mail transport system to record the route your message took,
+for diagnostic purposes.
+These are normally of no import and simply
+add clutter, so Alpine suppresses them in the MESSAGE TEXT display.
+This also includes other non-standard headers the message may contain.
+If you want to see these headers, there is a way to reveal them.
+<P>
+
+The Header Mode
+(<!--chtml if pinemode="function_key"-->F9<!--chtml else-->H<!--chtml endif-->)
+command is a toggle that controls Alpine's handling of these header
+lines. Normally, full headers is &quot;off&quot; and you only see a
+few lines about who a message is to and who it is from. When you
+press
+(<!--chtml if pinemode="function_key"-->F9<!--chtml else-->H<!--chtml endif-->)
+to turn full headers on, Alpine will show you
+the normal header lines as well as delivery headers, comment headers,
+MIME headers, and any other headers present.
+<P>
+
+Several different Alpine commands honor the header mode -- it affects how
+messages are displayed, how they appear in forward and reply email, how
+they are printed, how they are saved, and how they are exported.
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+The pipe command is also affected.
+<!--chtml endif-->
+<P>
+
+The presence or absence of the Header Mode command is determined by the
+<A HREF="h_config_enable_full_hdr">&quot;<!--#echo var="FEAT_enable-full-header-cmd"-->&quot;</A>
+Feature-List option in your Alpine configuration.
+
+<P>
+If you have also turned on the
+<A HREF="h_config_quote_suppression">&quot;Quote Suppression&quot;</A>
+option then the HdrMode command actually rotates through three states
+instead of just two.
+The first is the normal view with long quotes suppressed.
+The second is the normal view but with the long quotes included.
+The last enables the display of all headers in the message.
+When using Export, Pipe, Print, Forward, or Reply the quotes are
+never suppressed, so the first two states are identical.
+
+<P>
+The behavior of the Header Mode command may be altered slightly by
+turning on the
+<A HREF="h_config_quell_full_hdr_reset">&quot;<!--#echo var="FEAT_quell-full-header-auto-reset"-->&quot;</A>
+Feature-List option in your Alpine configuration.
+In particular, it will cause the Header Mode to be persistent when moving
+from message to message instead of resetting to the default for each message.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_print =======
+<HTML>
+<HEAD>
+<TITLE>Print Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Print Command</H1>
+
+The Print
+(<!--chtml if pinemode="function_key"-->F9<!--chtml else-->%<!--chtml endif-->)
+command allows you to print a copy of a message.
+There are many SETUP CONFIGURATION features that affect the
+Print command, including
+<A HREF="h_config_enable_y_print">&quot;<!--#echo var="FEAT_enable-print-via-y-command"-->&quot;</A>,
+<A HREF="h_config_print_index">&quot;<!--#echo var="FEAT_print-index-enabled"-->&quot;</A>,
+<A HREF="h_config_custom_print">&quot;<!--#echo var="FEAT_print-offers-custom-cmd-prompt"-->&quot;</A>,
+<A HREF="h_config_print_from">&quot;<!--#echo var="FEAT_print-includes-from-line"-->&quot;</A>, and
+<A HREF="h_config_ff_between_msgs">&quot;<!--#echo var="FEAT_print-formfeed-between-messages"-->&quot;</A>.
+You set up for printing by using the Printer option of the Setup command
+on the MAIN MENU.
+<P>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_take =======
+<HTML>
+<HEAD>
+<TITLE>TakeAddr Command</TITLE>
+</HEAD>
+<BODY>
+<H1>TakeAddr Command</H1>
+
+
+With the Take Address
+(<!--chtml if pinemode="function_key"-->F10<!--chtml else-->T<!--chtml endif-->)
+command, you can extract email addresses from an
+incoming message and save them in an address book. This is an easy way
+to add to your address book and avoid having to remember the email
+addresses of the people who write to you.
+<P>
+
+If the message is just to you individually, then you will only need to
+provide a nickname. If the message contains more than one email address,
+then you will see an address
+selection screen that lets you choose the address you want to save into
+your address book, or lets you choose several of them add to a
+personal distribution list.
+<P>
+
+Once you've added an entry to your address book, you can use it from the
+message composer by typing the nickname of the entry into one of the
+header fields (for example, into the To: field), or you can use ^T from
+the header field to select the entry from your address book.
+<P>
+
+If the configuration feature
+<A HREF="h_config_enable_role_take">&quot;<!--#echo var="FEAT_enable-rules-under-take"-->&quot;</A>
+is set, the behavior of the Take command is altered slightly.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_ge_import =======
+<HTML>
+<HEAD>
+<TITLE>Import File Selection</TITLE>
+</HEAD>
+<BODY>
+<H1>Import File Selection</H1>
+
+You are importing a file that you previously
+exported from Alpine.
+You are now being asked for the name of that file.
+The easiest way to select a file is probably with the &quot;^T&quot;
+&quot;To Files&quot; command.
+Alternatively, you may type in a file name.
+It may be an absolute pathname.
+Otherwise, it is a file located in your home directory
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A> option.
+In any case, you finish by typing a carriage return to accept the
+file name that is displayed.
+When the feature
+<A HREF="h_config_enable_tab_complete">&quot;<!--#echo var="FEAT_enable-tab-completion"-->&quot;</A>
+is turned on you may use TAB to complete partially typed in names.
+<P>
+You may cancel the import operation by typing &quot;^C&quot; after exiting
+this help.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_ge_allparts =======
+<HTML>
+<HEAD>
+<TITLE>Export Message File Selection</TITLE>
+</HEAD>
+<BODY>
+<H1>Export Message File Selection</H1>
+
+You are Exporting a message from an Alpine mail folder
+to a plain text file.
+You also have the option of exporting all of the attachments associated
+with the message.
+You are now being asked for the name of the file to export <EM>to</EM>.
+The easiest way to select a file is probably with the &quot;^T&quot;
+&quot;To Files&quot; subcommand.
+After returning from that subcommand you will still be allowed to
+edit the name you have selected.
+Alternatively, you may type in a file name.
+It may be an absolute pathname.
+Otherwise, it is a file located in your home directory
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A> option.
+In any case, you finish by typing a carriage return to accept the
+file name that is displayed.
+When the feature
+<A HREF="h_config_enable_tab_complete">&quot;<!--#echo var="FEAT_enable-tab-completion"-->&quot;</A>
+is turned on you may use TAB to complete partially typed in names.
+<P>
+The message you are exporting appears to have some attachments.
+If you wish to save <EM>all</EM> of the attachments at once,
+type the &quot;^P&quot; &quot;AllParts&quot; command to turn on
+saving of the attachments.
+You may turn it back off by typing &quot;^P&quot; again, which will now
+be labeled &quot;NoAllParts&quot; instead.
+If you want to save the parts the command displayed should be
+&quot;NoAllParts&quot;!
+When you choose to save attachments like this, the attachments will be saved
+in a newly created directory.
+That directory will have the same name as the file name you choose here,
+with the letters &quot;.d&quot; appended.
+If that directory already exists, then the letters &quot;.d_1&quot; will
+be tried, then &quot;.d_2&quot; and so on until a name that doesn't exist
+is found.
+For example, if you select the file name
+<P>
+<CENTER><SAMP>filename</SAMP></CENTER>
+<P>
+to export the message to, then the directory used for the attachments will be
+<P>
+<CENTER><SAMP>filename.d</SAMP></CENTER>
+<P>
+or perhaps
+<P>
+<CENTER><SAMP>filename.d_&lt;n&gt;</SAMP></CENTER>
+<P>
+The attachments will then be put into files inside that directory.
+The names for the attachment files will be derived from the attachments
+if possible.
+This is done in the same way as the default values are derived if you
+save them one at a time.
+(The &quot;filename&quot; parameter from the Content-Disposition header
+is the first choice. If that doesn't exist, the &quot;name&quot;
+parameter from the Content-Type header is used.)
+If a name for a particular attachment is not available, then the
+part number of the attachment is used, with the characters &quot;part_&quot;
+prepended.
+An example of that would be
+<P>
+<CENTER><SAMP>part_2.1</SAMP></CENTER>
+<P>
+If you want to save only some of the attachments or if you want more control
+over the directory and filename where an attachment is saved you may
+cancel out of this command and View the attachment list.
+From there you can save each attachment individually.
+<P>
+You may cancel the Export operation by typing &quot;^C&quot; after exiting
+this help.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_ge_export =======
+<HTML>
+<HEAD>
+<TITLE>Export File Selection</TITLE>
+</HEAD>
+<BODY>
+<H1>Export File Selection</H1>
+
+You are Exporting or Saving something from within the Alpine world
+(a message, an attachment, etc.)
+to a plain text file.
+You are now being asked for the name of the file to export <EM>to</EM>.
+The easiest way to select a file is probably with the &quot;^T&quot;
+&quot;To Files&quot; subcommand.
+After returning from that subcommand you will still be allowed to
+edit the name you have selected.
+Alternatively, you may type in a file name.
+It may be an absolute pathname.
+Otherwise, it is a file located in your home directory
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A> option.
+In any case, you finish by typing a carriage return to accept the
+file name that is displayed.
+When the feature
+<A HREF="h_config_enable_tab_complete">&quot;<!--#echo var="FEAT_enable-tab-completion"-->&quot;</A>
+is turned on you may use TAB to complete partially typed in names.
+<P>
+If the object you are exporting is a message with some attachments,
+you may wish to save all of the attachments by typing the &quot;^P&quot;
+&quot;AllParts&quot; command to turn on saving of the attachments.
+This subcommand will only be visible if the message actually has attachments.
+You may also View the attachment list and save individual attachments from
+there.
+<P>
+You may cancel the Export operation by typing &quot;^C&quot; after exiting
+this help.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_save =======
+<HTML>
+<HEAD>
+<TITLE>Save and Export Commands</TITLE>
+</HEAD>
+<BODY>
+<H1>Save and Export Commands</H1>
+
+Save
+(<!--chtml if pinemode="function_key"-->F11<!--chtml else-->S<!--chtml endif-->)
+and Export
+(<!--chtml if pinemode="function_key"-->F12<!--chtml else-->E<!--chtml endif-->)
+are the two alternatives Alpine gives you to keep a copy of the message
+you are reading. If you want to keep the message within Alpine's email
+world, use &quot;Save&quot;; if you want to use the message in another
+program, use &quot;Export&quot;.
+<P>
+
+When you Save a message, it is put into an existing folder or into a new
+folder in one of your existing folder collections. The message stays in
+email format and can be read by Alpine again. Alpine may use a special format
+for its mail folders -- never edit an Alpine folder by hand or with any
+program other than Alpine. The exact behavior of the Save command can be
+configured with the
+<A HREF="h_config_quote_all_froms">&quot;<!--#echo var="FEAT_save-will-quote-leading-froms"-->&quot;</A>,
+<A HREF="h_config_save_wont_delete">&quot;<!--#echo var="FEAT_save-will-not-delete"-->&quot;</A>,
+and
+<A HREF="h_config_save_advances">&quot;<!--#echo var="FEAT_save-will-advance"-->&quot;</A>
+feature list settings.
+The name of the folder offered as a default is controlled by the option
+<A HREF="h_config_saved_msg_name_rule">&quot;<!--#echo var="VAR_saved-msg-name-rule"-->&quot;</A>.
+<P>
+
+When you use Export, the message is placed in a plain text file in your
+home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. In the normal case, only minimal
+headers are exported with the message; however, if the full header mode
+(whose availability may be disabled by setting the feature
+<A HREF="h_config_enable_full_hdr">&quot;<!--#echo var="FEAT_enable-full-header-cmd"-->&quot;</A>
+in SETUP CONFIGURATION) is
+toggled on, then complete headers are exported along with the message
+text. (If you have any <A HREF="h_config_display_filters"><!--#echo var="VAR_display-filters"--></A>
+defined, they may affect the contents of the exported file.)
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_bounce =======
+<HTML>
+<HEAD>
+<TITLE>Bounce Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Bounce Command</H1>
+
+The Bounce
+(<!--chtml if pinemode="function_key"-->F10<!--chtml else-->B<!--chtml endif-->)
+command allows you to re-send, or &quot;remail&quot;, a
+message, as if you were never in the loop. It is analogous to crossing
+out your address on a postal letter, writing a different address on the
+envelope, and putting it into the mailbox. Bounce is used primarily to
+redirect email that was sent to you in error.
+Also, some owners of email
+lists need the bounce command to handle list traffic.
+Bounce is not anonymous.
+A ReSent-From header is added to the message so that the recipient may
+tell that you Bounced it to them.
+<P>
+
+The presence or absence of the Bounce command is determined by the
+<A HREF="h_config_enable_bounce">&quot;<!--#echo var="FEAT_enable-bounce-cmd"-->&quot;</A>
+feature in your Alpine configuration.
+The feature
+<A HREF="h_config_fcc_on_bounce">&quot;<!--#echo var="FEAT_fcc-on-bounce"-->&quot;</A>
+affects the behavior of the Bounce command.
+Also, it is possible that Bounce could be
+administratively disabled by your system manager; if it doesn't work,
+please check with your local help desk before reporting a bug.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_reply =======
+<HTML>
+<HEAD>
+<TITLE>Reply and Forward Commands</TITLE>
+</HEAD>
+<BODY>
+
+<H1>Reply and Forward Commands</H1>
+
+Replying
+(<!--chtml if pinemode="function_key"-->F11<!--chtml else-->R<!--chtml endif-->)
+and Forwarding
+(<!--chtml if pinemode="function_key"-->F12<!--chtml else-->F<!--chtml endif-->)
+are your two alternatives for following up on the
+message you are reading. You would use reply if you want to get email
+back to the author of the message and/or the other people who have
+already seen it. You use forward if you want somebody new to see the
+message.
+<P>
+
+In the normal case, the only thing that you must supply when forwarding a
+message is the name/email address of the new recipient.
+Alpine will include the text of the forwarded message.
+Alpine will also include any attachments to the message.
+There is space above the forwarded text for you to include additional comments.
+<P>
+
+When replying, you usually have to answer some questions.
+If the message is to multiple people and/or specified with a Reply-To: header,
+then you will have to decide who should get the reply.
+You also need to decide whether or not to include the previous
+message in your reply.
+Some of this is configurable.
+Specifically, see the
+<A HREF="h_config_include_header">&quot;<!--#echo var="FEAT_include-header-in-reply"-->&quot;</A>,
+<A HREF="h_config_auto_include_reply">&quot;<!--#echo var="FEAT_include-text-in-reply"-->&quot;</A>,
+<A HREF="h_config_attach_in_reply">&quot;<!--#echo var="FEAT_include-attachments-in-reply"-->&quot;</A>,
+and
+<A HREF="h_config_auto_reply_to">&quot;<!--#echo var="FEAT_reply-always-uses-reply-to"-->&quot;</A>
+configuration features.
+<P>
+
+Both the Reply and Forward commands react to the full header mode toggle.
+If the full header mode is on, then all the header and delivery lines are
+included with the text of the message in your reply/forward.
+<P>
+
+Other configuration features that affect the Reply command are
+<A HREF="h_config_sig_at_bottom">&quot;<!--#echo var="FEAT_signature-at-bottom"-->&quot;</A>,
+<A HREF="h_config_sigdashes">&quot;<!--#echo var="FEAT_enable-sigdashes"-->&quot;</A>, and
+<A HREF="h_config_strip_sigdashes">&quot;<!--#echo var="FEAT_strip-from-sigdashes-on-reply"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_delete =======
+<HTML>
+<HEAD>
+<TITLE>Delete and Undelete Commands</TITLE>
+</HEAD>
+<BODY>
+<H1>Delete and Undelete Commands</H1>
+
+Delete
+(<!--chtml if pinemode="function_key"-->F9<!--chtml else-->D<!--chtml endif-->)
+and Undelete
+(<!--chtml if pinemode="function_key"-->F10<!--chtml else-->U<!--chtml endif-->)
+allow you to change the Deleted flag for the current message.
+Delete marks a message Deleted (turns on the Deleted flag) and Undelete
+removes the mark.
+In the MESSAGE INDEX, deleted messages have a &quot;D&quot; in the status field
+at the left hand edge of the index line.
+When viewing a deleted message, the letters &quot;DEL&quot; will be present
+in the upper right hand corner of the screen.
+Delete simply <EM>marks</EM> a message Deleted, it does not actually
+get rid of the message.
+The eXpunge command (available from the MESSAGE INDEX screen) actually
+removes all of the deleted messages in a folder.
+Once a message is eXpunged, it can't be retrieved.
+<P>
+
+The Delete command is affected by the setting of the configuration feature
+<A HREF="h_config_del_skips_del">&quot;<!--#echo var="FEAT_delete-skips-deleted"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_postpone =======
+<HTML>
+<HEAD>
+<TITLE>Postpone Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Postpone Command</H1>
+
+The postpone
+<!--chtml if pinemode="function_key"-->(F11)<!--chtml else-->(^O)<!--chtml endif-->
+command allows you to temporarily stop working on the current
+message so you may read
+other messages or compose another message. When you want to resume a
+message later, start to compose and answer &quot;yes&quot; to the
+&quot;Continue postponed composition?&quot; question. You may
+postpone as many messages as you like.
+
+<P>
+Note: If a <A HREF="h_config_form_folder"><!--#echo var="VAR_form-letter-folder"--></A> is defined
+in the Setup/Config screen, then the Postpone command will prompt you for
+the folder in which to store your outgoing message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_cancel =======
+<HTML>
+<HEAD>
+<TITLE>Cancel Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Cancel Command</H1>
+
+Cancel
+<!--chtml if pinemode="function_key"-->
+(F2)
+<!--chtml else-->
+(^C)
+<!--chtml endif-->
+
+The Cancel command returns you to Alpine's normal mail processing and
+causes the message currently under composition to be thrown out.
+The message text <EM>will be lost</EM>.
+
+<P>
+Note: Unless the <A HREF="h_config_quell_dead_letter"><!--#echo var="FEAT_quell-dead-letter-on-cancel"--></A> has been set, the text of the most recent composition cancelled
+will be preserved in the file named
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR&quot;.
+<!--chtml else-->
+&quot;dead.letter&quot; in your home directory.
+<!--chtml endif-->
+If you unintentionally cancel a message, look there for its text.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_addrcomplete =======
+<HTML>
+<HEAD>
+<TITLE>Address Completion</TITLE>
+</HEAD>
+<BODY>
+<H1>Address Completion</H1>
+
+When entering addresses in the address fields of the composer (To, Cc, etc.)
+the TAB key may be used to help complete the address.
+Type a partial nickname and tap the TAB key to complete the typing.
+The unambiguous part of the name will be filled in automatically.
+Typing TAB twice in succession will bring up a selection list of possibilities,
+making it easy to find and choose the correct address.
+
+<P>
+The matching algorithm is rather ad hoc.
+The search starts with a search of your address book.
+It counts as a match if the nickname, address, or fullname field of an
+entry begins with the text typed in so far. It is also a match if
+a later word in the fullname (for example, the middle name or last name)
+begins with the entered text.
+<P>
+Next comes an LDAP search.
+The search will happen for any servers that have the
+<A HREF="h_config_ldap_opts_impl">&quot;Use-Implicitly-From-Composer&quot;</A>
+feature set. You can set or unset the feature for each server independently
+in the Setup/Directory screen.
+<P>
+Finally, if you are replying to or forwarding a message, that message is
+searched for likely candidate addresses that match the typed-in text.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_richhdr =======
+<HTML>
+<HEAD>
+<TITLE>Rich Header Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Rich Header Command</H1>
+
+The Rich Header command allows you to toggle between the list of
+all message headers available for editing and those that are most
+common.
+
+<P>
+Use this toggle to expose headers that are not normally visible by
+default.
+This set usually includes the
+&quot;Bcc:&quot;,
+&quot;Fcc:&quot;,
+&quot;Lcc:&quot;,
+and &quot;Newsgroups&quot;
+headers.
+If you are posting to a newsgroup the set of defaults is a little different.
+Obviously, in that case, the Newsgroups header is of interest so is not
+hidden.
+For news posting the hidden set includes the
+&quot;To:&quot;,
+&quot;Cc:&quot;,
+&quot;Bcc:&quot;,
+&quot;Fcc:&quot;,
+and &quot;Lcc:&quot;
+headers.
+You won't normally want to edit these, which is why they are hidden,
+but it is sometimes useful to be able to set them manually.
+
+<P>
+The default sets of headers listed above can be altered.
+Any header that you have added to the
+<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>
+option, but not to the
+<A HREF="h_config_comp_hdrs"><!--#echo var="VAR_default-composer-hdrs"--></A>
+option will appear when you use the Rich Headers command to
+make the Rich Headers visible.
+(Headers listed in the <!--#echo var="VAR_default-composer-hdrs"--> list will be visible
+even without toggling the Rich Headers command.)
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_send =======
+<HTML>
+<HEAD>
+<TITLE>Send Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Send Command</H1>
+
+The Send command
+<!--chtml if pinemode="function_key"-->
+(F3)
+<!--chtml else-->
+(^X)
+<!--chtml endif-->
+tells Alpine you are finished composing.
+Before actually sending it, though, Alpine will ask you to confirm
+your intention, and, at the same time, redisplayed the message text
+with the recipients at the top of the screen to give you the opportunity
+to review and verify that the message is addressed to the people
+you intended.
+<P>
+If the feature
+<A HREF="h_config_send_wo_confirm">&quot;<!--#echo var="FEAT_send-without-confirm"-->&quot;</A> is set,
+then this confirmation prompt and any options it allows are skipped.
+
+<P>
+This confirmation prompt may also offer, depending
+on your particular Setup/Config, options allowing you to set
+<A HREF="h_config_compose_dsn">delivery status notifications</A>,
+include attachments in the &quot;Fcc&quot; (if you had previously
+specified that they <A HREF="h_config_no_fcc_attach">exclude attachments</A>,
+observe details of the
+<A HREF="h_config_verbose_post">message submission process</A>,
+choose the filter through which the
+<A HREF="h_config_sending_filter">outgoing text should first pass</A>,
+or turn of flowed text generation.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_markcutpaste =======
+<HTML>
+<HEAD>
+<TITLE>Mark, Cut and Paste Commands</TITLE>
+</HEAD>
+<BODY>
+<H1>Mark, Cut and Paste Commands</H1>
+
+You can define a &quot;block&quot; of text, which can subsequently
+ be deleted or
+copied as a unit, by setting a mark at the start of the block (Ctrl-^) and
+then moving the cursor to the end of the desired text block. You can then
+&quot;cut&quot; the block out
+<!--chtml if pinemode="function_key"-->
+&quot;F9&quot;,
+<!--chtml else-->
+&quot;Ctrl-K&quot;,
+<!--chtml endif-->
+move the cursor, and &quot;paste&quot; it
+<!--chtml if pinemode="function_key"-->
+&quot;F10&quot;,
+<!--chtml else-->
+&quot;Ctrl-U&quot;,
+<!--chtml endif-->
+in the new location. Also, you can paste more than once, allowing you
+to use this feature to copy a block of text.<P>
+
+If you press
+<!--chtml if pinemode="function_key"-->
+&quot;F9&quot;
+<!--chtml else-->
+&quot;^K&quot;
+<!--chtml endif-->
+without having marked anything, Alpine will delete
+a single line. If you delete a group of lines together, Alpine keeps them
+in the same buffer, so
+<!--chtml if pinemode="function_key"-->
+F10
+<!--chtml else-->
+^U
+<!--chtml endif-->
+will restore them as a block. About
+terminology: Mark is shown as &quot;^^&quot;. The first &quot;^&quot; means you should
+hold down the &quot;Control&quot; key on your keyboard. The second &quot;^&quot; means
+&quot;type the character ^&quot;.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_justify =======
+<HTML>
+<HEAD>
+<TITLE>Justify Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Justify Command</H1>
+
+The Justify
+<!--chtml if pinemode="function_key"-->
+(F4)
+<!--chtml else-->
+(^J)
+<!--chtml endif-->
+command reformats the text in the paragraph the cursor is in.
+Paragraphs are separated by one blank line or a line beginning with a space.
+This is useful when you have been editing a paragraph and the lines become
+uneven. The text is left aligned or justified and the right is ragged. If
+the text is already justified as typed with auto-wrap, no justification will
+be done.
+
+<P>
+If you have set a <A HREF="h_compose_markcutpaste">mark</A> to select a
+block of text, the Justify command is modified.
+Instead of automatically justifying the current paragraph you will be
+asked if you want to justify the paragraph, justify the selected region,
+or adjust the quote level of the selected region.
+Adjusting the quote level only works if you are using standard
+&quot;&gt;&nbsp;&quot; or &quot;&gt;&quot; quotes, which is the default if you haven't
+changed &quot;<A HREF="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></A>&quot;.
+
+<P>
+When composing a reply containing included text, the justify command
+will reformat text to the right of the
+&quot;<A HREF="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></A>&quot;,
+adding or removing indented lines as needed. Paragraphs are separated
+by a blank line, a line containing only the <!--#echo var="VAR_reply-indent-string"-->, or a
+line containing the indent string and one or more blank spaces.
+Included text that was previously indented (or &quot;quoted&quot;) is
+not preserved.
+
+<P>
+Because of the introduction of <A HREF="h_config_quell_flowed_text">Flowed Text</A>
+in 1999 and its wide-spread adoption since then, you will usually be better off if you
+use the standard
+&quot;&gt;&nbsp;&quot; or &quot;&gt;&quot; quotes.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_spell =======
+<HTML>
+<HEAD>
+<TITLE>Spell Check Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Spell Check Command</H1>
+
+The &quot;To Spell&quot;
+<!--chtml if pinemode="function_key"-->
+(F12)
+<!--chtml else-->
+(^T)
+<!--chtml endif-->
+command calls an external spell checking program to look over the
+message you are composing. By default, Alpine uses
+<P>
+<CENTER><SAMP>aspell --dont-backup --mode=email check</SAMP></CENTER>
+<P>
+if it knows where to find &quot;aspell&quot;.
+If there is no &quot;aspell&quot; command available but the command &quot;ispell&quot; is available
+then the command used is
+<P>
+<CENTER><SAMP>ispell -l</SAMP></CENTER>
+<P>
+Otherwise, the ancient &quot;spell&quot; command is used.
+<P>
+For PC-Alpine, you must install the aspell library code that you
+may get from
+<A HREF="http://aspell.net/win32/">http://aspell.net/win32/</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_alted =======
+<HTML>
+<HEAD>
+<TITLE>Alt Editor Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Alt Editor Command</H1>
+
+The &quot;Alt Editor&quot; command's availability depends on the
+Setup/Config variable &quot;<A HREF="h_config_editor"><!--#echo var="VAR_editor"--></A>&quot;.
+
+<P>
+When the variable specifies a valid editor on your system, this
+command will launch it with the current text of your message
+already filled in.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_compose_readfile =======
+<HTML>
+<HEAD>
+<TITLE>Read File Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Read File Command</H1>
+
+The &quot;Read File&quot;
+<!--chtml if pinemode="function_key"-->
+(F5)
+<!--chtml else-->
+(^R)
+<!--chtml endif-->
+command allows you to copy in text from an existing file. You will be
+prompted for the name of a file to be inserted into the message. The file
+name is relative to your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting; or, the file name must be specified as a full path name
+<!--chtml if pinemode="os_windows"-->
+-- for example: &quot;A:&#92;PAPER.TXT&quot;
+<!--chtml else-->
+-- for example: &quot;/tmp/wisdom-of-the-day&quot;
+<!--chtml endif-->
+(without the quotation marks).
+
+<P>
+The file will be inserted where the cursor is located. <B>The
+file to be read must be on the same system as Alpine.</B> If you use Alpine on a
+Unix machine but have files on a PC or Mac, the files must be transferred
+to the system Alpine is running on before they can be read. Please ask your
+local computer support people about the correct way to transfer a file to
+your Alpine system.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_tray_icon =======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-tray-icon"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-tray-icon"--></H1>
+
+PC-Alpine only.
+<P>
+This option restores a behavior of previous versions of PC-Alpine.
+These
+versions, when started, installed a PC-Alpine icon in the notification
+tray of Window's Taskbar. The primary use of this icon was to indicate
+new mail arrival by turning red (while the Taskbar icon remained green).
+Additionally, the icon now changes to yellow to signify that a mail folder
+has been closed unexpectedly.
+
+<P>
+Rather than add another icon to the Taskbar, this version of PC-Alpine will
+color its Taskbar entry's icon red (as well as the icon in the Window
+Title). This feature is only provided for backwards compatibility.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_common_suspend =======
+<HTML>
+<HEAD>
+<TITLE>Suspend Command</TITLE>
+</HEAD>
+<BODY>
+<H1>Suspend Command</H1>
+
+With the <A HREF="h_config_can_suspend"><!--#echo var="FEAT_enable-suspend"--></A> feature
+enabled, you can, at almost any time, temporarily halt your Alpine session,
+<!--chtml if pinemode="os_windows"-->
+minimizing it into an icon.
+<!--chtml else-->
+and return to your system prompt.
+<!--chtml endif-->
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_pipe_command =======
+<HTML>
+<HEAD>
+<TITLE>Pipe Command SubOptions</TITLE>
+</HEAD>
+<BODY>
+<H1>Pipe Command SubOptions</H1>
+
+By default, when you use the Pipe command, the processed text of the
+message is sent to the Unix command
+you specify and the output is captured by Alpine and shown to you.
+(This command is available in PC-Alpine, as well, but there aren't many
+Windows commands that work well with piping.)
+There are some sub-commands that may be used to alter this behavior.
+These are toggles that switch the behavior between two possibilities.
+They can be combined in any way you wish.
+<P>
+By default, the prompt at the bottom of the screen looks like
+<P>
+<CENTER><SAMP>Pipe message 37 to :</SAMP></CENTER>
+<P>
+or
+<P>
+<CENTER><SAMP>Pipe messages to :</SAMP></CENTER>
+<P>
+if you are piping more than one message.
+<P>
+The sub-command options are:
+<DL>
+ <DT>Shown Text or Raw Text</DT>
+ <DD>This option toggles between sending the shown (processed) text
+of the message to the Unix command, and sending the
+raw (unprocessed) text of the message to the Unix command.
+The default is to send the shown text.
+The raw version of the message will contain all of the headers and any
+MIME encoding that the message contains.
+If you've selected the Raw Text then the prompt will have the additional word
+&quot;RAW&quot; in it, like
+<P>
+<CENTER><SAMP>Pipe RAW messages to :</SAMP></CENTER>
+<P>
+You can experiment with this option by piping to something simple like the
+Unix &quot;cat&quot; command.
+ </DD>
+ <DT>Captured Output or Free Output</DT>
+ <DD>This option toggles between having Alpine capture the output of
+the Unix pipe command for display, and not capturing it.
+If the command you are piping to is a filter that will produce output
+you want to view, then you want to capture that output
+for display (the default).
+If the Unix command doesn't produce output or handles the display itself,
+then you want free output.
+When you've selected the Free Output option the prompt will change to
+<P>
+<CENTER><SAMP>Pipe messages to (uncaptured) :</SAMP></CENTER>
+<P>
+ </DD>
+ <DT>No Delimiter or With Delimiter</DT>
+ <DD>This option controls whether or not a Unix mailbox style delimiter
+will come before the text of the message.
+This is the delimiter used in the common Unix mailbox format.
+It's the single line that begins with the five characters
+&quot;From&quot; followed by a &lt;SPACE&gt; character.
+You'll usually only want to include this if the Unix command requires
+input in the format of a traditional Unix mailbox file.
+When you've selected the With Delimiter option the prompt will change to
+<P>
+<CENTER><SAMP>Pipe messages to (delimited) :</SAMP></CENTER>
+<P>
+ </DD>
+ <DT>To Same Pipe or To Individual Pipes</DT>
+ <DD>This option only shows up if you are running an aggregate
+pipe command.
+That is, the command was Apply Pipe, not just Pipe.
+You have the option of piping all of the selected messages through a
+single pipe to a single instance of the Unix command,
+or piping each individual message through a separate pipe to separate
+instances of the Unix command.
+The default is that all of the output will go through a single pipe
+to a single instance of the command.
+You can try this option with a command like &quot;less&quot;, with Free
+Output enabled.
+When you've selected the Individual Pipes option the prompt will change to
+<P>
+<CENTER><SAMP>Pipe messages to (new pipe) :</SAMP></CENTER>
+<P>
+ </DD>
+</DL>
+
+<P>
+As mentioned earlier, the options can be combined in any way you wish.
+You may leave them all off, turn them all on, or turn some of them on
+and some of them off.
+If you use the pipe command a second time in the same session the default
+options will be what you used the last time.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_emptydir_subfolder_name =========
+<HTML>
+<HEAD>
+<TITLE>ENTER SUBFOLDER NAME</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Subfolder Name</H1>
+
+<P>
+This is the name of a new subfolder in the directory you are creating.
+Because empty directories are hidden and therefore not useful, you must also
+create a subfolder in the directory you are creating in order that the
+directory remains visible.
+<P>
+Alternatively, you may turn off the configuration feature
+<A HREF="h_config_quell_empty_dirs"><!--#echo var="FEAT_quell-empty-directories"--></A>
+so that empty directories remain visible.
+If you do that, you will not be required to create the subfolder when you
+create a directory.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_incoming_add_folder_name =========
+<HTML>
+<HEAD>
+<TITLE>ENTER FOLDER NAME</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Folder Name</H1>
+
+<P>
+This is the name of the folder on the previously specified server.
+By default the folder name is interpreted as defining a section of your personal
+folder area. This area and how you specify it are defined by the
+server, if one is specified.
+
+<P>
+To define a folder outside the default area, prefix
+the path with the namespace to use when interpreting the
+given path. If a namespace is specified, the folder name begins with the
+sharp (#) character followed by the name of the namespace
+and then the namespace's path-element-delimiter. Aside from the
+name's format, namespaces can also imply access rights, content
+policy, audience, location, and, occasionally, access methods.
+
+<P>
+Each server exports its own set (possibly of size one) of
+namespaces.
+For a more detailed explanation read about
+<A HREF="h_folder_name_namespaces">Namespaces</A>.
+
+<P>
+To specify the default for INBOX on the server you can usually just enter
+&quot;INBOX&quot;, and the server will understand the special meaning of
+that word.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_incoming_add_folder_host =========
+<HTML>
+<HEAD>
+<TITLE>ENTER INCOMING FOLDER SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Incoming Folder Server</H1>
+
+You are being asked for the name of the server for use with this incoming
+folder.
+If the folder is on the machine where Alpine is running, then just enter
+RETURN without typing a server name.
+
+<P>
+If the folder is on an IMAP server then type the server's name followed
+by RETURN.
+You may use the ^X command if the server is the same as the server that
+the INBOX is on.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the IMAP server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+or you may need to specify a different protocol if the server is not an
+IMAP server. For example:
+
+<P>
+<CENTER><SAMP>foo.example.com/pop3</SAMP></CENTER>
+<P>
+
+for a POP server or
+
+<P>
+<CENTER><SAMP>foo.example.com/nntp</SAMP></CENTER>
+<P>
+
+for an NNTP news server.
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+There is a special command (^W) if you want to set up a folder that gets its
+mail from a
+<A HREF="h_maildrop">Mail Drop</A>.
+If you type that command, you will be prompted for the information for
+both the Mail Drop folder and the destination folder.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_incoming_add_inbox =========
+<HTML>
+<HEAD>
+<TITLE>ENTER INBOX SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter INBOX Server</H1>
+
+You are being asked for the name of the server for use with
+the INBOX folder.
+Type the server's name followed by RETURN.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+or you may need to specify a different protocol if the server is not an
+IMAP server. For example:
+
+<P>
+<CENTER><SAMP>foo.example.com/pop3</SAMP></CENTER>
+<P>
+
+for a POP server.
+<P>
+
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+If the INBOX folder is on the machine where Alpine is running, then just enter
+RETURN without typing a server name.
+
+<P>
+There is a special command (^W) if you want to set up a folder that gets its
+mail from a
+<A HREF="h_maildrop">Mail Drop</A>.
+If you type that command, you will be prompted for the information for
+both the Mail Drop folder and the destination folder, which will be used
+as your INBOX folder.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_incoming_add_maildrop_destn =========
+<HTML>
+<HEAD>
+<TITLE>ENTER DESTINATION SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Destination Server</H1>
+
+You are being asked for the name of the server where the destination
+folder is for use with this Mail Drop incoming folder.
+That is, you are using a Mail Drop for this incoming folder and
+you've already entered
+the server and folder name for the Mail Drop.
+Now you need to enter the server for the destination folder
+where the mail should be copied to.
+Mail will come from the Mail Drop and be copied to the destination folder.
+
+<P>
+Type the server's name followed by RETURN.
+If the folder is local to this computer, just type RETURN without entering
+a server name.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_inbox_add_maildrop_destn =========
+<HTML>
+<HEAD>
+<TITLE>ENTER DESTINATION SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Destination Server</H1>
+
+You are being asked for the name of the server where the destination
+folder is for use with your Mail Drop INBOX.
+That is, you are using a Mail Drop for your INBOX and you've already entered
+the server and folder name for the Mail Drop.
+Now you need to enter the server for the destination folder
+where the mail should be copied to.
+Mail will come from the Mail Drop and be copied to the destination folder.
+
+<P>
+Type the server's name followed by RETURN.
+If the folder is local to this computer, just type RETURN without entering
+a server name.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_inbox_add_maildrop =========
+<HTML>
+<HEAD>
+<TITLE>ENTER MAILDROP SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Mail Drop Server</H1>
+
+You are being asked for the name of the Mail Drop server for use with
+your INBOX.
+
+<P>
+Type the server's name followed by RETURN.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+or you may need to specify a different protocol if the server is not an
+IMAP server. For example:
+
+<P>
+<CENTER><SAMP>foo.example.com/pop3</SAMP></CENTER>
+<P>
+
+for a POP server or
+
+<P>
+<CENTER><SAMP>foo.example.com/nntp</SAMP></CENTER>
+<P>
+
+for an NNTP news server.
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_incoming_add_maildrop =========
+<HTML>
+<HEAD>
+<TITLE>ENTER MAILDROP SERVER</TITLE>
+</HEAD>
+<BODY>
+<H1>Enter Mail Drop Server</H1>
+
+You are being asked for the name of the Mail Drop server for use with
+this incoming folder.
+
+<P>
+Type the server's name followed by RETURN.
+You may use the ^X command if the server is the same as the server that
+the INBOX is on.
+
+<P>
+You may have to add optional extra information to the server name.
+For example, if the server is using a non-standard port number you
+would specify that by appending a colon (:) followed by the port number
+to the server name
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+or you may need to specify a different protocol if the server is not an
+IMAP server. For example:
+
+<P>
+<CENTER><SAMP>foo.example.com/pop3</SAMP></CENTER>
+<P>
+
+for a POP server or
+
+<P>
+<CENTER><SAMP>foo.example.com/nntp</SAMP></CENTER>
+<P>
+
+for an NNTP news server.
+For an explanation of all of the possibilities, see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>
+for folders.
+
+<P>
+If the Mail Drop folder is on the machine where Alpine is running, then just enter
+RETURN without typing a server name.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_maildrop =========
+<HTML>
+<HEAD>
+<TITLE>WHAT IS A MAIL DROP?</TITLE>
+</HEAD>
+<BODY>
+<H1>What is a Mail Drop?</H1>
+
+In some situaions it may make sense to have your mail delivered to one
+folder (the Mail Drop) and then when you want to read mail that has been
+delivered to the Mail Drop folder Alpine will move it to another
+destination folder.
+Often the Mail Drop will be a remote folder and messages will be moved from
+there to a local destination folder.
+
+<P>
+One example where this might make sense is if the Mail Drop folder is accessible
+only with the POP protocol.
+You could designate your POP inbox as the Mail Drop folder and have Alpine move
+mail from there to a local (on the same machine Alpine is running on)
+destination folder, where you'll read it.
+
+<P>
+A Mail Drop may only be used as your Inbox or as an
+<A HREF="h_config_enable_incoming">&quot;Incoming folder&quot;</A>.
+
+<P>
+There is no attempt to synchronize the contents of the destination folder
+with the contents of the Mail Drop folder.
+All that happens is that all of the messages in the Mail Drop folder are
+copied to the destination folder and then they are deleted and expunged (if possible)
+from the Mail Drop folder.
+The next time a check for new mail is made, any messages in the Mail
+Drop folder are once again copied to the destination folder and deleted
+and expunged from the Mail Drop folder.
+(If the Mail Drop folder is a news group, then the messages can't be
+expunged from the newsgroup. Instead, only Recent messages are copied from
+the newsgroup to the destination folder.)
+
+<P>
+Configuration of a Mail Drop is a little different from configuration of
+a folder that does not use a Mail Drop because you have to specify two
+folder names instead of one.
+The two folders may be any types of folders that Alpine can normally use.
+They don't have to be a remote folder and a local folder, that is
+simply the most common usage.
+When you use a Mail Drop folder Alpine will periodically re-open the Mail
+Drop to check for new mail.
+The new-mail checks will happen at the frequency set with the
+<A HREF="h_config_mailcheck"><!--#echo var="VAR_mail-check-interval"--></A> option,
+but with a minimum time
+(<A HREF="h_config_maildropcheck"><!--#echo var="VAR_maildrop-check-minimum"--></A>)
+between checks.
+Because of this minimum you may notice that new mail does not
+appear promptly when you expect it.
+The reason for this is to protect the server from over-zealous opening and
+closing of the Mail Drop folder.
+If the user initiates the check by typing ^L (Ctrl-L) or the Next command when at
+the end of the folder index, then the check will happen, regardless of how
+long it has been since the previous check.
+<P>
+If there is new mail, that mail will be copied to the destination folder
+and then will be deleted from the Mail Drop.
+Note that using a Mail Drop with a local destination folder does not make
+sense if you read mail from more than one machine, because the mail is
+downloaded to the destination folder (which is accessible from only one
+machine) and deleted from the Mail Drop.
+<P>
+The feature <A HREF="h_config_maildrops_preserve_state"><!--#echo var="FEAT_maildrops-preserve-state"--></A> modifies the operation of Mail Drops.
+
+<P>
+The actual syntax used by Alpine for a folder that uses a Mail Drop is:
+
+<P>
+<CENTER><SAMP>#move&lt;DELIM&gt;&lt;MailDropFolder&gt;&lt;DELIM&gt;&lt;DestinationFolder&gt;</SAMP></CENTER>
+<P>
+The brackets are not literal.
+<P>
+<CENTER><SAMP>&lt;DELIM&gt;</SAMP></CENTER>
+<P>
+is a single character that does not appear in the MailDropFolder name.
+If the name doesn't contain spaces then it can be a space character.
+The two folder names are full technical
+<A HREF="h_valid_folder_names">folder names</A>
+as used by Alpine.
+Here are a couple examples to give you an idea what is being talked about:
+
+<P>
+<CENTER><SAMP>#move&nbsp;{popserver.example.com/pop3}inbox&nbsp;localfolder</SAMP></CENTER>
+<P>
+<CENTER><SAMP>#move+{nntpserver.example.com/nntp}#news.comp.mail.pine+local&nbsp;folder</SAMP></CENTER>
+<P>
+
+A #move folder may only be used as an
+<A HREF="h_config_enable_incoming">&quot;Incoming folder&quot;</A> or
+an Inbox.
+When you are in the FOLDER LIST of Incoming Message Folders (after turning
+on the
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="FEAT_enable-incoming-folders"-->&quot;</A>
+option)
+the Add command has a subcommand &quot;Use Mail Drop&quot;
+which may be helpful for defining the folder in your Alpine configuration.
+The same is true when you edit the
+<A HREF="h_config_inbox_path"><!--#echo var="VAR_inbox-path"--></A>
+option in Setup/Config.
+Each of these configuration methods will also create the DestinationFolder
+if it doesn't already exist.
+If you are having problems, make sure the DestinationFolder exists.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_save =========
+<HTML>
+<HEAD>
+<TITLE>CHOOSE A FOLDER TO SAVE INTO</TITLE>
+</HEAD>
+<BODY>
+<H1>Choose a Folder to Save Into</H1>
+
+After Exiting from this help text,
+type the name of the folder you want to save into and press RETURN.
+<P>
+Press ^T to get a list of your folders to choose from.
+Press ^C to cancel the Save.
+<P>
+If you have Folder Collections defined you may use
+Ctrl-P (Previous collection) and Ctrl-N (Next collection) to switch
+the collection being saved to.
+<P>
+If Tab Completion is enabled (it is enabled by default)
+you may type a Tab character to have Alpine complete the folder name for you.
+<P>
+If Partial Match Lists is enabled (it is enabled by default) you may type
+Ctrl-X to get a list of matches to the prefix you've typed in so far.
+<P>
+If the Ctrl-R subcommand is present that means you can decide to Delete or
+not Delete the message you are saving after you save it.
+The label on that key gives the action to switch to.
+If it says Delete and you type Ctrl-R the label displayed will change to
+No Delete and the source message will be deleted after the save. If it
+says No Delete and you type Ctrl-R the label displayed will change to
+Delete and the message will not be deleted.
+You can control the default for the Delete parameter with the
+configuration feature <!--#echo var="FEAT_save-will-not-delete"-->.
+<P>
+Similarly, if the Ctrl-W subcommand is present that means you can decide
+to Preserve the order of the messages being saved or not.
+If it is labeled Preserve Order and you type Ctrl-W, the resulting Saved messages
+will be in the same order as you see them in the source folder now.
+The opposite action (which is usually the default) is that you don't care
+about the order.
+The Saved messages may or may not be in the same order in the destination folder.
+There may be a performance penalty for choosing to save the messages in order.
+You can control the default for the Preserve Order parameter with the
+configuration feature
+<!--#echo var="FEAT_save-aggregates-copy-sequence"-->.
+
+<P>
+If you haven't disabled the Save Input History and you've already done a
+Save earlier in this session then you may use the Up and Down arrows to retrieve
+a folder name used in a previous Save.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_simple_index ========================
+<HTML>
+<HEAD>
+<TITLE>SELECT POSTPONED MESSAGE</TITLE>
+</HEAD>
+<BODY>
+<H1>POSTPONED MESSAGE SELECTION COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ F5 Move to previous message F1 Show this help text
+ F6 Move to next message
+ F7 Show previous screen of messages
+ F8 Show next screen of messages
+
+Message Selection Commands
+--------------------------
+ F3 Exit the Message Select menu (canceling Send command)
+ F4 Select the currently highlighted message
+ F9 Mark the currently highlighted message as deleted
+ F10 Undelete (remove deletion mark from) the highlighted message
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous message ? Show this help text
+ N Move to next message
+ - Show previous screen of messages
+ Spc (space bar) Show next screen of messages
+
+Message Selection Commands
+--------------------------
+ E Exit the Message Select menu (canceling Send command)
+ S Select the currently highlighted message
+ D Mark the currently highlighted message as deleted
+ U Undelete (remove deletion mark from) the highlighted message
+</PRE>
+<!--chtml endif-->
+<P>
+<H2>Description of the Select Postponed Message Screen</H2>
+
+This screen allows you to select one of several previously postponed
+messages in order to continue composition. Your options are very limited
+-- the screen is not meant to let you manipulate these messages. However,
+you may now delete messages from this list. Once you choose a message,
+Alpine reads it in and puts you into the regular message composer.
+<P>
+
+Messages do not stay in this postponed state automatically. If you select
+a message and then want to postpone it again, use the normal postpone
+(Ctrl-O) command in the composer.
+<P>
+
+If you exit this screen without selecting a message, the Compose command
+that got you here is canceled. Other than messages explicitly marked
+&quot;Deleted&quot;, no messages will be removed.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_collection_screen ========================
+<HTML>
+<HEAD>
+<TITLE>COLLECTION LIST screen</TITLE>
+</HEAD>
+<BODY>
+<H1>COLLECTION LIST screen</H1>
+
+The COLLECTION LIST screen is used to select one of your
+collection definitions to display the folders they contain. See
+<A HREF="h_what_are_collections">Folder Collections Explained</A> for
+detailed explanation of collections.<P>
+
+To manage your collection definitions (Add, Change, Delete, etc.), use
+the <A HREF="h_collection_maint">Setup/collectionList</A> command on Alpine's
+MAIN MENU.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_collection_maint ========================
+<HTML>
+<HEAD>
+<TITLE>SETUP COLLECTION LIST screen</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP COLLECTION LIST screen</H1>
+
+The SETUP COLLECTION LIST screen lets you manage your collection
+definitions. See
+<A HREF="h_what_are_collections">Folder Collections Explained</A>
+for detailed explanation of collections.<P>
+
+Maintenance commands include:
+<DL>
+<DT>Change
+<!--chtml if pinemode="function_key"-->
+(F4)
+<!--chtml else-->
+(C)
+<!--chtml endif-->
+</DT>
+
+<DD>Modify attributes of the selected collection definition.
+
+<DT>Add Cltn
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(A)
+<!--chtml endif-->
+</DT>
+
+<DD>Create a new collection definition.
+</DD>
+
+<DT>Del Cltn
+<!--chtml if pinemode="function_key"-->
+(F10)
+<!--chtml else-->
+(D)
+<!--chtml endif-->
+</DT>
+
+<DD>Delete the selected collection definition.<BR>
+NOTE: The folders and directories referred to by the
+collection definition are <EM>NOT</EM> deleted. Folders must
+be deleted, if that's what you wish to do, from the
+<A HREF="h_folder_maint">FOLDER LIST screen</A>, which shows the
+individual folders in a collection.
+</DD>
+
+<DT>Shuffle
+<!--chtml if pinemode="function_key"-->
+(F11)
+<!--chtml else-->
+($)
+<!--chtml endif-->
+</DT>
+
+<DD>Change the order of the displayed collections. Alpine will offer
+to move the currently selected collection one position UP
+or DOWN.
+</DD>
+</DL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============ h_what_are_collections ==========
+<HTML>
+<HEAD>
+<TITLE>Folder Collections Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Folder Collections Explained</H1>
+
+
+Those of you with simple mail configurations will just see a list of all the
+folders you have when choosing FOLDER LIST from Alpine's MAIN MENU.
+The special folders for INBOX, sent mail and saved messages
+will appear at the top of the list. All others are in alphabetical order.
+<P>
+If you
+or your system administrator have defined more than one collection or if
+you have a collection (for newsgroups or email folders) defined on your
+system, then you will see the COLLECTION LIST screen first when choosing
+FOLDER LIST from Alpine's MAIN MENU.
+<P>
+<H2>Why have multiple folder collections?</H2>
+<P>
+For Alpine users who only maintain email folders (and not too many) on one host,
+a single folder collection is probably sufficient.<P>
+
+However, people who have more than one email account - for example, one
+at their university, and one with their personal Internet Service Provider -
+will have different sets of folders on different hosts, and they may want to
+access them all from the same installation of Alpine, rather than use different
+software and/or log into other hosts to manipulate messages in different
+accounts. (If in doubt whether one of your email accounts can be accessed
+with Alpine, contact the technical support people for that account.) Even people
+who have only one email account on one host, but have dozens or
+hundreds of email folders, may want to arrange these folders together in a
+meaningful way.<BR>
+That is where multiple collections come in.
+
+<H2>Types of Collections</H2>
+<DL>
+<DT>INCOMING FOLDERS</DT>
+<DD>&quot;Incoming Message Folders&quot;
+is a special collection typically used to supplement your single INBOX.
+All the folders here are meant to be ones that receive incoming messages,
+which you intend to check more or less frequently.
+You may have multiple folders like this because you or your systems
+administrator have set up an external program or you may have set up
+Alpine to filter incoming
+messages into different folders, based on certain criteria such as
+sender, recipient, or subject; or because you have multiple accounts and
+wish to check their INBOXes easily. This collection is established by
+setting the
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="FEAT_enable-incoming-folders"-->&quot;</A>
+feature in the SETUP CONFIGURATION screen, which is accessed from the
+MAIN MENU.
+</DD>
+
+<DT>NEWS</DT>
+<DD>You can also define a collection specifically for
+newsgroups. Alpine does this for you implicitly when you
+<A HREF="h_config_nntp_server">define an NNTP Server</A>
+in your Alpine configuration. The news collection appears last in the
+COLLECTION LIST (though you can shuffle it up in the order of presentation),
+and Alpine knows not to save messages there.
+</DD>
+
+<DT>DEFAULT COLLECTION</DT>
+<DD>This is the default collection for your saved and sent messages folders.
+</DD>
+</DL>
+
+<P>
+
+<H2>Defining Collections</H2>
+<P>
+In the absence of any folder-collection definitions, Alpine will assume a
+single default folder collection.
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+If necessary, Alpine will create the directory
+&quot;mail&quot; in your Unix home directory
+to hold your folders.
+<!--chtml endif-->
+<P>
+You can use the
+<A HREF="h_collection_maint">Setup/collectionList screen</A>, called up from
+the MAIN MENU, to manage your collection list.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_address_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT AN ADDRESS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT AN ADDRESS SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted address
+F5 Move highlight to previous address
+F6 Move highlight to next address
+F7 Previous page of addresses
+F8 Next page of addresses
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Address ? Display this help text
+ N Next Address E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted address
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Address Screen</H2>
+
+This screen gives you an easy way to select an address from all of
+the address book entries that match the prefix typed so far.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_rule_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT A RULE SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT A RULE SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted rule
+F5 Move highlight to previous rule
+F6 Move highlight to next rule
+F7 Previous page of rules
+F8 Next page of rules
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Rule ? Display this help text
+ N Next Rule E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted rule
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Rule Screen</H2>
+
+This screen just gives you an easy way to select a rule from all of your
+defined rules.
+The list of rules presented is the list of nicknames of all of the rules
+defined using Setup/Rules.
+For selecting messages, it is likely that the Indexcolor rules and possibly
+the Roles rules will be most useful.
+The others are there also, in case you find a use for them.
+<P>
+In order for this to be useful for selecting messages, the nicknames of
+the rules have to be different.
+Alpine actually just gets the nickname of the rule that you select and then
+looks up that rule using the nickname.
+So if there are duplicate nicknames, the first rule that has that
+nickname will be used.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_priority_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT A PRIORITY SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT A PRIORITY SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted priority
+F5 Move highlight to previous priority
+F6 Move highlight to next priority
+F7 Previous page of priorities
+F8 Next page of priorities
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Priority ? Display this help text
+ N Next Priority E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted priority
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Priority Screen</H2>
+
+This screen gives you a way to select a priority for the message you are sending.
+This priority will be placed in the non-standard X-Priority header of your outgoing mail.
+Some mail programs will display an indication of the priority level to
+the recipient of the message, some will ignore it.
+Even in cases where the mail programs of both the sender and the recipient
+agree on the meaning of this header, keep in mind that it is
+something that the sender sets so it is only an indication
+of the priority that the sender attaches to the mail.
+Alpine can be made to display an indication of this priority in incoming
+messages by use of one of the tokens
+(<A HREF="h_index_tokens">Tokens for Index and Replying</A>)
+PRIORITY, PRIORITYALPHA, or PRIORITY! in the
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_keyword_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT A KEYWORD SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT A KEYWORD SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted keyword
+F5 Move highlight to previous keyword
+F6 Move highlight to next keyword
+F7 Previous page of keywords
+F8 Next page of keywords
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Keyword ? Display this help text
+ N Next Keyword E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted keyword
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Keyword Screen</H2>
+
+This screen just gives you an easy way to select a keyword.
+The list of keywords presented is the list of all keywords defined in your
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option.
+If you have given a keyword a nickname, that nickname is displayed
+instead of the actual keyword.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_charset_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT A CHARACTER SET SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT A CHARACTER SET SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted character set
+F5 Move highlight to previous character set
+F6 Move highlight to next character set
+F7 Previous page of character sets
+F8 Next page of character sets
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Character Set ? Display this help text
+ N Next Character Set E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted character set
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select A Character Set Screen</H2>
+
+This screen just gives you an easy way to select a character set from the
+set of character sets Alpine knows about.
+The list presented will vary slightly depending on what option you are
+selecting the character set for.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_multcharsets_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT CHARACTER SETS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT CHARACTER SETS SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted charset (or chosen charsets in ListMode)
+F5 Move highlight to previous charset
+F6 Move highlight to next charset
+F7 Previous page of charsets
+F8 Next page of charsets
+F9 Toggle choices when using ListMode
+F10 Turn on/off ListMode (makes it easy to choose multiple charsets)
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Charset ? Display this help text
+ N Next Charset E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted charset (or chosen charsets in ListMode)
+ L Turn on ListMode (makes it easy to choose multiple charsets)
+ 1 Turn off ListMode
+ X Toggle choices when using ListMode
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Character Set Screen</H2>
+
+This screen just gives you an easy way to select a character set or a list of
+character sets.
+The list of character sets presented is the list of all character sets known to
+Alpine.
+You may select other character sets by typing them in directly.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_multkeyword_screen =====
+<HTML>
+<HEAD>
+<TITLE>SELECT KEYWORDS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT KEYWORDS SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the highlighted keyword (or chosen keywords in ListMode)
+F5 Move highlight to previous keyword
+F6 Move highlight to next keyword
+F7 Previous page of keywords
+F8 Next page of keywords
+F9 Toggle choices when using ListMode
+F10 Turn on/off ListMode (makes it easy to choose multiple keywords)
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Keyword ? Display this help text
+ N Next Keyword E Exit without selecting anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the highlighted keyword (or chosen keywords in ListMode)
+ L Turn on ListMode (makes it easy to choose multiple keywords)
+ 1 Turn off ListMode
+ X Toggle choices when using ListMode
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Keyword Screen</H2>
+
+This screen just gives you an easy way to select a keyword or a list of
+keywords.
+The list of keywords presented is the list of all keywords defined in your
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option.
+If you have given a keyword a nickname, that nickname is displayed
+instead of the actual keyword.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_select_incoming_to_monitor =====
+<HTML>
+<HEAD>
+<TITLE>SELECT FOLDERS TO CHECK SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT FOLDERS TO CHECK SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without selecting anything
+F4 Select the marked folders
+F5 Move highlight to previous folder
+F6 Move highlight to next folder
+F7 Previous page of folders
+F8 Next page of folders
+F9 Toggle choices on or off
+F11 Print
+F12 WhereIs
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Folder ? Display this help text
+ N Next Folder ^C exit without changing anything
+ - Previous page % Print
+Spc (space bar) Next page
+ W WhereIs
+
+Select Command
+------------------------------------------------
+ S Select the marked folders
+ X Toggle choices on or off
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Folders to Check Screen</H2>
+
+This screen is only useful if the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set.
+By default, when you set that feature all of your incoming folders
+will be checked periodically for Unseen messages.
+By using this screen, you may restrict the set of monitored folders to
+a subset of all of the incoming folders.
+<P>
+Mark the folders you want to monitor for Unseen messages with
+an &quot;X&quot;.
+When you've finished marking all your selections use the Select
+command to register your choices.
+To return to the default of checking all incoming folders
+delete all folders or unmark all folders.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_role_select =====
+<HTML>
+<HEAD>
+<TITLE>ROLES SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>ROLES SCREEN</H1>
+<H2>ROLES COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit without a selection
+F4 Select a role to use in composition
+F5 Move to previous role
+F6 Move to next role
+F7 Previous page of roles
+F8 Next page of roles
+F11 Change Default Role
+F12 Whereis (search role nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Role ? Display this help text
+ N Next Role E Exit without a selection
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in role nicknames)
+
+Select Role Commands
+------------------------------------------------
+ [Return] Select highlighted role
+ D Change Default Role
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Roles Screen</H2>
+
+With this screen you select a role to be used in the composition of a
+message.
+Use the Previous and Next commands to highlight the role you wish to
+use.
+When you type carriage return you will be placed in the composer using the highlighted role.
+<P>
+You don't have any non-default <A HREF="h_rules_roles">roles</A>
+available unless you set them up.
+You may do so by using the Setup/Rules command on the MAIN MENU.
+<P>
+By using the D command, you may set a default role that will persist until
+you change it or until you exit Alpine.
+The D command toggles through three states: set the default role, unset the
+default role, and leave the default role as it is.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_role_abook_select =====
+<HTML>
+<HEAD>
+<TITLE>SELECT ADDRESS BOOK SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT ADDRESS BOOK SCREEN</H1>
+<H2>COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Exit screen without selecting anything
+F4 Select highlighted address book
+F5 Move to previous address book
+F6 Move to next address book
+F7 Previous page of address books
+F8 Next page of address books
+F12 Whereis
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Previous addrbook ? Display this help text
+ N Next addrbook
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs
+
+Select Role Commands
+------------------------------------------------
+ S Select highlighted address book
+ E Exit screen without selecting anything
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Select Address Book Screen</H2>
+
+This screen helps you select one of your address books.
+Use the Previous and Next commands to highlight the address book you wish to
+select.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======== h_rule_patterns =============
+<HTML>
+<HEAD>
+<TITLE>PATTERNS</TITLE>
+</HEAD>
+<BODY>
+<H1>PATTERNS</H1>
+Patterns are used with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules.
+Patterns are compared with a message to see if there is a match.
+For Filtering, the messages being checked are all the messages in the
+folder, one at a time.
+For Index Line Coloring, each message that is visible on the screen is
+checked for matches with the Index Coloring Patterns.
+Roles are used with the Reply, Forward, and Compose commands.
+For Reply, the message used to compare the Pattern with is the message
+being replied to;
+for Forward, the message used to compare the Pattern with is the message
+being forwarded;
+and for Compose, there is no message, so the parts of the Pattern that depend
+on a message (everything other than Current Folder Type and the
+Beginning of Month and Year)
+are not used.
+Only the Current Folder Type matters for Compose (plus the Beginning of
+Month or Year, which you wouldn't usually use for a Role).
+For Scoring, the message being scored is compared with all of the Score
+Patterns, and the Score Values from the ones that match are added together to
+get the message's score.
+For Other Rules, there is no message. Only the Current Folder Type is checked
+for Other Rules.
+<P>
+Each Pattern has several possible parts, all of which are optional.
+In order for there to be a match, <EM>ALL</EM> of the
+<EM>defined</EM> parts of the Pattern must match the message.
+If a part is not defined it is considered a match, but note that a filtering
+Pattern must have at least one defined part or it will be ignored.
+For example, if the To pattern is not defined it will be
+displayed as
+<P>
+<CENTER>To pattern = &lt;No Value Set&gt;</CENTER>
+<P>
+That is considered a match because it is not defined.
+This means that the Pattern with nothing defined is a match if the
+Current Folder Type matches, but there is an exception that was mentioned
+in the previous paragraph.
+Because filtering is a potentially destructive action, filtering Patterns
+with nothing other than Current Folder Type defined are ignored.
+If you really want a filtering Pattern to match all messages (subject to
+Current Folder Type) the best way to do it is to define a Score interval
+that includes all possible scores.
+This would be the score interval <SAMP>(-INF,INF)</SAMP>.
+This can be used even if you haven't defined any rules to Set Scores.
+<P>
+There are six predefined header patterns called the To, From, Sender, Cc, News,
+and Subject patterns.
+Besides those six predefined header patterns, you may add
+additional header patterns with header fieldnames of your choosing.
+You add an extra header pattern by placing the cursor on one of the
+patterns while in the role editor and using the &quot;eXtraHdr&quot; command.
+The Recip pattern is a header pattern that stands for Recipient (To OR Cc)
+and the Partic pattern is a header pattern that stands for
+Participant (From OR To OR Cc).
+(Defining the Recip pattern does not have the same effect as defining both
+the To and Cc patterns. Recip is To <EM>OR</EM> Cc, not To <EM>AND</EM> Cc.)
+Similar to the header patterns are the AllText pattern and the BodyText pattern.
+Instead of comparing this pattern's text against only the contents of
+a particular header field, the text for the AllText pattern is compared
+with text anywhere in the message's header or body, and the text for the
+BodyText pattern is compared with text anywhere in the message's body.
+<P>
+Any of the header patterns, the AllText pattern, or the BodyText pattern may be negated with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+You can tell that <EM>NOT</EM> has been turned on by looking for the character
+&quot;!&quot; at the beginning of the pattern line.
+When the &quot;!&quot; is present, it reverses the meaning of the match.
+That is, if the pattern matches then it is considered to NOT be a match, and
+if it does not match it is considered to be a match.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+a pattern.
+For example, if you type the characters &quot;!urgent&quot; into the Subject
+pattern, the pattern will look like:
+<P>
+<PRE>
+ Subject pattern = !urgent
+</PRE>
+<P>
+This means you want to match the 7 character sequence &quot;!urgent&quot;.
+In order to match messages that do not have &quot;urgent&quot; in
+their Subject field, first type the characters &quot;urgent&quot; followed
+by carriage return for the value of the Subject pattern, then negate it
+by typing the &quot;!&quot; command.
+It should look like
+<P>
+<PRE>
+ ! Subject pattern = urgent
+</PRE>
+<P>
+The contents of each of these header patterns (or the AllText or BodyText patterns) may
+be a complete email address, part of an address, or a random set of
+characters to match against.
+It may also be a list of such patterns, which means you
+are looking for a match against the first pattern in the list <EM>OR</EM>
+the second pattern <EM>OR</EM> the third and so on.
+For example, a Subject pattern equal to
+<P>
+<PRE>
+ Subject pattern = urgent
+ emergency
+ alert
+</PRE>
+<P>
+would match all messages with a subject that contained at least one
+of those words.
+It would also match subjects containing the words &quot;alerts&quot; or
+&quot;Urgently&quot;.
+<P>
+The same example with &quot;NOT&quot; turned on would be
+<P>
+<PRE>
+ ! Subject pattern = urgent
+ emergency
+ alert
+</PRE>
+<P>
+which would match all messages with a subject that did <EM>NOT</EM> contain any of
+those words.
+You can use the &quot;Add Value&quot; command to add new words to the list,
+or you can enter them as a comma-separated list.
+<P>
+(It is not possible to specify two patterns that must <EM>BOTH</EM> be
+present for a match.
+It is only possible to specify that <EM>EITHER</EM> pattern1 <EM>OR</EM>
+pattern2 must be present,
+and that is exactly what using a list does.)
+<P>
+The &quot;Current Folder Type&quot; and the &quot;Score Interval&quot; are
+also part of the Pattern, although the &quot;Score Interval&quot; is not used
+when checking for matches for Scoring.
+There are five similar settings that relate to the status of the message.
+These settings rely on the message being New or not, Deleted or not,
+Answered or not, Important or not, and Recent or not.
+There are also some other miscellaneous settings.
+The first is the Age of the message in days.
+Another is the Size of the message, in bytes.
+The third is a setting that detects whether or not the Subject of a
+message contains raw 8-bit characters (unencoded characters with the most
+significant bit set).
+There is a setting that detects whether or not this is the first time
+Alpine has been run this month (doesn't depend on individual messages),
+and another that detects whether or not this is the first time Alpine has
+been run this year.
+Other parts of the Pattern detect whether or not the From address of a
+message appears in your address book, whether or not certain keywords
+are set for a message, and whether or not certain character sets are
+used in a message.
+
+<H2>Parts of a Pattern</H2>
+
+<H3>Header patterns</H3>
+
+A header pattern is simply text that is searched for in the corresponding
+header field.
+For example, if a Pattern has a From header pattern with the value
+&quot;@company.com&quot;, then only messages that have a From header
+that contains the text &quot;@company.com&quot; will be possible
+matches.
+Matches don't have to be exact.
+For example, if the relevant field of a message contains the text
+&quot;mailbox@domain&quot; somewhere
+in it, then header patterns of &quot;box&quot;, or &quot;x@d&quot;, or
+&quot;mailbox@domain&quot; are all matches.
+<P>
+All parts of the Pattern must match so, for example,
+if a message matches a defined
+From pattern, it still must be checked against the other parts of the
+Pattern that have been defined.
+The To header pattern is a slightly special case.
+If the message being checked has a Resent-To header
+and the feature <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> is turned on, the addresses
+there are used in place of the addresses in the To header.
+This is only true for the To header.
+Resent-cc and Resent-From headers are never used unless you add them
+with the eXtraHdrs command.
+<P>
+The meaning of a header pattern may be negated with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+You can tell that <EM>NOT</EM> has been turned on by looking for the character
+&quot;!&quot; at the beginning of the pattern line.
+It would look something like
+<P>
+<PRE>
+ ! From pattern = susan@example.com
+</PRE>
+<P>
+When the &quot;!&quot; is present, it reverses the meaning of the match.
+<P>
+If you want to check for the presence of a header field but don't care
+about its value, then
+the empty pattern that you get by entering a pair of
+double quotes (&quot;&quot;) should match any message that
+has the corresponding header field.
+
+<H3><A NAME="pattern_alltext">AllText patterns</A></H3>
+
+AllText patterns are just like header patterns except that the text is
+searched for anywhere in the message's headers or body, not just in the
+contents of a particular header field.
+<P>
+
+<H3><A NAME="pattern_bodytext">BodyText patterns</A></H3>
+
+BodyText patterns are just like header patterns except that the text is
+searched for anywhere in the message's body, not just in the
+contents of a particular header field.
+<P>
+
+If there is more than one header pattern or AllText pattern or BodyText pattern
+for which you want to take the
+same action there is a shorthand notation that may be used.
+Any of these patterns may be a list of patterns instead of
+just a single pattern.
+If any one of the patterns in the list matches the message
+then it is considered a match.
+For example, if &quot;company1&quot; and &quot;company2&quot; both required
+you to use the same role when replying to messages, you might have
+a To pattern that looks like
+<P>
+<PRE>
+ To pattern = company1.com
+ company2.com
+</PRE>
+<P>
+This means that if the mail you are replying to was addressed to
+either &quot;anything@company1.com&quot; or &quot;anything@company2.com&quot;,
+then this Pattern is a match and the same actions will be taken.
+<P>
+The meaning of an AllText or BodyText pattern may be negated with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+You can tell that <EM>NOT</EM> has been turned on by looking for the character
+&quot;!&quot; at the beginning of the pattern line.
+When the &quot;!&quot; is present, it reverses the meaning of the match.
+<P>
+A technicality: Since comma is the character used to separate multiple values
+in any of the fields that may have multiple values (such as header patterns,
+AllText patterns, BodyText patterns, keywords, folder lists, and so on),
+you must escape comma with a
+backslash (&#92;) if you want to include a literal comma in one of those fields.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes (those not followed by a comma) are literal
+backslashes and should not be escaped.
+It's unlikely you'll ever need to enter a literal comma or backslash in
+any of the patterns.
+
+<H3><A NAME="pattern_current_folder">Current Folder Type</A></H3>
+
+The &quot;Current Folder Type&quot; may be set to one of four different
+values: &quot;Any&quot;, &quot;News&quot;, &quot;Email&quot;, or
+&quot;Specific&quot;.
+If the value is set to &quot;News&quot;, then the
+Pattern will only match if the currently open folder is a newsgroup.
+The value &quot;Email&quot; only matches if the current folder is not news and
+the value &quot;Any&quot; causes any folder to match.
+If the value of &quot;Current Folder Type&quot; is set to &quot;Specific&quot;,
+then you must fill in a value for &quot;Folder&quot;, which is on the line
+below the &quot;Specific&quot; line.
+In this case you will only get a match if the currently open folder is
+the specific folder you list.
+You may give a list of folders instead of just a single
+folder name, in which case the Pattern will match if the open folder is
+any one of the folders in the list.
+The name of each folder in the list may be either &quot;INBOX&quot;,
+the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are some samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+<CENTER><SAMP>mail/local-folder</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder&quot; field is to use
+the &quot;T&quot; command that is available when the &quot;Folder&quot; line is
+highlighted, or to use the &quot;Take&quot; command with the configuration
+feature
+<A HREF="h_config_enable_role_take">&quot;<!--#echo var="FEAT_enable-rules-under-take"-->&quot;</A>
+turned on.
+<P>
+When reading a newsgroup, there may be a performance penalty
+incurred when collecting the information necessary to check whether
+or not a Pattern matches a message.
+For this reason, the default Current Folder Type is set to &quot;Email&quot;.
+If you have Patterns with a Current Folder Type of either
+&quot;Any&quot; or &quot;News&quot; and those Patterns are used for
+Index Line Coloring or Scoring, you may experience
+slower screen redrawing in the MESSAGE INDEX screen when in a newsgroup.
+
+<H3><A NAME="pattern_age_interval">Age Interval</A></H3>
+
+The &quot;Age Interval&quot; may be set to an interval of message
+ages that should be considered a match.
+Like the other parts of the Pattern, if it is unset it will be ignored.
+The Age Interval looks like
+<P>
+<CENTER><SAMP>(min_age,max_age)</SAMP></CENTER>
+<P>
+where &quot;min_age&quot; and &quot;max_age&quot; are integers greater
+than or equal to zero.
+The special value &quot;INF&quot; may be used for
+the max value. It represents infinity.
+<P>
+Actually, this option may be defined as a list of intervals instead
+of just a single interval.
+The list is separated by commas.
+It can look like
+<P>
+<CENTER><SAMP>(min_age1,max_age1),(min_age2,max_age2),...</SAMP></CENTER>
+<P>
+When there is an Age Interval defined, it is a match if the age, in days, of
+the message is contained in any of the intervals.
+The intervals include both endpoints.
+<P>
+Even though this option is called Age, it isn't actually
+the <EM>age</EM> of the message.
+Instead, it is how many days ago the message arrived in one of your folders.
+If the current time is a little past midnight, then a message that arrived
+just before midnight arrived yesterday, even though the message is only
+a few minutes old.
+By default, the date being used is not the date in the Date
+header of the message.
+It is the date that the message arrived in one of your folders.
+When you Save a message from one folder to another that arrival date
+is preserved.
+If you would like to use the date in the Date header that is possible.
+Turn on the option
+<A HREF="h_config_filt_opts_sentdate">&quot;Use-Date-Header-For-Age&quot;</A>
+near the bottom of the rule definition.
+A value of 0 is today, 1 is yesterday, 2 is the day before yesterday, and so on.
+
+<H3><A NAME="pattern_size_interval">Size Interval</A></H3>
+
+The &quot;Size Interval&quot; may be set to an interval of message
+sizes that should be considered a match.
+Like the other parts of the Pattern, if it is unset it will be ignored.
+The Size Interval looks like
+<P>
+<CENTER><SAMP>(min_size,max_size)</SAMP></CENTER>
+<P>
+where &quot;min_size&quot; and &quot;max_size&quot; are integers greater
+than or equal to zero.
+The special value &quot;INF&quot; may be used for
+the max value. It represents infinity.
+<P>
+Actually, this option may be defined as a list of intervals instead
+of just a single interval.
+The list is separated by commas.
+It can look like
+<P>
+<CENTER><SAMP>(min_size1,max_size1),(min_size2,max_size2),...</SAMP></CENTER>
+<P>
+When there is a Size Interval defined, it is a match if the size, in bytes, of
+the message is contained in any of the intervals.
+The intervals include both endpoints.
+
+<H3><A NAME="pattern_score_interval">Score Interval</A></H3>
+
+The &quot;Score Interval&quot; may be set to an interval of message
+scores that should be considered a match.
+Like the other parts of the Pattern, if it is unset it will be ignored.
+The Score Interval looks like
+<P>
+<CENTER><SAMP>(min_score,max_score)</SAMP></CENTER>
+<P>
+where &quot;min_score&quot; and &quot;max_score&quot; are positive or
+negative integers, with min_score less than or equal to max_score.
+The special values &quot;-INF&quot; and &quot;INF&quot; may be used for
+the min and max values to represent negative and positive infinity.
+<P>
+Actually, a list of intervals may be used if you wish.
+A list would look like
+<P>
+<CENTER><SAMP>(min_score1,max_score1),(min_score2,max_score2),...</SAMP></CENTER>
+<P>
+When there is a Score Interval defined, it is a match if the score for
+the message is contained in any of the intervals in the list.
+The intervals include the endpoints.
+The score for a message is calculated by looking at every Score rule defined and
+adding up the Score Values for the ones that match the message.
+When deciding whether or not a Pattern matches a message for purposes of
+calculating the score, the Score Interval is ignored.
+
+<H3><A NAME="pattern_message_status">Message Status</A></H3>
+
+There are five separate message status settings.
+By default, all five are set to the value &quot;Don't care&quot;, which
+will match any message.
+The value &quot;Yes&quot; means that the particular status must be true
+for a match, and the value &quot;No&quot; means that the particular
+status must not be true for a match.
+For example, one of the five Message Status settings is whether a message
+is marked Important or not.
+A &quot;Yes&quot; means that the message must be Important to be
+considered a match and &quot;No&quot; means that the message must not be
+Important to be considered a match.
+The same is true of the other four message status settings that depend
+on whether or not the message is New; whether the message has
+been Answered or not; whether the message has been Deleted or not, and
+whether the message is Recent or not.
+<P>
+The nomenclature for New and Recent is a bit confusing:
+<P>
+New means that the message is Unseen.
+It could have been in your mailbox for a long time but if you haven't looked
+at it, it is still considered New.
+That matches the default Alpine index display that shows an N for such a
+message.
+<P>
+Recent means that the message was added to this folder since the last time
+you opened the folder.
+Alpine also shows an N by default for these types of messages.
+If you were to run two copies of Alpine that opened a folder one right after
+the other, a message would only show up as Recent in (at most) the first
+Alpine session.
+
+<H3><A NAME="pattern_message_keywords">Message Keywords</A></H3>
+
+Keywords are similar to Message Status, but they are chosen by the user.
+Provided the mail server allows for it, you may add a set of possible keywords
+to a folder and then you may set those keywords or not for each message
+in the folder (see <A HREF="h_common_flag">Flag Command</A>).
+The syntax of this part of the Pattern is similar to the header patterns.
+It is a list of keywords.
+The Keyword part of the Pattern is a match if the message has any of
+the keywords in the list set.
+Like other parts of the Pattern, if this is unset it will be ignored.
+
+<H3><A NAME="pattern_message_charsets">Message Character Sets</A></H3>
+
+A message may use one or more character sets.
+This part of the Pattern matches messages that make use of one or more of
+the character sets specified in the pattern.
+It will be considered a match if a message uses any of the character
+sets in the list you give here.
+
+<P>
+Besides actual character set names (for example, ISO-8859-7, KOI8-R, or
+GB2312) you may also use some shorthand names that Alpine provides.
+These names are more understandable shorthand names for sets of
+character set names.
+Two examples are &quot;Cyrillic&quot; and &quot;Greek&quot;.
+Selecting one of these shorthand names is equivalent to selecting all of
+the character sets that make up the set.
+You can see all of these shorthand names and the lists of character sets
+they stand for by typing the &quot;T&quot; command with the Character
+Set pattern highlighted.
+The syntax of this part of the Pattern is similar to the header patterns
+and the Message Keywords pattern.
+It is a list of character sets (or shorthand names).
+The Character Set part of the Pattern is a match if the message uses any
+of the character sets in the list.
+Like other parts of the Pattern, if this is unset it will be ignored.
+
+<H3><A NAME="pattern_8bit_subject">Raw 8-bit in Subject</A></H3>
+
+It seems that lots of unwanted email contains unencoded 8-bit characters
+in the Subject.
+Normally, characters with the 8th bit set are not allowed in the Subject
+header unless they are MIME-encoded.
+This option gives you a way to match messages that have Subjects that
+contain unencoded 8-bit characters.
+By default, the value of this option is &quot;Don't care&quot;, which
+will match any message.
+The value &quot;Yes&quot; means that there must be raw 8-bit characters in
+the Subject of the message in order for there to be a match,
+and the value &quot;No&quot; is the opposite.
+Setting this option will affect performance in large folders because the
+subject of each message in the folder has to be checked.
+
+<H3><A NAME="pattern_bom">Beginning of Month</A></H3>
+
+This option gives you a way to take some action once per month.
+By default, the value of this option is &quot;Don't care&quot;, which
+will always match.
+The value &quot;Yes&quot; means that this must be the first time Alpine has
+been run this month in order to count as a match,
+and the value &quot;No&quot; is the opposite.
+
+<H3><A NAME="pattern_boy">Beginning of Year</A></H3>
+
+This option gives you a way to take some action once per year.
+By default, the value of this option is &quot;Don't care&quot;, which
+will always match.
+The value &quot;Yes&quot; means that this must be the first time Alpine has
+been run this year in order to count as a match,
+and the value &quot;No&quot; is the opposite.
+
+<H3><A NAME="pattern_abookfrom">Address in Address Books</A></H3>
+
+This option gives you a way to match messages that have an address
+that is in one of your address books.
+Only the simple entries in your address books are searched.
+Address book distribution lists are ignored!
+By default, the value of this option is &quot;Don't care&quot;, which
+will match any message.
+The value &quot;Yes, in any address book&quot; means the address
+from the message must be in at least one of your
+address books in order to be a match.
+The value &quot;No, not in any address book&quot;
+means none of the addresses may
+be in any of your address books in order to be a match.
+The values &quot;Yes, in specific address books&quot; and
+&quot;No, not in any of specific address books&quot; are similar but instead
+of depending on all address books you are allowed to give a list of address
+books to look in.
+The addresses from the message that are checked for are determined by the
+setting you have for &quot;Types of addresses to check for in address book&quot;.
+If you set this to &quot;From&quot; the From address from the message will
+be looked up in the address book.
+If you set it to only &quot;To&quot; then the To addresses will be used.
+If any of the To addresses are in the address book then it is considered
+a match for &quot;Yes&quot; or not a match for &quot;No&quot;.
+You could set it to both From and To, in which case all of the From and To
+addresses are used.
+The &quot;Reply-To&quot; and &quot;Sender&quot; cases are a little unusual.
+Due to deficiencies in our tools, Reply-To uses the Reply-To address if it
+exists or the From address if there is no Reply-To address.
+Same for the Sender address.
+Setting this option may affect performance in large folders because the
+From and Reply-To of each message in the folder have to be checked.
+
+<H3><A NAME="pattern_categorizer">Categorizer Command</A></H3>
+
+This is a command that is run with its standard input set to the message
+being checked and its standard output discarded.
+The full directory path should be specified.
+The command will be run and then its exit status will be checked against
+the Exit Status Interval, which defaults to just the value zero.
+If the exit status of the command falls in the interval, it is considered
+a match, otherwise it is not a match.
+<P>
+
+This option may actually be a list of commands.
+The first one that exists and is executable is used.
+That makes it possible to use the same configuration with Unix Alpine and
+PC-Alpine.
+<P>
+
+If none of the commands in the list exists and is executable then the rule
+is <EM>not</EM> a match.
+If it is possible that the command may not exist, you should be careful
+to structure your rules so that nothing destructive
+happens when the command does not exist.
+For example, you might have a filter that filters away spam when there is
+a match but does nothing when there is not a match.
+That would continue to work correctly if the command didn't exist.
+However, if you have a filter that filters away spam when there is not
+a match and keeps it when there is a match, that would filter everything
+if the categorizer command didn't exist.
+<P>
+Here is an <A HREF="h_config_role_cat_cmd_example">example</A>
+setup for the bogofilter filter.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_roles =====
+<HTML>
+<HEAD>
+<TITLE>SETUP ROLES SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP ROLES SCREEN</H1>
+<H2>SETUP ROLES COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for role
+F5 Move to previous role F5 Include file in role config
+F6 Move to next role F6 Exclude file from config
+F7 Previous page of roles
+F8 Next page of roles
+F9 Add new role F9 Replicate existing role
+F10 Delete existing role
+F11 Shuffle the order of roles
+F12 Whereis (search role nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Role ? Display this help text
+ N Next Role E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in role nicknames)
+
+Setup Roles Commands
+------------------------------------------------
+ A Add new role $ Shuffle the order of roles
+ D Delete existing role C Change configuration for highlighted role
+ R Replicate existing role
+ I Include file in role config X Exclude file from role config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Roles Screen</H2>
+
+This screen lets you add, delete, modify, or change the order of the rules
+that determine the role you are playing when composing a message.
+<P>
+You may play different roles depending on who you are replying to.
+For example, if you are replying to a message addressed to help-desk you
+may be acting as a Help Desk Worker.
+That role may require that you use a different return address and/or
+a different signature.
+<P>
+Roles are optional.
+If you set up roles they work like this: Each role has a set of
+&quot;Uses&quot;, which indicate whether or not a role is eligible to be
+considered for a particular use; a &quot;Pattern&quot;,
+which is used to decide which of the eligible roles is used; and a set
+of &quot;Actions&quot;, which are taken when that role is used.
+When you reply to a message, the message you are replying to is compared
+with the Patterns of the roles marked as eligible for use when replying.
+The comparisons start with the first eligible role and keep going until there
+is a match.
+If a match is found, the matching role's Actions are taken.
+<P>
+It is also possible to set a default role and to change that role during
+your Alpine session.
+When you start Alpine no default role will be set.
+You may set or change the current default role by using the &quot;D&quot;
+command in the role selection screen.
+You'll see that screen while composing a message and being asked to select
+a role.
+An easy way to get to that screen is to use the <A HREF="h_common_role">Role Command</A> to
+compose a message.
+You may find a default role useful if you normally perform the duties of one
+of your roles for a while, then you switch to another role and stay in the
+new role for another period of time.
+It may be easier than using the Role Command to select the role each time you
+compose a message.
+
+<H2>Role Uses</H2>
+
+There are three types of use to be configured;
+one for Replying, one for Forwarding, and one for Composing.
+These indicate whether or not you want a role to be considered when you
+type the Reply, Forward, or Compose commands.
+(The Role command is an alternate form of the Compose command, and it is
+not affected by these settings.)
+Each of these Use types has three possible values.
+The value &quot;Never&quot;
+means that the role will never be considered as a candidate for use with
+the corresponding command.
+For example, if you set a role's Reply Use to Never, then when you Reply to
+a message, that role won't even be considered.
+(That isn't quite true. If the message you are replying to matches some other
+role that requires confirmation,
+then there will be a ^T command available which allows you to select a role
+from all of your roles, not just the reply-eligible roles.)
+<P>
+
+The options &quot;With confirmation&quot; and &quot;Without confirmation&quot;
+both mean that you do want to consider this role when using the corresponding
+command.
+For either of these settings the role's Pattern will
+be checked to see if it matches the message.
+For Reply Use, the message used to compare the Pattern with is the message
+being replied to.
+For Forward Use, the message used to compare the Pattern with is the message
+being forwarded.
+For Compose Use, there is no message, so the parts of the Pattern that depend
+on a message (everything other than Current Folder Type) are ignored.
+In all cases, the Current Folder Type is checked if defined.
+If there is a match then this role will either be used without confirmation
+or will be the default when confirmation is asked for, depending on
+which of the two options is selected.
+If confirmation is requested, you will have a chance to
+choose No Role instead of the offered role, or to
+change the role to any one of your other roles (with the ^T command).
+
+<H2>Role Patterns</H2>
+
+In order to determine whether or not a message matches a role the message is
+compared with the Role's Pattern.
+These Patterns are the same for use with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so are described in only one place,
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+<P>
+Since header patterns, AllText patterns, and BodyText patterns that are unset are ignored,
+a role that has all header patterns unset, the AllText pattern unset,
+the BodyText pattern unset,
+the Score Interval unset, and the Current Folder Type set to
+&quot;Any&quot; may be used as a default role.
+It should be put last in the list of roles since the matching
+starts at the beginning and proceeds until one of the roles is a match.
+If no roles at all match, then Alpine will
+use its regular methods of defining the role.
+If you wanted to, you could define a different &quot;default&quot; role
+for Replying, Forwarding, and Composing by setting the
+&quot;Use&quot; fields appropriately.
+
+<H2>Role Actions</H2>
+
+Once a role match is found, the role's Actions are taken.
+For each role there are several possible actions that may be defined.
+They are actions to set the From address, the Reply-To address,
+the Fcc, the Signature, the Template file, and Other Headers.
+
+<H3>Set From</H3>
+
+The From address is the address used on the From line of the message
+you are sending.
+
+<H3>Set Reply-To</H3>
+
+The Reply-To address is the address used on the Reply-To line of the message
+you are sending.
+You don't need a Reply-To address unless it is different from the From address.
+
+<H3>Set Other Headers</H3>
+
+If you want to set the value of the From or Reply-To headers, use
+the specific fields &quot;Set From&quot; or &quot;Set Reply-To&quot;.
+If you want to set the values of other headers, use this field.
+This field is similar to the
+<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A> configuration option.
+Each header you specify here must include the header tag
+(&quot;To:&quot;, &quot;Approved:&quot;, etc.)
+and may optionally include a value for that header.
+It is different from the <!--#echo var="VAR_customized-hdrs"--> in that the value you give
+for a header here will replace any value that already exists.
+For example, if you are Replying to a message there will be at least one
+address in the To header (the address you are Replying to).
+However, if you Reply using a role that sets the To header, that role's
+To header value will be used instead.
+
+<H3>Set Fcc</H3>
+
+The Fcc is used as the Fcc for the message you are sending.
+
+<H3>Set Signature or Set LiteralSig</H3>
+
+The Signature is the name of a file to be used as the signature file when
+this role is being used.
+If the name of the file has a vertical bar following it (|)
+then it is assumed that the file is a program that should be run to
+produce the signature.
+If the LiteralSig is set, then it is used instead of the signature file.
+LiteralSig is just a different way to store the signature.
+It is stored in the pine configuration file instead of in a separate
+signature file.
+If the <A HREF="h_config_literal_sig"><!--#echo var="VAR_literal-signature"--></A> option is defined
+either in the role or as the default signature in the Setup/Config screen,
+then the signature file is ignored.
+
+<H3>Set Template</H3>
+
+A Template is the name of a file to be included in the message when this
+role is being used.
+If the name of the file has a vertical bar following it (|)
+then it is assumed that the file is a program that should be run to
+produce the template.
+
+<P>
+Both signature files and template files may be stored remotely on an IMAP
+server.
+In order to do that you just give the file a remote name.
+This works just like the regular
+<A HREF="h_config_signature_file"><!--#echo var="VAR_signature-file"--></A>
+option that is configured from the Setup/Configuration screen.
+A remote signature file name might look like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/sig3</SAMP></CENTER>
+<P>
+Once you have named the remote signature or template file you create its
+contents by using the &quot;F&quot; &quot;editFile&quot; command when the
+cursor is on the &quot;Set Signature&quot; or &quot;Set Template&quot;
+line of the role editor.
+
+<P>
+Both signature files and template files (or the output of signature programs
+and template file programs) may contain special tokens
+that are replaced with contents
+that depend on the message being replied to or forwarded.
+See the help for the individual fields inside the role editor for more
+information on tokens.
+
+<H3>Use SMTP Server</H3>
+
+If this field has a value, then it will be used as the SMTP server
+to send mail when this role is being used (unless the SMTP server variable
+is set in the system-wide fixed configuration file).
+It has the same semantics as the
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+variable in the Setup/Config screen.
+When you postpone the composition this SMTP server list will be saved
+with the postponed composition and it cannot be changed later.
+Because of that, you may want to make this a list of SMTP servers
+with the preferred server at the front of the list and alternate servers
+later in the list.
+
+<P>
+If any of the actions are left unset, then the action depends on what
+is present in the &quot;Initialize settings using role&quot; field.
+If you've listed the nickname of another one of your roles there, then the
+corresponding action from that role will be used here.
+If that action is also blank, or if there is no nickname specified,
+then Alpine will do whatever it normally does to set these actions.
+This depends on other configuration options and features you've set.
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new role definition to your set of
+roles.
+The new role will be added after the highlighted role.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted role.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname, Uses, Pattern,
+and Actions of the currently highlighted role.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the roles.
+You may move the currently highlighted role up or down in the list.
+The order of the roles is important since the roles are tested for a
+match starting with the first role and continuing until a match is found.
+You should place the roles with more specific Patterns near the beginning
+of the list, and those with more general Patterns near the end so that
+the more specific matches will happen when appropriate.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing role and modify it.
+The new role will be added after the highlighted role.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a roles file to your configuration.
+Usually, your roles will be contained in your Alpine configuration file.
+If you wish, some or all of your roles may be stored in a separate file.
+If a roles file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted role.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty roles file in your configuration, you may use
+the Shuffle command to move roles into it.
+In fact, that's the only way to get the initial role into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a roles file from your roles configuration.
+A limitation of the program is that in order to exclude a roles file
+that file must have at least one role
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy role to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_other =====
+<HTML>
+<HEAD>
+<TITLE>SETUP OTHER RULES SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP OTHER RULES SCREEN</H1>
+<H2>SETUP OTHER RULES COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for rule
+F5 Move to previous rule F5 Include file in rule config
+F6 Move to next rule F6 Exclude file from config
+F7 Previous page of rules
+F8 Next page of rules
+F9 Add new rule F9 Replicate existing rule
+F10 Delete existing rule
+F11 Shuffle the order of rules
+F12 Whereis (search rule nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev rule ? Display this help text
+ N Next rule E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in rule nicknames)
+
+Setup Other Rules Commands
+------------------------------------------------
+ A Add new rule $ Shuffle the order of rules
+ D Delete existing rule C Change configuration for highlighted rule
+ R Replicate existing rule
+ I Include file in rule config X Exclude file from rule config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Other Rules Screen</H2>
+
+This is where you may set various actions that do not fit well into the
+other Rules categories.
+
+<H2>Patterns</H2>
+
+Other Rules are a little different from the rest of the Rules because
+they depend only on the current folder, and not on a particular message.
+In order to determine whether or not a rule's actions should be applied
+the current folder is compared with the rule's Pattern, which consists
+of only the Current Folder Type.
+Current Folder Type works the same for Other Rules as it does for Roles,
+Filtering, Index Coloring, and Scoring.
+Keep in mind that the only part of the Pattern that applies to Other
+Rules is the Current Folder Type when looking at the description of
+Patterns given
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+
+<H2>The Actions</H2>
+
+<H3>Set Sort Order</H3>
+
+When you enter a new folder, these rules will be checked to see if you
+have set a sort order that is different from your default sort order.
+The default is set in the Setup/Config screen with
+the &quot;<A HREF="h_config_sort_key"><!--#echo var="VAR_sort-key"--></A>&quot; option.
+If the Sort Order action is set, then the folder will be displayed sorted in
+that sort order instead of in the default order.
+<P>
+A possible point of confusion arises when you change the configuration
+of the Sort Order for the currently open folder.
+The folder will normally be re-sorted when you go back to viewing the
+index.
+However, if you have manually sorted the folder with the
+Sort
+(<!--chtml if pinemode="function_key"-->F7<!--chtml else-->$<!--chtml endif-->)
+command, then it will not be re-sorted until the next time it is opened.
+
+<H3>Set Index Format</H3>
+
+When you enter a new folder, these rules will be checked to see if you
+have set an Index Format that is different from your default Index Format,
+which is set with the
+<A HREF="h_config_index_format">&quot;<!--#echo var="VAR_index-format"-->&quot;</A> option.
+If so, the index will be displayed with this format instead of the default.
+
+<H3>Set Startup Rule</H3>
+
+When you enter a new folder, these rules will be checked to see if you
+have set a startup rule that is different from the default startup rule.
+The default for incoming folders is set in the Setup/Config screen with
+the &quot;<!--#echo var="VAR_incoming-startup-rule"-->&quot; option.
+The default for folders other than INBOX that are not part of your
+incoming collection
+(see <A HREF="h_config_enable_incoming"><!--#echo var="FEAT_enable-incoming-folders"--></A> feature)
+is to start with the last message in the folder.
+If the Startup Rule is set to something other than &quot;default&quot;,
+then the rule will determine which message will be the current message when
+the folder is first opened.
+<P>
+The various startup rule possibilities work the same here as they do in
+the incoming collection, so check
+<A HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A>
+for more help.
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new rule definition to your set of
+rules.
+The new rule will be added after the highlighted rule.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted rule.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname, Pattern,
+and Action of the currently highlighted rule.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the rules.
+You may move the currently highlighted rule up or down in the list.
+The order of the rules is important since the rules are tested for a
+match starting with the first rule and continuing until a match is found.
+You should place the rules with more specific Patterns near the beginning
+of the list, and those with more general Patterns near the end so that
+the more specific matches will happen when appropriate.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing rule definition and modify it.
+The new rule will be added after the highlighted rule.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a rules file to your configuration.
+Usually, your rules will be contained in your Alpine configuration file.
+If you wish, some or all of your rules may be stored in a separate file.
+If a rules file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted rule.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty rules file in your configuration, you may use
+the Shuffle command to move rules into it.
+In fact, that's the only way to get the initial rule into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a rules file from your rules configuration.
+A limitation of the program is that in order to exclude a rules file
+that file must have at least one rule
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy rule to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_srch =====
+<HTML>
+<HEAD>
+<TITLE>SETUP SEARCH RULES SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP SEARCH RULES SCREEN</H1>
+<H2>SETUP SEARCH RULES COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for rule
+F5 Move to previous rule F5 Include file in rule config
+F6 Move to next rule F6 Exclude file from config
+F7 Previous page of rules
+F8 Next page of rules
+F9 Add new rule F9 Replicate existing rule
+F10 Delete existing rule
+F11 Shuffle the order of rules
+F12 Whereis (search rule nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev rule ? Display this help text
+ N Next rule E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in rule nicknames)
+
+Setup Search Rules Commands
+------------------------------------------------
+ A Add new rule $ Shuffle the order of rules
+ D Delete existing rule C Change configuration for highlighted rule
+ R Replicate existing rule
+ I Include file in rule config X Exclude file from rule config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Search Rules Screen</H2>
+
+One of the commands that becomes available when that feature is turned on
+is the &quot;; Select&quot; command, which is used in the MESSAGE INDEX
+screen to select a set of messages.
+One way of selecting messages is to use a Rule.
+All of the messages that match (or don't match if you wish)
+a Rule's Pattern will be selected.
+<P>
+Any of your Rules may be used for this purpose.
+You might already have Rules set up for filtering, index line color, scores, or roles;
+and you may use any of those Rules with the Select command.
+However, you might find it more convenient to set up a separate set of Rules
+just for this purpose without having to worry about what other effects
+they may cause.
+That is the purpose of these Select Rules.
+
+<P>
+Each rule has a &quot;Pattern&quot;
+that is used to decide which messages are selected when you use it with
+the Select command.
+
+<H2>Patterns</H2>
+
+In order to determine whether or not a message should be selected
+the message is compared with the rule's Pattern.
+These Patterns are the same for use with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so are described in only one place,
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new rule definition to your set of
+rules.
+The new rule will be added after the highlighted rule.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted rule.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname and Pattern
+of the currently highlighted rule.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the rules.
+This affects only the order they are presented in when you use the
+^T subcommand of the Select by Rule command.
+You may move the currently highlighted rule up or down in the list.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing rule definition and modify it.
+The new rule will be added after the highlighted rule.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a rules file to your configuration.
+Usually, your rules will be contained in your Alpine configuration file.
+If you wish, some or all of your rules may be stored in a separate file.
+If a rules file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted rule.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty rules file in your configuration, you may use
+the Shuffle command to move rules into it.
+In fact, that's the only way to get the initial rule into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a rules file from your rules configuration.
+A limitation of the program is that in order to exclude a rules file
+that file must have at least one rule
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy rule to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_incols =====
+<HTML>
+<HEAD>
+<TITLE>SETUP INDEX LINE COLORS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP INDEX LINE COLORS SCREEN</H1>
+<H2>SETUP INDEX LINE COLORS COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for rule
+F5 Move to previous rule F5 Include file in rule config
+F6 Move to next rule F6 Exclude file from config
+F7 Previous page of rules
+F8 Next page of rules
+F9 Add new rule F9 Replicate existing rule
+F10 Delete existing rule
+F11 Shuffle the order of rules
+F12 Whereis (search rule nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev rule ? Display this help text
+ N Next rule E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in rule nicknames)
+
+Setup Index Color Commands
+------------------------------------------------
+ A Add new rule $ Shuffle the order of rules
+ D Delete existing rule C Change configuration for highlighted rule
+ R Replicate existing rule
+ I Include file in rule config X Exclude file from rule config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Index Line Colors Screen</H2>
+
+Index Line Color causes lines in the MESSAGE INDEX screen to be colored.
+This action is only available if your terminal is capable of displaying
+color and color display has been enabled with the
+<A HREF="h_config_color_style">Color Style</A> option within the
+Setup Color screen.
+(In PC-Alpine, color is always enabled so there is no option to turn on.)
+This screen lets you add, delete, modify, or change the order of the rules
+that cause the lines in the MESSAGE INDEX to be displayed in different
+colors.
+<P>
+Each rule has a &quot;Pattern&quot;,
+which is used to decide which of the rules is used; and the color that
+is used if the Pattern matches a particular message.
+
+<H2>Index Color Patterns</H2>
+
+In order to determine whether or not a message matches an Index Color Rule
+the message is compared with the rule's Pattern.
+These Patterns are the same for use with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so are described in only one place,
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+
+<P>
+If none of the Index Color rules is a match for a particular index line,
+then the color used is set using
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+
+<H2>Index Line Color</H2>
+
+This is the color that index lines are colored when there is a matching
+Pattern.
+This colors the whole index line, except possibly the status letters,
+which may be colored separately using
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new rule definition to your set of
+rules.
+The new rule will be added after the highlighted rule.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted rule.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname, Pattern,
+and Index Line Color of the currently highlighted rule.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the rules.
+You may move the currently highlighted rule up or down in the list.
+The order of the rules is important since the rules are tested for a
+match starting with the first rule and continuing until a match is found.
+You should place the rules with more specific Patterns near the beginning
+of the list, and those with more general Patterns near the end so that
+the more specific matches will happen when appropriate.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing rule definition and modify it.
+The new rule will be added after the highlighted rule.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a rules file to your configuration.
+Usually, your rules will be contained in your Alpine configuration file.
+If you wish, some or all of your rules may be stored in a separate file.
+If a rules file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted rule.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty rules file in your configuration, you may use
+the Shuffle command to move rules into it.
+In fact, that's the only way to get the initial rule into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a rules file from your rules configuration.
+A limitation of the program is that in order to exclude a rules file
+that file must have at least one rule
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy rule to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_filter =====
+<HTML>
+<HEAD>
+<TITLE>SETUP FILTERING SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP FILTERING SCREEN</H1>
+<H2>SETUP FILTERING COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for filter
+F5 Move to previous filter F5 Include file in filter config
+F6 Move to next filter F6 Exclude file from config
+F7 Previous page of filters
+F8 Next page of filters
+F9 Add new filter F9 Replicate existing filter
+F10 Delete existing filter
+F11 Shuffle the order of filters
+F12 Whereis (search filter nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Filter ? Display this help text
+ N Next Filter E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in filter nicknames)
+
+Setup Filters Commands
+------------------------------------------------
+ A Add new filter $ Shuffle the order of filters
+ D Delete existing filter C Change configuration for highlighted filter
+ R Replicate existing filter
+ I Include file in filter config X Exclude file from filter config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Filtering Screen</H2>
+
+This screen lets you add, delete, modify, or change the order of the rules
+that determine the filtering Alpine does on folders you view.
+<P>
+The software that actually delivers mail (the stuff that happens
+before Alpine is involved) for you is in a better position to do mail filtering
+than Alpine itself.
+If possible, you may want to look into using that sort of mail filtering to
+deliver mail to different folders, delete it, or forward it.
+However, if you'd like Alpine to help with this, Alpine's filtering is for you.
+<P>
+Filtering is a way to automatically move certain messages from one folder
+to another or to delete messages.
+It can also be used to set message status (Important, Deleted, New,
+Answered) and to set <A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> for messages.
+Alpine doesn't have the ability to forward mail to another address or
+to deliver vacation messages.
+<P>
+Each filtering rule has a &quot;Pattern&quot; and a &quot;Filter Action&quot;.
+When a folder is opened, when new mail arrives in an open folder, or
+when mail is Expunged from a folder; each
+message is compared with the Patterns of your filtering rules.
+The comparisons start with the first rule and keep going until there
+is a match.
+If a match is found, the message may be deleted or moved, depending on
+the setting of the Filter Action.
+If the message is not deleted, it may have its status altered.
+
+<P>
+<EM>NOTE:</EM>
+When setting up a Pattern used to delete messages,
+it is recommended that you test the Pattern first with a &quot;Move&quot;
+folder specified in
+case unintended matches occur. Messages that are deleted will be removed
+from the folder and <EM>unrecoverable</EM> from within Alpine after the
+next Expunge command or once the folder being filtered has been closed.
+
+<H2>Filter Patterns</H2>
+
+In order to determine whether or not a message matches a filter the message is
+compared with the Filter's Pattern.
+These Patterns are the same for use with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so are described in only one place,
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+<P>
+Since filtering is a potentially destructive action, if you have a filtering
+Pattern with nothing other than Current Folder Type set, that filtering
+rule is ignored.
+
+<H2>Filter Actions</H2>
+
+Once a filter match is found for a particular message, there are some actions
+that may be taken.
+First, the message may have its status changed.
+This is the same message status that you can manipulate manually using the
+<a href="h_common_flag">Flag Command</a>.
+There are always four elements of message status that you can control.
+You can set or clear the Important status, the New status, the Deleted
+status, and the Answered status.
+Of course, if the filter is going to delete the message,
+then there is no point in setting message status.
+You may also be able to set user-defined keywords for a message.
+Read a little about keywords in the help text for the
+<A HREF="h_common_flag">Flag</A> command.
+<P>
+Second, the filter may delete or move the message.
+Deleting the message marks it Deleted and removes it from view.
+It is effectively gone forever (though it technically is still there until
+the next expunge command, which may happen implicitly).
+Moving the message moves it from the open folder into the folder
+listed on the &quot;Folder List&quot; line of the filter configuration.
+If you list more than one folder name (separated by commas) then the message
+will be copied to each of those folders.
+In any case, if &quot;Delete&quot; or &quot;Move&quot; is set then the
+message is removed from the current folder.
+If you just want to set the messages status without deleting it from
+the folder, then set the filter action to
+&quot;Just Set Message Status&quot;.
+<P>
+(There is no way to do a Copy instead of a Move, due to the difficulties
+involved in keeping track of whether or not a message has
+already been copied.)
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new filter definition to your set of
+filters.
+The new filter will be added after the highlighted filter.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted filter.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname, Pattern,
+and Folder of the currently highlighted filter.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the filters.
+You may move the currently highlighted filter up or down in the list.
+The order of the filters is important since the filters are tested for a
+match starting with the first filter and continuing until a match is found.
+You should place the filters with more specific Patterns near the beginning
+of the list, and those with more general Patterns near the end so that
+the more specific matches will happen when appropriate.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing filter and modify it.
+The new filter will be added after the highlighted filter.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a filters file to your configuration.
+Usually, your filters will be contained in your Alpine configuration file.
+If you wish, some or all of your filters may be stored in a separate file.
+If a filters file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted filter.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty filters file in your configuration, you may use
+the Shuffle command to move filters into it.
+In fact, that's the only way to get the initial filter into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a filters file from your filters configuration.
+A limitation of the program is that in order to exclude a filters file
+that file must have at least one filter
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy filter to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+<H3>Performance Considerations</H3>
+The number and type of patterns being tested can
+adversely effect performance. Issues to be aware
+of include:
+<P>
+<UL>
+ <LI> The more filters you have defined the longer it will take to run down
+the list. Deleting unused filters is a good idea.
+ <LI> Filtering in newsgroups served by an NNTP server will be slow
+if your patterns include tests other than &quot;From:&quot;
+or &quot;Subject:&quot;.
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_rules_score =====
+<HTML>
+<HEAD>
+<TITLE>SETUP SCORING SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP SCORING SCREEN</H1>
+<H2>SETUP SCORING COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Back to MAIN Alpine menu
+F4 Change configuration for rule
+F5 Move to previous rule F5 Include file in rule config
+F6 Move to next rule F6 Exclude file from config
+F7 Previous page of rules
+F8 Next page of rules
+F9 Add new rule F9 Replicate existing rule
+F10 Delete existing rule
+F11 Shuffle the order of rules
+F12 Whereis (search rule nicknames)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev rule ? Display this help text
+ N Next rule E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in rule nicknames)
+
+Setup Scoring Commands
+------------------------------------------------
+ A Add new rule $ Shuffle the order of rules
+ D Delete existing rule C Change configuration for highlighted rule
+ R Replicate existing rule
+ I Include file in rule config X Exclude file from rule config
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Scoring Screen</H2>
+
+Most people will not use scores at all, but if you do use them, here's how
+they work in Alpine.
+Using this screen, you may define Scoring rules.
+The score for a message is calculated by looking at every Score rule defined
+and adding up the Score Values for the ones that match the message.
+If there are no matches for a message, it has a score of zero.
+Message scores may be used a couple of ways in Alpine.
+
+<H3>Sorting by Score</H3>
+
+One of the methods you may use to sort message indexes is to sort by
+score.
+The scores of all the messages in a folder will be calculated and then
+the index will be ordered by placing the messages in order of ascending or
+descending score.
+
+<H3>Scores for use in Patterns</H3>
+
+The Patterns used for Roles, Index Line Coloring, and Filtering have a
+category labeled &quot;Score Interval&quot;.
+When a message is being compared with a Pattern to check for a match, if
+the Score Interval is set only messages that have a score somewhere in
+the interval are a match.
+
+<H2>Scoring Rule Patterns</H2>
+
+In order to determine whether or not a message matches a scoring rule
+the message is compared with the rule's Pattern.
+These Patterns are the same for use with Roles, Filtering, Index Coloring,
+Scoring, Other Rules, and Search Rules, so are described in only one place,
+&quot;<A HREF="h_rule_patterns">here</A>&quot;.
+
+<P>
+Actually, Scoring rule Patterns are slightly different from the other types of
+Patterns because Scoring rule Patterns don't contain a Score Interval.
+In other words, when calculating the score for a message, which is done
+by looking at the Scoring rule Patterns, scores aren't used.
+
+<H2>Score Value</H2>
+
+This is the value that will be added to the score for a message if the
+rule's Pattern is a match.
+Each individual Score Value is an integer between -100 and 100, and the
+values from matching rules are added together to get a message's score.
+There is also a way to extract the value from a particular header of each
+message. See the help text for Score Value for further information.
+
+<H2>Command Descriptions</H2>
+
+<H3>Add</H3>
+
+The Add command is used to add a new scoring rule definition.
+The new rule will be added after the highlighted rule.
+
+<H3>Delete</H3>
+
+The Delete command deletes the currently highlighted scoring rule.
+
+<H3>Change</H3>
+
+The Change command lets you edit the nickname, Pattern,
+and Score Value of the currently highlighted scoring rule.
+
+<H3>Shuffle</H3>
+
+The Shuffle command allows you to change the order of the scoring rules.
+You may move the currently highlighted rule up or down in the list.
+The order of the rules is important since the rules are tested for a
+match starting with the first rule and continuing until a match is found.
+You should place the rules with more specific Patterns near the beginning
+of the list, and those with more general Patterns near the end so that
+the more specific matches will happen when appropriate.
+
+<H3>Replicate</H3>
+
+The Replicate command is used to copy an existing rule and modify it.
+The new rule will be added after the highlighted rule.
+
+<H3>IncludeFile</H3>
+
+The IncludeFile command allows you to add a rules file to your configuration.
+Usually, your rules will be contained in your Alpine configuration file.
+If you wish, some or all of your rules may be stored in a separate file.
+If a rules file already exists (maybe it was made by somebody else using
+Alpine), you may insert it before the currently highlighted rule.
+You may also insert an empty file or a file that does not yet exist.
+Once you have an empty rules file in your configuration, you may use
+the Shuffle command to move rules into it.
+In fact, that's the only way to get the initial rule into the file.
+
+<H3>eXcludeFile</H3>
+
+The eXcludeFile command removes a rules file from your rules configuration.
+A limitation of the program is that in order to exclude a rules file
+that file must have at least one rule
+in it, otherwise you won't be able to highlight a line in the file.
+So you may have to add a dummy rule to the file in order to exclude the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_direct_config =====
+<HTML>
+<HEAD>
+<TITLE>SETUP LDAP DIRECTORY SERVERS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP LDAP DIRECTORY SERVERS SCREEN</H1>
+<H2>SETUP LDAP DIRECTORY SERVERS COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Back to MAIN Alpine menu
+F4 Change configuration for directory server
+F5 Move to previous directory server
+F6 Move to next directory server
+F7 Previous page of directory servers
+F8 Next page of directory servers
+F9 Add new directory server
+F10 Delete existing directory server
+F11 Shuffle the order of directory servers
+F12 Whereis (search directory server titles)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Directory Server ? Display this help text
+ N Next Directory Server E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in directory server titles)
+
+Setup LDAP Directory Server Commands
+------------------------------------------------
+ A Add new directory server $ Shuffle the order of directory servers
+ D Delete existing dir server C Change configuration for highlighted server
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup LDAP Directory Servers Screen</H2>
+
+This screen lets you add, delete, modify, or change the order of your
+directory servers. You may also set some optional behavior for each server.
+The &quot;Add Dir&quot; command brings up a blank form to
+fill in. You will have to supply at least the name of the LDAP server.
+You will often have to supply a search base to be used with that server,
+as well. Once the form has been brought up on your screen, there is help
+available for each of the options you may set.
+<P>
+The &quot;Del Dir&quot; command allows you to remove a directory server
+from your configuration.
+<P>
+The &quot;Change&quot; command is similar to the &quot;Add Dir&quot; command.
+The difference is that instead of bringing up a form for a new server
+configuration, you are changing the configuration of an existing entry.
+For example, you might want to correct a typing error, change a
+nickname, or change one of the options set for that server.
+<P>
+The &quot;Shuffle&quot; command is used to change the order of directory
+servers.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_address_display ========================
+<HTML>
+<HEAD>
+<TITLE>SEARCH RESULTS INDEX</TITLE>
+</HEAD>
+<BODY>
+<H1>SEARCH RESULTS INDEX</H1>
+This screen shows the results, if any, of your Directory Server search.
+Commands (besides those for screen navigation) are:
+<DL>
+<DT>View
+<!--chtml if pinemode="function_key"-->
+(F4)
+<!--chtml else-->
+(V)
+<!--chtml endif-->
+</DT>
+<DD>See the full information for the selected entry.
+
+<DT>Compose
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(C)
+<!--chtml endif--></DT>
+<DD>Compose a message with the selected entry as the recipient.
+
+<DT>Role
+<!--chtml if pinemode="function_key"-->
+(F2)
+<!--chtml else-->
+(#)
+<!--chtml endif--></DT>
+<DD>Compose a message with the selected entry as the recipient. This differs
+from Compose in that you may select a role before beginning your composition.
+
+<DT>Forward
+<!--chtml if pinemode="function_key"-->
+(F10)
+<!--chtml else-->
+(F)
+<!--chtml endif--></DT>
+<DD>Send the full information for the selected entry as an
+email message to someone else.
+
+<DT>Save
+<!--chtml if pinemode="function_key"-->
+(F11)
+<!--chtml else-->
+(S)
+<!--chtml endif-->
+</DT>
+<DD>Save to your address book:
+<UL>
+<LI>the result of the search (as just found through your query) for the
+selected entry; or
+<LI>the selected entry for repeated Directory Server searching when used
+in the future.
+</UL>
+or<BR>
+Export to a file (external to Alpine):
+<UL>
+<LI>the full information for the selected entry; or
+<LI>the email address from the selected entry; or
+<LI>the selected entry in <A HREF="h_whatis_vcard">vCard</A> format.
+</UL>
+<DT>WhereIs
+<!--chtml if pinemode="function_key"-->
+(F12)
+<!--chtml else-->
+(W)
+<!--chtml endif-->
+</DT>
+<DD>Search for text in the SEARCH RESULTS INDEX screen. (Searches only the
+displayed text, not the full records for each entry.)
+</DL>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_address_select ========================
+<HTML>
+<HEAD>
+<TITLE>SEARCH RESULTS INDEX</TITLE>
+</HEAD>
+<BODY>
+<H1>SEARCH RESULTS INDEX</H1>
+This screen shows the results, if any, of your Directory Server search.
+Commands (besides those for screen navigation) are:
+<DL>
+<DT>Select
+</DT>
+<DD>Select this entry for use.
+
+<DT>ExitSelect
+<DD>Exit without selecting any of the entries.
+
+<DT>WhereIs
+</DT>
+<DD>Search for text in the SEARCH RESULTS INDEX screen. (Searches only the
+displayed text, not the full records for each entry.)
+</DL>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_maint =====
+<HTML>
+<HEAD>
+<TITLE>Help for Folder List</TITLE>
+</HEAD>
+<BODY>
+<H1>FOLDER LIST COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 MAIN MENU Screen F3 Quit Alpine
+F4 Select folder and view it F4 MAIN MENU Screen
+F5 Move to previous folder
+F6 Move to next folder F6 Specify a folder to go to
+F7 Show previous screen of listing F7 Show MESSAGE INDEX of current folder
+F8 Show next screen of listing F8 Compose a message
+F9 Add a new folder F9 Print folder listing
+F10 Delete selected folder
+F11 Rename selected folder
+F12 Whereis (search folder names)
+
+Available Commands -- Group 3
+F1 Show Help Text
+F2 See commands in next group
+F5 Go to next new message
+ (or count recent messages if <A HREF="h_config_tab_checks_recent"><!--#echo var="FEAT_tab-checks-recent"--></A> is set)
+F8 Compose a message using roles
+F9 Export folder to a file
+F10 Import the file back to a folder
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the Folder Screen Operations on the Selected Folder
+---------------------------- ---------------------------------
+ P Move to previous folder V View Index of selected folder
+ N Move to next folder D Delete
+ - Show previous page of listing R Rename
+Spc (space bar) Show next page E Export to file
+ U Import from file to folder
+
+FOLDER LIST Screen Commands General Alpine Command
+--------------------------- ---------------------
+ A Add a folder O Show all other available commands
+ G Specify a folder to go to ? Show Help text
+ I Show MESSAGE INDEX of current folder M MAIN MENU Screen
+ W Whereis (search folder names) Q Quit Alpine
+ % Print folder listing C Compose a message
+ # Compose a message using roles
+</PRE>
+<!--chtml endif-->
+<P>
+These commands are only available in the FOLDER LIST screen when the
+<A HREF="h_config_enable_agg_ops">&quot;<!--#echo var="FEAT_enable-aggregate-command-set"-->&quot;
+feature</A> is set in the SETUP CONFIGURATION screen:<DL>
+<DT>Select:</DT>
+<DD>Select folders by certain criteria:<UL>
+<LI>All: of limited use, since there is no Apply command.
+<LI>by Property: <UL>
+ <LI>folder contains messages not yet seen
+ <LI>folder contains new messages
+ <LI>folder contains exactly as many, more, or fewer messages
+than a given number
+ </UL>
+<LI>by Text: <UL>
+ <LI>contained in name of folder (Name Select)
+ <LI>contained in messages in folder (Content Select)
+ </UL>
+</UL></DD>
+
+<DT>Select current:</DT>
+<DD>Select the folder the cursor is on. (Can be used to &quot;manually&quot;
+add one or more folders to a set created with the Select command described
+above.)</DD>
+<DT>Zoom mode:</DT>
+<DD>Toggles display of only selected folders or all folders on and off.</DD>
+</DL>
+<P>
+If the feature
+<A HREF="h_config_tab_checks_recent">&quot;<!--#echo var="FEAT_tab-checks-recent"-->&quot;</A>
+is set then the TAB key will display the number of recent messages and
+the total number of messages in the highlighted folder.
+<P>
+The &quot;Export&quot; command causes the lowest common denominator style
+mailbox to be written to a file.
+If the file already exists, you are asked if you want to delete it.
+If you say No, then the operation is aborted.
+Export might be a reasonable way to store a backup or an archival copy of
+a folder.
+The exported-to file is a local file on the system where you are running Alpine.
+The &quot;Import&quot; command is the opposite of the Export command.
+It reads a file created by Export and asks where it should save it in your
+folders.
+This could be a new folder or an existing folder.
+If the folder already exists, the messages from the exported file will be
+appended to the folder.
+<P>
+<CENTER>Description of the FOLDER LIST Screen</CENTER>
+
+The purpose of the FOLDER LIST screen is to help you browse and manage
+the folders and directories (also known as &quot;hierarchy&quot;)
+contained within a collection.
+
+<P>
+Folders and directories are arranged alphabetically across lines of
+the screen. Directories, if present, are denoted by a special
+character at the end of the name known as the hierarchy delimiter
+(typically, &quot;/&quot;). By default, folders and directories are
+mixed together. The
+"<A HREF="h_config_fld_sort_rule"><!--#echo var="VAR_folder-sort-rule"--></A>"
+configuration option can be used to group directories toward the
+beginning or end of the list.
+
+<P>
+The Next/Prev Page commands help browse the list, the Next/Prev Fldr
+commands change the &quot;selected&quot; (i.e., highlighted) folder or
+directory, and the View Fldr/Dir commands will &quot;open&quot; the
+selected item. Folder and directory management is provided via the
+Rename, Delete and Add commands.
+
+<P><CENTER>About Folders</CENTER>
+What are Folders?<P>
+
+Folders are simply files where messages are kept. Every message has to be
+in a folder. Most every Alpine user starts out with 3 folders: an INBOX, a
+folder for sent mail and a folder for saved messages.<P>
+
+You may create as many other folders as you wish. They must be given
+names that can be filenames on the filesystem.
+<P>
+
+You can move messages from one folder to another by opening the original
+folder and saving messages into the other folder just as you can save
+message from your INBOX to any other folder.<P>
+
+Folders are typically just files in the filesystem. However, the files
+that are
+folders have some special formatting in them (so that Alpine knows where one
+message ends and another begins) and should <EM>not</EM> be edited outside of
+Alpine. If you want copies of your messages in text files that you can edit
+or otherwise manipulate, use the Export command to copy them from Alpine into
+your regular file area.
+
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+<CENTER>About Directories</CENTER>
+<P>
+A directory is simply a container used to group folders within a
+folder list. You can create as many directories as you like. And
+directories can even contain directories themselves.
+
+<P>
+SPECIAL NOTES: When accessing folders on an IMAP server, it is important
+to note that not all IMAP servers support directories. If you find that
+the Add command fails to offer the &quot;Create Directory&quot; subcommand,
+then it's likely that directories are not supported by the server serving
+in that collection.
+
+<P>
+Similarly, servers that do provide for directories may not do so in
+the same way. On some servers, for example, each folder name you
+create is at the same time capable of being a directory. When this
+happens, Alpine will display both the folder name and the name of the
+directory (with trailing hierarchy delimiter) in the folder list.
+
+<P>
+Another issue with IMAP access, though with a much smaller set of servers,
+is that not all servers accept the request to list out the available
+folders and directories in the same way. If you find yourself having
+trouble viewing folders on your server, you might investigate the
+&quot;<A HREF="h_config_lame_list_mode"><!--#echo var="FEAT_enable-lame-list-mode"--></A>&quot;
+feature.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========= h_valid_folder_names ========
+<HTML>
+<HEAD>
+<TITLE>Explanation of Valid Folder Names</TITLE>
+</HEAD>
+<BODY>
+<H1>Folder Name Syntax Explained</H1>
+
+Once your folder collections are defined, you can usually refer to
+folders by their simple (unqualified) name, or pick from a FOLDER LIST
+display. However, understanding the complete syntax for folder names,
+both local and remote, is handy when using the Goto command and when
+you are adding new folder collections via the Setups/collectionList screen.
+<P>
+An Alpine folder name looks like
+
+<P>
+<CENTER><SAMP>[{&lt;remote-specification&gt;}][#&lt;namespace&gt;]&lt;namespace-specific-part&gt;</SAMP></CENTER>
+<P>
+
+The square brackets ([]) mean that the part is optional.
+
+<P>
+If there is no remote-specification, then the folder name is interpreted
+locally on the computer running Alpine.
+Local folder names depend on the operating system used by the computer
+running Alpine, as well as the configuration of that system. For example,
+&quot;C:&#92;PINE&#92;FOLDERS&#92;OCT-94&quot; might exist on a PC, and
+&quot;~/mail/september-1994&quot; might be a reasonable folder name on a
+system running Unix.
+
+<P>
+Alpine users have the option of using folders that are stored on some other
+computer. Alpine accesses remote folders via IMAP (the Internet Message
+Access Protocol), or in the case of news, via NNTP (the Network News
+Transport Protocol). To be able to access remote folders in Alpine, the
+remote host must be running the appropriate server software (imapd or
+nntpd) and you must correctly specify the name of the folder to Alpine,
+including the domain name of the remote machine. For example,
+<P>
+<CENTER><SAMP>&#123;monet.art.example.com}INBOX</SAMP></CENTER>
+<P>
+could be a remote folder specification, and so could
+<P>
+<CENTER><SAMP>&#123;unixhost.art.example.com}~/mail/september-1994</SAMP></CENTER>
+and
+<P>
+<CENTER><SAMP>&#123;winhost.art.example.com}&#92;mymail&#92;SEP-94</SAMP></CENTER>
+<P>
+Note that in the case of remote folders, the directory/file path in the specification is
+determined by the operating system of the remote computer, <B>not</B> by
+the operating system of the computer on which you are running Alpine.
+<P>
+As you can tell, the name of the computer is in &#123;} brackets
+followed immediately by the name of the folder. (In each of these cases the
+optional namespace is missing.) If, as in these
+examples, there is no remote access protocol specified, then IMAP is
+assumed. Check
+<A HREF="h_folder_server_syntax">here</A>
+for a more detailed look at what options can be placed between the brackets.
+If there are no brackets at all, then the folder name is interpreted locally
+on the computer on which you are running Alpine.
+
+<P>
+To the right of the brackets when a server name is present, or at the
+start of the foldername if no server is present, the sharp sign,
+&quot;#&quot;, holds special meaning. It indicates a folder name
+outside the area reserved for your personal folders. In fact, it's
+used to indicate both the name of the folder, and a special phrase
+telling Alpine how to interpret the name that follows.
+
+<P>
+So, for example, Alpine can be used to access a newsgroup that might be
+available on your computer using:
+<P>
+<CENTER><SAMP>#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The sharp sign indicates the folder name is outside your personal
+folder area. The &quot;news.&quot; phrase after it tells Alpine to
+interpret the remainder of the name as a newsgroup.
+
+<P>
+Similarly, to access a newsgroup on your IMAP server, you might
+use something like:
+<P>
+<CENTER><SAMP>&#123;wharhol.art.example.com}#news.comp.mail.misc</SAMP></CENTER>
+
+<P>
+There are a number of such special phrases (or &quot;namespaces&quot;)
+available. For a more detailed explanation read about
+<A HREF="h_folder_name_namespaces">Namespaces</A>.
+
+<P>
+Note that &quot;INBOX&quot; has special meaning in both local and remote folder
+names. The name INBOX refers to your &quot;principal incoming
+message folder&quot; and will be mapped to the actual file name used for your
+INBOX on any given host. Therefore, a name like
+&quot;&#123;xxx.art.example.com}INBOX&quot; refers to whatever file is used to
+store incoming mail for you on that particular host.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_folder_name_namespaces =======
+<HTML>
+<HEAD>
+<TITLE>FOLDER NAME NAMESPACES EXPLAINED</TITLE>
+</HEAD>
+<BODY>
+<H1>Folder Name Namespaces Explained</H1>
+
+An Alpine folder name looks like
+
+<P>
+<CENTER><SAMP>[{&lt;remote-specification&gt;}][#&lt;namespace&gt;][&lt;namespace-specific-part&gt;]</SAMP></CENTER>
+<P>
+
+The local part of a folder name has an optional &quot;Namespace&quot; which
+tells Alpine how to interpret the rest of the name.
+
+<P>
+By default the folder name is interpreted as defining a section of your personal
+folder area. This area and how you specify it are defined by the
+server, if one is specified, or, typically, the home
+directory, if no server is defined.
+
+<P>
+If a namespace is specified, it begins with the
+sharp, &quot;#&quot;, character followed by the name of the namespace
+and then the namespace's path-element-delimiter. Aside from the
+path's format, namespaces can also imply access rights, content
+policy, audience, location, and, occasionally, access methods.
+
+<P>
+Each server exports its own set (possibly of size one) of
+namespaces. Hence, it's likely communication with your server's
+administrator will be required for specific configurations. Some of
+the more common namespaces, however, include:
+
+<DL>
+<DT>#news.</DT>
+<DD>This specifies a set of folders in the newsgroup namespace. Newsgroup
+names are hierarchically defined with each level delimited by a period.
+<P>
+<CENTER><SAMP>#news.comp.mail.pine</SAMP></CENTER>
+<P>
+</DD>
+<DT>#public/</DT>
+<DD>This specifies a folder area that the server may export to the general
+public.
+</DD>
+<DT>#shared/</DT>
+<DD>This specifies a folder area that the server may export to groups
+of users.
+</DD>
+<DT>#ftp/</DT>
+<DD>This specifies a folder area that is the same as that it may have
+exported via the &quot;File Transfer Protocol&quot;.
+</DD>
+<DT>#mh/</DT>
+<DD>This specifies the personal folder area associated with folders
+and directories that were created using the MH message handling system.
+</DD>
+<DT>#move/</DT>
+<DD>This namespace is interpreted locally by Alpine. It has an unusual interpretation and format.
+<P>
+<CENTER><SAMP>#move&lt;DELIM&gt;&lt;MailDropFolder&gt;&lt;DELIM&gt;&lt;DestinationFolder&gt;</SAMP></CENTER>
+<P>
+The #move namespace is followed by two folder names separated by a delimiter
+character.
+The delimiter character may be any character that does not appear in
+the MailDropFolder name.
+The meaning of #move is that mail will be copied from the MailDropFolder to
+the DestinationFolder and then deleted (if possible) from the MailDropFolder.
+Periodic checks at frequency
+<A HREF="h_config_mailcheck"><!--#echo var="VAR_mail-check-interval"--></A>, but with a minimum
+time between checks set by
+<A HREF="h_config_maildropcheck"><!--#echo var="VAR_maildrop-check-minimum"--></A>,
+are made for new mail arriving in the MailDropFolder.
+An example that copies mail from a POP inbox to a local folder follows
+<P>
+<CENTER><SAMP>#move+{popserver.example.com/pop3/ssl}inbox+local folder</SAMP></CENTER>
+<P>
+To you it appears that mail is being delivered to the local folder when it
+is copied from the MailDropFolder, and you read mail from the local folder.
+<P>
+Note that if the DestinationFolder does not exist then the messages are not
+copied from the MailDropFolder.
+A #move folder may only be used as an
+<A HREF="h_config_enable_incoming">&quot;Incoming folder&quot;</A> or
+an Inbox.
+When you are in the FOLDER LIST of Incoming Message Folders (after turning
+on the
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="FEAT_enable-incoming-folders"-->&quot;</A>
+option)
+the Add command has a subcommand &quot;Use Mail Drop&quot;
+which may be helpful for defining the folder in your Alpine configuration.
+The same is true when you edit the
+<A HREF="h_config_inbox_path"><!--#echo var="VAR_inbox-path"--></A>
+option in Setup/Config.
+Each of these configuration methods will also create the DestinationFolder
+if it doesn't already exist.
+If you are having problems, make sure the DestinationFolder exists.
+You may find some more useful information about Mail Drops at
+<A HREF="h_maildrop">What is a Mail Drop?</A>.
+</DD>
+</DL>
+<P>
+
+In addition, the server may support access to other user's folders,
+provided you have suitable permissions. Common methods use a prefix
+of either &quot;~<VAR>user</VAR>/&quot;, or &quot;/<VAR>user</VAR>/&quot; to
+indicate the root of the other user's folder area.
+
+<P>
+No, nothing's simple.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_whatis_vcard ========================
+<HTML>
+<HEAD>
+<TITLE>VCARD EXPLAINED</TITLE>
+</HEAD>
+<BODY>
+<H1>What is the vCard format?</H1>
+A &quot;vCard&quot; is a sort of electronic business card, for exchanging
+information about and among people and organizations electronically.
+More information about vCard can be found (as of May 1998) on the WWW site
+of the Internet Mail Consortium at the URL:
+<P>
+<CENTER><A HREF="http://www.imc.org/pdi/">http://www.imc.org/pdi/</A></CENTER>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_open =====
+<HTML>
+<HEAD>
+<TITLE>Explanation of Folder Selection</TITLE>
+</HEAD>
+<BODY>
+<BR>
+<BR>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to open.
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_subscribe =====
+<HTML>
+<HEAD>
+<TITLE>Newsgroup Subcribe Screen explained</TITLE>
+</HEAD>
+<BODY>
+<H1>FOLDER SUBSCRIBE HELP</H1>
+
+This screen is designed to help you subscribe to newsgroups you are
+not currently subscribed to. The screen display is a list of all
+available newsgroups (or possibly a partial list if you specified a
+partial name when entering the screen). Groups you have already
+subscribed to have the letters &quot;SUB&quot; next to them. You may
+select a single new group to subscribe to by moving the cursor to that
+group and pressing &quot;S&quot; or carriage return. Alternatively,
+you may change into ListMode with the &quot;ListMode&quot; command.
+The display will change slightly so that each group has a checkbox in
+front of it. Use the cursor and the Set/Unset command to place an
+&quot;X&quot; in front of each newsgroup you wish to subscribe to.
+<P>
+
+When you are finished marking groups, the &quot;Subscribe&quot;
+command will subscribe you to those groups you have marked. Note, you
+may not unsubscribe to groups with this command. Instead of the
+&quot;A&quot; &quot;Subscribe&quot; command, use the &quot;D&quot;
+UnSbscrbe command.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Newsgroups General Alpine Commands
+--------------------------------- ---------------------
+F5 Move to previous group F1 Show this help text
+F6 Move to next group
+F7 Show previous screen of groups
+F8 Show next screen of groups
+F12 WhereIs (search group names)
+F9 Use ListMode
+
+Group Selection Commands
+-------------------------
+F3 Exit the News Subscribe menu (without selecting any groups)
+F4 Subscribe to the currently highlighted newsgroup
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Newsgroups General Alpine Commands
+--------------------------------- ---------------------
+ P Move to previous group ? Show this help text
+ N Move to next group
+ - Show previous screen of groups
+Spc (space bar) Show next screen
+ W WhereIs (search group names)
+ L Use ListMode
+
+Group Selection Commands
+-------------------------
+ E Exit the News Subscribe menu (without selecting any groups)
+ S Subscribe to the currently highlighted newsgroup
+</PRE>
+<!--chtml endif-->
+<P>
+When in ListMode, there is an additional command for marking groups to
+subscribe to:
+<P>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+ListMode Commands
+-------------------------
+F9 Set or unset the highlighted group
+</PRE>
+<!--chtml else-->
+<PRE>
+ListMode Commands
+-------------------------
+X Set or unset the highlighted group
+</PRE>
+<!--chtml endif-->
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_postnews =====
+<HTML>
+<HEAD>
+<TITLE>Newsgroup selecting for Posting explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey
+the available newsgroups and select one to post news to.
+<P>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Newsgroups General Alpine Commands
+--------------------------------- ---------------------
+F5 Move to previous group F1 Show this help text
+F6 Move to next group
+F7 Show previous screen of groups
+F8 Show next screen of groups
+F12 WhereIs (search group names)
+
+Group Selection Commands
+-------------------------
+F3 Exit the Selection menu (without selecting a group)
+F4 Select the currently highlighted newsgroup
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Newsgroups General Alpine Commands
+--------------------------------- ---------------------
+ P Move to previous group ? Show this help text
+ N Move to next group
+ - Show previous screen of groups
+Spc (space bar) Show next screen of groups
+ W WhereIs (search group names)
+
+Group Selection Commands
+-------------------------
+ E Exit the Selection menu (without selecting a group)
+ S Select the currently highlighted newsgroup
+</PRE>
+<!--chtml endif-->
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_save =====
+<HTML>
+<HEAD>
+<TITLE>Folder Select for Save Explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to use for saving the current message.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+F11 AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen of folders
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+ A AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_fcc =====
+<HTML>
+<HEAD>
+<TITLE>Folder Select for Fcc Explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to use as the file carbon copy (fcc) for the
+current message.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+F11 AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen of folders
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+ A AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_pattern_roles =====
+<HTML>
+<HEAD>
+<TITLE>Folder Select for Current Folder Explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to use as the specific Current Folder
+in a Pattern.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+F11 AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen of folders
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+ A AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_stayopen_folders =====
+<HTML>
+<HEAD>
+<TITLE>Folder Select Explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to use as a Stay-Open folder.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+F11 AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen of folders
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+ A AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_folder_action_roles =====
+<HTML>
+<HEAD>
+<TITLE>Folder Select Explained</TITLE>
+</HEAD>
+<BODY>
+This screen is designed to allow you to quickly and easily survey your
+folders and select one to use as the folder into which messages
+matching this filter will be moved.
+<P>
+
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+F5 Move to previous folder F1 Show this help text
+F6 Move to next folder
+F7 Show previous screen of folders
+F8 Show next screen of folders
+F12 WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+F3 Exit the Folder Select menu (without selecting a folder)
+F4 Select the currently highlighted folder
+F11 AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Folders General Alpine Commands
+------------------------------ ---------------------
+ P Move to previous folder ? Show this help text
+ N Move to next folder
+ - Show previous screen of folders
+Spc (space bar) Show next screen of folders
+ W WhereIs (search folder names)
+
+Folder Selection Commands
+-------------------------
+ E Exit the Folder Select menu (without selecting a folder)
+ S Select the currently highlighted folder
+ A AddNew folder (just like Select, but you type in a new folder name)
+</PRE>
+<!--chtml endif-->
+<P>
+FOR MORE INFORMATION: See the section on
+<A HREF="h_valid_folder_names">Valid Folder Names</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_config =====
+<HTML>
+<HEAD>
+<TITLE>SETUP ADDRESS BOOKS SCREEN</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP ADDRESS BOOKS SCREEN</H1>
+<H2>SETUP ADDRESS BOOKS COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands
+-------------------------------
+F1 Show Help Text
+F3 Back to MAIN Alpine menu
+F4 Change configuration for address book
+F5 Move to previous address book
+F6 Move to next address book
+F7 Previous page of address books
+F8 Next page of address books
+F9 Add new address book
+F10 Delete existing address book
+F11 Shuffle the order of address books
+F12 Whereis (search address book titles)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Address Book ? Display this help text
+ N Next Address Book E Back to MAIN Alpine menu
+ - Previous page
+Spc (space bar) Next page
+ W WhereIs (search for word in address book titles)
+
+Setup Address Books Commands
+------------------------------------------------
+ A Add new address book $ Shuffle the order of address books
+ D Delete existing address book C Change configuration for address book
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Address Books Screen</H2>
+
+This screen lets you add, delete, modify, or change the order of your
+address books. The &quot;Add Abook&quot; command brings up a blank form to
+fill in. If you are adding a remote address book on an IMAP server
+you should fill in the name of the IMAP server. Otherwise, leave
+that field blank. (Note that remote IMAP address books are an Alpine
+concept and are unlikely to interoperate with other mail clients.)
+For a remote address book, fill in the name of the remote folder
+in the Folder field. This should be a folder that is used only for
+this one purpose, not a general purpose folder you expect to store
+messages in.
+<P>If you are adding a local address book, fill in the
+Folder Name field with a local file name (e.g., .addressbook).
+<P>
+<B>Please note:</B> Remote address books stored on an IMAP server are
+of an entirely different format (namely, a special-purpose
+&quot;mail folder&quot;) than that of the local addressbook familiar
+to Alpine users. Therefore,
+you cannot use &quot;add a remote address book&quot; to make an existing
+Alpine .addressbook file you may have on a remote IMAP server accessible to
+Alpine running on a different host.
+<P>
+
+The &quot;Del Abook&quot; command allows you to remove an address book
+from your configuration. It will also ask you if you wish to remove
+the data for that address book, which would erase all traces of the
+address book if you answer Yes.
+<P>
+
+The &quot;Change&quot; command is similar to the &quot;Add Abook&quot; command.
+The difference is that instead of adding a new address book to your
+configuration, you are changing the configuration of an existing entry.
+For example, you might want to correct a typing error or change a
+nickname. The &quot;Change&quot; command is not a move command. If you
+change the folder name or server name the data will not be moved for you.
+<P>
+
+The &quot;Shuffle&quot; command is used for two purposes. If you shuffle
+an address book toward another address book in the same group then
+the order of those two address books will be swapped. If you shuffle
+the last Personal address book down towards the Global address book
+section, it will become a Global address book. If you shuffle
+the first Global address book up it will become a Personal address
+book. The main difference between Personal and Global address
+books is that Global address books are forced read-only.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_top =====
+<HTML>
+<HEAD>
+<TITLE>ADDRESS BOOK LIST COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>ADDRESS BOOK LIST COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Exit to MAIN MENU F3 Quit Alpine
+F4 View/Edit selected address book
+F5 Move to previous address book F5 FOLDER LIST screen
+F6 Move to next address book F6 Specify a folder to go to
+F7 Previous page F7 MESSAGE INDEX screen
+F8 Next page F9 Print list of address books
+F12 Whereis (search for word)
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigation General Alpine Commands
+----------------------- ---------------------
+ P Previous Entry ? Display this help text
+ N Next Entry O Show all other available commands
+ - Previous page &lt; Back to MAIN Alpine menu
+Spc (space bar) Next page Q Quit Alpine
+ W WhereIs (search for word) L FOLDER LIST screen
+ G Specify a folder to go to
+Address Book Commands I MESSAGE INDEX screen
+------------------------------------------------
+ &gt; View/Edit selected address book
+ or
+ &gt; Search on selected directory server
+
+ % Print list of address books and directory servers
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Address Book List Screen</H2>
+
+From this screen you may choose which address book you wish to view
+or edit. For more information on address books, view one of your
+address books (with
+<!--chtml if pinemode="function_key"-->
+F4
+<!--chtml else-->
+&quot;&gt;&quot;
+<!--chtml endif-->)
+and see the Help Text there.<P>
+
+You may also choose a directory server on which to search for entries.
+You do that by highlighting the directory server line and using
+<!--chtml if pinemode="function_key"-->
+F4
+<!--chtml else-->
+&quot;&gt;&quot;
+<!--chtml endif-->.<P>
+
+If you wish to define new address books or directory servers go to the Main
+menu and choose Setup. You may then either choose to setup AddressBooks or
+Directory (among other things). It's possible that the Directory option
+will not be there if the Alpine you are using does not contain LDAP directory
+lookup functionality.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_opened =====
+<HTML>
+<HEAD>
+<TITLE>THE ALPINE ADDRESS BOOK</TITLE>
+</HEAD>
+<BODY>
+<H1>THE ALPINE ADDRESS BOOK</H1>
+<H2>ADDRESS BOOK COMMANDS</H2>
+<PRE>
+<!--chtml if pinemode="function_key"-->
+Available Commands -- Group 1 Available Commands -- Group 2
+------------------------------- ------------------------------
+F1 Show Help Text F1 Show Help Text
+F2 See commands in next group F2 See commands in next group
+F3 Exit this screen F3 Quit Alpine
+F4 View/Edit selected entry F4 Go to MAIN MENU screen
+F5 Move to previous entry F5 FOLDER LIST screen
+F6 Move to next entry F6 Specify a folder to go to
+F7 Previous page of address book F7 MESSAGE INDEX screen
+F8 Next page of address book F8 Compose to entry using roles
+F9 Add new entry to address book F9 Print address book
+F10 Delete selected entry F10 TakeAddr to another addrbook
+F11 Compose to selected entry F11 Save or Export addrbook selections
+F12 Whereis (search address book) F12 Forward entry by mail
+
+Available Commands -- Group 3
+------------------------------
+F3 Select F6 Zoom (or unZoom)
+F5 Select Current F7 Apply Command to Selection
+<!--chtml else-->
+Address Book Navigation General Alpine Commands
+----------------------- ---------------------
+ P Prev Address ? Display this help text
+ N Next Address O Show all other available commands
+ - Previous page of address book M Back to MAIN MENU
+Spc (space bar) Next page Q Quit Alpine
+ W WhereIs (search for word C Compose message to selected addr
+ or name in address book) # Compose to addr using roles
+ &lt; To List of Address Books if L FOLDER LIST screen
+ more than one, else to MAIN G Specify a folder to go to
+ I MESSAGE INDEX screen
+
+Address Book Commands
+----------------------------------------------------
+ &gt; View/Update selected entry D Delete selected entries
+ % Print address book S Save or Export address book selections
+ F Forward entries by mail @ Add new entry to address book
+
+ ; Select command Z Toggle Zoom Mode
+ : Select highlighted entry A Apply command to selected entries
+
+<!--chtml endif-->
+</PRE>
+Note: The presence or absence of the final four commands above is
+controlled by the option
+<A HREF="h_config_enable_agg_ops">&quot;<!--#echo var="FEAT_enable-aggregate-command-set"-->&quot;</A>.
+<P>
+
+<H2>Description of the Address Book Screen</H2>
+
+This screen lets you edit and manage entries in your address book. It
+also acts as a short-cut for composing messages to people in the address
+book. When, from this screen, you press <!--chtml if pinemode="function_key"-->F11<!--chtml else-->&quot;C&quot;<!--chtml endif--> for ComposeTo, the
+message starts &quot;pre-addressed&quot; to whatever address book entry is
+currently selected. If you use the <!--chtml if pinemode="function_key"-->F8<!--chtml else-->&quot;#&quot;<!--chtml endif--> for Role, you may first select a
+role to use in your composition.
+<P>
+Alpine's address book helps you keep a list of addresses you send email to so
+you do not have to remember addresses that are often complex. Each entry
+in the address book has five fields, all of them optional. The three
+elements that are usually visible on the ADDRESS BOOK display, are: <DL>
+
+ <P><DT>NICKNAME: <DD>A short easy-to-remember label to identify the entry.
+ This is what you type in as you are addressing the message in the
+ composer. If there is a matching entry in your address book(s),
+ Alpine will extract the corresponding FullName and Address fields to
+ generate the actual address for your message.
+
+ <P><DT>FULLNAME: <DD>A longer field where you can put the full name
+of the
+ person or organization. Usually the full names are put in last
+ name first so they sort nicely in alphabetical order. Whatever
+ you put as the name here will appear on the message when it is
+ finally delivered. Examples:<PRE>
+ Garcia Marquez, Gabriel
+ Henscheid, Eckhard
+ Alpine-Info mailing list
+ Library materials renewal requests
+ Kim An-guk
+ &quot;George III, King of Great Britain, 1738-1820&quot;
+</PRE>
+(In the second-to-last example, no comma is used in the name so that
+the family name appears first in the address book and when the entry is
+used in the composer.
+In the last example, retaining the commas is intended;
+double-quotation marks surround the name to
+prevent the transposition of its parts when the entry is used in
+the composer.)
+
+ <P><DT>ADDRESS: <DD>This is the actual email address itself. This must be
+ a valid Internet address that conforms to the Internet message
+ header standard, RFC-822. (See also <A HREF="h_address_format">Explanation of Address formats</A>.)</DL>
+
+The two fields that aren't usually visible are:<DL>
+
+ <P><DT>FCC: <DD>The name of the folder you would like a copy of any outgoing
+ message to this address to be saved in. If this field is set, and
+ this address is the first one in the message's To: header, then
+ Alpine will use this folder name for the FCC in lieu of the normal
+ FCC folder name.
+
+ <P><DT>COMMENTS: <DD>This field contains arbitrary text for your convenience.
+ </DL>
+<P>
+Due to screen width limitations, these last two fields do not show up in
+the normal ADDRESS BOOK display. You may select the
+&quot;View/Update&quot; command to
+view or modify them. You may use the configuration variable
+<A HREF="h_config_abook_formats">&quot;<!--#echo var="VAR_addressbook-formats"-->&quot;</A>
+to add these fields to your ADDRESS BOOK
+display, or to modify the format of the display.
+
+<H2>Sorting the Address book</H2>
+
+By default, address book entries are sorted alphabetically on the full
+name with distribution lists sorted to the end. Sorting can be changed by
+resetting the address book sort rule in the Alpine SETUP CONFIGURATION screen
+--assuming you have &quot;write&quot; permission for the address book file.
+<P>
+Unlike the sorting of folders (which only changes presentation), sorting an
+address book actually changes the file as it is kept on the computer. For
+this reason you won't be able to sort a shared or system-wide address
+book.
+
+<H2>Adding New Entries</H2>
+
+The easiest way to add new entries to your address book is to use the
+&quot;TakeAddr&quot; command when viewing a message.
+This command allows you to take addresses from the header and body of the
+message and put them into your address book, without having to type
+them in.
+<P>
+
+To manually add a new entry from within the address book screen, use the AddNew
+(<!--chtml if pinemode="function_key"-->F9<!--chtml else-->&quot;@&quot;<!--chtml endif-->) command.
+Use this command both for adding a simple alias and for adding a
+distribution list.
+
+<H2>Distribution Lists</H2>
+
+Address book entries can be simple cases of aliases (a single nickname is linked
+to a single email address) or distribution lists (a single nickname
+pointing at more than one email address). Each distribution list has a
+nickname, a full name and a list of addresses. The addresses may be
+actual addresses or they may be other nicknames in your address book.
+They may even refer to other distribution lists.
+There's really no difference between a simple alias and a distribution list,
+other than the number of addresses.
+Therefore, you can turn a simple alias with one address into a distribution
+list simply by adding more addresses.
+To add entries to an existing list or alias
+use the View/Update (<!--chtml if pinemode="function_key"-->F4<!--chtml else-->&quot;&gt;&quot;<!--chtml endif-->) command. Delete (<!--chtml if pinemode="function_key"-->F10<!--chtml else-->&quot;D&quot;<!--chtml endif-->) will delete
+a single address from the list if the cursor is placed on the address;
+it will delete the entire distribution list if the cursor is on the
+nickname/fullname line. View/Update may also be used to delete addresses
+from a list.
+<P>
+Address field entries in distribution lists may take any one of three
+forms: a nickname existing in any of the defined address books, a normal
+address of the form &quot;jsmith@art.example.com&quot;, or a complete
+fullname/address combination, e.g. &quot;John Smith
+&lt;jsmith@art.example.com&gt;&quot;.
+<P>
+Distribution lists in Alpine address books can only be used by the person or
+people who have access to that address book. They are not usually used to
+implement discussion groups, but can be used to facilitate small
+discussion groups if all the participants have access to the same shared
+address book.
+
+<H2>FCC and Comments</H2>
+
+As mentioned above, each entry in the address book also has two other optional
+fields, Fcc and Comments. The command to look at or change either of these
+is the same View/Update command used for all of the fields (<!--chtml if pinemode="function_key"-->F4<!--chtml else-->&quot;&gt;&quot;<!--chtml endif-->). The
+Comments field is just for your own use. The Fcc field overrides the
+default Fcc if this address is the first one on the To line. The WhereIs
+command may be used to search for particular strings in the address book,
+including fields that are not visible (like Comment and Fcc by default).
+
+<H2>Aggregate Operations</H2>
+
+If the feature
+<A HREF="h_config_enable_agg_ops">&quot;<!--#echo var="FEAT_enable-aggregate-command-set"-->&quot;</A>
+is turned on (the default), then the four commands &quot;Select&quot;,
+&quot;Select Current&quot;, &quot;Zoom&quot;, and &quot;Apply&quot;
+are available. The two selection commands allow you to mark a set of
+address book entries as being selected. If you have more than one address
+book, the selections may be in more than one of those address books.
+The &quot;Zoom&quot; command will toggle between displaying only the selected
+entries and all of the entries. The &quot;Apply&quot; command allows you to
+apply one of the regular address book commands to all of the selected
+entries. Usually the address book commands apply to only the entry
+highlighted by the cursor. The &quot;Apply&quot; command works with the
+commands &quot;ComposeTo&quot;, &quot;Delete&quot;, &quot;Print&quot;,
+&quot;Save&quot;, &quot;Forward&quot;, and &quot;Role&quot;.
+
+<H2>Exporting and Forwarding Address book entries</H2>
+
+Under the save option, when you use the Export (<!--chtml if pinemode="function_key"-->F11<!--chtml else-->&quot;X&quot;<!--chtml endif-->) command, the currently highlighted
+address book entry is placed in a plain text file in your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. If you have some entries selected and use the
+Apply (<!--chtml if pinemode="function_key"-->F7<!--chtml else-->&quot;A&quot;<!--chtml endif-->) Export command, all of the selected addresses will be
+placed in the text file.
+<P>
+When you use the Forward (<!--chtml if pinemode="function_key"-->F12<!--chtml else-->&quot;F&quot;<!--chtml endif-->) command, the currently highlighted
+address book entry is placed in a special attachment and you are put into
+the composer. You can fill in some comments in the body of the message,
+if you'd like, and send it to somebody else who uses Alpine. The recipient
+may use the TakeAddr command on that message to insert the address book
+entry you sent in their own address book. If you have some entries
+selected and use the Apply Forward command all of the selected entries
+will be forwarded in a single message. You may
+use Apply (<!--chtml if pinemode="function_key"-->F7<!--chtml else-->&quot;A&quot;<!--chtml endif-->) Forward to forward a copy of an entire address book.
+The recipient must be using Alpine in order to receive this correctly.
+One way for the recipient to handle this might be to create an empty
+address book and then &quot;Take&quot; your forwarded address book entries into
+that empty address book.
+
+<H2>Multiple and/or Site-Wide Address books</H2>
+
+You may have more than one personal address book. In addition, there may
+be one or more global address books. This capability allows you to have
+multiple personal address books (some of which may be shared) and it also
+allows system administrators to implement site-wide address books that
+contain entries for users on multiple machines within the organization.
+<P><DL>
+<DT>Searching
+ <DD> If you enter a nickname when composing a message, your
+ personal address books will be searched through in order, and then the
+ global address book(s) searched. If more than one address book has an entry
+ for the nickname, Alpine uses the first one that it finds, so an entry in
+ your personal address book would override a global address book entry. If
+ after searching all the address books there is still no match, (Unix) Alpine
+ then searches the local host password file on the assumption that you have
+ entered a local user name rather than an address book nickname.
+ You may change the search order of your address books with the <!--chtml if pinemode="function_key"-->F3<!--chtml else-->$<!--chtml endif--> Shuffle
+ command, but global address books are always searched after personal
+ address books.
+
+ <P><DT>Tab completion
+ <DD> If the
+ <A HREF="h_config_enable_tab_complete"><!--#echo var="FEAT_enable-tab-completion"--></A>
+ feature is turned on (the default) then the Tab key may be used
+ in the composer to complete partially typed nicknames in the To
+ or Cc lines. You type the first few letters of a nickname and then
+ press the Tab key. It there is only one nickname that matches it will
+ be filled in by Alpine. If there is more than one the unambiguous part
+ of the nicknames will be filled in. For example, if your address book or
+ books contains only the two entries &quot;barley&quot; and &quot;barbecue&quot;
+ beginning with the letters &quot;ba&quot;, then if you type &quot;ba&quot;
+ followed by a Tab character Alpine will fill in &quot;bar&quot; and stop.
+ If you then type a second Tab character you will be presented with a list
+ of matching nicknames to select from. Alternatively, you could type another
+ &quot;b&quot; resulting in &quot;barb&quot; and then a Tab would fill
+ in the entire &quot;barbecue&quot; entry.
+
+ <P><DT>Defining
+ <DD> You define multiple personal address books in the
+ <A HREF="h_abook_config">SETUP AddressBooks</A> screen, which you may reach
+ from the MAIN MENU.
+ You may add as many as you like. Global address books are usually
+ site-wide address books defined by the System administrator, but
+ you may define global address books of your own just like you define
+ personal address books.
+
+ <P><DT>Creating and updating
+ <DD> Personal address books are normally created empty
+ and populated by explicit additions from within Alpine, e.g. via the
+ TakeAddr command. Unlike personal address books, global address books may
+ not be modified/updated from within Alpine; that is, they are Read-Only.
+ Thus, global address books are created, populated and updated outside of
+ Alpine. They might be hand-edited, generated by a program from another
+ database, or by copying an existing address book. They might also be
+ some other user's personal address book, and so be modified normally by
+ that user but accessed Read-Only by you. See the Alpine Technical
+ Notes document (included in the Alpine distribution) for more information on
+ this.
+
+ <P><DT>Accessing
+ <DD>There are two different types of address books in Alpine.
+ A local address book is stored in a regular file and the normal file
+ access permissions apply. A remote address book is stored on an IMAP
+ server in a special folder that contains only messages pertaining to
+ that address book. The last message in the remote folder contains a
+ copy of the address book data, and that data is copied to a local cache
+ file in your home directory. From there it is accessed just like a local
+ address book. The name of the cache file is kept track of in a special
+ file called the
+ <A HREF="h_config_abook_metafile"><!--#echo var="VAR_remote-abook-metafile"--></A>,
+ the name of which is stored in
+ your Alpine configuration file the first time you use a remote address book.
+ Just as local Alpine address books use a format that only Alpine understands,
+ remote Alpine address books do the same and other mail reading programs
+ are unlikely to be able to understand them.<P>
+ While global address books are explicitly intended to be shared, there is
+ nothing to prevent you from sharing a personal address book with other
+ Alpine users. This might be useful in the case of a small workgroup.
+ However, it is recommended that updates to shared personal address books
+ be done when other Alpine users are not accessing the address book. Alpine
+ does not do any file-locking to manage concurrent updates to the
+ addressbook, but it does check to see if the file has been modified before
+ making any changes. Consequently, inadvertent concurrent updates will
+ only cause other Alpine users to have to restart their address book
+ operation, which will cause Alpine to reopen the updated file.
+
+ <P><DT>Converting to Remote
+ <DD>The easiest way to convert an existing local
+ address book into a remote address book is to create an empty new remote
+ personal address book by typing &quot;A&quot; to execute the
+ &quot;Add Pers Abook&quot; command in the SETUP Addressbook screen.
+ Make sure you add a <EM>personal</EM> address book, not a <EM>global</EM>
+ address book.
+ After you have added the empty
+ remote address book, go into the screen for the address book you wish
+ to copy and &quot;Select&quot; &quot;All&quot;.
+ This selects every entry in that
+ address book. Then type the command &quot;Apply Save&quot;.
+ You will be asked for the address book to save to. You may use ^P and ^N
+ to get to the new empty address book, then hit RETURN and the addresses
+ will be copied.
+ At this point you'll probably want to unselect all the entries in the local
+ address book before proceeding. You do that with
+ &quot;Select&quot; &quot;unselect All&quot;.
+
+</DL>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_addr =====
+<HTML>
+<HEAD>
+<TITLE>Addressbook Selection Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT ADDRESS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+F5 Move to previous entry F1 Show this help text
+F6 Move to next entry
+F7 Show previous screen of address book
+F8 Show next screen of address book
+F12 WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+F3 Exit the Address Select screen (without selecting an address)
+F4 Select the currently highlighted entry
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous entry ? Show this help text
+ N Move to next entry
+ - Show previous screen of address book
+Spc (space bar) Show next screen of address book
+ W WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+ E Exit the Address Select screen (without selecting an address)
+ S Select the currently highlighted entry
+</PRE>
+<!--chtml endif-->
+<P>
+
+This screen is designed to let you easily scan your address book(s) in
+order to select an entry for the message you are composing. You cannot
+edit your address book in any way at this time, for address book
+maintenance, select the address book command when not composing a message.
+<P>
+
+If you are composing a message and know the nickname of the person/list you
+want, you can bypass this screen by simply typing in the nickname on the
+appropriate header line (To:, Cc:, etc.) Exiting this screen without
+selecting an entry does not cancel your message.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_top =====
+<HTML>
+<HEAD>
+<TITLE>Addressbook Selection Navigation Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>NAVIGATING WHILE SELECTING ADDRESSES</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Address Books General Alpine Commands
+------------------------------- ---------------------
+F4 View the highlighted address book
+F5 Move to previous address book F1 Show this help text
+F6 Move to next address book
+F7 Show previous screen of address books
+F8 Show next screen of address books
+F12 WhereIs (search through address books)
+
+Address Selection Commands
+--------------------------
+F3 Exit the Address Select screen (without selecting an address)
+F4 Select the currently selected entries (if using ListMode)
+F9 Change to ListMode
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Address Books General Alpine Commands
+------------------------------- ---------------------
+ &gt; View the highlighted address book
+ P Move to previous address book ? Show this help text
+ N Move to next address book
+ - Show previous screen of address books
+Spc (space bar) Show next screen of address books
+ W WhereIs (search through address books)
+
+Address Selection Commands
+--------------------------
+ E Exit the Address Select screen (without selecting an address)
+ S Select the currently selected entries (if using ListMode)
+ L Change to ListMode
+</PRE>
+<!--chtml endif-->
+<P>
+
+This screen is designed to let you easily scan your address book(s) in
+order to select entries for the message you are composing. You cannot
+edit your address book in any way at this time. For address book
+maintenance, select the address book command when not composing a message.
+<P>
+
+If you are composing a message and know the nickname of the person/list you
+want, you can bypass this screen by simply typing in the nickname on the
+appropriate header line (To:, Cc:, etc.) Exiting this screen without
+selecting an entry does not cancel your message.
+<P>
+
+The ListMode command will add a column at the left edge of the screen.
+You mark the entries that you wish to select with the &quot;X&quot; command.
+This allows you to choose more than one entry at a time.
+<P>
+
+An alternative method of composing a message to entries in your
+address book(s) is to first use the &quot;Select&quot; command from
+the address book maintenance screen and then the &quot;Apply&quot;
+&quot;ComposeTo&quot; command to start the composer composing to the
+selected entries.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_listmode =====
+<HTML>
+<HEAD>
+<TITLE>Address Listmode Selection from Composer Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>COMPOSER: SELECT ADDRESSES</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+F5 Move to previous entry F1 Show this help text
+F6 Move to next entry
+F7 Show previous screen of address book
+F8 Show next screen of address book
+F12 WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+F3 Exit the Address Select screen (without selecting an address)
+F4 Select the currently highlighted entry
+F9 Change to ListMode
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous entry ? Show this help text
+ N Move to next entry
+ - Show previous screen of address book
+Spc (space bar) Show next screen of address book
+ W WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+ E Exit the Address Select screen (without selecting an address)
+ S Select the currently highlighted entry
+ L Change to ListMode
+</PRE>
+<!--chtml endif-->
+<P>
+
+This screen is designed to let you easily scan your address book(s) in
+order to select entries for the message you are composing. You cannot
+edit your address book in any way at this time, for address book
+maintenance, select the address book command when not composing a message.
+<P>
+
+If you are composing a message and know the nickname of the person/list you
+want, you can bypass this screen by simply typing in the nickname on the
+appropriate header line (To:, Cc:, etc.) Exiting this screen without
+selecting an entry does not cancel your message.
+<P>
+
+The ListMode command will add a column at the left edge of the screen.
+You mark the entries that you wish to select with the &quot;X&quot; command.
+This allows you to choose more than one entry at a time.
+<P>
+
+An alternative method of composing a message to entries in your
+address book(s) is to first use the &quot;Select&quot; command from
+the address book maintenance screen and then the &quot;Apply&quot;
+&quot;ComposeTo&quot; command to start the composer composing to the
+selected entries.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_checks =====
+<HTML>
+<HEAD>
+<TITLE>Address Selection from Composer Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>COMPOSER: SELECT ADDRESSES</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+F5 Move to previous entry F1 Show this help text
+F6 Move to next entry
+F7 Show previous screen of address book
+F8 Show next screen of address book
+F12 WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+F3 Exit the Address Select screen (without selecting an address)
+F4 Select the currently highlighted entry
+F8 Either Sets or Unsets all entries in this address book
+F9 Set or Unset the highlighted entry
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous entry ? Show this help text
+ N Move to next entry
+ - Show previous screen of address book
+Spc (space bar) Show next screen of address book
+ W WhereIs (search through address book)
+
+Address Selection Commands
+--------------------------
+ E Exit the Address Select screen (without selecting an address)
+ S Select the currently highlighted entry
+ X Set or Unset the highlighted entry
+ A Either Sets or Unsets all entries in this address book
+</PRE>
+<!--chtml endif-->
+<P>
+
+Mark the entries you wish to select with the &quot;X Set/Unset&quot;
+command. Type &quot;S Select&quot; to select all of the entries you
+have marked, just as if you had typed them in by hand.
+<P>
+
+An alternative method of composing a message to entries in your
+address book(s) is to first use the &quot;Select&quot; command from
+the address book maintenance screen and then the &quot;Apply&quot;
+&quot;ComposeTo&quot; command to start the composer composing to the
+selected entries.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_nicks_take =====
+<HTML>
+<HEAD>
+<TITLE>Take Address Nickname Selection Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>TAKEADDR: SELECT NICKNAME</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+F5 Move to previous entry F1 Show this help text
+F6 Move to next entry
+F7 Show previous screen of address book
+F8 Show next screen of address book
+F12 WhereIs (search through address book)
+
+Message Selection Commands
+--------------------------
+F3 Exit the Nickname Select screen (without selecting an address)
+F4 Select the currently highlighted entry
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous entry ? Show this help text
+ N Move to next entry
+ - Show previous screen of address book
+Spc (space bar) Show next screen of address book
+ W WhereIs (search through address book)
+
+Message Selection Commands
+--------------------------
+ E Exit the Nickname Select screen (without selecting an address)
+ S Select the currently highlighted entry
+</PRE>
+<!--chtml endif-->
+<P>
+
+This screen is designed to let you modify or add to an existing
+address book entry. You have already selected the name(s) and
+address(es) through &quot;Take Address&quot;. This screen simply lets
+you scan your address books and select the nickname to be
+changed/augmented. If you want to add a new entry, then you are in
+the wrong place-- Select &quot;Exit&quot; command.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_abook_select_nick =====
+<HTML>
+<HEAD>
+<TITLE>Nickname Selection Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>SELECT NICKNAME</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+F5 Move to previous entry F1 Show this help text
+F6 Move to next entry
+F7 Show previous screen of address book
+F8 Show next screen of address book
+F12 WhereIs (search through address book)
+
+Message Selection Commands
+--------------------------
+F3 Exit the Nickname Select screen (without selecting an address)
+F4 Select the currently highlighted entry
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Messages General Alpine Commands
+------------------------------- ---------------------
+ P Move to previous entry ? Show this help text
+ N Move to next entry
+ - Show previous screen of address book
+Spc (space bar) Show next screen of address book
+ W WhereIs (search through address book)
+
+Message Selection Commands
+--------------------------
+ E Exit the Nickname Select screen (without selecting an address)
+ S Select the currently highlighted entry
+</PRE>
+<!--chtml endif-->
+<P>
+
+This screen is designed to let you look at the nicknames in your address
+books before choosing a new one.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_takeaddr_screen =====
+<HTML>
+<HEAD>
+<TITLE>Take Address Screen Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>TAKE ADDRESS COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Addresses Address Selection Commands
+-------------------------------- --------------------------
+ F5 Move to previous entry F3 Exit without taking address
+ F6 Move to next entry F4 Take current address(es)
+ F7 Show previous page of address list
+ F8 Show next page of address list
+ F2 WhereIs (search list)
+ --------------
+Mode Toggle F9 Set/Unset current address
+----------- F10 Set all
+ F12 Toggle between List and single mode F11 Unset all
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Addresses Address Selection Commands
+-------------------------------- --------------------------
+ P Move to previous entry &lt; Exit without taking address
+ N Move to next entry T Take address
+ - Show previous page of address list
+Spc (space bar) Show next page of address list
+ W WhereIs (search list) List Mode
+ ---------
+Single Mode X Set/Unset current address
+----------- A Set all addresses
+ L Switch to list mode U Unset all addresses
+ S Switch to single mode
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Take Address Screen</H2>
+
+This screen is designed to let you select one or more address/name
+combinations from the current message and put them into your address book.
+The cursor is initially placed on the line with the message author.
+Other lines include the names of people and/or mailing lists who also
+received the message. Other people &quot;involved&quot; in the
+message (e.g. the person named as Reply-To:) are also listed here.
+<P>
+
+The simple case is adding a new, single entry into your address book. To
+do this, simply highlight the correct line and press
+<!--chtml if pinemode="function_key"-->
+F4.
+<!--chtml else-->
+&quot;T&quot;.
+<!--chtml endif-->
+To create a new list or add to an existing list, switch the screen display
+into List Mode by pressing
+<!--chtml if pinemode="function_key"-->
+F12.
+<!--chtml else-->
+&quot;L&quot;.
+<!--chtml endif-->
+In List Mode, you select the
+group of addresses you wish to manipulate by marking them with an
+&quot;X&quot;.
+The Set/Unset
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(&quot;X&quot;)
+<!--chtml endif-->
+command will turn the &quot;X&quot; on for the
+highlighted address if it was off or turn it off if it was previously on.
+The SetAll command will select all of the addresses, and the UnSetAll
+command will turn off all the selections. Once you've gotten the
+selection the way you want it, you may create a new list by pressing
+<!--chtml if pinemode="function_key"-->
+F4.
+<!--chtml else-->
+&quot;T&quot;.
+<!--chtml endif-->
+<P>
+
+In both the simple and list cases, after choosing to take the address,
+you will be asked for the nickname of the entry. Typing in a new name
+creates the new entry/list. Entering an existing nickname will replace
+the entry (simple case) or add to the list (list case). Alternatively,
+you can press Ctrl-T at the nickname prompt and select an existing
+nickname from your address book.
+<P>
+
+You will normally start in Single Mode, unless you used the Apply command
+to startup the TakeAddr screen, in which case you will start in List Mode.
+You may switch between the two modes at any time. If you've already
+selected several addresses in List Mode, those will be remembered when you
+switch to Single Mode and then back to List Mode. The set of addresses
+that are pre-selected when you start in List Mode are the From addresses
+of all of the messages you are operating on. You may, of course, easily
+erase those selections with the UnSetAll command.
+<P>
+
+If you have more than one writable address book, you will be prompted for
+the name of the address book you wish to add the new entry to before
+anything else. You can use ^N and ^P to choose among the defined address
+books, or type in the address book name.
+<P>
+
+FOR MORE INFORMATION on addressing see
+<A HREF="h_address_format">Explanation of Address formats</A>, and the
+<A HREF="h_abook_opened">Address Book Screen's</A>
+help text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_takeexport_screen =====
+<HTML>
+<HEAD>
+<TITLE>Take Export Screen Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>TAKE EXPORT COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Navigating the List of Addresses Address Selection Commands
+-------------------------------- --------------------------
+ F5 Move to previous entry F3 Exit without taking address
+ F6 Move to next entry F4 Take current address(es)
+ F7 Show previous page of address list
+ F8 Show next page of address list
+ F2 WhereIs (search list)
+ --------------
+Mode Toggle F9 Set/Unset current address
+----------- F10 Set all
+ F12 Toggle between List and single mode F11 Unset all
+</PRE>
+<!--chtml else-->
+<PRE>
+Navigating the List of Addresses Address Selection Commands
+-------------------------------- --------------------------
+ P Move to previous entry &lt; Exit without taking address
+ N Move to next entry T Take address
+ - Show previous page of address list
+Spc (space bar) Show next page of address list
+ W WhereIs (search list) List Mode
+ ---------
+Single Mode X Set/Unset current address
+----------- A Set all addresses
+ L Switch to list mode U Unset all addresses
+ S Switch to single mode
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Take Export Screen</H2>
+
+This screen is designed to let you select one or more addresses
+from the current message and put them into a file.
+Only the user@domain_name part of each address is put into the file.
+<P>
+
+To put a single entry into a file simply highlight the correct line and press
+<!--chtml if pinemode="function_key"-->
+F4.
+<!--chtml else-->
+&quot;T&quot;.
+<!--chtml endif-->
+To put more than one entry into a file
+switch the screen display
+into List Mode by pressing
+<!--chtml if pinemode="function_key"-->
+F12.
+<!--chtml else-->
+&quot;L&quot;.
+<!--chtml endif-->
+In List Mode, you select the
+group of addresses you wish to manipulate by marking them with an
+&quot;X&quot;.
+The Set/Unset
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(&quot;X&quot;)
+<!--chtml endif-->
+command will turn the &quot;X&quot; on for the
+highlighted address if it was off or turn it off if it was previously on.
+The SetAll command will select all of the addresses, and the UnSetAll
+command will turn off all the selections. Once you've gotten the
+selection the way you want it, you may put the addresses in a file by typing
+<!--chtml if pinemode="function_key"-->
+F4.
+<!--chtml else-->
+&quot;T&quot;.
+<!--chtml endif-->
+<P>
+
+You will be asked for the name of a file to put the addresses in.
+If the file already exists, you will be asked whether you want to Overwrite
+(replace) the contents of the file or Append to the contents of the file.
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_abook_view ========================
+<HTML>
+<HEAD>
+<TITLE>Address Book View Explained</TITLE>
+</HEAD>
+<BODY>
+This function allows you to view the contents of an address book entry. You
+can only view one entry at a time.
+<P>
+<DL>
+<DT>Help
+<!--chtml if pinemode="function_key"-->
+(F1)
+<!--chtml else-->
+(?)
+<!--chtml endif-->
+</DT>
+<DD>
+Display this help text.
+
+<DT>Abook
+<!--chtml if pinemode="function_key"-->
+(F3)
+<!--chtml else-->
+(&lt;)
+<!--chtml endif-->
+</DT>
+<DD>
+Go back to index of address book entries.
+
+<DT>Update
+<!--chtml if pinemode="function_key"-->
+(F4)
+<!--chtml else-->
+(U)
+<!--chtml endif-->
+</DT>
+<DD>
+Update (modify) this entry.
+
+<DT>ComposeTo
+<!--chtml if pinemode="function_key"-->
+(F5)
+<!--chtml else-->
+(C)
+<!--chtml endif-->
+</DT>
+<DD>Compose a message to the address(es) in this entry.
+
+<DT>Role
+<!--chtml if pinemode="function_key"-->
+(F6)
+<!--chtml else-->
+(#)
+<!--chtml endif-->
+</DT>
+<DD>Compose a message to the address(es) in this entry using roles.
+
+<DT>Prev Page
+<!--chtml if pinemode="function_key"-->
+(F7)
+<!--chtml else-->
+(-)
+<!--chtml endif-->
+</DT>
+<DD>
+Show the previous page of the current entry.
+
+<DT>Next Page
+<!--chtml if pinemode="function_key"-->
+(F8)
+<!--chtml else-->
+(Space)
+<!--chtml endif-->
+</DT>
+<DD>
+Show the next page of the current entry.
+
+<DT>Print
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(%)
+<!--chtml endif-->
+</DT>
+<DD>Print the current entry. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>WhereIs
+<!--chtml if pinemode="function_key"-->
+(F10)
+<!--chtml else-->
+(W)
+<!--chtml endif-->
+</DT>
+<DD>Search the entry for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+
+<DT>Fwd Email
+<!--chtml if pinemode="function_key"-->
+(F11)
+<!--chtml else-->
+(F)
+<!--chtml endif-->
+</DT>
+<DD>Begin composition of a new mail message with the displayed
+text already inserted in the message body.
+
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_ldap_view ========================
+<HTML>
+<HEAD>
+<TITLE>LDAP Response View Explained</TITLE>
+</HEAD>
+<BODY>
+This function allows you to view the contents of a directory entry. You
+can only view one entry at a time.
+<P>
+<DL>
+<DT>Help
+<!--chtml if pinemode="function_key"-->
+(F1)
+<!--chtml else-->
+(?)
+<!--chtml endif-->
+</DT>
+<DD>
+Display this help text.
+
+<DT>Results Index
+<!--chtml if pinemode="function_key"-->
+(F3)
+<!--chtml else-->
+(&lt;)
+<!--chtml endif-->
+</DT>
+<DD>Go back to index of search results.
+
+<DT>ComposeTo
+<!--chtml if pinemode="function_key"-->
+(F5)
+<!--chtml else-->
+(C)
+<!--chtml endif-->
+</DT>
+<DD>Compose a message to the address(es) in this entry.
+
+<DT>Role
+<!--chtml if pinemode="function_key"-->
+(F6)
+<!--chtml else-->
+(#)
+<!--chtml endif-->
+</DT>
+<DD>Compose a message to the address(es) in this entry using roles.
+
+<DT>Prev Page
+<!--chtml if pinemode="function_key"-->
+(F7)
+<!--chtml else-->
+(-)
+<!--chtml endif-->
+</DT>
+<DD>
+Show the previous page of the current entry.
+
+<DT>Next Page
+<!--chtml if pinemode="function_key"-->
+(F8)
+<!--chtml else-->
+(Space)
+<!--chtml endif-->
+</DT>
+<DD>
+Show the next page of the current entry.
+
+<DT>Print
+<!--chtml if pinemode="function_key"-->
+(F9)
+<!--chtml else-->
+(%)
+<!--chtml endif-->
+</DT>
+<DD>Print the current entry on paper. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>WhereIs
+<!--chtml if pinemode="function_key"-->
+(F10)
+<!--chtml else-->
+(W)
+<!--chtml endif-->
+</DT>
+<DD>Search the entry for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+
+<DT>Fwd Email
+<!--chtml if pinemode="function_key"-->
+(F11)
+<!--chtml else-->
+(F)
+<!--chtml endif-->
+</DT>
+<DD>Begin composition of a new mail message with the displayed
+text already inserted in the message body.
+
+<DT>Save
+<!--chtml if pinemode="function_key"-->
+(F12)
+<!--chtml else-->
+(S)
+<!--chtml endif-->
+</DT>
+<DD>Save the displayed entry to one of your address books or export
+it to a file.
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_attachment_screen =====
+<HTML>
+<HEAD>
+<TITLE>Attachment Index Screen Explained</TITLE>
+</HEAD>
+<BODY>
+The &quot;ATTACHMENT INDEX&quot; displays a list of the current
+message's attachments, and allows various operations on them. The
+first attachment is usually the message text, but does not include the
+header portion of the message.
+<P>
+Available commands include:
+<P>
+<DL>
+
+<DT>Help</DT>
+<DD>Show this help text.
+
+<DT>Msg #<I>num</I></DT>
+<DD>Leave this screen without displaying or saving any attachments.
+
+<DT>View</DT>
+<DD>View the currently selected attachment.
+
+<DT>Prev Attach</DT>
+<DD>Move to previous attachment.
+
+<DT>Next Attach</DT>
+<DD>Move to next attachment.
+
+<DT>Prev Page</DT>
+<DD>Previous page of the listed attachments.
+
+<DT>Next Page</DT>
+<DD>Next page of the listed attachments.
+
+<DT>Delete</DT>
+<DD>Mark the currently selected attachment for Deletion.
+This does not modify the current message by deleting the attachment from
+it, but instead the delete flag <EM>only</EM> has an effect when saving
+the message to a folder.
+Attachments marked for deletion are not copied to the destination folder
+along with the rest of the message when it is saved.
+It is ok for the destination folder to be the same as the current folder.
+In addition, the delete mark <EM>only</EM> applies to this Alpine session.
+
+<DT>Undelete</DT>
+<DD>Turn off the Delete flag for the selected attachment.
+
+<DT>Save</DT>
+<DD>Save the selected attachment to a file. If the attachment is of
+type &quot;RFC822/Message&quot;, then the attachment will be saved to
+the specified mail folder.
+
+<DT>Export</DT>
+<DD>If the attachment is of
+type &quot;RFC822/Message&quot;, then &quot;Export&quot; is used to
+copy the message to a file in the same way this command works on
+messages in the MESSAGE INDEX and MESSAGE TEXT screens.
+
+<DT>Pipe</DT>
+<DD>Pipe the attachment contents into a UNIX command (if enabled).
+A description of the Pipe sub-commands is <A HREF="h_pipe_command">here</A>.
+
+<DT>WhereIs</DT>
+<DD>Find a matching string in the attachment list.
+
+<DT>AboutAttch</DT>
+<DD>Examine various aspects of the selected attachment.
+
+<DT>Print</DT>
+<DD>Print the selected attachment.
+
+<DT>Forward</DT>
+<DD>Forward the selected attachment as an attachment.
+</DL>
+
+<P>
+
+All attachments can be saved or piped into a UNIX command, but some may
+not be readily displayed by either Alpine or an external tool. In such
+cases, the reason why the message cannot be displayed is displayed on
+Alpine's message line.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_mail_text_att_view ========================
+<HTML>
+<HEAD>
+<TITLE>Attachment View Screen Explained</TITLE>
+</HEAD>
+<BODY>
+This function allows you to view the contents of a text attachment. You
+can only view one attachment at a time.
+<P>
+Available commands include:
+<P>
+<DL>
+
+<DT>Help</DT>
+<DD>Display this help text
+
+<DT>AttchIndex</DT>
+<DD>Leave viewer and return to the &quot;ATTACHMENT INDEX&quot; screen
+
+<DT>Prev Page</DT>
+<DD>Show the previous page of the current attachment.
+
+<DT>Next Page</DT>
+<DD>Show the next page of the current attachment by pressing the space bar.
+
+<DT>Delete</DT>
+<DD>Mark the viewed attachment for Deletion. The delete
+flag <EM>only</EM> has affect when saving the message to a folder.
+Attachments marked for deletion are exluded from the messsage when
+it is saved. In addition, the delete mark <EM>only</EM> applies to
+this Alpine session.
+
+<DT>Undelete</DT>
+<DD>Turn off the Delete flag for the selected attachment.
+
+<DT>Save</DT>
+<DD>Copy the current attachment to a file. If you just enter
+a filename, the attachment will be saved with that name in
+your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. You may enter the full
+path and filename to save it in another directory instead.
+
+<DT>Export</DT>
+<DD>If the attachment is of
+type &quot;RFC822/Message&quot;, then &quot;Export&quot; is used to
+copy the message to a file in the same way this command works on
+messages in the MESSAGE INDEX and MESSAGE TEXT screens.
+(If you have any <A HREF="h_config_display_filters"><!--#echo var="VAR_display-filters"--></A>
+defined, they may affect the contents of the exported file.)
+
+<DT>Pipe</DT>
+<DD>Pipe the attachment contents into a UNIX command (if enabled)
+
+<DT>WhereIs</DT>
+<DD>Search the attachment for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+
+<DT>Print</DT>
+<DD>Print the current attachment on paper. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>Forward</DT>
+<DD>Forward the selected attachment as an attachment.
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_journal ==============
+<HTML>
+<HEAD>
+<TITLE>Recent Message Journal Explained</TITLE>
+</HEAD>
+<BODY>
+
+The following commands are available on this screen:
+<P>
+<DL>
+<DT>Help</DT>
+<DD>Show this help text
+
+<DT>Exit</DT>
+<DD>Exit Viewer, and go back to mail processing
+
+<DT>Prev Page</DT>
+<DD>Show the previous page text
+
+<DT>Next Page</DT>
+<DD>Show the next page of text by pressing the space bar
+
+<DT>Print</DT>
+<DD>Print the displayed text on paper. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>Fwd Email</DT>
+<DD>Begin composition of a new mail message with the displayed
+text already inserted in the message body.
+
+<DT>Save</DT>
+<DD>Copy the displayed text to a file. If you just enter
+a filename, the text will be saved with that name in
+your
+home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. You may enter the full
+path and filename to save it in another directory instead.
+
+<DT>WhereIs</DT>
+<DD>Search the text for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_debugjournal ==============
+<HTML>
+<HEAD>
+<TITLE>Debug Journal Explained</TITLE>
+</HEAD>
+<BODY>
+
+The following commands are available on this screen:
+<P>
+<DL>
+<DT>Help</DT>
+<DD>Show this help text
+
+<DT>Exit</DT>
+<DD>Exit Viewer, and go back to mail processing
+
+<DT>Timestamps</DT>
+<DD>Turn on or off timestamps.
+
+<DT>DebugView</DT>
+<DD>Set the level of debugging you want to see. The level may be any number
+in the range 0-9. Higher numbers show more debugging detail. Note that the
+debugging information has already been captured. This setting just causes the
+debugging information that you see to be filtered. If you set this to
+the number &quot;5&quot; then you will be shown all of the debugging information
+at levels 5 and below.
+It's actually a bit more complicated than that. A fixed amount of memory
+is used to store the debug information.
+Since the amount of memory used is limited the debugging information
+has to be trimmed back when it gets too large.
+
+<DT>Prev Page</DT>
+<DD>Show the previous page text
+
+<DT>Next Page</DT>
+<DD>Show the next page of text by pressing the space bar
+
+<DT>Print</DT>
+<DD>Print the displayed text on paper. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>Fwd Email</DT>
+<DD>Begin composition of a new mail message with the displayed
+text already inserted in the message body.
+
+<DT>Save</DT>
+<DD>Copy the displayed text to a file. If you just enter
+a filename, the text will be saved with that name in
+your
+home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. You may enter the full
+path and filename to save it in another directory instead.
+
+<DT>WhereIs</DT>
+<DD>Search the text for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+============= h_simple_text_view ==============
+<HTML>
+<HEAD>
+<TITLE>Simple Text View Screen Explained</TITLE>
+</HEAD>
+<BODY>
+
+The following commands are available on this screen:
+<P>
+<DL>
+<DT>Help</DT>
+<DD>Show this help text
+
+<DT>Exit</DT>
+<DD>Exit Viewer, and go back to mail processing
+
+<DT>Prev Page</DT>
+<DD>Show the previous page text
+
+<DT>Next Page</DT>
+<DD>Show the next page of text by pressing the space bar
+
+<DT>Print</DT>
+<DD>Print the displayed text on paper. You can select the
+printer or the print command via the &quot;Setup&quot; command
+on the MAIN MENU.
+
+<DT>Fwd Email</DT>
+<DD>Begin composition of a new mail message with the displayed
+text already inserted in the message body.
+
+<DT>Save</DT>
+<DD>Copy the displayed text to a file. If you just enter
+a filename, the attachment will be saved with that name in
+your
+home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+or current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->, depending on the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+configuration setting. You may enter the full
+path and filename to save it in another directory instead.
+
+<DT>WhereIs</DT>
+<DD>Search the attachment for a string of letters. If it is
+found, move to it. The string can be one word or a phrase.
+If there are multiple occurrences, the cursor moves to the
+first occurrence beyond the current cursor position.
+</DL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_pine_for_windows ========
+<HTML>
+<HEAD>
+<TITLE>GETTING HELP IN PC-ALPINE</TITLE>
+</HEAD>
+<BODY>
+<H1>Getting Help In PC-Alpine</H1>
+
+<P>
+PC-Alpine offers general and specific help text. From the <A
+HREF="main_menu_tx">MAIN MENU</A>, you will find an overview in the MAIN
+MENU HELP and the <A HREF="h_news">Release Notes</A>. On all screens,
+specific help for that screen is available from the toolbar Help menu or
+with the
+<!--chtml if pinemode="function_key"-->
+&quot;F1&quot; key.
+<!--chtml else-->
+&quot;?&quot; or &quot;Ctrl-G&quot; keys. &quot;Ctrl-G&quot; is used where
+typing &quot;?&quot; would be mistaken as entering text.
+<!--chtml endif-->
+
+<P>
+Although this version of Alpine is for Microsoft Windows, it is not
+considered a full &quot;Graphical User Interface&quot; application.
+Yet, many of the controls that Windows users are accustomed to seeing,
+such as scrollbars and toolbars, are available.
+
+<P>
+PC-Alpine offers considerable mouse support. You can view what is
+&quot;click-able&quot; by dragging your mouse over any screen; when the
+arrow cursor changes into a hand, you found something. Mouse-click
+possibilities include navigating between screens and folders and
+double-clicking on hyperlinks to open your Web browser.
+Context-sensitive pop-up menus appear with a right-click on your PC-Alpine
+screen. Examples of right-click options include &quot;copy&quot; after
+selecting text to copy and &quot;View in New Window&quot; when you click
+on a particular message in the Message Index. The menu choices available
+to you will vary based upon what screen is open, where on the screen your
+cursor is located, and even what action you have already taken.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_composer =====
+<HTML>
+<HEAD>
+<TITLE>COMPOSER COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>COMPOSER COMMANDS</H1>
+
+CURSOR&nbsp;MOTION&nbsp;KEYS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|EDITING&nbsp;KEYS<BR>
+&nbsp;&nbsp;^B&nbsp;(Left&nbsp;Arrow)&nbsp;&nbsp;&nbsp;Back&nbsp;character&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Delete&nbsp;current&nbsp;character<BR>
+&nbsp;&nbsp;^F&nbsp;(Right&nbsp;Arrow)&nbsp;&nbsp;Forward&nbsp;character&nbsp;&nbsp;|&nbsp;^H&nbsp;(DEL)&nbsp;Delete&nbsp;previous&nbsp;character<BR>
+&nbsp;&nbsp;^P&nbsp;(Up&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^^&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Set&nbsp;a&nbsp;<A HREF="h_compose_markcutpaste">mark</A><BR>
+&nbsp;&nbsp;^N&nbsp;(Down&nbsp;Arrow)&nbsp;&nbsp;&nbsp;Next&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<!--chtml if pinemode="function_key"-->F9<!--chtml else-->^K<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_markcutpaste">Cut</A>&nbsp;marked&nbsp;text&nbsp;or<BR>
+&nbsp;&nbsp;^A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Beginning&nbsp;of&nbsp;line&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;current&nbsp;line<BR>
+&nbsp;&nbsp;^E&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End&nbsp;of&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<!--chtml if pinemode="function_key"-->F10<!--chtml else-->^U<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_markcutpaste">Paste</A>&nbsp;text,&nbsp;undelete&nbsp;lines<BR>
+&nbsp;&nbsp;^Y&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cut&nbsp;with&nbsp;^K,&nbsp;or&nbsp;unjustify<BR>
+&nbsp;&nbsp;^V&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-------------------------------------<BR>
+&nbsp;&nbsp;^@&nbsp;(Ctrl-SPACE)&nbsp;&nbsp;&nbsp;Next&nbsp;word&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|SCREEN/COMPOSITION&nbsp;COMMANDS<BR>
+---------------------------------------|&nbsp;<!--chtml if pinemode="function_key"-->F6<!--chtml else-->^W<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_composer_search">Whereis</A>&nbsp;(search&nbsp;for&nbsp;string)<BR>
+MESSAGE&nbsp;COMMANDS&nbsp;|&nbsp;GENERAL&nbsp;COMMANDS&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<!--chtml if pinemode="function_key"-->F12<!--chtml else-->^T&nbsp;<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_spell">Spell&nbsp;checker</A><BR>
+&nbsp;<!--chtml if pinemode="function_key"-->F3<!--chtml else-->^C<!--chtml endif-->&nbsp;&nbsp;&nbsp;<A HREF="h_compose_cancel">Cancel</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;<!--chtml if pinemode="function_key"-->F1<!--chtml else-->^G<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;Get&nbsp;help&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<!--chtml if pinemode="function_key"-->F4<!--chtml else-->^J<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_justify">Justify</A>&nbsp;paragraph<BR>
+&nbsp;<!--chtml if pinemode="function_key"-->F11<!--chtml else-->^O&nbsp;<!--chtml endif-->&nbsp;&nbsp;<A HREF="h_common_postpone">Postpone</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;^Z&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_common_suspend">Suspend</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^L&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Redraw&nbsp;Screen<BR>
+&nbsp;<!--chtml if pinemode="function_key"-->F2<!--chtml else-->^X<!--chtml endif-->&nbsp;&nbsp;&nbsp;<A HREF="h_compose_send">Send</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;<!--chtml if pinemode="function_key"-->F6<!--chtml else-->^_<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_alted">Alt.&nbsp;editor</A>&nbsp;&nbsp;|&nbsp;<!--chtml if pinemode="function_key"-->F5<!--chtml else-->^R<!--chtml endif-->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_readfile">Read&nbsp;in&nbsp;a&nbsp;file</A><BR>
+
+<P>
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI> The availability of certain commands
+is determined by Alpine configuration files and system capabilities.
+At some sites, certain commands may not be available due to security or
+support concerns.
+ <LI>Alpine does not use the following keys: Ctrl-S, Ctrl-Q, Ctrl-],
+Ctrl-&#92;, ESC.
+ <LI>For special handling of Ctrl-S and Ctrl-Q see special comments regarding
+<A HREF="h_special_xon_xoff">&quot;XOFF/XON&quot;</A>.
+</OL>
+
+<P>
+HINT: To move rapidly to the bottom of a message you are composing,
+enter ^W^V. To go to the top, ^W^Y. These can be used in conjunction
+with the Mark and Cut commands to eliminate large amounts of unwanted
+text in a Reply.
+
+<H2>Description of Composer</H2>
+
+Alpine has a built-in editing program that allows you to compose messages
+without having to leave Alpine. The editor is designed to be very simple to
+use so that you can get started writing email right away.
+
+<P>
+Messages are usually just text, about 80 columns wide. Using upper and
+lower case is encouraged. On some systems the size limit of the message
+is about 100,000 characters, which is about 2,000 lines. You can include
+punctuation and special characters found on most keyboards, but you can't
+include characters with diacritical marks and certain special symbols.
+
+<P>
+Text automatically wraps as you type past the end of a line so you do not
+have to hit return. Using the
+&quot;<A HREF="h_compose_justify">Justify</A>&quot; command,
+you can also reformat text explicitly, perhaps after you have
+deleted some text.
+
+<P>
+You can include other text files with the
+&quot;<A HREF="h_compose_readfile">Read File</A>&quot; command,
+which will prompt you for the name of the file to insert at the
+current cursor postion.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_composer_browse =====
+<HTML>
+<HEAD>
+<TITLE>BROWSER</TITLE>
+</HEAD>
+<BODY>
+<H1>BROWSER</H1>
+This screen lets you browse your files and directories. To go to another
+directory (identified by &quot;(dir)&quot;), move the cursor to it and
+choose &quot;Select&quot; (the default choice on the menu);
+or choose &quot;Goto&quot; and enter the name of the directory.
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+In Unix Alpine, you may use
+&quot;~&quot; to refer to your home directory or &quot;~user&quot; to refer
+to another's home directory.
+<!--chtml endif--><P>
+To select a file, move the cursor to it and
+choose &quot;Select&quot; (the default choice on the menu).
+<P>
+<UL>
+<LI>Note <B>if</B> you are currently using the BROWSER for choosing a file for
+inclusion in the
+message body (that is, you chose &quot;Read File&quot; with the cursor under
+the
+&quot;----- Message Text -----&quot; line
+while composing, then &quot;To Files&quot;): Since the file
+selected will become part of the message text, it must be in a format
+suitable for that (Alpine does not check!), such as a plain text file.
+Files of other formats (for example, graphics, databases, software
+programs) should be
+<B>attached</B> to the message instead --
+by moving the cursor in the COMPOSE MESSAGE screen into the
+message header area and pressing
+<!--chtml if pinemode="function_key"-->
+F6.
+<!--chtml else-->
+Ctrl-J.
+<!--chtml endif-->
+
+<P><LI>
+Note <B>if</B> you are currently using the BROWSER for saving a message
+attachment, or exporting a message, to a file: You can use the Add command to
+provide the name for a new file to save/export to, and then select that name
+to use it for the save/export operation. Back at the prompt
+&quot;EXPORT: Copy message to file in ...&quot; hit Enter, then choose
+either Overwrite or Append (it doesn't make a difference, since the file is
+so far empty). Note: If you cancel the
+operation at that point, the file created with the Add command will remain
+0 bytes in size.
+
+</UL>
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_composer_ins =====
+<HTML>
+<HEAD>
+<TITLE>INSERT TEXT FILE</TITLE>
+</HEAD>
+<BODY>
+<H1>INSERT TEXT FILE</H1>
+
+Use this function to insert a text file. The file name
+given can be an absolute file path name for your system
+<!--chtml if pinemode="os_windows"-->
+(for example, &quot;H:&#92;SIGFILES&#92;FULLINFO.TXT&quot;), a file
+with a relative pathname, or simply a file name without
+drive or directory specification.
+<!--chtml else-->
+(for example, &quot;/tmp/exported.earlier&quot; on Unix hosts),
+a file in your home directory, or a file path relative to your
+home directory. In Unix Alpine, you may use &quot;~&quot; to refer to
+your home directory or &quot;~user&quot; to refer to another
+account's home directory.
+<!--chtml endif-->
+<P>
+No wild card characters may be used.
+The file must reside on the system running Alpine.
+<P>
+If the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+feature is set, names are relative to your current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->
+rather than your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_composer_ins_m =====
+<HTML>
+<HEAD>
+<TITLE>INSERT MESSAGE</TITLE>
+</HEAD>
+<BODY>
+<H1>INSERT MESSAGE</H1>
+
+Type in the number of a message in the currently open folder to insert it
+into your message.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_composer_search =====
+<HTML>
+<HEAD>
+<TITLE>Explanation of Composer Whereis Command </TITLE>
+</HEAD>
+<BODY>
+<H1>Help For Whereis Command</H1>
+
+Whereis is used to search the message for a word or part of a word.
+When searching in the composer, only the message part of your mail is
+searched, and the cursor is put on the first occurrence appearing
+after the location of the cursor. The search will wrap to the
+beginning of the message when it no longer finds matches in the
+remainder of the message.
+
+To search for the same string a second time, press
+<!--chtml if pinemode="function_key"-->
+&quot;F6&quot;
+<!--chtml else-->
+&quot;^W&quot;
+<!--chtml endif-->
+to begin search and then just press RETURN to accept the previous
+search string shown in square brackets rather than entering a new
+search string.<P>
+
+The &quot;Search&quot; prompt has several sub-command available:
+
+<DL>
+<DT>Get Help</DT>
+<DD> Takes you to this help page.
+
+<DT>Cancel</DT>
+<DD> Cancels the prompt. No search takes place.
+
+<DT>First Line</DT>
+<DD> Takes you back to the composer with the cursor on the first character
+of the first line of text.
+
+<DT>Last Line</DT>
+<DD> Takes you back to the composer with the cursor on the last character
+of the last line of text.
+
+<DT>Replace (Optional)</DT>
+<DD> This sub-command is enabled by the
+<A HREF="h_config_enable_search_and_repl">&quot;<!--#echo var="FEAT_enable-search-and-replace"-->&quot;</A>
+feature (which is on by default); see its help screen for details on how replacing works.
+
+</DL>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_sigedit_search =====
+<HTML>
+<HEAD>
+<TITLE>Explanation of Whereis Command </TITLE>
+</HEAD>
+<BODY>
+<H1>Help For Whereis Command</H1>
+
+Whereis is used to search for a word or part of a word.
+When searching the cursor is put on the first occurrence appearing
+after the location of the cursor. The search will wrap to the
+beginning of the signature when it no longer finds matches in the
+remainder of the signature.
+
+To search for the same string a second time, press
+<!--chtml if pinemode="function_key"-->
+&quot;F6&quot;
+<!--chtml else-->
+&quot;^W&quot;
+<!--chtml endif-->
+to begin search and then just press RETURN to accept the previous
+search string shown in square brackets rather than entering a new
+search string.<P>
+
+The &quot;Search&quot; prompt has several sub-command available:
+
+<DL>
+<DT>Get Help</DT>
+<DD> Takes you to this help page.
+
+<DT>Cancel</DT>
+<DD> Cancels the prompt. No search takes place.
+
+<DT>First Line</DT>
+<DD> Takes you back to the composer with the cursor on the first character
+of the first line of text.
+
+<DT>Last Line</DT>
+<DD> Takes you back to the composer with the cursor on the last character
+of the last line of text.
+
+<DT>Replace (Optional)</DT>
+<DD> This sub-command is enabled by the
+<A HREF="h_config_enable_search_and_repl">&quot;<!--#echo var="FEAT_enable-search-and-replace"-->&quot;</A>
+feature (which is on by default); see its help screen for details on how replacing works.
+
+</DL>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_to ====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S TO FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S TO FIELD</H1>
+
+<H2>The &quot;To:&quot; field</H2>
+The address you enter here must be a valid email address that is reachable
+from your site.
+
+<H2>Email Address Format</H2>
+You may enter a full name and email address,
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+a local (meaning, on the same
+host as the one you are running Alpine on) username that Alpine will
+complete for you,
+<!--chtml endif-->
+the nickname of someone in a
+<A HREF="h_abook_opened">Alpine Address Book</A>, or a local
+mail alias defined by your system administrator. When you move the cursor
+out of this field, the nicknames will be expanded to the addresses in your
+address book, and the local usernames will be expanded to include the
+persons' actual names. You may enter as many addresses as you wish, but they
+must be separated by commas. You can move around this and other header fields
+with the arrow keys and use many of the usual composer editing keys.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+
+<P>
+<H2>MESSAGE HEADER COMMANDS</H2>
+<!--chtml if pinemode="function_key"-->
+CURSOR&nbsp;MOTION&nbsp;KEYS----------------------|EDITING&nbsp;KEYS-------------------------<BR>
+^B&nbsp;(Left&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;Back&nbsp;character&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Delete&nbsp;current&nbsp;character<BR>
+^F&nbsp;(Right&nbsp;Arrow)&nbsp;&nbsp;&nbsp;Forward&nbsp;character&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^H&nbsp;(DEL)&nbsp;Delete&nbsp;previous&nbsp;character<BR>
+^P&nbsp;(Up&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<BR>
+^N&nbsp;(Down&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;F9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cut&nbsp;marked&nbsp;text&nbsp;or<BR>
+^A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Beginning&nbsp;of&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;current&nbsp;line<BR>
+^E&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End&nbsp;of&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;F10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Undelete&nbsp;line(s)<BR>
+F7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^W&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_composer_search">Whereis</A>&nbsp;(search&nbsp;text)<BR>
+F8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-------------------------------------<BR>
+^@&nbsp;(Ctrl-SPACE)&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;word&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|SCREEN/COMPOSITION&nbsp;COMMANDS<BR>
+----------------------------------------|<BR>
+MESSAGE&nbsp;COMMANDS&nbsp;&nbsp;|&nbsp;GENERAL&nbsp;COMMANDS&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;F12&nbsp;&nbsp;&nbsp;&nbsp;To&nbsp;Addressbook/Browser<BR>
+F3&nbsp;&nbsp;&nbsp;<A HREF="h_compose_cancel">Cancel</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;F1&nbsp;&nbsp;&nbsp;&nbsp;Get&nbsp;help&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;F4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Attach&nbsp;File<BR>
+F11&nbsp;&nbsp;<A HREF="h_common_postpone">Postpone</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;^Z&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_common_suspend">Suspend</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^L&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Redraw&nbsp;Screen<BR>
+F2&nbsp;&nbsp;&nbsp;<A HREF="h_compose_send">Send</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;F5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_richhdr">Rich&nbsp;Headers</A><BR>
+<!--chtml else-->
+CURSOR&nbsp;MOTION&nbsp;KEYS----------------------|EDITING&nbsp;KEYS-------------------------<BR>
+^B&nbsp;(Left&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;Back&nbsp;character&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Delete&nbsp;current&nbsp;character<BR>
+^F&nbsp;(Right&nbsp;Arrow)&nbsp;&nbsp;&nbsp;Forward&nbsp;character&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^H&nbsp;(DEL)&nbsp;Delete&nbsp;previous&nbsp;character<BR>
+^P&nbsp;(Up&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>
+^N&nbsp;(Down&nbsp;Arrow)&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^K&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cut&nbsp;marked&nbsp;text&nbsp;or<BR>
+^A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Beginning&nbsp;of&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;current&nbsp;line<BR>
+^E&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End&nbsp;of&nbsp;line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^U&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Undelete&nbsp;line(s)<BR>
+^Y&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Previous&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<BR>
+^V&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;page&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-------------------------------------<BR>
+^@&nbsp;(Ctrl-SPACE)&nbsp;&nbsp;&nbsp;&nbsp;Next&nbsp;word&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|SCREEN/COMPOSITION&nbsp;COMMANDS<BR>
+----------------------------------------|&nbsp;^R&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_richhdr">Rich&nbsp;Headers</A><BR>
+MESSAGE&nbsp;COMMANDS&nbsp;&nbsp;|&nbsp;GENERAL&nbsp;COMMANDS&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^T&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;To&nbsp;Addressbook/Browser<BR>
+^C&nbsp;&nbsp;&nbsp;<A HREF="h_compose_cancel">Cancel</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;^G&nbsp;&nbsp;&nbsp;&nbsp;Get&nbsp;help&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^J&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Attach&nbsp;File<BR>
+^O&nbsp;&nbsp;&nbsp;<A HREF="h_common_postpone">Postpone</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;^Z&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_common_suspend">Suspend</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;^L&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Redraw&nbsp;Screen<BR>
+^X&nbsp;&nbsp;&nbsp;<A HREF="h_compose_send">Send</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;TAB&nbsp;&nbsp;&nbsp;&nbsp;<A HREF="h_compose_addrcomplete">Address&nbsp;Completion</A><BR>
+<!--chtml endif-->
+
+<P>
+NOTE:
+<OL>
+ <LI>For help on a particular command, highlight the bold text associated
+with it above and hit Return.
+ <LI> The availability of certain commands
+is determined by Alpine configuration files and system capabilities.
+At some sites, certain commands may not be available due to security or
+support concerns.
+</OL>
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_cc ====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S CC FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S CC FIELD</H1>
+The Cc: field is just like the To: field, except it is used for addressees
+that you wish to send a &quot;carbon&quot; copy to. That is, the message is
+not directly meant directly &quot;for&quot; these recipients, but you wanted
+them to see the message. The only difference the recipients see is that their
+name is in the Cc: field, rather than the To: field.
+<P>
+For help with Cc: field editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_bcc ====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S BCC FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S BCC FIELD</H1>
+The &quot;Bcc:&quot; (Blind carbon copy) header is used when you wish to send
+a copy of the message to one or more people whose addresses you do not
+wish disclosed, either to reduce clutter or for confidentiality.
+<P>
+The format of the Bcc: field is just the same as the To: and Cc: fields in
+the way the addresses are entered. The recipients listed here will
+receive a copy of the message, but --assuming your site's mail transport
+software is properly configured-- their addresses will not show up in the
+headers of the message, as delivered to all of the recipients. The To:
+and Cc: recipients will not know a copy was sent to the Bcc: recipients.
+<P>
+Note: if there is no To: or Cc: or Lcc: address in the message, Alpine
+will automatically generate and place in the To: field a pseudo-address of
+ &quot;undisclosed-recipients: ;&quot;
+or whatever string has been specified in the
+<A HREF="h_config_empty_hdr_msg">&quot;<!--#echo var="VAR_empty-header-message"-->&quot;</A>
+variable.
+<P>
+The reason for this is to avoid embarrassment caused by some Internet
+mail transfer software that interprets a &quot;missing&quot; To: header as
+an error and replaces it with an Apparently-to: header that may contain
+the addresses you entered on the Bcc: line. In addition, it may be
+less disconcerting to Bcc: recipients to see <B>something</B> in the To: field.
+<P>
+You can manipulate what text ends up on the (originally) empty To:
+field. Just remember to put a colon and semicolon at the end of the
+field, which is a special notation denoting that it is not a real address.
+<P>
+For information on message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_lcc ====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S LCC FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S LCC FIELD</H1>
+The &quot;Lcc:&quot; (List carbon copy) header is intended to be used when
+you wish to send a message to a list of people but avoid having all
+of their addresses visible, in order to reduce clutter when the
+message is received.
+<P>
+It is similar to the
+<A HREF="h_composer_bcc">&quot;Bcc&quot; (Blind carbon copy) header</A>
+in that individual
+addressees are hidden, but Lcc is designed to work specifically with
+distribution lists you have created in your
+<A HREF="h_abook_opened">Alpine Address Book</A>. Placing
+the nickname of the list on the Lcc line will result in the full name of
+your Alpine Address Book list being placed on the To: line of the message,
+using a special notation that distinguishes it from a real address. You
+must leave the To: line blank for your list name to appear there.
+<P>
+For example, if you have this list entered in your Address Book:<PRE>
+
+ largo Key Largo List DISTRIBUTION LIST:
+ bogie@mgm.com
+ lauren@mgm.com
+ walter@mgm.com</PRE>
+
+
+and you enter &quot;largo&quot; on the Lcc: line while composing a message,
+the result is:<PRE>
+
+ To : Key Largo List: ;
+ Cc :
+ Bcc :
+ Fcc : sent-mail
+ Lcc : Key Largo List &lt;bogie@mgm.com&gt;,
+ lauren@mgm.com,
+ walter@mgm.com
+ Subject :</PRE>
+
+Each recipient listed on the Lcc: line receives a copy of the message
+without their address being visible (as though they were listed on the
+Bcc: line). The colon-semicolon notation used to put the full-name of the
+list on the To: line is a special address format that doesn't specify any
+actual addressees, but does give some information to the recipients of the
+message.
+<P>
+Note: if after entering an LCC, you delete the list name that is placed
+on the To: line, then recipients will see <PRE>
+ To: undisclosed-recipients: ;</PRE>
+
+(or whatever string is defined in the
+<A HREF="h_config_empty_hdr_msg">&quot;<!--#echo var="VAR_empty-header-message"-->&quot;</A>
+variable) just as in the BCC case.
+<P>
+For help with Lcc: field editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_from =======
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S FROM FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S FROM FIELD</H1>
+
+This header carries your return address. It is the address toward which
+replies (and often, future unrelated correspondence) will be directed,
+unless you have <A HREF="h_config_custom_hdrs">defined an optional
+&quot;Reply-To:&quot; header</A> in the SETUP CONFIGURATION screen. Make
+sure this address is correct.
+<P>
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_reply_to =======
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S REPLY-TO FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S REPLY-TO FIELD</H1>
+
+Most people should not need this header. The Reply-To: header is used in
+cases where you would like replies to your messages to be directed to an
+address other than your normal &quot;From:&quot; address. This is atypical,
+but can happen when you use multiple machines and do not have the same account
+name on each one, or when you wish to direct certain replies to accounts
+or folders designated for specific classes of correspondence.
+<P>
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_custom_addr ====
+<HTML>
+<HEAD>
+<TITLE>CUSTOMIZED HEADER FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>CUSTOMIZED HEADER FIELD</H1>
+This is a customized header, i.e. not one that is part of Alpine's normal
+set of Compose headers.
+<P>
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_address_format">Explanation of Address formats</A>
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_custom_free ====
+<HTML>
+<HEAD>
+<TITLE>CUSTOMIZED HEADER FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>CUSTOMIZED HEADER FIELD</H1>
+This is a customized header, i.e. not one that is part of Alpine's normal
+set of Compose headers.
+<P>
+This field consists of arbitrary text.
+<P>
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_composer_news =====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S NEWSGRPS LINE</TITLE>
+</HEAD>
+<BODY>
+<h1>THE MESSAGE COMPOSER'S NEWSGRPS LINE</h1>
+Use the newsgroups line to specify any and all USENET newsgroups to which
+your message should be posted. When composing a message from scratch, this
+line may be hidden. If so, just press the rich headers command
+(<!--chtml if pinemode="function_key"-->F5<!--chtml else-->^R<!--chtml endif-->)
+to make it visible.
+<P>
+<EM>Be aware</EM> that when you post to a newsgroup thousands of
+people will be reading your message. Also, you or your system manager
+must have defined an &quot;<!--#echo var="VAR_nntp-server"-->&quot; in your Alpine configuration
+in order for you to be able to post.
+<P>
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_fcc ====
+<HTML>
+<HEADER>
+<TITLE>THE MESSAGE COMPOSER'S FCC FIELD</TITLE>
+</HEADER>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S FCC FIELD</H1>
+The FCC (File Carbon Copy) specifies the folder used to keep a copy of
+each outgoing message. The default value can be configured with the
+&quot;<!--#echo var="VAR_default-fcc"-->&quot; and &quot;<!--#echo var="VAR_fcc-name-rule"-->&quot; options. You can change or remove
+the file carbon copy on any message you send by editing the FCC header.<p>
+
+You may type ^T to get a list of all your folders and select one to use as
+the FCC for this message.<P>
+
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_subject ====
+<HTML>
+<HEADER>
+<TITLE>THE MESSAGE COMPOSER'S SUBJECT FIELD</TITLE>
+</HEADER>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S SUBJECT FIELD</H1>
+
+The subject header provides a place to enter a few words that summarize
+the topic of the message you are sending. You may leave this line blank,
+but it is considered a courtesy to use a meaningful subject.<p>
+
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_attachment ====
+<HTML>
+<HEAD>
+<TITLE>THE MESSAGE COMPOSER'S ATTCHMNT FIELD</TITLE>
+</HEAD>
+<BODY>
+<H1>THE MESSAGE COMPOSER'S ATTCHMNT FIELD</H1>
+
+The &quot;Attchmnt:&quot; field is where you specify what file or
+files you'd like attached to
+the message you are composing. Those files must reside on the machine
+running Alpine. If your file is on a PC or Mac and you run Alpine with an
+account on a Unix machine, you'll have to transfer it before attaching it.
+Contact local computer support people for assistance with transferring.
+
+<P>
+The file name
+given can be an absolute file path name for your system
+<!--chtml if pinemode="os_windows"-->
+(for example, &quot;H:&#92;SIGFILES&#92;FULLINFO.TXT&quot;), a file
+with a relative pathname, or simply a file name without
+drive or directory specification.
+<!--chtml else-->
+(for example, &quot;/tmp/exported.earlier&quot; on Unix hosts),
+a file in your home directory, or a file path relative to your
+home directory. In Unix Alpine, you may use &quot;~&quot; to refer to
+your home directory or &quot;~user&quot; to refer to another
+account's home directory.
+<!--chtml endif--><P>
+No wild card characters may be used.
+<P>If the
+<A HREF="h_config_use_current_dir">&quot;<!--#echo var="FEAT_use-current-dir"-->&quot;</A>
+feature is set, names are relative to your current working directory
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->
+rather than your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+.
+<P>
+Alpine uses MIME encoding for attachments, so binaries and files of any
+length can safely be delivered to any MIME-capable mail reading program.
+If you send an attachment to someone who does not have a MIME-capable mail
+reading program yet, then the main message text will be readable, but
+attachments (even attachments that are just plain text) are not.
+<P>
+
+Typing the filename on the Attchmnt: line achieves the same
+result as using the
+<!--chtml if pinemode="function_key"-->
+F6
+<!--chtml else-->
+Ctrl-J
+<!--chtml endif--> command.
+<P>
+
+If you Forward a message with attachments, you may delete them from your
+Forwarded message by editing the Attchmnt header line.
+<P>
+
+For help with message header editing
+commands, check the <A HREF="h_composer_to">Help for the To:</A> header.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_ctrl_j ====
+<HTML>
+<HEAD>
+<TITLE>COMPOSER ATTACH</TITLE>
+</HEAD>
+<BODY>
+After the
+<!--chtml if pinemode="function_key"-->
+F6
+<!--chtml else-->
+Ctrl-J
+<!--chtml endif--> command:
+At the &quot;File to attach:&quot; prompt, enter the name of the
+existing file to attach to your message.
+When the feature
+<A HREF="h_config_enable_tab_complete">&quot;<!--#echo var="FEAT_enable-tab-completion"-->&quot;</A>
+is set
+you need only enter the beginning of the filename (enough of it to uniquely
+identify the file) and press TAB to complete it.
+
+Or, press ^T to use the BROWSER screen for
+selecting the file. <P>
+For more information on attaching files, see the help screen for the
+composer's
+<A HREF="h_composer_attachment">Attchmnt: field</A>, which is normally hidden,
+but can be revealed using the
+<!--chtml if pinemode="function_key"-->
+F5
+<!--chtml else-->
+Ctrl-R
+<!--chtml endif-->
+command with the cursor positioned above the
+&quot;----- Message Text -----&quot; line in the COMPOSE MESSAGE screen.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_edit_nav_cmds =========
+<HTML>
+<HEAD>
+<TITLE>Composer Editing Commands Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>EDITING and NAVIGATION COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+CURSOR MOTION KEYS----------------------|EDITING KEYS-------------------------
+^B (Left Arrow) Back character | ^D Delete current character
+^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+^P (Up Arrow) Previous line |
+^N (Down Arrow) Next line | F9 Cut marked text or
+^A Beginning of line | delete current line
+^E End of line | F10 Undelete line(s)
+F7 Previous page |
+F8 Next page |-------------------------------------
+^@ (Ctrl-SPACE) Next word | MISCELLANEOUS COMMANDS
+----------------------------------------|
+EXIT COMMANDS | GENERAL COMMANDS | F12 To Addressbook
+F3 Cancel | F1 Get help | F12 RichView (expand lists)
+F2 eXit/save | ^Z Suspend | ^L Redraw Screen
+</PRE>
+<!--chtml else-->
+<PRE>
+CURSOR MOTION KEYS----------------------|EDITING KEYS-------------------------
+^B (Left Arrow) Back character | ^D Delete current character
+^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+^P (Up Arrow) Previous line |
+^N (Down Arrow) Next line | ^K Cut marked text or
+^A Beginning of line | delete current line
+^E End of line | ^U Undelete line(s)
+^Y Previous page |
+^V Next page |-------------------------------------
+^@ (Ctrl-SPACE) Next word | MISCELLANEOUS COMMANDS
+----------------------------------------|
+EXIT COMMANDS | GENERAL COMMANDS | ^T To Addressbook
+^C Cancel | ^G Get help | ^R RichView (expand lists)
+^X eXit/save | ^Z Suspend | ^L Redraw Screen
+</PRE>
+<!--chtml endif-->
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_composer_sigedit =====
+<HTML>
+<HEAD>
+<TITLE>Signature Editor Commands Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>SIGNATURE EDITOR COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+CURSOR MOTION KEYS |EDITING KEYS
+ ^B (Left Arrow) Back character | ^D Delete current character
+ ^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+ ^P (Up Arrow) Previous line | ^^ Set a mark
+ ^N (Down Arrow) Next line | F9 Cut marked text or
+ ^A Beginning of line | delete current line
+ ^E End of line | F10 Paste text, undelete lines
+ F7 Previous page | cut with ^K, or unjustify
+ F8 Next page |-------------------------------------
+ ^@ (Ctrl-SPACE) Next word |SCREEN/COMPOSITION COMMANDS
+---------------------------------------| F6 Whereis (search for string)
+MESSAGE COMMANDS | GENERAL COMMANDS | F12 Spell checker
+ F3 Cancel | F1 Get help | F4 Justify paragraph
+ | ^Z Suspend | ^L Redraw Screen
+ F2 Send | F6 Alt. editor | F5 Read in a file
+</PRE>
+<!--chtml else-->
+<PRE>
+CURSOR MOTION KEYS |EDITING KEYS
+ ^B (Left Arrow) Back character | ^D Delete current character
+ ^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+ ^P (Up Arrow) Previous line | ^^ Set a mark
+ ^N (Down Arrow) Next line | ^K Cut marked text or
+ ^A Beginning of line | delete current line
+ ^E End of line | ^U Paste text, undelete lines
+ ^Y Previous page | cut with ^K, or unjustify
+ ^V Next page |-------------------------------------
+ ^@ (Ctrl-SPACE) Next word |SCREEN/COMPOSITION COMMANDS
+---------------------------------------| ^W <A HREF="h_composer_search">Whereis</A> (search text)
+MESSAGE COMMANDS | GENERAL COMMANDS | ^T Spell checker
+ ^C Cancel | ^G Get help | ^J <A HREF="h_compose_justify">Justify</A> paragraph
+ | ^Z <A HREF="h_common_suspend">Suspend</A> | ^L Redraw Screen
+ ^X Send | ^_ Alt. editor | ^R Read in a file
+</PRE>
+<!--chtml endif-->
+
+NOTE: The presence or absence of the following commands is determined
+by &quot;Feature-List&quot; options in your Alpine configuration. Also,
+some of these commands may be administratively disabled by your system
+manager; if they don't work, please check with your local help desk
+before reporting a bug.
+<P>
+<UL>
+ <LI>Suspend (suspends Alpine and gives a system prompt)
+ <LI>Alternate editor (allows you to compose with your own editor)
+</UL>
+<P>
+
+Alpine does not use the following keys: Ctrl-S, Ctrl-Q, Ctrl-],
+Ctrl-&#92;, ESC
+<P>
+
+NOTE: For special handling of Ctrl-S and Ctrl-Q see special comments regarding
+<A HREF="h_special_xon_xoff">&quot;XOFF/XON&quot;</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_composer_commentedit =====
+<HTML>
+<HEAD>
+<TITLE>Comment Editor Commands Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>COMMENT EDITOR COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+CURSOR MOTION KEYS |EDITING KEYS
+ ^B (Left Arrow) Back character | ^D Delete current character
+ ^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+ ^P (Up Arrow) Previous line | ^^ Set a mark
+ ^N (Down Arrow) Next line | F9 Cut marked text or
+ ^A Beginning of line | delete current line
+ ^E End of line | F10 Paste text, undelete lines
+ F7 Previous page | cut with ^K, or unjustify
+ F8 Next page |-------------------------------------
+ ^@ (Ctrl-SPACE) Next word |SCREEN/COMPOSITION COMMANDS
+---------------------------------------| F6 Whereis (search for string)
+MESSAGE COMMANDS | GENERAL COMMANDS | F12 Spell checker
+ F3 Cancel | F1 Get help | F4 Justify paragraph
+ | ^Z Suspend | ^L Redraw Screen
+ F2 Send | F6 Alt. editor | F5 Read in a file
+</PRE>
+<!--chtml else-->
+<PRE>
+CURSOR MOTION KEYS |EDITING KEYS
+ ^B (Left Arrow) Back character | ^D Delete current character
+ ^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+ ^P (Up Arrow) Previous line | ^^ Set a mark
+ ^N (Down Arrow) Next line | ^K Cut marked text or
+ ^A Beginning of line | delete current line
+ ^E End of line | ^U Paste text, undelete lines
+ ^Y Previous page | cut with ^K, or unjustify
+ ^V Next page |-------------------------------------
+ ^@ (Ctrl-SPACE) Next word |SCREEN/COMPOSITION COMMANDS
+---------------------------------------| ^W <A HREF="h_composer_search">Whereis</A> (search text)
+MESSAGE COMMANDS | GENERAL COMMANDS | ^T Spell checker
+ ^C Cancel | ^G Get help | ^J <A HREF="h_compose_justify">Justify</A> paragraph
+ | ^Z <A HREF="h_common_suspend">Suspend</A> | ^L Redraw Screen
+ ^X Send | ^_ Alt. editor | ^R Read in a file
+</PRE>
+<!--chtml endif-->
+
+NOTE: The presence or absence of the following commands is determined
+by &quot;Feature-List&quot; options in your Alpine configuration. Also,
+some of these commands may be administratively disabled by your system
+manager; if they don't work, please check with your local help desk
+before reporting a bug.
+<P>
+<UL>
+ <LI>Suspend (suspends Alpine and gives a system prompt)
+ <LI>Alternate editor (allows you to compose with your own editor)
+</UL>
+<P>
+
+Alpine does not use the following keys: Ctrl-S, Ctrl-Q, Ctrl-],
+Ctrl-&#92;, ESC
+<P>
+
+NOTE: For special handling of Ctrl-S and Ctrl-Q see special comments regarding
+<A HREF="h_special_xon_xoff">&quot;XOFF/XON&quot;</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_nick =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+This is a short nickname for this address book entry. If it is used in
+place of an address from the composer, the composer will fill in the
+address(es) for the entry that matches the nickname.
+<P>
+
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_full =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Fullname Explained</TITLE>
+</HEAD>
+<BODY>
+This is the full name field for this entry. If this is going to be a
+distribution list (more than one address), it should be a descriptive
+phrase describing the list. It will be included in the mail header if you
+put the list in the To: or CC: field, or in the To: line if you put the
+list in the Lcc: field. It's OK to leave this field blank (and OK to
+leave any of the other fields blank, too). If this address book entry is
+going to be a simple entry with just one address, then this field is the
+person's name. When you send mail to this entry, this is the field to the
+left of the brackets. That is, it is the most readable part of the
+address. For example, in the sample address:
+<PRE>
+ John Doe &lt;jdoe@some.domain&gt;
+</PRE>
+"John Doe" is the full name field. If you are sorting your address book
+with one of the options that uses full names, then it might be useful to
+enter the full name as "Last, First", for example:
+<PRE>
+ Doe, John
+</PRE>
+so that it will be sorted using Doe instead of John. This will be changed
+back into John Doe when you use it.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_fcc =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Fcc Explained</TITLE>
+</HEAD>
+<BODY>
+If this entry is the first one in the To: line of an outgoing message,
+this field will be used for the Fcc (File Carbon Copy) instead of whatever
+you would normally get (which depends on which
+<A HREF="h_config_saved_msg_name_rule">&quot;<!--#echo var="VAR_saved-msg-name-rule"-->&quot;</A>
+you've chosen).
+<P>
+If this field consists of two double quotes (&quot;&quot;) that tells Alpine
+that you don't want any Fcc associated with this entry.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_combined_abook_display =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_combined-addrbook-display"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_combined-addrbook-display"--></H1>
+
+This feature affects the address book display screens.
+Normally, expanding an address book from the ADDRESS BOOK LIST screen
+will cause the remaining address books and directory servers to disappear
+from the screen, leaving only the entries of the expanded address book.
+If this feature is set, then the other address books will remain on the screen,
+so that all of the address books can be present at once.
+
+<P>
+The way that commands work won't be changed.
+For example, the Select All command will select all of the entries in the
+current address book, not all of the entries in all of the address books.
+The WhereIs command will change a little.
+It will search through all of the text on the screen plus all of the entries
+from expanded address books.
+
+<P>
+When this feature is set, the setting of the feature
+<A HREF="h_config_expanded_addrbooks">&quot;<!--#echo var="FEAT_expanded-view-of-addressbooks"-->&quot;</A>
+has an effect.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_titlebar_color_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_titlebar-color-style"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_titlebar-color-style"--></H1>
+
+This option affects the colors used to display the titlebar (the top
+line on the screen) when viewing a message.
+
+<P>
+The available options include:
+<P>
+
+<DL>
+<DT>default</DT>
+<DD>The color of the titlebar will be the color you set for the
+<A HREF="h_config_title_color">Title Color</A>.
+The Title Color may be set by using the
+<A HREF="h_color_setup">Setup Kolor</A> screen.
+</DD>
+
+<DT>indexline</DT>
+<DD>The color of the titlebar will be the same as the color of the
+index line corresponding to the message being viewed.
+The rules that determine what color the index line will be may be set
+up by going to the Setup/Rules/Indexcolor screen.
+If the index line for a message is not colored explicitly by the
+Indexcolor rules, then the titlebar will be colored the same as for
+the &quot;default&quot; option above (which is not the same color that
+the index line itself will have).
+</DD>
+
+<DT>reverse-indexline</DT>
+<DD>This is similar to the &quot;indexline&quot; option except the
+foreground and background colors from the corresponding index line will
+be reversed.
+For example, if the index line color is red letters on a white background,
+then the titlebar will be white letters on a red background.
+If the index line for a message is not colored explicitly by the
+Indexcolor rules, then the titlebar will be colored the same as for
+the &quot;default&quot; option above (which is not the same color that
+the index line itself will have).
+</DD>
+</DL>
+
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_color_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_current-indexline-style"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_current-indexline-style"--></H1>
+
+This option affects the colors used to display the current line in the
+MESSAGE INDEX screen.
+If you do not have Index Color Rules defined, then this option will
+have no effect in the index.
+Those Rules may be defined by going to the Setup/Rules/Indexcolor screen.
+<P>
+If the option
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is turned on and the
+<A HREF="h_config_incunseen_color">Incoming Unseen Color</A>
+is set to something other than the default, then
+this option also affects the color used to display the current folder
+in the Incoming FOLDER LIST screen.
+
+<P>
+The available options include:
+<P>
+
+<DL>
+<DT>flip-colors</DT>
+<DD>This is the default.
+If an index line is colored because it matches one of your
+Index Color Rules, then its colors will be reversed when it is the currently
+highlighted line.
+For example, if the line is normally red text on a blue background, then
+when it is the current line it will be drawn as blue text on a red background.
+<P>
+The rest of the option values all revert to this flip-colors behavior if
+there is no Reverse Color defined.
+</DD>
+
+<DT>reverse</DT>
+<DD>With this option the Reverse color is always used to highlight the
+current line.
+</DD>
+
+<DT>reverse-fg</DT>
+<DD>The foreground part of the Reverse Color is used to highlight
+the current line.
+If this would cause the text to be unreadable (because the foreground and
+background colors are the same) or if it would cause no change in the
+color of the index line, then the colors are flipped instead.
+<P>
+Some people think this works particularly well if you use different
+background colors to emphasize &quot;interesting&quot; lines,
+but always with the same Normal foreground color,
+and you use a different foreground color for the Reverse Color.
+</DD>
+
+<DT>reverse-fg-no-ambiguity</DT>
+<DD>With the &quot;reverse-fg&quot; rule above, it is possible that
+the resulting color will be exactly the same as the regular Reverse
+Color.
+That can lead to some possible confusion because an
+&quot;interesting&quot;
+line that is the current line will be displayed exactly the same as a
+non-interesting line that is current.
+You can't tell whether the line is just a regular current line or if it is
+an &quot;interesting&quot; current line by looking at the color.
+Setting the option to this value removes that ambiguity.
+It is the same as the &quot;reverse-fg&quot; setting unless the resulting
+interesting current line would look just like a non-interesting current line.
+In that case, the interesting line's colors are simply flipped (like in the
+default behavior).
+<P>
+As an alternative way to preserve the line's interestingness in this case,
+you may find that using both a different foreground and a different
+background color for the interesting line will help.
+</DD>
+
+<DT>reverse-bg</DT>
+<DD>The background part of the Reverse Color is used to highlight
+the current line.
+If this would cause the text to be unreadable (because the foreground and
+background colors are the same) or if it would cause no change in the
+color of the index line, then the colors are flipped instead.
+<P>
+Some people think this works particularly well if you use different
+foreground colors to emphasize &quot;interesting&quot; lines,
+but always with the same Normal background color,
+and you use a different background color for the Reverse Color.
+</DD>
+
+<DT>reverse-bg-no-ambiguity</DT>
+<DD>As with the &quot;reverse-fg&quot; case, the &quot;reverse-bg&quot;
+rule may also result in a color that is exactly the same as the regular
+Reverse Color.
+Setting the option to this value removes that ambiguity.
+It is the same as the &quot;reverse-bg&quot; setting unless the resulting
+current line has the same color as the Reverse Color.
+In that case, the interesting line's colors are simply flipped (like in the
+default behavior).
+</DD>
+</DL>
+
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expanded_addrbooks =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expanded-view-of-addressbooks"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expanded-view-of-addressbooks"--></H1>
+
+If multiple address books (either personal or global) are defined, and you
+wish to have them all expanded implicitly upon entering the ADDRESS BOOK
+screen, then set this feature. This feature will have no effect unless the
+feature
+<A HREF="h_config_combined_abook_display">&quot;<!--#echo var="FEAT_combined-addrbook-display"-->&quot;</A>
+is also set.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_combined_folder_display =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_combined-folder-display"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_combined-folder-display"--></H1>
+
+This feature affects the folder list display screens.
+Normally, each folder list is viewed within its collection only. This
+command allows folder lists to be viewed within a single screen that
+combines the contents of all collections.
+
+<P>
+The way that commands work won't be changed.
+For example, the Select All command will select all of the folders in the
+current collection, not all of the entries in all of the collections.
+The WhereIs command will change a little.
+It will search through all of the folders in the current collection as well
+as all the folder in any other expanded collection.
+
+<P>
+When this feature is set, the setting of the feature
+<A HREF="h_config_expanded_folders">&quot;<!--#echo var="FEAT_expanded-view-of-folders"-->&quot;</A>
+has an effect.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_combined_subdir_display =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_combined-subdirectory-display"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_combined-subdirectory-display"--></H1>
+
+This feature affects the Folder List screen when
+the
+<A HREF="h_config_combined_folder_display">&quot;<!--#echo var="FEAT_combined-folder-display"-->&quot;</A>
+feature is enabled. Normally, selecting a directory from the Folder
+List takes you into a new screen displaying only the contents of
+that directory.
+
+<P>
+Enabling this feature will cause the contents of the selected
+directory to be
+displayed within the boundaries of the &quot;Collection&quot; it
+is a part of. All previously displayed collections will remain
+in the screen.
+
+<P>
+The way that commands work won't be changed.
+For example, the Select All command will select all of the folders in the
+directory, as opposed to all of the entries in all of the collections.
+The WhereIs command will change a little.
+It will search through all of the folders in the current collection as well
+as all the folder in any other expanded collection.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_separate_fold_dir_view =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_separate-folder-and-directory-entries"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_separate-folder-and-directory-entries"--></H1>
+
+This feature affects folder collections wherein a folder
+and directory can have the same name. By default, Alpine displays them
+only once, denoting that it is both a folder and directory by appending
+the folder name with the hierarchy character enclosed
+in square brackets.
+
+
+<P>
+Enabling this feature will cause Alpine to display such names
+separately marking the name representing a directory with a trailing
+hierarchy delimiter (typically the slash, &quot;/&quot;, character).
+
+<P>
+The feature also alters the command set slightly. By default, the
+right-arrow descends into the directory, while hitting the Return key will
+cause the folder by that name to be opened.
+
+<P>
+With this feature set, the Return key will open the highlighted folder, or
+enter the highlighted directory.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expanded_folders =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expanded-view-of-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expanded-view-of-folders"--></H1>
+
+If multiple folder collections are defined, and you
+wish to have them all expanded implicitly upon entering the FOLDER LIST
+screen, then set this feature. This feature will have no effect unless the
+feature
+<A HREF="h_config_combined_folder_display">&quot;<!--#echo var="FEAT_combined-folder-display"-->&quot;</A>
+is also set.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_server =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: <!--#echo var="VAR_ldap-servers"--></TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: <!--#echo var="VAR_ldap-servers"--></H1>
+This is the name of the host where an LDAP server is running.
+For redundancy, this may be a space-delimited set of server names, in which
+case the first server that answers is used.
+Each of the server names may be optionally followed by
+a colon and a port number.
+If this form is used then the port number configured below in the
+<EM>port</EM> field is not used.
+<P>
+To find out whether your organization has its own LDAP server,
+contact its computing support staff.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_base =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Search-Base</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Search-Base</H1>
+
+This is the search base to be used on this server. It functions as a filter
+by restricting your searches in the LDAP server database
+to the specified contents of the specified fields. Without it, searches
+submitted to this directory server may fail. It might be something
+like:
+
+<PRE>
+ O = &lt;Your Organization Name&gt;, C = US
+</PRE>
+or it might be blank.
+(Some LDAP servers actually ignore anything specified here.)
+<P>
+If in doubt what parameters you should specify here,
+contact the maintainers of the LDAP server.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_port =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Port</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Port</H1>
+
+This is the TCP port number to be used with this LDAP server. If you leave
+this blank port 389 will be used.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_nick =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Nickname</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Nickname</H1>
+
+This is a nickname to be used in displays. If you don't supply a
+nickname the server name
+(<A HREF="h_config_ldap_server">&quot;<!--#echo var="VAR_ldap-servers"-->&quot;</A>)
+will be used instead. This option is strictly for your convenience.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_binddn =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Bind-DN</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Bind-DN</H1>
+
+You may need to authenticate to the LDAP server before you are able to use it.
+This is the Distinguished Name to bind to when authenticating to this server.
+Try leaving this blank until you know you need it.
+<P>
+Alpine only knows about LDAP Simple authentication.
+It does not attempt LDAP SASL authentication.
+The DN and password will be sent in the clear unless TLS encryption is
+being used on this connection.
+Because of this, you may want to set the LDAP feature
+<A HREF="h_config_ldap_opts_tls">&quot;Attempt-TLS-On-Connection&quot;</A>
+or the feature
+<A HREF="h_config_ldap_opts_tlsmust">&quot;Require-TLS-On-Connection&quot;</A>
+if you are going to be providing a password.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_opts_impl =======
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Use-Implicitly-From-Composer</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Use-Implicitly-From-Composer</H1>
+
+Set this to have lookups done to this server implicitly from the composer.
+If an address doesn't look like a fully-qualified address, it will be looked
+up in your address books, and if it doesn't match a nickname there, then it
+will be looked up on the LDAP servers that have this feature set.
+The lookups will also be done when using the address completion feature
+(TAB command) in the composer if any of the serves have this feature set.
+Also see the LDAP feature
+<A HREF="h_config_ldap_opts_rhs">&quot;Lookup-Addrbook-Contents&quot;</A>
+and the Setup/Config feature
+<A HREF="h_config_add_ldap">&quot;<!--#echo var="FEAT_ldap-result-to-addrbook-add"-->&quot;</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_opts_tls =======
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Attempt-TLS-On-Connection</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Attempt-TLS-On-Connection</H1>
+
+When connecting to this server Alpine will attempt to use TLS encryption
+on the connection.
+Also see the closely related feature
+<A HREF="h_config_ldap_opts_tlsmust">&quot;Require-TLS-On-Connection&quot;</A>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_opts_tlsmust =======
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Require-TLS-On-Connection</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Require-TLS-On-Connection</H1>
+
+When connecting to this server Alpine will attempt to use TLS encryption
+on the connection.
+If the StartTLS operation fails then the connection will not be used.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ldap_opts_rhs =====
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Lookup-Addrbook-Contents</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Lookup-Addrbook-Contents</H1>
+
+Normally implicit LDAP lookups from the composer are done only for the
+strings you type in from the composer screen. In other words, you type in
+something in the To or CC field and press return, then the string is looked up.
+First that string is looked up in your address books. If a match is found
+there, then the results of that match are looked up again. If you place
+a string in your address book that you want to have looked up on the LDAP
+directory server, you need to turn on this feature. If you set this feature
+for a server, you almost always will also want to set the
+<A HREF="h_config_ldap_opts_impl">&quot;Use-Implicitly-From-Composer&quot;</A>
+feature. An example might serve to best illustrate this feature.
+<P>
+If an LDAP lookup of &quot;William Clinton&quot; normally returns an
+entry with an
+address of pres@whitehouse.gov, then you might put an entry in your address
+book that looks like:
+<P>
+<CENTER><SAMP>Nickname = bill</SAMP></CENTER><BR>
+<CENTER><SAMP>Address = &quot;William Clinton&quot;</SAMP></CENTER>
+<P>
+Now, when you type &quot;bill&quot; into an
+address field in the composer Alpine will
+find the &quot;bill&quot; entry in your address book.
+It will replace &quot;bill&quot; with
+&quot;William Clinton&quot;.
+It will then search for an entry with that nickname
+in your address book and not find one. If this feature
+is set, Alpine will then attempt to lookup
+&quot;William Clinton&quot; on the LDAP server and find the entry with address
+pres@whitehouse.gov.
+<P>
+A better way to accomplish the same thing is probably to use the feature
+<A HREF="h_config_ldap_opts_ref">&quot;Save-Search-Criteria-Not-Result&quot;</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ldap_opts_ref =====
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Save-Search-Criteria-Not-Result</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Save-Search-Criteria-Not-Result</H1>
+
+Normally when you save the results of an LDAP directory lookup to your
+address book the results of the lookup are saved. If this feature is set
+and the entry being saved was found on this directory server, then the
+search criteria is saved instead of the results of the search. When this
+address book entry is used in the future, instead of copying the results
+from the address book the directory lookup will be done again. This could
+be useful if the copied result might become stale because the data on
+the directory server changes (for example, the entry's email address changes).
+You probably don't want to set this feature if the server is at all slow or
+unreliable.
+<P>
+The way this actually works is that instead of saving the email address
+in your address book, Alpine saves enough information to look up the same
+directory entry again. In particular, it saves the server name and the
+distinguished name of the entry. It's possible that the server administrators
+might change the format of distinguished names on the server, or that the
+entry might be removed from the server. If Alpine notices this, you will be warned
+and a backup copy of the email address will be used. You may want to create
+a new entry in this case, since you will get the annoying warning every
+time you use the old entry. You may do that by Saving the entry to a new
+nickname in the same address book. You will be asked whether or not you
+want to use the backup email address.
+<P>
+A related feature in the Setup/Config screen is
+<A HREF="h_config_add_ldap">&quot;<!--#echo var="FEAT_ldap-result-to-addrbook-add"-->&quot;</A>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_opts_nosub =======
+<HTML>
+<HEAD>
+<TITLE>LDAP FEATURE: Disable-Ad-Hoc-Space-Substitution</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP FEATURE: Disable-Ad-Hoc-Space-Substitution</H1>
+
+Spaces in your input are normally handled specially.
+Each space character is replaced
+by
+<P>
+<CENTER><SAMP>*&nbsp;&lt;SPACE&gt;</SAMP></CENTER>
+<P>
+in the search query (but not by &quot;* &lt;SPACE&gt; *&quot;).
+The reason this is done is so the input string
+<P>
+<CENTER><SAMP>Greg Donald</SAMP></CENTER>
+<P>
+(which is converted to &quot;Greg* Donald&quot;) will match
+the names &quot;Greg Donald&quot;,
+&quot;Gregory Donald&quot;, &quot;Greg F. Donald&quot;, and
+&quot;Gregory F Donald&quot;; but it won't match &quot;Greg McDonald&quot;.
+If the &quot;Search-Rule&quot; you were using was &quot;begins-with&quot;,
+then it would also match the name &quot;Greg Donaldson&quot;.
+<P>
+Turning on this feature will disable this substitution.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ldap_searchtypes =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Search-Type</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Search-Type</H1>
+
+This affects the way that LDAP searches are done.
+In particular, this tells the server where to look for the string to be matched.
+If set to &quot;name&quot; then the string that is being searched for will
+be compared with the string in the
+&quot;Name&quot; field on the server
+(technically, it is the &quot;commonname&quot; field on the server).
+&quot;Surname&quot; means we're looking for a
+match in the &quot;Surname&quot; field on the
+server (actually the &quot;sn&quot; field).
+&quot;Givenname&quot; really is &quot;givenname&quot;
+and &quot;email&quot; is the electronic mail address (this is actually the field
+called &quot;mail&quot; or &quot;electronicmail&quot; on the server).
+The other three types are combinations of
+the types listed so far. &quot;Name-or-email&quot;
+means the string should appear
+in either the &quot;name&quot; field OR the &quot;email&quot; field.
+Likewise, &quot;surname-or-givenname&quot;
+means &quot;surname&quot; OR &quot;givenname&quot;
+and &quot;sur-or-given-or-name-or-email&quot; means the obvious thing.
+<P>
+This search TYPE is combined with the
+search <A HREF="h_config_ldap_searchrules">RULE</A>
+to form the actual search query.
+<P>
+The usual default value for this
+option is &quot;sur-or-given-or-name-or-email&quot;.
+This type of search may be slow on some servers.
+Try &quot;name-or-email&quot;, which is often
+faster, or just &quot;name&quot; if the performance seems to be a problem.
+<P>
+Some servers have been configured with different attribute names for
+these four fields.
+In other words, instead of using the attribute name &quot;mail&quot;
+for the email address field, the server might be configured to use something
+else, for example, &quot;rfc822mail&quot; or &quot;internetemailaddress&quot;.
+Alpine can be configured to use these different attribute names by using
+the four configuration options:
+<P><UL>
+<LI><A HREF="h_config_ldap_email_attr">&quot;EmailAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_cn_attr">&quot;NameAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_sn_attr">&quot;SurnameAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_gn_attr">&quot;GivennameAttribute&quot;</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ldap_searchrules =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Search-Rule</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Search-Rule</H1>
+
+This affects the way that LDAP searches are done.
+If set to &quot;equals&quot; then
+only exact matches count.
+&quot;Contains&quot; means that the string you type in
+is a substring of what you are matching against.
+&quot;Begins-with&quot; and &quot;ends-with&quot;
+mean that the string starts or ends with the string you type in.
+<P>
+Spaces in your input are normally handled specially, but you can turn that
+special handling off with the
+<A HREF="h_config_ldap_opts_nosub">&quot;Disable-Ad-Hoc-Space-Substitution&quot;</A>
+feature.
+<P>
+The usual default value for this option is &quot;begins-with&quot;.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_email_attr =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: EmailAttribute</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: EmailAttribute</H1>
+
+This is the name of the attribute that is searched for when looking for
+an email address. The default value for this option is &quot;mail&quot; or
+&quot;electronicmail&quot;.
+If the server you are using uses a different attribute name for the email
+address, put that attribute name here.
+<P>
+This will affect the search filter used if your Search-Type is one that
+contains a search for &quot;email&quot;.
+It will also cause the attribute value matching this attribute name to be used
+as the email address when you look up an entry from the composer.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_sn_attr =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: SurnameAttribute</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: SurnameAttribute</H1>
+
+This is the name of the attribute that is searched for when looking for
+the surname of the entry. The default value for this option is &quot;sn&quot;.
+If the server you are using uses a different attribute name for the surname,
+put that attribute name here.
+This will affect the search filter used if your Search-Type is one that
+contains a search for &quot;surname&quot;.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_gn_attr =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: GivennameAttribute</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: GivennameAttribute</H1>
+
+This is the name of the attribute that is searched for when looking for
+the given name of the entry. The default value for this option is &quot;givenname&quot;.
+If the server you are using uses a different attribute name for the given name,
+put that attribute name here.
+This will affect the search filter used if your Search-Type is one that
+contains a search for &quot;givenname&quot;.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_cn_attr =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: NameAttribute</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: NameAttribute</H1>
+
+This is the name of the attribute that is searched for when looking for
+the name of the entry. The default value for this option is &quot;cn&quot;, which
+stands for common name.
+If the server you are using uses a different attribute name for the name,
+put that attribute name here.
+This will affect the search filter used if your Search-Type is one that
+contains a search for &quot;name&quot;.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_time =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Timelimit</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Timelimit</H1>
+
+This places a limit on the number of seconds the LDAP search will continue.
+The default is 30 seconds. A value of 0 means no limit. Note that some servers
+may place limits of their own on searches.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_size =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Sizelimit</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Sizelimit</H1>
+
+This places a limit on the number of entries returned by the LDAP server.
+A value of 0 means no limit. The default is 0. Note that some servers
+may place limits of their own on searches.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_ldap_cust =======
+<HTML>
+<HEAD>
+<TITLE>LDAP OPTION: Custom-Search-Filter</TITLE>
+</HEAD>
+<BODY>
+<H1>LDAP OPTION: Custom-Search-Filter</H1>
+
+This one is for advanced users only! If you define this, then the
+&quot;Search-Type&quot; and &quot;Search-Rule&quot; defined are both ignored.
+However, the feature
+<A HREF="h_config_ldap_opts_nosub">&quot;Disable-Ad-Hoc-Space-Substitution&quot;</A>
+is still in effect.
+That is, the space substitution will take place even in a custom filter unless
+you disable it.
+<P>
+If your LDAP service stops working and you suspect it might be because
+of your custom filter, just delete this filter and try using the
+&quot;Search-Type&quot; and &quot;Search-Rule&quot; instead.
+Another option that sometimes causes trouble is the
+<A HREF="h_config_ldap_base">&quot;Search-Base&quot;</A> option.
+<P>
+This variable may be set to the string representation of an LDAP search
+filter (see RFC1960). In the places where you want the address string to be
+substituted in, put a '%s' in this filter string. Here are some examples:
+<P>
+A &quot;Search-Type&quot; of &quot;name&quot; with &quot;Search-Rule&quot; of &quot;begins-with&quot;
+is equivalent to the &quot;Custom-Search-Filter&quot;
+<PRE>
+ (cn=%s*)
+</PRE>
+When you try to match against the string &quot;string&quot; the program replaces
+the &quot;%s&quot; with &quot;string&quot; (without the quotes). You may have multiple &quot;%s&quot;'s and
+they will all be replaced with the string. There is a limit of 10 &quot;%s&quot;'s.
+<P>
+A &quot;Search-Type&quot; of &quot;name-or-email&quot; with &quot;Search-Rule&quot;
+of &quot;contains&quot; is equivalent to
+<PRE>
+ (|(cn=*%s*)(mail=*%s*))
+</PRE>
+<P>
+If your server uses a different attribute <EM>name</EM> than
+Alpine uses by default,
+(for example, it uses &quot;rfc822mail&quot; instead of &quot;mail&quot;),
+then you may be able to use one or more of the four attribute configuration
+options instead of defining a custom filter:
+<P><UL>
+<LI><A HREF="h_config_ldap_email_attr">&quot;EmailAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_cn_attr">&quot;NameAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_sn_attr">&quot;SurnameAttribute&quot;</A>
+</UL>
+<P><UL>
+<LI><A HREF="h_config_ldap_gn_attr">&quot;GivennameAttribute&quot;</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_comment =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Comment Explained</TITLE>
+</HEAD>
+<BODY>
+This is a comment to help you remember what this entry is. The WhereIs
+command searches comments so that it is easier to find an entry with a comment
+you know about attached to it. This field is not used in the outgoing message.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_addrs =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Lists</TITLE>
+</HEAD>
+<BODY>
+<H1>Addressbook Lists</H1>
+
+This is a list of addresses to send to when sending to this address book
+entry. Each member of the list may be an address or another nickname from
+any of your address books. If it is an address, it is OK to include the
+full name field as well as the electronic address portion of that address.
+For example, the following are all legitimate entries in this field:
+
+<DL><DT>&nbsp;</DT>
+<DD>john&nbsp;&nbsp;&nbsp;&nbsp;(a nickname in your address book)
+<DD>jdoe@some.domain
+<DD>John Doe &lt;jdoe@some.domain&gt;
+</DL>
+
+The addresses should be listed separated by commas, just like you would
+enter them from the composer.
+
+<P>
+
+The only difference between a distribution list and a simple entry with a
+single address, is that a distribution list has more than one address
+listed in the Addresses: field, whereas a simple personal entry has just
+one address.
+
+<P>
+
+For individual address book entries, if there is a full name in the
+Fullname: field (filling in the Fullname: field is not required), it is
+used. If the full name is specified in the Address: field and not in the
+Fullname: field, then the full name from the Address: field is used.
+
+<P>
+
+If you type the nickname of a distribution list from one of your address
+books in the Lcc: field, then the full name of that list is used in the
+To: field. If you put a list in the To: or Cc: fields, that list will be
+expanded into all of its addresses. If the list has a full name, then
+that will appear at the beginning of the addresses.
+
+<DL><DT>&nbsp;</DT>
+<DD>Sewing Club &lt;john@somewhere&gt;, nancy@something.else, Sal
+&lt;sal@here.there&gt;
+</DL>
+
+If the first address in the distribution list also has a full name, then
+the list full name and that full name are combined into something like the
+following:
+
+<DL><DT>&nbsp;</DT>
+<DD>Sewing Club -- John Smith &lt;john@somewhere&gt;
+</DL>
+
+
+If you specify a list via Lcc, the full name is used in the To: line. If
+you specify a list in the To: or Cc: fields, then it uses the same method
+as for individual entries for filling in the full name.
+
+<P>
+
+For help with editing and navigation commands, check the Help for the
+Nickname: field.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_nick =======
+<HTML>
+<HEAD>
+<TITLE>Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Nickname Explained</H1>
+
+This is a nickname to help you.
+You should have a different nickname for each role you define.
+The nickname will be used in the SETUP ROLE RULES screen to allow you to
+pick a role to edit.
+It will also be used when you send a message to let you know you are
+sending with a different role than you use by default, and
+it will be useful for choosing a role when composing with the Role command
+or when composing with one of the Role Uses set to With Confirmation.
+This field is not used in the outgoing message.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_comment =======
+<HTML>
+<HEAD>
+<TITLE>Comment Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Comment Explained</H1>
+
+This is a comment to help you.
+This comment does not play any functional role, it is simply an optional
+comment to help you remember what the rule is for.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_other_nick =======
+<HTML>
+<HEAD>
+<TITLE>Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Nickname Explained</H1>
+
+This is a nickname to help you.
+You should have a different nickname for each rule you define.
+The nickname will be used in the SETUP OTHER RULES screen to allow you to
+pick a rule to edit.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_score_nick =======
+<HTML>
+<HEAD>
+<TITLE>Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Nickname Explained</H1>
+
+This is a nickname to help you.
+You should have a different nickname for each scoring rule you define.
+The nickname will be used in the SETUP SCORING RULES screen to allow you to
+pick a rule to edit.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_incol_nick =======
+<HTML>
+<HEAD>
+<TITLE>Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Nickname Explained</H1>
+
+This is a nickname to help you.
+You should have a different nickname for each color rule you define.
+The nickname will be used in the SETUP INDEX COLOR RULES screen to allow you to
+pick a rule to edit.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_nick =======
+<HTML>
+<HEAD>
+<TITLE>Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Nickname Explained</H1>
+
+This is a nickname to help you.
+You should have a different nickname for each filtering rule you define.
+The nickname will be used in the SETUP FILTERING RULES screen to allow you to
+pick a rule to edit.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_score_topat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;To:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;To:&quot; Pattern Explained</H1>
+
+Any text you enter as the &quot;To pattern&quot;
+will be compared to the recipients from the To: line of
+the message being scored.
+When the text you entered matches
+all or part of the To: line of a message, then the Score Value
+you have specified will be added to the score for the message.
+(Any other non-blank parts of the Pattern must match, too.)
+<P>
+
+You may enter a complete email address, part of an address, or a
+list of addresses or partial addresses.
+For example:
+<P>
+
+<PRE>
+ To pattern = friend@public.com
+
+ To pattern = rated.net
+
+ To pattern = xxx@adults.com
+ admin@msn.com
+ fool@motleyfool.com
+</PRE>
+
+<P>
+Each of those are valid To patterns.
+<P>
+
+Messages match those patterns if any of the
+addresses in the To: line of the message contains the pattern.
+If the pattern is a list of patterns
+(like the last example above) then it is a match if any of the patterns in
+the list match any of the addresses in the To: line.
+(It is not possible to specify two addresses that must <EM>BOTH</EM> be
+present for a match.
+It is only possible to specify that <EM>EITHER</EM> address1 <EM>OR</EM>
+address2 must be present.
+That is exactly what using a list does.)
+<P>
+
+Some messages may be &quot;bounced&quot; to you, and will
+have a &quot;Resent-To:&quot; header line.
+If the message contains a Resent-To: line
+and the feature <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> is turned on,
+Alpine will look for
+matches to your &quot;To patterns&quot; there, and <EM>NOT</EM> in
+the original To: line.
+<P>
+
+When entering a pattern, you may choose an address from your address book
+with the &quot;T&quot; command.
+<P>
+
+It is possible to add a <EM>NOT</EM> to the To Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the To pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+addresses in the To: line and the list of To patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the To
+pattern, the pattern will look like:
+<P>
+<PRE>
+ To pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their To field, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the To pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! To pattern = frizzle
+</PRE>
+<P>
+You are not limited to using the six standard header patterns that are
+normally shown (To, From, Sender, Cc, News, and Subject).
+You may add any other header to a Pattern by
+using the &quot;eXtraHdr&quot; command to specify a different
+message header line; and then the Add or Change command to fill in
+a pattern for the new header line, just like you would for a standard header.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_incol_topat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;To:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;To:&quot; Pattern Explained</H1>
+
+Any text you enter as the &quot;To pattern&quot;
+will be compared to the recipients from the To: lines of
+the messages in the index.
+When the text you entered matches
+all or part of the To: line of a message, then the Index Line Color you have
+specified will be used for that line in the index.
+(Any other non-blank parts of the Pattern must match, too.)
+<P>
+
+You may enter a complete email address, part of an address, or a
+list of addresses or partial addresses.
+For example:
+<P>
+
+<PRE>
+ To pattern = friend@public.com
+ To pattern = rated.net
+ To pattern = xxx@adults.com
+ admin@msn.com
+ fool@motleyfool.com
+</PRE>
+
+<P>
+Each of those are valid To patterns.
+<P>
+
+Messages match those patterns if any of the
+addresses in the To: line of the message contains the pattern.
+If the pattern is a list of patterns
+(like the last example above) then it is a match if any of the patterns in
+the list match any of the addresses in the To: line.
+(It is not possible to specify two addresses that must <EM>BOTH</EM> be
+present for a match.
+It is only possible to specify that <EM>EITHER</EM> address1 <EM>OR</EM>
+address2 must be present.
+That is exactly what using a list does.)
+<P>
+
+Some messages may be &quot;bounced&quot; to you, and will
+have a &quot;Resent-To:&quot; header line.
+If the message contains a Resent-To: line
+and the feature <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> is turned on,
+Alpine will look for
+matches to your &quot;To patterns&quot; there, and <EM>NOT</EM> in
+the original To: line.
+<P>
+
+When entering a pattern, you may choose an address from your address book
+with the &quot;T&quot; command.
+<P>
+
+It is possible to add a <EM>NOT</EM> to the To Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the To pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+addresses in the To: line and the list of To patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the To
+pattern, the pattern will look like:
+<P>
+<PRE>
+ To pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their To field, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the To pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! To pattern = frizzle
+</PRE>
+<P>
+
+You are not limited to using the six standard header patterns that are
+normally shown (To, From, Sender, Cc, News, and Subject).
+You may add any other header to a Pattern by
+using the &quot;eXtraHdr&quot; command to specify a different
+message header line; and then the Add or Change command to fill in
+a pattern for the new header line, just like you would for a standard header.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_other_topat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;To:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;To:&quot; Pattern Explained</H1>
+
+For some of the OTHER RULES actions, there is no message that is being
+compared against.
+If that is the case, then only the Current Folder Type is checked.
+In particular, this To pattern is ignored.
+Actions that fall into this category include both
+Sort Order and Index Format.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_topat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;To:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;To:&quot; Pattern Explained</H1>
+
+Any text you enter as the &quot;To pattern&quot;
+will be compared to the recipients from the To: line of
+messages when Alpine opens folders.
+When the text you entered matches
+all or part of the To: line of a message, then the Filter Action you have
+specified will be carried out.
+(Any other non-blank parts of the Pattern must match, too.)
+<P>
+
+You may enter a complete email address, part of an address, or a
+list of addresses or partial addresses.
+For example:
+<P>
+
+<PRE>
+ To pattern = friend@public.com
+ To pattern = rated.net
+ To pattern = xxx@adults.com
+ admin@msn.com
+ fool@motleyfool.com
+</PRE>
+
+<P>
+Each of those are valid To patterns.
+<P>
+
+Messages match those patterns if any of the
+addresses in the To: line of the message contains the pattern.
+If the pattern is a list of patterns
+(like the last example above) then it is a match if any of the patterns in
+the list match any of the addresses in the To: line.
+(It is not possible to specify two addresses that must <EM>BOTH</EM> be
+present for a match.
+It is only possible to specify that <EM>EITHER</EM> address1 <EM>OR</EM>
+address2 must be present.
+That is exactly what using a list does.)
+<P>
+
+Some messages may be &quot;bounced&quot; to you, and will
+have a &quot;Resent-To:&quot; header line.
+If the message contains a Resent-To: line
+and the feature <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> is turned on,
+Alpine will look for
+matches to your &quot;To patterns&quot; there, and <EM>NOT</EM> in
+the original To: line.
+<P>
+
+When entering a pattern, you may choose an address from your address book
+with the &quot;T&quot; command.
+<P>
+
+It is possible to add a <EM>NOT</EM> to the To Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the To pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+addresses in the To: line and the list of To patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the To
+pattern, the pattern will look like:
+<P>
+<PRE>
+ To pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their To field, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the To pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! To pattern = frizzle
+</PRE>
+<P>
+
+You are not limited to using the six standard header patterns that are
+normally shown (To, From, Sender, Cc, News, and Subject).
+You may add any other header to a Pattern by
+using the &quot;eXtraHdr&quot; command to specify a different
+message header line; and then the Add or Change command to fill in
+a pattern for the new header line, just like you would for a standard header.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_topat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;To:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;To:&quot; Pattern Explained</H1>
+
+Any text you enter as the &quot;To pattern&quot;
+will be compared to the recipients from the To: line of
+the message being replied to or forwarded.
+(Any other non-blank parts of the Pattern must match, too.)
+In the case of the Compose command, this pattern and the other header
+patterns are ignored.
+<P>
+
+You may enter a complete email address, part of an address, or a
+list of addresses or partial addresses.
+For example:
+<P>
+
+<PRE>
+ To pattern = friend@public.com
+ To pattern = rated.net
+ To pattern = xxx@adults.com
+ admin@msn.com
+ fool@motleyfool.com
+</PRE>
+
+<P>
+Each of those are valid To patterns.
+<P>
+
+Messages match those patterns if any of the
+addresses in the To: line of the message contains the pattern.
+If the pattern is a list of patterns
+(like the last example above) then it is a match if any of the patterns in
+the list match any of the addresses in the To: line.
+(It is not possible to specify two addresses that must <EM>BOTH</EM> be
+present for a match.
+It is only possible to specify that <EM>EITHER</EM> address1 <EM>OR</EM>
+address2 must be present.
+That is exactly what using a list does.)
+<P>
+
+Some messages may be &quot;bounced&quot; to you, and will
+have a &quot;Resent-To:&quot; header line.
+If the message contains a Resent-To: line
+and the feature <A HREF="h_config_use_resentto"><!--#echo var="FEAT_use-resent-to-in-rules"--></A> is turned on,
+Alpine will look for
+matches to your &quot;To patterns&quot; there, and <EM>NOT</EM> in
+the original To: line.
+<P>
+
+When entering a pattern, you may choose an address from your address book
+with the &quot;T&quot; command.
+<P>
+
+It is possible to add a <EM>NOT</EM> to the To Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the To pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+addresses in the To: line and the list of To patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the To
+pattern, the pattern will look like:
+<P>
+<PRE>
+ To pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their To field, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the To pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! To pattern = frizzle
+</PRE>
+<P>
+
+You are not limited to using the six standard header patterns that are
+normally shown (To, From, Sender, Cc, News, and Subject).
+You may add any other header to a Pattern by
+using the &quot;eXtraHdr&quot; command to specify a different
+message header line; and then the Add or Change command to fill in
+a pattern for the new header line, just like you would for a standard header.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_frompat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;From:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;From:&quot; Pattern Explained</H1>
+
+This is just like the &quot;To pattern&quot; except that it is compared with
+the address in the From: line of the message
+instead of the addresses from the To: line.
+See the help for the To pattern for more information on header patterns.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_senderpat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;Sender:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;Sender:&quot; Pattern Explained</H1>
+
+This is just like the &quot;To pattern&quot; except that it is compared with
+the address from the Sender: line of the message
+instead of the addresses from the To: line.
+See the help for the To pattern for more information on header patterns.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_ccpat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;Cc:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;Cc:&quot; Pattern Explained</H1>
+
+This is just like the &quot;To pattern&quot; except that it is compared with
+the addresses from the Cc: line of the message
+instead of the addresses from the To: line.
+See the help for the To pattern for more information on header patterns.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_recippat =======
+<HTML>
+<HEAD>
+<TITLE>Recipient Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Recipient Pattern Explained</H1>
+
+This is just like the &quot;To pattern&quot; except that it is compared with
+the addresses from both the To: line and the Cc: line of the
+message instead of just the addresses from the To: line.
+In other words, it is considered a match if the pattern matches
+<EM>EITHER</EM> an address in the To: line <EM>OR</EM> an address
+in the Cc: line.
+(Notice that defining the Recipient pattern does not have the same
+effect as defining both the To and Cc patterns.
+Recipient is To <EM>OR</EM> Cc; not To <EM>AND</EM> Cc.
+It is equivalent to having two different rules;
+one with a To pattern and the other with the same Cc pattern.)
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_particpat =======
+<HTML>
+<HEAD>
+<TITLE>Participant Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Participant Pattern Explained</H1>
+
+This is just like the &quot;To pattern&quot; except that it is compared with
+the addresses from the From: line, the To: line, and the Cc: line of the
+message instead of just the addresses from the To: line.
+In other words, it is considered a match if the pattern matches
+<EM>EITHER</EM> an address in the From: line, <EM>OR</EM> an address
+in the To: line, <EM>OR</EM> an address in the Cc: line.
+(Notice that defining the Participant pattern does not have the same
+effect as defining all of the From, To, and Cc patterns.
+Participant is From <EM>OR</EM> To <EM>OR</EM> Cc; not
+From <EM>AND</EM> To <EM>AND</EM> Cc.
+It is equivalent to having three different rules;
+one with a From pattern, another with the same To pattern, and a third with
+the same Cc pattern.)
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_newspat =======
+<HTML>
+<HEAD>
+<TITLE>News Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>News Pattern Explained</H1>
+
+If this pattern is non-blank, then for this rule to be considered a
+match, at least one of the newsgroups from
+the Newsgroups line of the message must match this pattern.
+If this pattern is a list of patterns, then at least one of the
+newsgroups must match at least one of the patterns.
+(Any other non-blank parts of the Pattern must match, too.)
+<P>
+It is possible to add a <EM>NOT</EM> to the News Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the News pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+addresses in the Newsgroups: line and the list of News patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the News
+pattern, the pattern will look like:
+<P>
+<PRE>
+ News pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their Newsgroups header, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the News pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! News pattern = frizzle
+</PRE>
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_subjpat =======
+<HTML>
+<HEAD>
+<TITLE>&quot;Subject:&quot; Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>&quot;Subject:&quot; Pattern Explained</H1>
+
+This is similar to the other parts of the Pattern.
+It is compared with
+the contents from the Subject of the message.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+It is possible to add a <EM>NOT</EM> to the Subject Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the Subject pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+text in the Subject: line and the list of Subject patterns.
+<P>
+
+If you wish to have a header pattern that is not one of the six standard
+header patterns, you may add it with the &quot;eXtraHdr&quot; command.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_alltextpat =======
+<HTML>
+<HEAD>
+<TITLE>AllText Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>AllText Pattern Explained</H1>
+
+This is similar to the header patterns.
+Instead of comparing with text in a particular header field it
+is compared with all of the text in the message header and body.
+<P>
+It is possible to add a <EM>NOT</EM> to the AllText Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the AllText pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+text of the message and the list of AllText patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the AllText
+pattern, the pattern will look like:
+<P>
+<PRE>
+ AllText pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+the text of the message, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the AllText pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! AllText pattern = frizzle
+</PRE>
+<P>
+It is possible that you may notice degraded performance when using
+AllText Patterns.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_bodytextpat =======
+<HTML>
+<HEAD>
+<TITLE>BodyText Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>BodyText Pattern Explained</H1>
+
+This is similar to the header patterns.
+Instead of comparing with text in a particular header field it
+is compared with all of the text in the message body.
+<P>
+It is possible to add a <EM>NOT</EM> to the BodyText Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the BodyText pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+text of the body of the message and the list of BodyText patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the BodyText
+pattern, the pattern will look like:
+<P>
+<PRE>
+ BdyText pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their BodyText, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the BodyText pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! BodyText pattern = frizzle
+</PRE>
+<P>
+It is possible that you may notice degraded performance when using
+BodyText Patterns.
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_charsetpat =======
+<HTML>
+<HEAD>
+<TITLE>Character Set Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Character Set Pattern Explained</H1>
+
+A message may use one or more character sets.
+This part of the Pattern matches messages that make use of
+certain specified character sets.
+It will be considered a match if a message uses any of the character
+sets in the list you give here.
+
+<P>
+When filling in a value for this field, you may use
+the &quot;T&quot; command, which presents you with a large list of
+possible character sets to choose from.
+You may also just type in the name of a character set, and it need not
+be one that Alpine knows about.
+
+<P>
+Besides actual character set names (for example, ISO-8859-7, KOI8-R, or
+GB2312) you may also use some shorthand names that Alpine provides.
+These names are more understandable shorthand names for sets of
+character set names.
+Two examples are &quot;Cyrillic&quot; and &quot;Greek&quot;.
+Selecting one of these shorthand names is equivalent to selecting all of
+the character sets that make up the set.
+You can see all of these shorthand names and the lists of character sets
+they stand for by typing the &quot;T&quot; command.
+
+<P>
+For the purposes of this Pattern,
+Alpine will search through a message for all of the text parts and
+collect the character sets declared for each part.
+It will also look in the Subject line for a character set used there.
+Alpine does not actually look at the text of the message or the text
+of the Subject to determine if a declared character set is actually
+used, it looks only at the declarations themselves in the MIME part headers
+and in the Subject.
+
+<P>
+It is possible to add a <EM>NOT</EM> to the Character Set Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the Character Set pattern so that
+it has the opposite meaning.
+It will be considered a match if none of the character sets in the
+list are used in a message.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!GB2312&quot; into the
+Character Set pattern, the pattern will look like:
+<P>
+<PRE>
+ Charset pattern = !GB2312
+</PRE>
+<P>
+This means you want to match the 7 character sequence &quot;!GB2312&quot;.
+In order to match messages that do not have the
+character set &quot;GB2312&quot;
+set, first type the characters &quot;GB2312&quot; followed
+by carriage return for the value of the Character Set pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! Charset pattern = GB2312
+</PRE>
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_keywordpat =======
+<HTML>
+<HEAD>
+<TITLE>Keyword Pattern Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Keyword Pattern Explained</H1>
+
+A folder may have user-defined keywords.
+These are similar to the Important flag, which the user may set using the
+Flag command.
+The difference is that the Important flag is always present for each folder.
+User-defined keywords are picked by the user.
+You may add new keywords by defining them in the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen.
+After you have added a potential keyword with the <!--#echo var="VAR_keywords"--> option,
+the Flag command may be used to set or clear the keyword on individual messages.
+If you have given a keyword a nickname when configuring it,
+that nickname may be used instead of the actual keyword.
+
+<P>
+When filling in a value for this field, it may be easiest to use
+the &quot;T&quot; command, which presents you with a list of the keywords
+you have defined to choose from.
+
+<P>
+This part of the Pattern matches messages with certain keywords set.
+It will be considered a match if a message has any of the keywords in the
+list set.
+A keyword that you have not defined using the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen
+will not be a match.
+
+<P>
+It is possible to add a <EM>NOT</EM> to the Keyword Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the Keyword pattern so that it has the opposite meaning.
+It will be considered a match if none of the keywords in the list are set
+for a message.
+A keyword that you have not defined using the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen
+will not be a match, so a <EM>NOT</EM> of that keyword does match.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the Keyword
+pattern, the pattern will look like:
+<P>
+<PRE>
+ Keyword pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have the keyword &quot;frizzle&quot;
+set, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the Keyword pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! Keyword pattern = frizzle
+</PRE>
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_arbpat =======
+<HTML>
+<HEAD>
+<TITLE>Extra Header Patterns Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Extra Header Patterns Explained</H1>
+
+The header patterns that come after the Participant pattern but before the
+AllText pattern are extra header patterns that you have added to a rule's
+Pattern. These are just like the other header patterns except that
+the contents of the particular header listed on the left hand side will
+be used for comparisons.
+<P>
+The &quot;eXtraHdr&quot; command may be used to add more of these
+header patterns to the rule you are editing.
+<P>
+The &quot;RemoveHdr&quot; command may be used to delete the highlighted
+extra header pattern from the rule you are editing.
+<P>
+It is possible to add a <EM>NOT</EM> to the Extra Header Pattern meaning with the
+&quot;!&quot; &quot;toggle NOT&quot; command.
+This changes the meaning of the pattern so that it has the opposite meaning.
+It will be considered a match if there are no matches between the
+text in the header line and the list of patterns.
+<P>
+Don't make the mistake of putting the &quot;!&quot; in the data field for
+the pattern.
+For example, if you type the characters &quot;!frizzle&quot; into the
+pattern, the pattern will look like:
+<P>
+<PRE>
+ Xyz pattern = !frizzle
+</PRE>
+<P>
+This means you want to match the 8 character sequence &quot;!frizzle&quot;.
+In order to match messages that do not have &quot;frizzle&quot; in
+their Xyz field, first type the characters &quot;frizzle&quot; followed
+by carriage return for the value of the pattern, then negate it
+by typing the &quot;!&quot; command.
+It should end up looking like
+<P>
+<PRE>
+ ! Xyz pattern = frizzle
+</PRE>
+
+<P>
+A technicality: Since comma is the character used to separate multiple
+values in a pattern field, you have to escape comma with a backslash (&#92;) if
+you want to include a literal comma in the field.
+In other words, if you type a backslash followed by a comma it will
+be interpreted as a comma by Alpine, instead of as a separator between
+pattern values.
+All other backslashes are literal backslashes and should not be escaped.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_cat_cmd =======
+<HTML>
+<HEAD>
+<TITLE>Categorizer Command Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Categorizer Command Explained</H1>
+
+This is a command that is run with its standard input set to the message
+being checked and its standard output discarded.
+The full directory path should be specified.
+The command will be run and then its exit status will be checked against
+the Exit Status Interval, which defaults to just the value zero.
+If the exit status of the command falls in the interval, it is considered
+a match, otherwise it is not a match.
+<P>
+
+This option may actually be a list of commands.
+The first one that exists and is executable is used.
+That makes it possible to use the same configuration with Unix Alpine and
+PC-Alpine.
+<P>
+
+If none of the commands in the list exists and is executable then the rule
+is <EM>not</EM> a match.
+If it is possible that the command may not exist, you should be careful
+to structure your rules so that nothing destructive
+happens when the command does not exist.
+For example, you might have a filter that filters away spam when there is
+a match but does nothing when there is not a match.
+That would cause no harm if the command didn't exist.
+However, if you have a filter that filters away spam when there is not
+a match and keeps it when there is a match, that would filter everything
+if the categorizer command didn't exist.
+<P>
+Here is an <A HREF="h_config_role_cat_cmd_example">example</A>
+setup for the bogofilter filter.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_cat_cmd_example =======
+<HTML>
+<HEAD>
+<TITLE>Categorizer Command Example</TITLE>
+</HEAD>
+<BODY>
+<H1>Categorizer Command Example</H1>
+
+Bogofilter
+(<A HREF="http://bogofilter.sourceforge.net/">http://bogofilter.sourceforge.net/</A>)
+is a mail filter that attempts to classify mail as spam or
+non-spam using statistical analysis of the message content.
+When run with no arguments and a message as standard input, it exits with
+exit status 0 if it thinks a message is spam and 1 if it thinks
+it is not spam.
+To use bogofilter as your Categorizer Command you would simply set Command to
+the pathname of the bogofilter program.
+For example,
+<P>
+<CENTER><SAMP>Command = /usr/local/bin/bogofilter</SAMP></CENTER>
+<P>
+Exit status of zero is what you are interested in, so you'd set the
+Exit Status Interval to
+<P>
+<CENTER><SAMP>Exit Status Interval = (0,0)</SAMP></CENTER>
+<P>
+
+In order to prevent downloading an entire huge message to check for spam, you
+might want to set the Character Limit to a few thousand characters (the
+assumption being that the spam will reveal itself in those characters)
+<P>
+<CENTER><SAMP>Character Limit = 50000</SAMP></CENTER>
+<P>
+
+You would probably use bogofilter in an Alpine Filter Rule, and have the action
+be to move the message to a spam folder.
+It would usually be wise to also check the &quot;Message is Recent&quot;
+part of the rule so that messages are only checked when they first arrive,
+and to restrict the Current Folder Type to just your INBOX.
+The reason for checking only Recent messages is to save the time it takes
+to run bogofilter on each message.
+As an experiment, you might start out by using this in an Indexcolor Rule
+instead of a Filter Rule.
+In that case, you probably wouldn't check the Recent checkbox.
+<P>
+The use described above assumes that you are somehow maintaining bogofilter's
+database of words associated with spam and non-spam messages.
+One way to start your database would be to select a bunch of spam messages
+in Alpine (you might Save spam messages to a special folder or use Alpine's
+Select command to select several) and then Apply
+(<A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>)
+a pipe command to the spam messages.
+For example, you could have a shell script or an alias
+called <EM>this_is_spam</EM>, which would simply be the command
+<P>
+<CENTER><SAMP>bogofilter -s</SAMP></CENTER>
+<P>
+
+It is probably best to use the pipe command's Raw Text, With Delimiter,
+and Free Output options,
+which are at the bottom of the screen when you type the pipe command.
+That's because bogofilter expects the raw message as input, and uses
+the Delimiters to tell when a new message starts.
+You would not need to use a separate pipe for each message, because
+bogofilter can handle multiple messages at once.
+<P>
+Similarly, you would select a group of non-spam messages
+and run them through a <EM>this_is_nonspam</EM> script
+that was something like
+<P>
+<CENTER><SAMP>bogofilter -n</SAMP></CENTER>
+<P>
+
+For the more adventurous, the next step might be to automate the upkeep of
+the bogofilter database.
+It might make more sense to have bogofilter be part of the delivery process,
+but it is also possible to do it entirely from within Alpine.
+Instead of using just plain &quot;bogofilter&quot; as the Categorizer Command,
+the &quot;-u&quot; argument will cause bogofilter to update the database.
+<P>
+<CENTER><SAMP>Command = /usr/local/bin/bogofilter -u</SAMP></CENTER>
+<P>
+You'd want a couple more aliases or shell scripts called something like
+<EM>change_to_spam</EM>
+<P>
+<CENTER><SAMP>bogofilter -Ns</SAMP></CENTER>
+<P>
+and
+<EM>change_to_nonspam</EM>
+<P>
+<CENTER><SAMP>bogofilter -Sn</SAMP></CENTER>
+<P>
+When you run across a message in your INBOX that should have been
+classified as spam you would pipe it to the change_to_spam script, and
+when you run across a message in your spam folder that should have been
+left in your INBOX you would pipe it through change_to_nonspam.
+
+<P>
+There is a technical problem with this approach.
+Alpine may check your filters more than once.
+In particular, every time you start Alpine the filters will be checked for
+each message.
+Also, if you have any filters that depend on message state (New, Deleted, etc.)
+then Alpine will recheck for matches in messages that have changed state
+at the time you close the folder and before expunging.
+This is usually ok.
+However, in this case it is a problem because the command
+<P>
+<CENTER><SAMP>Command = /usr/local/bin/bogofilter -u</SAMP></CENTER>
+<P>
+has the side effect of updating the database.
+So you run the risk of updating the database multiple times for a single
+message instead of updating it just once per message.
+There are some ways to work around this problem.
+What you need is a way to mark the message after you have run the filter.
+One way to mark messages is with the use of a keyword (say &quot;Bogo&quot;).
+Besides having the filter move the message to a spam folder, also have it
+set the Bogo keyword.
+(Note that you will have to set up the &quot;Bogo&quot; keyword in the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in Setup/Config.)
+This rule can only set the Bogo keyword for the messages that it matches.
+You will also need to add a second rule right after this one that
+matches all the messages that don't have the Bogo keyword set
+(put the keyword in the Keyword pattern and toggle
+the Not with the ! command)
+and takes the action of setting it.
+Then change the &quot;bogofilter -u&quot; rule so that it won't be a match
+(and so it won't re-run the bogofilter command) if the keyword is already
+set.
+<P>
+What you will end up with is a rule that runs &quot;bogofilter -u&quot;
+on all messages that don't have the Bogo keyword set.
+This will have the side effect of inserting that message in the bogofilter
+database, match or not.
+If this rule matches (it is spam), the Bogo keyword will be set and
+the message will be moved to a spam folder.
+If it does not match, the
+following rule will mark the message by turning on the keyword.
+This second rule should be a non-terminating
+(<A HREF="h_config_filt_opts_nonterm">Dont-Stop-Even-if-Rule-Matches</A>)
+rule so that it doesn't stop the filtering process before the rest of
+your rules are consulted.
+
+<P>
+In summary, the first rule is something like
+<PRE>
+ Nickname = bogofilter -u rule
+ Current Folder Type =
+ (*) Specific
+ Folder = INBOX
+
+ ! Keyword pattern = Bogo
+
+ External Categorizer Commands =
+ Command = /usr/local/bin/bogofilter -u
+ Exit Status Interval = (0,0)
+ Character Limit = <No Value Set: using "-1"> (optionally set this)
+
+ Filter Action =
+ (*) Move
+ Folder = spam
+
+ Set These Keywords = Bogo
+</PRE>
+<P>
+and the following rule is
+<PRE>
+ Nickname = Set Bogo Keyword
+ Current Folder Type =
+ (*) Specific
+ Folder = INBOX
+
+ ! Keyword pattern = Bogo
+
+ Filter Action =
+ (*) Just Set Message Status
+
+ Set These Keywords = Bogo
+
+ Features =
+ [X] dont-stop-even-if-rule-matches
+</PRE>
+<P>
+If it is possible for you to insert bogofilter in the delivery process instead
+of having it called from Alpine you could prevent having to wait
+for the bogofilter processing while you read your mail.
+You would have bogofilter add a header to the message at the time of delivery
+that identified it as spam or nonspam.
+With this method, you could avoid using a Categorizer Command while running Alpine,
+and just match on the header instead.
+You might still want to use the scripts mentioned above to initialize the
+database or to re-classify wrongly classified messages.
+
+<P>
+Finally, it isn't for the faint-hearted,
+but it is also possible to run bogofilter from PC-Alpine.
+You can install Cygwin from
+<A HREF="http://www.cygwin.com/">http://www.cygwin.com/</A> and
+then compile bogofilter in the cygwin environment, and run it from
+within PC-Alpine.
+You would end up with a Categorizer command that looked something like
+<P>
+<CENTER><SAMP>Command = C:&#92;cygwin&#92;bin&#92;bogofilter.exe -u</SAMP></CENTER>
+<P>
+Note that the &quot;.exe&quot; extension is explicit,
+and that the bogofilter.exe executable should be in the same directory
+as cygwin1.dll.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_cat_status =======
+<HTML>
+<HEAD>
+<TITLE>Exit Status Interval Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Exit Status Interval Explained</H1>
+
+The categorizer command is run and the result is the exit status of
+that command.
+If that exit status falls in the Exit Status Interval
+then it is considered a match, otherwise it is not a match.
+Of course for the entire rule to match, it must also be checked against
+the other defined parts of the Pattern.
+<P>
+The Exit Status Interval defaults to the single value 0 (zero).
+If you define it, it should be set to something like:
+<P>
+<CENTER><SAMP>(min_exit_value,max_exit_value)</SAMP></CENTER>
+<P>
+where &quot;min_exit_value&quot; and &quot;max_exit_value&quot; are integers.
+The special values &quot;INF&quot; and &quot;-INF&quot; may be used for large
+positive and negative integers.
+<P>
+Actually, a list of intervals may be used if you wish.
+A list would look like
+<P>
+<CENTER><SAMP>(min_exit_value1,max_exit_value1),(min_exit_value2,max_exit_value2),...</SAMP></CENTER>
+<P>
+When there is an Exit Status Interval defined, it is a match if the exit status
+of the categorizer command is contained in any of the intervals.
+The intervals include both endpoints.
+<P>
+The default interval is
+<P>
+<CENTER><SAMP>(0,0)</SAMP></CENTER>
+<P>
+and it matches only if the command exits with exit status equal to zero.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_cat_limit =======
+<HTML>
+<HEAD>
+<TITLE>Character Limit Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Character Limit Explained</H1>
+
+Setting this option makes it possible to limit how much of the message
+is made available to the categorizer command as input.
+The default value (-1) means that the entire message is fed to the
+command.
+A value of 0 (zero) means that only the headers of the message are
+made available.
+A positive integer means that the headers plus that many characters from
+the body of the message are passed to the categorizer.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_age =======
+<HTML>
+<HEAD>
+<TITLE>Age Interval Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Age Interval Explained</H1>
+
+The Age Interval, if defined, is part of the Pattern.
+If you use this, it should be set to something like:
+<P>
+<CENTER><SAMP>(min_age,max_age)</SAMP></CENTER>
+<P>
+where &quot;min_age&quot; and &quot;max_age&quot; are non-negative integers.
+The special value &quot;INF&quot; may be used for the max value.
+It represents infinity.
+<P>
+In rare cases it may be useful to use the more general form of the value,
+which is a comma-separated list of intervals.
+It would look something like:
+<P>
+<CENTER><SAMP>(min_age1,max_age1),(min_age2,max_age2),...</SAMP></CENTER>
+<P>
+When there is an Age Interval defined, it is a match if the age, in days, of
+the message is contained in the interval.
+The interval includes both endpoints.
+If the option is set to a list of intervals then it is a match if the
+age of the message is contained in any of the intervals.
+<P>
+Even though this option is called Age, it isn't actually
+the <EM>age</EM> of the message.
+Instead, it is how many days ago the message arrived in one of your folders.
+If the current time is a little past midnight, then a message that arrived
+just before midnight arrived yesterday, even though the message is only
+a few minutes old.
+By default, the date being used is not the date in the Date
+header of the message.
+It is the date that the message arrived in one of your folders.
+When you Save a message from one folder to another that arrival date
+is preserved.
+If you would like to use the date in the Date header that is possible.
+Turn on the option
+<A HREF="h_config_filt_opts_sentdate">&quot;Use-Date-Header-For-Age&quot;</A>
+near the bottom of the rule definition.
+<P>
+A value of 0 is today, 1 is yesterday, 2 is the day before yesterday, and so on.
+The age interval
+<P>
+<CENTER><SAMP>(2,2)</SAMP></CENTER>
+<P>
+matches all messages that arrived on the day before yesterday.
+The interval
+<P>
+<CENTER><SAMP>(180,INF)</SAMP></CENTER>
+<P>
+matches all messages that arrived at least 180 days before today.
+The interval
+<P>
+<CENTER><SAMP>(0,1)</SAMP></CENTER>
+<P>
+matches all messages that arrived today or yesterday.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_size =======
+<HTML>
+<HEAD>
+<TITLE>Size Interval Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Size Interval Explained</H1>
+
+The Size Interval, if defined, is part of the Pattern.
+If you use this, it should be set to something like:
+<P>
+<CENTER><SAMP>(min_size,max_size)</SAMP></CENTER>
+<P>
+where &quot;min_size&quot; and &quot;max_size&quot; are non-negative integers.
+The special value &quot;INF&quot; may be used for the max value.
+It represents infinity.
+<P>
+In rare cases it may be useful to use the more general form of the value,
+which is a comma-separated list of intervals.
+It would look something like:
+<P>
+<CENTER><SAMP>(min_size1,max_size1),(min_size2,max_size2),...</SAMP></CENTER>
+<P>
+When there is a Size Interval defined, it is a match if the size of
+the message is contained in the interval.
+The interval includes both endpoints.
+If the option is set to a list of intervals then it is a match if the
+size of the message is contained in any of the intervals.
+<P>
+The size interval
+<P>
+<CENTER><SAMP>(10000,50000)</SAMP></CENTER>
+<P>
+matches all messages with sizes greater than or equal to 10000, and less
+than or equal to 50000.
+The interval
+<P>
+<CENTER><SAMP>(100000,INF)</SAMP></CENTER>
+<P>
+matches all messages with sizes greater than or equal to 100000.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_scorei =======
+<HTML>
+<HEAD>
+<TITLE>Score Interval Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Score Interval Explained</H1>
+
+The Score Interval, if defined, is part of the Pattern.
+If you use this, it should be set to something like:
+<P>
+<CENTER><SAMP>(min_score,max_score)</SAMP></CENTER>
+<P>
+where &quot;min_score&quot; and &quot;max_score&quot; are integers between
+-32000 and 32000.
+The special values &quot;-INF&quot; and &quot;INF&quot; may be used for
+the min and max values.
+These represent negative and positive infinity.
+<P>
+Actually, the value may be a list of intervals rather than just a
+single interval if that is useful.
+The elements of the list are separated by commas like:
+<P>
+<CENTER><SAMP>(min_score1,max_score1),(min_score2,max_score2),...</SAMP></CENTER>
+<P>
+When there is a Score Interval defined, it is a match if the score for
+the message is contained in any of the intervals.
+The intervals include both endpoints.
+The score for a message is calculated by looking at every scoring rule
+defined and adding up the Score Values for the rules that match the message.
+Scoring rules are created using the
+<A HREF="h_rules_score">&quot;SETUP SCORING&quot;</A> screen.
+
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_fldr_type =======
+<HTML>
+<HEAD>
+<TITLE>Current Folder Type Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Current Folder Type Explained</H1>
+
+The Current Folder Type is part of the role's Pattern.
+It refers to the type of the currently open folder, which is the folder
+you were last looking at from the MESSAGE INDEX or MESSAGE TEXT screen.
+In order for a role to be considered a match, the current folder must
+be of the type you set here.
+The three types &quot;Any&quot;, &quot;News&quot;, and &quot;Email&quot; are
+all what you might think.
+<P>
+If the Current Folder Type for a role's Pattern is set to &quot;News&quot;, for
+example, then
+that role will only be a match if the current folder is a newsgroup and
+the rest of the Pattern matches.
+The value &quot;Specific&quot; may be used when you want to limit the match
+to a specific folder (not just a specific type of folder), or to a list of
+specific folders.
+<P>
+In order to match a specific folder you Select the &quot;Specific&quot;
+button <EM>AND</EM> fill in
+the name (or list of names) of
+the folder in the &quot;Folder List&quot; field.
+If the current folder is any of the folders in the list, that is considered
+a match.
+The name of each folder in the list may be either &quot;INBOX&quot;,
+the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are a couple samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the &quot;T&quot; command that is available when the &quot;Folder List&quot; line is
+highlighted, or to use the &quot;Take&quot; command with the configuration
+feature
+<A HREF="h_config_enable_role_take">&quot;<!--#echo var="FEAT_enable-rules-under-take"-->&quot;</A>
+turned on.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Current Folder Type is set to &quot;Specific&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Specific&quot;.
+<P>
+When reading a newsgroup, there may be a performance penalty
+incurred when collecting the information necessary to check a Pattern.
+For this reason, the default Current Folder Type is set to &quot;Email&quot;.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_rule_type =======
+<HTML>
+<HEAD>
+<TITLE>Filter Action Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Filter Action Explained</H1>
+
+The Filter Action specifies the action to be taken when the Pattern is a
+match.
+It may be set to &quot;Delete&quot; &quot;Move&quot;, or
+&quot;Just Set Message Status&quot;.
+<P>
+If it is set to &quot;Delete&quot;, then the message that matches the
+Pattern will be deleted from the open folder.
+<P>
+If it is set to &quot;Move&quot;, then the name of the folder to which
+the matching message should be moved is given in the &quot;Folder List&quot; field on the
+next line of the screen.
+A list of folders separated by commas may be given, in which case the
+message will be copied to all of the folders in the list before it is
+deleted.
+<P>
+If it is set to neither of those two values (it is set to the value
+labeled &quot;Just Set Message Status&quot;) then the message status
+setting will happen
+but the message will not be deleted or moved.
+<P>
+If you are Moving a message you may also set Message Status if you wish.
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the T command that is available when the &quot;Folder List&quot; line is
+highlighted.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Filter Action is set to &quot;Move&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Move&quot;.
+<P>
+There are a few tokens that may be used in the names in the Folder List.
+They are all related to the date on which the filtering is taking place.
+The tokens are words surrounded by underscores.
+For example, if you want your filter to move messages to a folder named
+<P><CENTER><SAMP>abc-year-mon</SAMP></CENTER><P>
+you could specify the folder as
+<P><CENTER><SAMP>abc-_CURYEAR_-_CURMONTHABBREV_</SAMP></CENTER><P>
+which would result in a file named something like
+<P><CENTER><SAMP>abc-2004-oct</SAMP></CENTER><P>
+or
+<P><CENTER><SAMP>abc-_CURYEAR2DIGIT_-_CURMONTH2DIGIT_</SAMP></CENTER><P>
+which would result in a file named something like
+<P><CENTER><SAMP>abc-04-10</SAMP></CENTER><P>
+The available tokens are listed
+<A HREF="h_index_tokens">here</A>.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_score_fldr_type =======
+<HTML>
+<HEAD>
+<TITLE>Current Folder Type Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Current Folder Type Explained</H1>
+
+The Current Folder Type is part of the scoring rule's Pattern.
+It refers to the type of the folder that
+the message being scored is in.
+In order for a rule to be considered a match, the current folder must
+be of the type you set here.
+The three types &quot;Any&quot;, &quot;News&quot;, and &quot;Email&quot; are
+all what you might think.
+<P>
+If the Current Folder Type for a Pattern is set to &quot;News&quot;, for
+example, then
+that Pattern will only match if the current folder is a newsgroup and
+the rest of the Pattern matches.
+The value &quot;Specific&quot; may be used when you want to limit the match
+to a specific folder (not just a specific type of folder), or to a list of
+specific folders.
+<P>
+In order to match a specific folder you Select the &quot;Specific&quot;
+button <EM>AND</EM> fill in
+the name (or list of names) of
+the folder in the &quot;Folder List&quot; field.
+If the current folder is any of the folders in the list, that is considered
+a match.
+The name of each folder in the list may be either &quot;INBOX&quot;, the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are a couple samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the T command that is available when the &quot;Folder List&quot; line is
+highlighted.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Current Folder Type is set to &quot;Specific&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Specific&quot;.
+<P>
+When reading a newsgroup, there may be a performance penalty
+incurred when collecting the information necessary to check a Pattern.
+For this reason, the default Current Folder Type is set to &quot;Email&quot;.
+For example, if you have Index Line Coloring rules that have Score Intervals
+defined then the scores for all the visible messages will need to be calculated.
+If some of your Scoring rules have
+a Current Folder Type of
+&quot;Any&quot; or &quot;News&quot; this may cause the MESSAGE INDEX
+screen to draw more slowly when in a newsgroup.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_other_fldr_type =======
+<HTML>
+<HEAD>
+<TITLE>Current Folder Type Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Current Folder Type Explained</H1>
+
+The Current Folder Type is part of the rule's Pattern.
+It refers to the type of the folder being viewed.
+In order for a rule to be considered a match, the current folder must
+be of the type you set here.
+The three types &quot;Any&quot;, &quot;News&quot;, and &quot;Email&quot; are
+all what you might think.
+<P>
+If the Current Folder Type for a Pattern is set to &quot;News&quot;, for
+example, then
+that Pattern will only match if the current folder is a newsgroup.
+The value &quot;Specific&quot; may be used when you want to limit the match
+to a specific folder (not just a specific type of folder), or to a list of
+specific folders.
+<P>
+In order to match a specific folder you Select the &quot;Specific&quot;
+button <EM>AND</EM> fill in
+the name (or list of names) of
+the folder in the &quot;Folder List&quot; field.
+If the current folder is any of the folders in the list, that is considered
+a match.
+The name of each folder in the list may be either &quot;INBOX&quot;, the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are a couple samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the T command that is available when the &quot;Folder List&quot; line is
+highlighted.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Current Folder Type is set to &quot;Specific&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Specific&quot;.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_incol_fldr_type =======
+<HTML>
+<HEAD>
+<TITLE>Current Folder Type Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Current Folder Type Explained</H1>
+
+The Current Folder Type is part of the Line Coloring rule's Pattern.
+It refers to the type of the folder for which the MESSAGE INDEX is
+being viewed.
+In order for a rule to be considered a match, the current folder must
+be of the type you set here.
+The three types &quot;Any&quot;, &quot;News&quot;, and &quot;Email&quot; are
+all what you might think.
+<P>
+If the Current Folder Type for a Pattern is set to &quot;News&quot;, for
+example, then
+that Pattern will only match if the current folder is a newsgroup and
+the rest of the Pattern matches.
+The value &quot;Specific&quot; may be used when you want to limit the match
+to a specific folder (not just a specific type of folder), or to a list of
+specific folders.
+<P>
+In order to match a specific folder you Select the &quot;Specific&quot;
+button <EM>AND</EM> fill in
+the name (or list of names) of
+the folder in the &quot;Folder List&quot; field.
+If the current folder is any of the folders in the list, that is considered
+a match.
+The name of each folder in the list may be either &quot;INBOX&quot;, the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are a couple samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the T command that is available when the &quot;Folder List&quot; line is
+highlighted.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Current Folder Type is set to &quot;Specific&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Specific&quot;.
+<P>
+When reading a newsgroup, there may be a performance penalty
+incurred when collecting the information necessary to check a Pattern.
+For this reason, the default Current Folder Type is set to &quot;Email&quot;.
+For example, a rule with a non-Normal Index Line Color
+and a Current Folder Type of
+&quot;Any&quot; or &quot;News&quot; may cause the MESSAGE INDEX
+screen to draw more slowly when in a newsgroup.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_fldr_type =======
+<HTML>
+<HEAD>
+<TITLE>Current Folder Type Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Current Folder Type Explained</H1>
+
+The Current Folder Type is part of the Filtering rule's Pattern.
+It refers to the type of the folder for which the filtering is being done.
+In order for a rule to be considered a match, the current folder must
+be of the type you set here.
+The three types &quot;Any&quot;, &quot;News&quot;, and &quot;Email&quot; are
+all what you might think.
+<P>
+If the Current Folder Type for a Pattern is set to &quot;News&quot;, for
+example, then
+that Pattern will only match if the current folder is a newsgroup and
+the rest of the Pattern matches.
+The value &quot;Specific&quot; may be used when you want to limit the match
+to a specific folder (not just a specific type of folder), or to a list of
+specific folders.
+<P>
+In order to match a specific folder you Select the &quot;Specific&quot;
+button <EM>AND</EM> fill in
+the name (or list of names) of
+the folder in the &quot;Folder List&quot; field.
+If the current folder is any of the folders in the list, that is considered
+a match.
+The name of each folder in the list may be either &quot;INBOX&quot;, the technical specification
+of the folder (like what appears in your configuration file) or, if the
+folder is one of your incoming folders, it may be the nickname you've given
+the folder.
+Here are a couple samples of specific folder names:
+<P>
+<CENTER><SAMP>{monet.art.example.com}mail/art-class</SAMP></CENTER>
+<P>
+<CENTER><SAMP>{news.example.com/nntp}#news.comp.mail.pine</SAMP></CENTER>
+<P>
+The easiest way to fill in the &quot;Folder List&quot; field is to use
+the T command that is available when the &quot;Folder List&quot; line is
+highlighted.
+Note that you won't be able to edit the &quot;Folder List&quot; line unless the
+Current Folder Type is set to &quot;Specific&quot;, and any value that
+&quot;Folder List&quot; has is ignored unless the type
+is set to &quot;Specific&quot;.
+<P>
+When reading a newsgroup, there may be a performance penalty
+incurred when collecting the information necessary to check a Pattern.
+For this reason, the default Current Folder Type is set to &quot;Email&quot;.
+For example, a rule with a Current Folder Type of either
+&quot;Any&quot; or &quot;News&quot; may cause the filtering to happen
+more slowly when opening a newsgroup.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_imp =======
+<HTML>
+<HEAD>
+<TITLE>Message Important Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Message Important Status Explained</H1>
+
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the message must be
+flagged &quot;Important&quot; in order to be a match; or &quot;No&quot;, which
+means the message must <EM>not</EM> be flagged &quot;Important&quot; in order
+to be a match.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_new =======
+<HTML>
+<HEAD>
+<TITLE>Message New Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Message New Status Explained</H1>
+
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the message must be
+&quot;New&quot; in order to be a match; or &quot;No&quot;, which
+means the message must <EM>not</EM> be &quot;New&quot; in order
+to be a match.
+&quot;New&quot; is the same as <EM>Unseen</EM> and not &quot;New&quot; is the
+same as <EM>Seen</EM>.
+<P>
+The nomenclature for New and Recent is a bit confusing:
+<P>
+New means that the message is Unseen.
+It could have been in your mailbox for a long time but if you haven't looked
+at it, it is still considered New.
+That matches the default Alpine index display that shows an N for such a
+message.
+<P>
+Recent means that the message was added to this folder since the last time
+you opened the folder.
+Alpine also shows an N by default for these types of messages.
+If you were to run two copies of Alpine that opened a folder one right after
+the other, a message would only show up as Recent in (at most) the first
+Alpine session.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_recent =======
+<HTML>
+<HEAD>
+<TITLE>Message Recent Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Message Recent Status Explained</H1>
+
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the message must be
+&quot;Recent&quot; in order to be a match; or &quot;No&quot;, which
+means the message must <EM>not</EM> be &quot;Recent&quot; in order
+to be a match.
+&quot;Recent&quot; means that the message was added to the folder since
+the last time the folder was opened.
+If more than one mail client has the folder opened, the message will
+appear to be &quot;Recent&quot; to only one of the clients.
+<P>
+The nomenclature for New and Recent is a bit confusing:
+<P>
+New means that the message is Unseen.
+It could have been in your mailbox for a long time but if you haven't looked
+at it, it is still considered New.
+That matches the default Alpine index display that shows an N for such a
+message.
+<P>
+Recent means that the message was added to this folder since the last time
+you opened the folder.
+Alpine also shows an N by default for these types of messages.
+If you were to run two copies of Alpine that opened a folder one right after
+the other, a message would only show up as Recent in (at most) the first
+Alpine session.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_del =======
+<HTML>
+<HEAD>
+<TITLE>Message Deleted Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Message Deleted Status Explained</H1>
+
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the message must be
+marked &quot;Deleted&quot; in order to be a match; or &quot;No&quot;, which
+means the message must <EM>not</EM> be marked &quot;Deleted&quot; in order
+to be a match.
+<P>
+If you are thinking of using this part of the Pattern as a way to prevent
+messages from being filtered more than once in a Filter Pattern,
+take a look at the Filter Option
+<A HREF="h_config_filt_opts_notdel">&quot;Move-Only-if-Not-Deleted&quot;</A>
+instead.
+It should work better than using this field since it will hide the filtered
+messages even if they are already Deleted.
+That option is at the bottom of the Filter configuration screen.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_ans =======
+<HTML>
+<HEAD>
+<TITLE>Message Answered Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Message Answered Status Explained</H1>
+
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the message must be
+marked &quot;Answered&quot; in order to be a match; or &quot;No&quot;, which
+means the message must <EM>not</EM> be marked &quot;Answered&quot; in order
+to be a match.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_abookfrom =======
+<HTML>
+<HEAD>
+<TITLE>Address in Address Book Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Address in Address Book Explained</H1>
+
+This option gives you a way to match messages that contain an address
+that is in one of your address books.
+Only the simple entries in your address books are searched.
+Address book distribution lists are ignored!
+<P>
+This part of the Pattern may have one of five possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The value &quot;Yes, in any address book&quot; means at least one of the addresses
+from the message must be in at least one of your
+address books in order to be a match.
+The value &quot;No, not in any address book&quot;
+means none of the addresses may
+be in any of your address books in order to be a match.
+<P>
+The values &quot;Yes, in specific address books&quot; and
+&quot;No, not in any of specific address books&quot; are similar but instead
+of depending on all address books you are allowed to give a list of address
+books to look in.
+Usually this would be a single address book but it may be a
+list of address books as well.
+For each of these &quot;specific&quot; address book options you Select which
+of the Specific options you want (Yes or No) <EM>AND</EM> fill in the
+name (or list of names) of the address book in the
+&quot;Abook List&quot; field.
+The names to be used are those that appear in the ADDRESS BOOK LIST screen.
+The easiest way to fill in the Abook List field it to use
+the &quot;T&quot; command that is available when the &quot;Abook List&quot;
+line is highlighted.
+Note that you won't be able to edit the &quot;Abook List&quot; line unless the
+option is set to one of the two &quot;Specific&quot;, values.
+<P>
+The addresses from the message that are checked for are determined by the
+setting you have for &quot;Types of addresses to check for in address book&quot;.
+If you set this to &quot;From&quot; the From address from the message will
+be looked up in the address book.
+If you set it to &quot;To&quot; instead then the To addresses will be used.
+If any of the To addresses are in the address book then it is considered
+a match for &quot;Yes&quot; or not a match for &quot;No&quot;.
+You could set it to both From and To, in which case all of the From and To
+addresses are used.
+The &quot;Reply-To&quot; and &quot;Sender&quot; cases are a little unusual.
+Due to deficiencies in our tools, Reply-To uses the Reply-To address if it
+exists or the From address if there is no Reply-To address.
+Same for the Sender address.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_inabook_from =======
+<HTML>
+<HEAD>
+<TITLE>From</TITLE>
+</HEAD>
+<BODY>
+<H1>From</H1>
+
+Setting the From line will cause the address from the From header line
+of the message to be checked for in the address book.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_inabook_replyto =======
+<HTML>
+<HEAD>
+<TITLE>Reply-To</TITLE>
+</HEAD>
+<BODY>
+<H1>Reply-To</H1>
+
+Setting the Reply-To line will cause the address from the Reply-To header line
+of the message to be checked for in the address book.
+However, if there is no Reply-To header line in the message the From header
+line will be used instead.
+We understand this is dumb but we don't have an easy way around it.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_inabook_sender =======
+<HTML>
+<HEAD>
+<TITLE>Sender</TITLE>
+</HEAD>
+<BODY>
+<H1>Sender</H1>
+
+Setting the Sender line will cause the address from the Sender header line
+of the message to be checked for in the address book.
+However, if there is no Sender header line in the message the From header
+line will be used instead.
+We understand this is dumb but we don't have an easy way around it.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_inabook_to =======
+<HTML>
+<HEAD>
+<TITLE>To</TITLE>
+</HEAD>
+<BODY>
+<H1>To</H1>
+
+Setting the To line will cause the address from the To header line
+of the message to be checked for in the address book.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_inabook_cc =======
+<HTML>
+<HEAD>
+<TITLE>CC</TITLE>
+</HEAD>
+<BODY>
+<H1>CC</H1>
+
+Setting the CC line will cause the address from the CC header line
+of the message to be checked for in the address book.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_stat_8bitsubj =======
+<HTML>
+<HEAD>
+<TITLE>Raw 8-bit in Subject Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Raw 8-bit in Subject Explained</H1>
+
+It seems that lots of unwanted email contains unencoded 8-bit characters
+in the Subject.
+Normally, characters with the 8th bit set are not allowed in the Subject
+header unless they are MIME-encoded.
+This option gives you a way to match messages that have Subjects that
+contain unencoded 8-bit characters.
+<P>
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means the Subject of
+the message must contain unencoded 8-bit characters (characters with the
+most significant bit set)
+in order to be a match; or &quot;No&quot;, which
+means the Subject must <EM>not</EM>
+contain unencoded 8-bit characters in order to be a match.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_bom =======
+<HTML>
+<HEAD>
+<TITLE>Beginning of Month</TITLE>
+</HEAD>
+<BODY>
+<H1>Beginning of Month</H1>
+
+This option gives you a limited ability to take different actions depending on whether
+this is the first time Alpine has been run this month or not.
+Though it would be nice to have such an option available, this is not the
+same as whether or not this is the first time a paricular folder has been
+opened this month.
+If you want some action (probably Filtering) to take place in a folder each
+month, then you will need to be sure that the folder is opened during the
+first Alpine session of the month in order for this option to be helpful.
+<P>
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means this is the first
+time Alpine has been run this month;
+or &quot;No&quot;, which
+means this is not the first time Alpine has been run this month.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Here are some technical details.
+The way that Alpine decides if it is the beginning of the month or not is
+to compare today's date with the date stored in the
+<A HREF="h_config_prune_date"><!--#echo var="VAR_last-time-prune-questioned"--></A>
+variable in the config file.
+If the month of today's date is later than the month stored in the variable,
+then this is considered to be the first time you have run Alpine this month, and
+that turns the Beginning of the Month option on.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_boy =======
+<HTML>
+<HEAD>
+<TITLE>Beginning of Year</TITLE>
+</HEAD>
+<BODY>
+<H1>Beginning of Year</H1>
+
+This option gives you a limited ability to take different actions depending on whether
+this is the first time Alpine has been run this year or not.
+Though it would be nice to have such an option available, this is not the
+same as whether or not this is the first time a paricular folder has been
+opened this year.
+If you want some action (probably Filtering) to take place in a folder each
+year, then you will need to be sure that the folder is opened during the
+first Alpine session of the year in order for this option to be helpful.
+<P>
+This part of the Pattern may have one of three possible values.
+The default value is &quot;Don't care&quot;, which matches any message.
+The other two values are &quot;Yes&quot;, which means this is the first
+time Alpine has been run this year;
+or &quot;No&quot;, which
+means this is not the first time Alpine has been run this year.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Here are some technical details.
+The way that Alpine decides if it is the beginning of the year or not is
+to compare today's date with the date stored in the
+<A HREF="h_config_prune_date"><!--#echo var="VAR_last-time-prune-questioned"--></A>
+variable in the config file.
+If the year of today's date is later than the year stored in the variable,
+then this is considered to be the first time you have run Alpine this year, and
+that turns the Beginning of the Year option on.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_inick =======
+<HTML>
+<HEAD>
+<TITLE>Initialize Values From Role Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Initialize Values From Role Explained</H1>
+
+This is a power user feature.
+You will usually want to leave this field empty.
+The value of this field is the nickname of another one of your roles.
+The Action values from that other role
+are used as the initial values of the Action items for this role.
+If you put something in any of the action fields for this role, that will
+override whatever was in the corresponding field of the initializer role.
+<P>
+You might use this field if the &quot;Action&quot; part of one of your roles
+is something you want to use in more than one role.
+Instead of filling in those action values again for each role, you
+may give the nickname of the role where the values are filled in.
+It's just a shortcut way to define Role Actions.
+<P>
+Here's an example to help explain how this works.
+Suppose you have a role with nickname &quot;role1&quot; and role1 has
+(among other things)
+<P>
+<CENTER><SAMP>Set Reply-To = The Pres &lt;president@example.com&gt;</SAMP></CENTER>
+<P>
+set.
+If in &quot;role2&quot; you set &quot;Initialize settings using role&quot; to
+&quot;role1&quot;, then role2 will inherit the Set Reply-To value
+from role1 by default (and any of the other inheritable action values
+that are set).
+So if role2 had
+<P>
+<CENTER><SAMP>Set Reply-To = &lt;No Value Set&gt;</SAMP></CENTER>
+<P>
+defined, the Reply-To used with role2 would be &quot;The Pres &lt;president@example.com&gt;&quot;
+However, if role2 had
+<P>
+<CENTER><SAMP>Set Reply-To = VP &lt;vicepresident@example.com&gt;</SAMP></CENTER>
+<P>
+defined, then the Reply-To used with role2 would be &quot;VP &lt;vicepresident@example.com&gt;&quot; instead.
+<P>
+If you wish,
+you may choose a nickname from your list of roles by using the
+&quot;T&quot; command.
+If the role you are using to initialize also has a role it initializes from,
+then that initialization happens first.
+That is, inheritance works as expected with the grandparent and
+great-grandparent (and so on) roles having the expected effect.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setfrom =======
+<HTML>
+<HEAD>
+<TITLE>Set From Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set From Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field consists of a single address that will be used as the From
+address on the message you are sending.
+This should be a fully-qualified address like
+<P>
+<CENTER><SAMP>Full Name &lt;user@domain&gt;</SAMP></CENTER>
+<P>
+or just
+<P>
+<CENTER><SAMP>user@domain</SAMP></CENTER>
+<P>
+If you wish,
+you may choose an address from your address book with the
+&quot;T&quot; command.
+<P>
+If this is left blank, then your normal From address will be used.
+<P>
+You may also find it useful to add the changed From address to the
+<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
+configuration option.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setreplyto =======
+<HTML>
+<HEAD>
+<TITLE>Set Reply-To Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Reply-To Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field consists of a single address that will be used as the Reply-To
+address on the message you are sending.
+This may be a fully-qualified address like
+<P>
+<CENTER><SAMP>Full Name &lt;user@domain&gt;</SAMP></CENTER>
+<P>
+or just
+<P>
+<CENTER><SAMP>user@domain</SAMP></CENTER>
+<P>
+If you wish,
+you may choose an address from your address book with the
+&quot;T&quot; command.
+<P>
+If this is left blank, then there won't be a Reply-To address unless
+you have configured one specially with the
+"<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>"
+configuration option.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setfcc =======
+<HTML>
+<HEAD>
+<TITLE>Set Fcc Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Fcc Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field consists of a single folder name that will be used in
+the Fcc field of the message you are sending.
+You may put anything here that you would normally type into the Fcc
+field from the composer.
+<P>
+In addition, an fcc of &quot;&quot; (two double quotation marks) means
+no Fcc.
+<P>
+A blank field here means that Alpine will use its normal rules for deciding
+the default value of the Fcc field.
+For many roles, perhaps most, it may make more sense for you to use the
+other Alpine facilities for setting the Fcc.
+In particular, if you want the Fcc to depend on who you are sending the
+message to then the <A HREF="h_config_fcc_rule">&quot;<!--#echo var="VAR_fcc-name-rule"-->&quot;</A>
+is probably more useful.
+In that case, you would want to leave the Fcc field here blank.
+However, if you have a role that depends on who the message you are replying
+to was From, or what address that message was sent to;
+then it might make sense to set the Fcc for that role here.
+<P>
+If you wish,
+you may choose a folder from your folder collections by using the
+&quot;T&quot; command.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_usesmtp =======
+<HTML>
+<HEAD>
+<TITLE>Use SMTP Server Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Use SMTP Server Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+If this field has a value, then it will be used as the SMTP server
+to send mail when this role is being used (unless the SMTP server variable
+is set in the system-wide fixed configuration file).
+It has the same semantics as the
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+variable in the Setup/Config screen.
+<P>
+If you are using this to post from home when you are at home and from
+work when you are at work you need to be careful about postponing messages.
+When you postpone a composition that was using a role with this variable
+set, the SMTP server list will be saved
+with the postponed composition.
+It cannot be changed later.
+Because of this, you may want to make this a list of SMTP servers
+with the preferred server at the front of the list and alternate servers
+later in the list.
+In your &quot;Home&quot; role you would put the home SMTP server first and
+the work SMTP server last.
+In your &quot;Work&quot; role you would put the work SMTP server first and
+the home SMTP server last.
+Then if you start a composition as &quot;Work&quot;, postpone
+it, and then later resume it from home the work SMTP server will fail but
+the home SMTP server later in the list will succeed.
+<P>
+You may be able to simplify things by making the regular
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+variable in the Setup/Config screen a list instead of using roles
+to set the SMTP server.
+
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_usenntp =======
+<HTML>
+<HEAD>
+<TITLE>Use NNTP Server Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Use NNTP Server Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+If this field has a value, then it will be used as the NNTP server
+to post to newsgroups when this role is being used (unless the NNTP server
+variable
+is set in the system-wide fixed configuration file).
+It has the same semantics as the
+<A HREF="h_config_nntp_server">&quot;<!--#echo var="VAR_nntp-server"-->&quot;</A>
+variable in the Setup/Config screen.
+<P>
+This role setting can facilitate posting to the right nntp server for someone
+who reads news from various news sources. The feature
+<A HREF="h_config_predict_nntp_server">&quot;<!--#echo var="FEAT_predict-nntp-server"-->&quot;</A>
+allows for setting the correct <!--#echo var="VAR_nntp-server"--> without having to individually
+set a role for that <!--#echo var="VAR_nntp-server"-->, but for greater flexibility, setting
+nntp servers for roles may be more desirable for some people.
+<P>
+If you are using this to post from home when you are at home and from
+work when you are at work you need to be careful about postponing messages.
+When you postpone a composition that was using a role with this variable
+set, the NNTP server list will be saved
+with the postponed composition.
+It cannot be changed later.
+Because of this, you may want to make this a list of NNTP servers
+with the preferred server at the front of the list and alternate servers
+later in the list.
+In your &quot;Home&quot; role you would put the home NNTP server first and
+the work NNTP server last.
+In your &quot;Work&quot; role you would put the work NNTP server first and
+the home NNTP server last.
+Then if you start a composition as &quot;Work&quot;, postpone
+it, and then later resume it from home the work NNTP server will fail but
+the home NNTP server later in the list will succeed.
+<P>
+You may be able to simplify things by making the regular
+<A HREF="h_config_nntp_server">&quot;<!--#echo var="VAR_nntp-server"-->&quot;</A>
+variable in the Setup/Config screen a list instead of using roles
+to set the NNTP server.
+
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setotherhdr =======
+<HTML>
+<HEAD>
+<TITLE>Set Other Headers Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Other Headers Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field gives you a way to set values for headers besides
+&quot;From&quot; and &quot;Reply-To&quot;.
+If you want to set either of those, use the specific
+&quot;Set From&quot; and &quot;Set Reply-To&quot; settings above.
+<P>
+This field is similar to the
+"<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>" option.
+Each header you specify here must include the header tag
+(&quot;To:&quot;, &quot;Approved:&quot;, etc.)
+and may optionally include a value for that header.
+In order to see these headers when you compose using this role you
+must use the rich header
+<!--chtml if pinemode="function_key"-->(F5)
+<!--chtml else-->(Ctrl-R)<!--chtml endif--> command.
+Here's an example that shows how you might set the To address.
+<P>
+<CENTER><SAMP>Set Other Hdrs = To: Full Name &lt;user@domain&gt;</SAMP></CENTER>
+<P>
+Headers set in this way are different from headers set with the
+<!--#echo var="VAR_customized-hdrs"--> option in that the value you give for a header here
+will replace any value that already exists.
+For example, if you are Replying to a message there will already be at
+least one address in the To header (the address you are Replying to).
+However, if you Reply using a role that sets the To header, that role's
+To header value will be used instead.
+<P>
+Limitation: Because commas are used to separate the list of
+Other Headers, it is not possible to have the value of a
+header contain a comma;
+nor is there currently an &quot;escape&quot; mechanism provided
+to make this work.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setlitsig =======
+<HTML>
+<HEAD>
+<TITLE>Set Literal Signature Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Literal Signature Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field contains the actual text for your signature, as opposed to
+the name of a file containing your signature.
+If this is defined it takes precedence over any value set in the
+&quot;Set Signature&quot; field.
+<P>
+This is simply a different way to store the signature.
+The signature is stored inside your Alpine configuration file instead of in
+a separate file.
+Tokens work the same way they do with
+<A HREF="h_config_role_setsig">Set Signature</A>, so refer to the
+help text there for more information.
+<P>
+
+The two character sequence &#92;n (backslash followed by
+the character n) will be used to signify a line-break in your signature.
+You don't have to enter the &#92;n, but it will be visible in the
+CHANGE THIS ROLE RULE window after you are done editing the signature.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_setsig =======
+<HTML>
+<HEAD>
+<TITLE>Set Signature Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Signature Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+<P>
+If either the default <A HREF="h_config_literal_sig"><!--#echo var="VAR_literal-signature"--></A>
+option from Setup/Config
+or the &quot;Set LiteralSig&quot; option for this role are defined,
+then this option will be ignored.
+You can tell that that is the case because the value of this
+option will show up as
+<P>
+<CENTER><SAMP>&lt;Ignored: using LiteralSig instead&gt;</SAMP></CENTER>
+<P>
+You may either use all Literal Signatures (signatures stored in your
+configuration file) throughout Alpine, or all signature files.
+You can't mix the two.
+<P>
+This field consists of a filename that will be used as the signature
+file when using this role.
+<P>
+If the filename is followed by a vertical bar (|) then instead
+of reading the contents of the file the file is assumed to be a
+program that will produce the text to be used on its standard output.
+The program can't have any arguments and doesn't receive any input from Alpine,
+but the rest of the processing works as if the contents came from a file.
+<P>
+Instead of storing the data in a local file, the
+signature data may be stored remotely in an IMAP folder.
+In order to do this,
+you must use a remote name for the signature.
+A remote signature name might look like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/sig3</SAMP></CENTER>
+<P>
+
+The syntax used here is the same as the syntax used for a remote
+<A HREF="h_config_signature_file"><!--#echo var="VAR_signature-file"--></A>.
+Note that you may not access an existing signature file remotely,
+you have to create a new <EM>folder</EM> that contains the signature data.
+If the name you use here for the signature data is a remote name, then when
+you edit the file using the &quot;F&quot; command the data will
+be saved remotely in the folder.
+You aren't required to do anything special to create the folder, it
+gets created if you use a remote name.
+
+<P>
+If you type &quot;F&quot; you may edit the contents of the file (as opposed to
+the name of the file) you have specified.
+If you type &quot;T&quot; you may use a browser to choose an existing filename.
+<P>
+Besides containing regular text, a signature file may also
+contain (or a signature program may produce) tokens that are replaced with text
+that depends on the message you are replying to or forwarding.
+The tokens all look like _word_ (a word surrounded by underscores).
+For example, if the token
+<P>
+<CENTER><SAMP>_DATE_</SAMP></CENTER>
+<P>
+is included in the text of the signature file, then when you reply to
+or forward a message, the token will be replaced with the actual date
+the message you are replying to or forwarding was sent.
+<P>
+If you use a role that has a signature file for a plain composition
+(that is, not a reply or forward) then there is no original message, so
+any tokens that depend on the message will be replaced with nothing.
+So if you want a signature file to be useful for new compositions it
+shouldn't include any of the tokens that depend on the message being
+replied to or forwarded.
+<P>
+The list of available tokens is
+<A HREF="h_index_tokens">here</A>.
+<P>
+Actually, for the adventurous, there is a way to conditionally include text based
+on whether or not a token would result in specific replacement text.
+For example, you could include some text based on whether or not
+the _NEWS_ token would result in any newsgroups if it was used.
+It's explained in detail
+<A HREF="h_reply_token_conditionals">here</A>.
+<P>
+In the very unlikely event that you want to include a literal token in
+a signature file, you must precede it with a backslash character.
+For example, to include the literal text _DATE_ you must actually use
+&#92;_DATE_.
+It is not possible to have a literal backslash followed by an expanded token.
+<P>
+A blank field here means that Alpine will use its normal rules for deciding
+which file (if any) to use for the signature file.
+<P>
+An alternate method for storing the signature is available in
+<A HREF="h_config_role_setlitsig">Set Literal Signature</A>.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_settempl =======
+<HTML>
+<HEAD>
+<TITLE>Set Template Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Template Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+role is a match.
+This field consists of a filename that will be used as the template
+file when using this role.
+The template file is a file that is included at the top of the message you
+are composing.
+<P>
+If the filename is followed by a vertical bar (|) then instead
+of reading the contents of the file the file is assumed to be a
+program that will produce the text to be used on its standard output.
+The program can't have any arguments and doesn't receive any input from Alpine,
+but the rest of the processing works as if the contents came from a file.
+<P>
+Instead of storing the data in a local file, the
+template may be stored remotely in an IMAP folder.
+In order to do this,
+you must use a remote name for the template.
+A remote template name might look like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/templ3</SAMP></CENTER>
+<P>
+
+The syntax used here is the same as the syntax used for a remote
+<A HREF="h_config_signature_file"><!--#echo var="VAR_signature-file"--></A>.
+Note that you may not access an existing template file remotely,
+you have to create a new <EM>folder</EM> that contains the template data.
+If the name you use here for the template is a remote name, then when
+you edit the file using the &quot;F&quot; command the data will
+be saved remotely in the folder.
+You aren't required to do anything special to create the folder, it
+gets created if you use a remote name.
+<P>
+If you type &quot;F&quot; you may edit the contents of the file (as opposed to
+the name of the file) you have specified.
+If you type &quot;T&quot; you may use a browser to choose an existing filename.
+<P>
+Besides containing regular text, the template file may also
+contain (or a template file program may produce) tokens that are replaced with text
+that depends on the message you are replying to or forwarding.
+The tokens all look like _word_ (a word surrounded by underscores).
+For example, if the token
+<P>
+<CENTER><SAMP>_DATE_</SAMP></CENTER>
+<P>
+is included in the text of the template file, then when you reply to
+or forward a message, the token will be replaced with the actual date
+the message you are replying to or forwarding was sent.
+<P>
+If you use a role that has a template file for a plain composition
+(that is, not a reply or forward) then there is no original message, so
+any tokens that depend on the message will be replaced with nothing.
+So if you want a template file to be useful for new compositions it
+shouldn't include any of the tokens that depend on the message being
+replied to or forwarded.
+<P>
+The list of available tokens is
+<A HREF="h_index_tokens">here</A>.
+<P>
+Actually, for the adventurous, there is a way to conditionally include text based
+on whether or not a token would result in specific replacement text.
+For example, you could include some text based on whether or not
+the _NEWS_ token would result in any newsgroups if it was used.
+It's explained in detail
+<A HREF="h_reply_token_conditionals">here</A>.
+<P>
+In the very unlikely event that you want to include a literal token in
+a template file, you must precede it with a backslash character.
+For example, to include the literal text _DATE_ you must actually use
+&#92;_DATE_.
+It is not possible to have a literal backslash followed by an expanded token.
+<P>
+A blank template field means that Alpine will not use a template file when
+this role is being used.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_stat_imp =======
+<HTML>
+<HEAD>
+<TITLE>Set Important Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Important Status Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+If set to &quot;Don't change it&quot; then this does nothing.
+If set to &quot;Set this state&quot; then the Important flag is set
+for the matching message.
+If set to &quot;Clear this state&quot; then the Important flag is cleared
+for the matching message.
+The important flag usually causes an asterisk to show up in the MESSAGE
+INDEX.
+It may also be useful when selecting a set of messages
+with the Select command.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_stat_new =======
+<HTML>
+<HEAD>
+<TITLE>Set New Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set New Status Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+If set to &quot;Don't change it&quot; then this does nothing.
+If set to &quot;Set this state&quot; then the
+matching message is marked New.
+If set to &quot;Clear this state&quot; then the
+matching message is marked Seen.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_stat_ans =======
+<HTML>
+<HEAD>
+<TITLE>Set Answered Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Answered Status Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+If set to &quot;Don't change it&quot; then this does nothing.
+If set to &quot;Set this state&quot; then the Answered flag is set
+for the matching message.
+If set to &quot;Clear this state&quot; then the Answered flag is cleared
+for the matching message.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_stat_del =======
+<HTML>
+<HEAD>
+<TITLE>Set Deleted Status Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Deleted Status Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+If set to &quot;Don't change it&quot; then this does nothing.
+If set to &quot;Set this state&quot; then the
+matching message is marked Deleted.
+If set to &quot;Clear this state&quot; then the
+matching message is marked UnDeleted.
+<P>
+You should not use this option unless you are prepared to have matching
+messages expunged from the folder permanently.
+For example, if you type the Expunge command, this filter is applied
+before the expunge, so matching messages will be marked Deleted and then
+will be permanently expunged from the folder.
+However, since the index isn't redrawn in between the time that the message
+is marked Deleted and the time that you are asked to expunge, the only
+indication that you are expunging the message comes in the number of messages
+being expunged.
+The same thing may happen when you close a folder.
+It is also possible that an expunge not initiated by you will
+delete matching messages.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_scoreval =======
+<HTML>
+<HEAD>
+<TITLE>Score Value Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Score Value Explained</H1>
+
+A message's score is the sum of the Score Values from all of the Scoring rules
+with Patterns that match the message.
+The value you give here is the Score Value associated with this rule.
+A Score Value is an integer between -100 and 100, with the default
+value of zero.
+<P>
+Alternatively, if the
+<A HREF="h_config_role_scorehdrtok">&quot;Score From Header&quot;</A>
+field is defined
+(on the line right below the &quot;Score Value&quot; field)
+then the &quot;Score Value&quot; is ignored and
+the &quot;Score From Header&quot; field is used instead.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_scorehdrtok =======
+<HTML>
+<HEAD>
+<TITLE>Score Value From Header Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Score Value From Header Explained</H1>
+
+This option provides a way to use a number that appears in the headers of your
+messages as the message's score, or as a component of that score.
+If this field is defined then it is used instead of the &quot;Score Value&quot;.
+The idea behind this option is that there may be a score embedded in the
+headers of messages that has already been calculated outside of Alpine.
+For example, messages delivered to you may contain an &quot;X-Spam&quot; header and
+somewhere in that header there is a score.
+<P>
+The value for this option is the name of the header followed by parentheses
+with two arguments inside:
+<P>
+<CENTER><SAMP>HeaderName(field_number,field_separators)</SAMP></CENTER>
+<P>
+No space is allowed between the comma and the start of the field_separators.
+It would be interpreted as the first separator if it was there.
+Field 0 is the whole line, Field 1 is the data up to the first separator, Field 2
+starts after that and goes to the second separator, and so on.
+It's easier to explain with examples.
+<P>
+<CENTER><SAMP>X-Spam(2,&quot;&nbsp;&quot;)</SAMP></CENTER>
+<P>
+In the above example the header that is used is the &quot;X-Spam&quot; header.
+The value of that header (the part after the colon and the space) is split
+into fields separated by spaces.
+<P>
+<CENTER><SAMP>Field1 &lt;space&gt; Field2 &lt;space&gt; Field3 ...</SAMP></CENTER>
+<P>
+The second field is selected and converted to an integer. It only makes sense
+if Field2 really is an integer.
+<P>
+Here's an example of a SpamAssassin header.
+The exact look of the header will vary, but if your incoming mail
+contains headers that look like the following
+<P>
+<CENTER><SAMP>X-Spam-Status: Yes, hits=10.6 tagged_above=-999.0 required=7.0 tests=BAYE...</SAMP></CENTER>
+<P>
+you might want to use the hits value as a score.
+Since the score is an integer value you can't make use of the decimal part of
+the number, but
+you might split off the hits=10 part as a score by using the characters &quot;=&quot;
+and &quot;.&quot; as your separators.
+<P>
+<CENTER><SAMP>X-Spam-Status(2,&quot;=.&quot;)</SAMP></CENTER>
+<P>
+The first field starts with the Y in Yes and goes until the &quot;=&quot; after
+hits.
+The second field is &quot;10&quot; so the score value would be 10.
+<P>
+Another example we've seen has headers that look like
+<P>
+<CENTER><SAMP>X-Spam: Gauge=IIIIIII, Probability=7%, Report=...</SAMP></CENTER>
+<P>
+Because there are two equals before the 7% the value
+<P>
+<CENTER><SAMP>X-Spam(3,&quot;=%&quot;)</SAMP></CENTER>
+<P>
+should capture the probability as the score.
+<P>
+The Score From Header scoring value actually works just like the
+regular Score Value in that the rest of the pattern has to match before
+it is used and the scores from all the different scoring rules that
+match for a particular message are added together.
+When using the Score From Header method it may (or may not) make sense to
+use only a single scoring rule with a pattern that matches every message.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_replyuse =======
+<HTML>
+<HEAD>
+<TITLE>Reply Use Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Reply Use Explained</H1>
+
+This option determines how this particular role will be used when Replying
+to a message.
+There are three possible values for this option.
+The value &quot;Never&quot;
+means that this role will not be a candidate for use when Replying.
+The role's Pattern will not be checked for a match, however the role will
+be available to be manually switched to if there is a confirmation prompt.
+<P>
+
+The options &quot;With confirmation&quot; and &quot;Without confirmation&quot;
+mean that you do want to consider this role when Replying.
+For either of these settings, the role's Pattern will be compared with
+the message being replied to.
+If there is a match then this role will either be used without confirmation
+or will be the default when confirmation is asked for, depending on
+which of the two options is selected.
+If confirmation is requested, you will also have a chance to
+manually change the role to any one of your other roles.
+<P>
+
+You won't be prompted for confirmation if none of your role Patterns
+match the message being replied to.
+This is independent of the value of the current option.
+The <A HREF="h_config_confirm_role"><!--#echo var="FEAT_confirm-role-even-for-default"--></A>
+feature may be used to change this behavior.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_forwarduse =======
+<HTML>
+<HEAD>
+<TITLE>Forward Use Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Forward Use Explained</H1>
+
+This option determines how this particular role will be used when Forwarding
+a message.
+There are three possible values for this option.
+The value &quot;Never&quot;
+means that this role will not be a candidate for use when Forwarding.
+The role's Pattern will not be checked for a match, however the role will
+be available to be manually switched to if there is a confirmation prompt.
+<P>
+
+The options &quot;With confirmation&quot; and &quot;Without confirmation&quot;
+mean that you do want to consider this role when Forwarding.
+For either of these settings, the role's Pattern will be compared with
+the message being forwarded.
+If there is a match then this role will either be used without confirmation
+or will be the default when confirmation is asked for, depending on
+which of the two options is selected.
+If confirmation is requested, you will also have a chance to
+manually change the role to any one of your other roles.
+<P>
+
+You won't be prompted for confirmation if none of your role Patterns
+match the message being forwarded.
+This is independent of the value of the current option.
+The <A HREF="h_config_confirm_role"><!--#echo var="FEAT_confirm-role-even-for-default"--></A>
+feature may be used to change this behavior.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_role_composeuse =======
+<HTML>
+<HEAD>
+<TITLE>Compose Use Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Compose Use Explained</H1>
+
+This option determines how this particular role will be used when Composing
+a new message using the &quot;Compose&quot; command.
+This does not affect what happens when using the &quot;Role&quot; command
+to compose a new message.
+The &quot;Role&quot; command allows you to select a role from all of the
+roles you have defined, regardless of what Uses you've assigned to those
+roles.
+<P>
+
+There are three possible values for this option.
+The value &quot;Never&quot;
+means that this role will not be a candidate for use when Composing.
+The role's Current Folder Type will not be checked for a match, however the role
+will be available to be manually switched to if there is a confirmation prompt.
+<P>
+
+The options &quot;With confirmation&quot; and &quot;Without confirmation&quot;
+mean that you do want to consider this role when Composing.
+For either of these settings,
+the role's Current Folder Type will be checked (since there is no message
+to compare with, the rest of the Pattern is considered a match).
+If there is a match then this role will either be used without confirmation
+or will be the default when confirmation is asked for, depending on
+which of the two options is selected.
+If confirmation is requested, you will also have a chance to
+manually change the role to any one of your other roles.
+<P>
+
+When using the Compose command the role checking is a little different
+because there is no message being replied to or forwarded.
+Because of this the Current Folder Type is checked but the header pattern
+fields, the AllText pattern, the BodyText pattern, and the Score Interval are all ignored.
+A role is considered to be a match if it is a candidate for Compose Use and
+its Current Folder Type matches the currently open folder.
+This could be useful if you want to set a role based on the folder you
+are reading, or the type of folder you are reading.
+<P>
+
+You won't be prompted for confirmation if none of your role Patterns
+are a match.
+This is independent of the value of the current option.
+The <A HREF="h_config_confirm_role"><!--#echo var="FEAT_confirm-role-even-for-default"--></A>
+feature may be used to change this behavior.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filter_folder =======
+<HTML>
+<HEAD>
+<TITLE>Filter Folder Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Filter Folder Explained</H1>
+
+When the Filter Action is set to &quot;Move&quot;,
+the folder or folders specified here will be used to store messages matching
+the provided pattern.
+
+<P>
+If you set the Filter Action to &quot;Move&quot; you must give a folder name
+here.
+
+<P>
+If you wish,
+you may choose a folder from your folder collections by using the
+&quot;T&quot; command.
+<P>
+Besides regular text, the folder name may also contain
+tokens that are replaced with text representing the current date
+when you run Alpine.
+For example, if the folder name you use is
+<P>
+<CENTER><SAMP>abc-_CURYEAR_-_CURMONTHABBREV_</SAMP></CENTER>
+<P>
+that is replaced with something like
+<P>
+<CENTER><SAMP>abc-2004-oct</SAMP></CENTER>
+<P>
+Or,
+<P>
+<CENTER><SAMP>abc-_CURYEAR2DIGIT_-_CURMONTH2DIGIT_</SAMP></CENTER>
+<P>
+becomes
+<P>
+<CENTER><SAMP>abc-04-10</SAMP></CENTER>
+<P>
+The token names must be surrounded by underscores in order to be recognized
+as tokens.
+The tokens that may be used are those that are derived from the current date.
+They're listed near the bottom of the list of tokens give
+<A HREF="h_index_tokens">here</A>.
+<P>
+Look &quot;<A HREF="h_rule_patterns">here</A>&quot;
+for more information on Patterns.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filter_kw_set =======
+<HTML>
+<HEAD>
+<TITLE>Set These Keywords Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Set These Keywords Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+Read a little about keywords in the help text for the
+<A HREF="h_common_flag">Flag</A> command.
+This option is a list of keywords that will be Set when there is a match.
+If you wish, you may choose keywords from the list of keywords you have
+defined with the &quot;T&quot; command.
+You may add new keywords by defining them in the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen.
+If you have given a keyword a nickname when configuring it,
+that nickname may be used instead of the actual keyword.
+
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filter_kw_clr =======
+<HTML>
+<HEAD>
+<TITLE>Clear These Keywords Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Clear These Keywords Explained</H1>
+
+This describes part of the action to be taken if the Pattern for this
+filter is a match.
+Read a little about keywords in the help text for the
+<A HREF="h_common_flag">Flag</A> command.
+This option is a list of keywords that will be Cleared when there is a match.
+If you wish, you may choose keywords from the list of keywords you have
+defined with the &quot;T&quot; command.
+You may add new keywords by defining them in the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen.
+If you have given a keyword a nickname when configuring it,
+that nickname may be used instead of the actual keyword.
+
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_index_tokens =======
+<HTML>
+<HEAD>
+<TITLE>Tokens for Index and Replying</TITLE>
+</HEAD>
+<BODY>
+
+This set of special tokens may be used in the
+<A HREF="h_config_index_format">&quot;<!--#echo var="VAR_index-format"-->&quot;</A> option,
+in the <A HREF="h_config_reply_intro">&quot;<!--#echo var="VAR_reply-leadin"-->&quot;</A> option,
+in signature files,
+in template files used in
+<A HREF="h_rules_roles">&quot;roles&quot;</A>, and in the folder name
+that is the target of a Filter Rule.
+Some of them aren't available in all situations.
+<P>
+The tokens are used as they appear below for the &quot;<!--#echo var="VAR_index-format"-->&quot;
+option, but they must be surrounded by underscores for the
+&quot;<!--#echo var="VAR_reply-leadin"-->&quot; option, in signature and template files,
+and in the target of Filter Rules.
+<P>
+<P>
+
+<H1><EM>Tokens Available for all Cases (except Filter Rules)</EM></H1>
+
+<DL>
+<DT>SUBJECT</DT>
+<DD>
+This token represents the Subject the sender gave the message.
+Alternatives for use in the index screen are
+SUBJKEY, SUBJKEYINIT, SUBJECTTEXT, SUBJKEYTEXT, and SUBJKEYINITTEXT.
+You may color the subject text in the MESSAGE INDEX screen differently by using the
+<A HREF="h_config_index_subject_color">Index Subject Color</A> and the
+<A HREF="h_config_index_opening_color">Index Opening Color</A>
+options available from
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+</DD>
+
+<DT>FROM</DT>
+<DD>
+This token represents the personal name (or email address if the name
+is unavailable) of the person specified in the message's &quot;From:&quot;
+header field.
+You may color the from text in the MESSAGE INDEX screen differently by using the
+<A HREF="h_config_index_from_color">Index From Color</A>
+option available from
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+</DD>
+
+<DT>ADDRESS</DT>
+<DD>
+This is similar to the &quot;FROM&quot; token, only it is always the
+email address, never the personal name.
+For example, &quot;mailbox@domain&quot;.
+</DD>
+
+<DT>MAILBOX</DT>
+<DD>
+This is the same as the &quot;ADDRESS&quot; except that the
+domain part of the address is left off.
+For example, &quot;mailbox&quot;.
+</DD>
+
+<DT>SENDER</DT>
+<DD>
+This token represents the personal name (or email address) of the person
+listed in the message's &quot;Sender:&quot; header field.
+</DD>
+
+<DT>TO</DT>
+<DD>
+This token represents the personal names (or email addresses if the names
+are unavailable) of the persons specified in the
+message's &quot;To:&quot; header field.
+</DD>
+
+<DT>NEWSANDTO</DT>
+<DD>
+This token represents the newsgroups from the
+message's &quot;Newsgroups:&quot; header field <EM>and</EM>
+the personal names (or email addresses if the names
+are unavailable) of the persons specified in the
+message's &quot;To:&quot; header field.
+</DD>
+
+<DT>TOANDNEWS</DT>
+<DD>
+Same as &quot;NEWSANDTO&quot; except in the opposite order.
+</DD>
+
+<DT>NEWS</DT>
+<DD>
+This token represents the newsgroups from the
+message's &quot;Newsgroups:&quot; header field.
+</DD>
+
+<DT>CC</DT>
+<DD>
+This token represents the personal names (or email addresses if the names
+are unavailable) of the persons specified in the
+message's &quot;Cc:&quot; header field.
+</DD>
+
+<DT>RECIPS</DT>
+<DD>
+This token represents the personal names (or email addresses if the names
+are unavailable) of the persons specified in both the
+message's &quot;To:&quot; header field and
+the message's &quot;Cc:&quot; header field.
+</DD>
+
+<DT>NEWSANDRECIPS</DT>
+<DD>
+This token represents the newsgroups from the
+message's &quot;Newsgroups:&quot; header field <EM>and</EM>
+the personal names (or email addresses if the names
+are unavailable) of the persons specified in the
+message's &quot;To:&quot; and &quot;Cc:&quot; header fields.
+</DD>
+
+<DT>RECIPSANDNEWS</DT>
+<DD>
+Same as &quot;NEWSANDRECIPS&quot; except in the opposite order.
+</DD>
+
+<DT>INIT</DT>
+<DD>
+This token represents the initials from the personal name
+of the person specified in the message's &quot;From:&quot;
+header field.
+If there is no personal name, it is blank.
+</DD>
+
+<DT>DATE</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format MMM DD. For example, &quot;Oct 23&quot;.
+The feature
+<A HREF="h_config_dates_to_local"><!--#echo var="FEAT_convert-dates-to-localtime"--></A>,
+which adjusts for the timezone the message was sent from,
+may have an affect on the value of this token as well as the values of
+all of the other DATE or TIME tokens.
+Some of the DATE and TIME tokens are displayed in a locale-specific
+way unless the option
+<A HREF="h_config_disable_index_locale_dates"><!--#echo var="FEAT_disable-index-locale-dates"--></A> is set.
+</DD>
+
+<DT>SMARTDATE</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It is &quot;Today&quot; if the message was sent today,
+&quot;Yesterday&quot; for yesterday,
+&quot;Wednesday&quot; if it was last Wednesday, and so on. If the
+message is from last year and is more than six months old it includes the year, as well.
+See the SMARTDATE alternatives below, as well.
+</DD>
+
+<DT>SMARTTIME</DT>
+<DD>
+This token represents the most relevant elements of the date on which
+the message was sent (according to the &quot;Date&quot; header field),
+in a compact form. If the message was sent today, only the time is used
+(e.g. &quot;9:22am&quot;, &quot;10:07pm&quot;); if it was sent during
+the past week, the day of the week and the hour are used
+(e.g. &quot;Wed09am&quot;, &quot;Thu10pm&quot;); other dates are
+given as date, month, and year (e.g. &quot;23Aug00&quot;,
+&quot;9Apr98&quot;).
+</DD>
+
+<DT>SMARTDATETIME</DT>
+<DD>
+This is a combination of SMARTDATE and SMARTTIME.
+It is SMARTDATE unless the SMARTDATE value is &quot;Today&quot;, in which
+case it is SMARTTIME.
+See the SMARTDATETIME alternatives below, as well.
+</DD>
+
+<DT>DATEISO</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format YYYY-MM-DD. For example, &quot;1998-10-23&quot;.
+</DD>
+
+<DT>SHORTDATEISO</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format YY-MM-DD. For example, &quot;98-10-23&quot;.
+</DD>
+
+<DT>SHORTDATE1</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format MM/DD/YY. For example, &quot;10/23/98&quot;.
+</DD>
+
+<DT>SHORTDATE2</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format DD/MM/YY. For example, &quot;23/10/98&quot;.
+</DD>
+
+<DT>SHORTDATE3</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format DD.MM.YY. For example, &quot;23.10.98&quot;.
+</DD>
+
+<DT>SHORTDATE4</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format YY.MM.DD. For example, &quot;98.10.23&quot;.
+</DD>
+
+<DT>LONGDATE</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It has the format MMM DD, YYYY. For example, &quot;Oct 23, 1998&quot;.
+</DD>
+
+<DT>SMARTDATE alternatives</DT>
+<DD>
+There are several versions of SMARTDATE that are all the same except
+for the way they format dates far in the past.
+SMARTDATE formats the date using the information from your locale settings
+to format the date string. It may end up formatting dates so that they look
+like DATEISO tokens, or SHORTDATE2 tokens, or something else entirely.
+The feature
+<A HREF="h_config_dates_to_local"><!--#echo var="FEAT_convert-dates-to-localtime"--></A>
+may have an affect on the values of these tokens.
+If you want more control you may use one of the following.
+ <DL>
+ <DT>SMARTDATE</DT> <DD>If the option
+<A HREF="h_config_disable_index_locale_dates"><!--#echo var="FEAT_disable-index-locale-dates"--></A> is not set
+then this will be locale specific. Control this with the
+LC_TIME locale setting on a UNIX system. On Windows
+the Regional Options control panel may be used to set the Short date
+format. At the programming level, the strftime routine is what Alpine
+uses to print the date.
+If the <!--#echo var="FEAT_disable-index-locale-dates"--> option is set then this is equivalent
+to SMARTDATES1.</DD>
+ <DT>SMARTDATEISO</DT> <DD>DATEISO format. See text above.</DD>
+ <DT>SMARTDATESHORTISO</DT> <DD>SHORTDATEISO format.</DD>
+ <DT>SMARTDATES1</DT> <DD>SHORTDATE1 format.</DD>
+ <DT>SMARTDATES2</DT> <DD>SHORTDATE2 format.</DD>
+ <DT>SMARTDATES3</DT> <DD>SHORTDATE3 format.</DD>
+ <DT>SMARTDATES4</DT> <DD>SHORTDATE4 format.</DD>
+ </DL>
+</DD>
+
+<DT>SMARTDATETIME alternatives</DT>
+<DD>
+There are several versions of SMARTDATETIME that are all very similar.
+The ones that end in 24 use a 24-hour clock for Today's messages instead
+of a 12-hour clock.
+The other variation is
+for the way they format dates far in the past.
+SMARTDATETIME and SMARTDATETIME24 format the date using the information from your locale settings
+to format the date string. It may end up formatting dates so that they look
+like DATEISO tokens, or SHORTDATE2 tokens, or something else entirely.
+The feature
+<A HREF="h_config_dates_to_local"><!--#echo var="FEAT_convert-dates-to-localtime"--></A>
+may have an affect on the values of these tokens.
+The possible choices are:
+ <DL>
+ <DT>SMARTDATETIME</DT> <DD>If the option
+<A HREF="h_config_disable_index_locale_dates"><!--#echo var="FEAT_disable-index-locale-dates"--></A> is not set
+then this will be locale specific. Control this with the
+LC_TIME locale setting on a UNIX system. On Windows
+the Regional Options control panel may be used to set the Short date
+format. At the programming level, the strftime routine is what Alpine
+uses to print the date.
+If the <!--#echo var="FEAT_disable-index-locale-dates"--> option is set then this is equivalent
+to SMARTDATETIMES1.</DD>
+ <DT>SMARTDATETIME24</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMEISO</DT> <DD>DATEISO format. See text above.</DD>
+ <DT>SMARTDATETIMEISO24</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMESHORTISO</DT> <DD>SHORTDATEISO format.</DD>
+ <DT>SMARTDATETIMESHORTISO24</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMES1</DT> <DD>SHORTDATE1 format.</DD>
+ <DT>SMARTDATETIMES124</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMES2</DT> <DD>SHORTDATE2 format.</DD>
+ <DT>SMARTDATETIMES224</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMES3</DT> <DD>SHORTDATE3 format.</DD>
+ <DT>SMARTDATETIMES324</DT> <DD>Use TIME24 for Today</DD>
+ <DT>SMARTDATETIMES4</DT> <DD>SHORTDATE4 format.</DD>
+ <DT>SMARTDATETIMES424</DT> <DD>Use TIME24 for Today</DD>
+ </DL>
+</DD>
+
+<DT>DAYDATE</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It looks like &quot;Sat, 23 Oct 1998&quot;.
+This token is never converted in any locale-specific way.
+</DD>
+
+<DT>PREFDATE</DT>
+<DD>
+This token represents the date on which the message was sent, according
+to the &quot;Date&quot; header field.
+It is your operating system's idea of the preferred date representation for the current locale.
+Internally it uses the %x version of the date from the strftime routine.
+</DD>
+
+<DT>PREFTIME</DT>
+<DD>
+This token represents the time at which the message was sent, according
+to the &quot;Date&quot; header field.
+It is the preferred time representation for the current locale.
+Internally it uses the %X version of the time from the strftime routine.
+</DD>
+
+<DT>PREFDATETIME</DT>
+<DD>
+This token represents the date and time at which the message was sent, according
+to the &quot;Date&quot; header field.
+It is the preferred date and time representation for the current locale.
+Internally it uses the %c version of the time from the strftime routine.
+</DD>
+
+<DT>DAY</DT>
+<DD>
+This token represents the day of the month on which the message was sent,
+according to the &quot;Date&quot; header field.
+For example, &quot;23&quot; or &quot;9&quot;.
+</DD>
+
+<DT>DAY2DIGIT</DT>
+<DD>
+This token represents the day of the month on which the message was sent,
+according to the &quot;Date&quot; header field.
+For example, &quot;23&quot; or &quot;09&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>DAYORDINAL</DT>
+<DD>
+This token represents the ordinal number that is the day of
+the month on which the message was sent,
+according to the &quot;Date&quot; header field.
+For example, &quot;23rd&quot; or &quot;9th&quot;.
+</DD>
+
+<DT>DAYOFWEEK</DT>
+<DD>
+This token represents the day of the week on which the message was sent,
+according to the &quot;Date&quot; header field.
+For example, &quot;Sunday&quot; or &quot;Wednesday&quot;.
+</DD>
+
+<DT>DAYOFWEEKABBREV</DT>
+<DD>
+This token represents the day of the week on which the message was sent,
+according to the &quot;Date&quot; header field.
+For example, &quot;Sun&quot; or &quot;Wed&quot;.
+</DD>
+
+<DT>MONTHABBREV</DT>
+<DD>
+This token represents the month the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;Oct&quot;.
+</DD>
+
+<DT>MONTHLONG</DT>
+<DD>
+This token represents the month in which the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;October&quot;.
+</DD>
+
+<DT>MONTH</DT>
+<DD>
+This token represents the month in which the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;10&quot; or &quot;9&quot;.
+</DD>
+
+<DT>MONTH2DIGIT</DT>
+<DD>
+This token represents the month in which the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;10&quot; or &quot;09&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>YEAR</DT>
+<DD>
+This token represents the year the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;1998&quot; or &quot;2001&quot;.
+</DD>
+
+<DT>YEAR2DIGIT</DT>
+<DD>
+This token represents the year the message was sent, according
+to the &quot;Date&quot; header field.
+For example, &quot;98&quot; or &quot;01&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>TIME24</DT>
+<DD>
+This token represents the time at which the message was sent, according
+to the &quot;Date&quot; header field.
+There is no adjustment made for different time zones, so you'll get
+the time the message was sent according to the time zone the sender
+was in.
+It has the format HH:MM. For example, &quot;17:28&quot;.
+</DD>
+
+<DT>TIME12</DT>
+<DD>
+This token represents the time at which the message was sent, according
+to the &quot;Date&quot; header field.
+This time is for a 12 hour clock.
+It has the format HH:MMpm.
+For example, &quot;5:28pm&quot; or &quot;11:13am&quot;.
+</DD>
+
+<DT>TIMEZONE</DT>
+<DD>
+This token represents the numeric timezone from
+the &quot;Date&quot; header field.
+It has the format [+-]HHMM. For example, &quot;-0800&quot;.
+</DD>
+
+</DL>
+
+<P>
+<H1><EM>Tokens Available Only for <!--#echo var="VAR_index-format"--></EM></H1>
+
+<DL>
+<DT>MSGNO</DT>
+<DD>
+This token represents the message's current position in the folder that,
+of course, may change as the folder is sorted or new mail arrives.
+</DD>
+
+<DT>STATUS</DT>
+<DD>
+This token represents a three character wide field displaying various
+aspects of the message's state.
+The first character is either blank,
+a '*' for message marked Important, or a '+' indicating a message
+addressed directly to you (as opposed to your having received it via a
+mailing list, for example).
+When the feature
+&quot;<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>&quot;
+is set, if the first character would have been
+blank then it will instead be a '-' if the message is cc'd to you.
+The second character is typically blank,
+though the arrow cursor may occupy it if either the
+&quot;<A HREF="h_config_force_low_speed"><!--#echo var="FEAT_assume-slow-link"--></A>&quot;
+or the
+&quot;<A HREF="h_config_force_arrow"><!--#echo var="FEAT_force-arrow-cursor"--></A>&quot; feature
+is set (or you actually are on a slow link).
+The third character is either '<A HREF="h_flag_deleted">D</A>' (Deleted),
+'<A HREF="h_flag_answered">A</A>' (Answered),
+'<A HREF="h_flag_forwarded">F</A>' (Forwarded),
+'<A HREF="h_flag_new">N</A>' (New), or blank.
+<P>
+If you are using a threaded view of the index and this message is at the
+top of a collapsed portion of a thread,
+then this token refers to all of the messages in the collapsed portion of
+the thread instead of just the top message.
+The first character will be a '*' if <EM>any</EM> of the messages in the thread
+are marked Important, else a '+' if any of the messages are addressed
+to you, else a '-' if any of the messages are cc'd to you.
+The third character will be a 'D' if <EM>all</EM> of the messages
+in the collapsed thread are marked deleted,
+an 'A' if <EM>all</EM> of the messages
+in the collapsed thread are marked answered,
+it will be an 'N' if any of
+the messages are undeleted and unseen, and it will be blank otherwise.
+</DD>
+
+<DT>FULLSTATUS</DT>
+<DD>
+This token represents a less abbreviated alternative
+to the &quot;STATUS&quot; token.
+It is six characters wide.
+The first character is '+', '-', or blank, the
+second blank, the third either '*' or blank, the fourth
+'<A HREF="h_flag_new">N</A>' or blank,
+the fifth '<A HREF="h_flag_answered">A</A>'
+or blank, and the sixth character is
+either '<A HREF="h_flag_deleted">D</A>' or
+blank.
+<P>
+If you are using a threaded view of the index and this message is at the
+top of a collapsed portion of a thread,
+then this token refers to all of the messages in the collapsed portion of
+the thread instead of just the top message.
+The first character is '+', '-', or blank depending on whether <EM>any</EM>
+of the messages in the collapsed thread are addressed to you or cc'd to you.
+The third character will be '*' if any of the messages are marked
+Important.
+The fourth character will be 'N' if all of the messages in the thread
+are New, else 'n' if some of the messages in the thread are New, else blank.
+The fifth character will be 'A' or 'a' or blank, and the sixth character
+will be 'D' or 'd' or blank.
+</DD>
+
+<DT>IMAPSTATUS</DT>
+<DD>
+This token represents an even less abbreviated alternative to the
+&quot;STATUS&quot; token.
+It differs from &quot;FULLSTATUS&quot; in only the fourth character, which is
+an 'N' if the message is new to this folder since the last time
+it was opened <EM>and</EM> it has not been viewed, an 'R' (Recent) if the message
+is new to the folder and has been viewed, a 'U' (Unseen) if the message is not
+new to the folder since it was last opened <EM>but</EM> has not been
+viewed, or a blank if the message has been in the folder since it was
+last opened and has been viewed.
+<P>
+If you are using a threaded view of the index and this message is at the
+top of a collapsed portion of a thread,
+then the fourth character will be
+'N' if all of the messages in the thread are unseen and recent;
+else 'n' if some of the messages in the thread are unseen and recent;
+else 'U' if all of the messages in the thread are unseen and not recent;
+else 'u' if some of the messages in the thread are unseen and not recent;
+else 'R' if all of the messages in the thread are seen and recent;
+else 'r' if some of the messages in the thread are seen and recent;
+else blank.
+</DD>
+
+<DT>SHORTIMAPSTATUS</DT>
+<DD>
+This is the same as the last four of the six characters of IMAPSTATUS,
+so the '+' To Me information will be missing.
+</DD>
+
+<DT>SIZE</DT>
+<DD>
+This token represents the total size, in bytes, of the message.
+If a &quot;K&quot; (Kilobyte)
+follows the number, the size is approximately 1,000
+times that many bytes (rounded to the nearest 1,000).
+If an &quot;M&quot; (Megabyte) follows the number, the size is approximately
+1,000,000 times that many bytes.
+Commas are not used in this field.
+This field is seven characters wide, including the enclosing parentheses.
+Sizes are rounded when &quot;K&quot; or &quot;M&quot; is present.
+The progression of sizes used looks like:
+
+<P>
+<CENTER><SAMP>0 1 ... 9999 10K ... 999K 1.0M ... 99.9M 100M ... 2000M</SAMP></CENTER>
+<P>
+</DD>
+
+<DT>SIZECOMMA</DT>
+<DD>
+This token represents the total size, in bytes, of the message.
+If a &quot;K&quot; (Kilobyte)
+follows the number, the size is approximately 1,000
+times that many bytes (rounded to the nearest 1,000).
+If an &quot;M&quot; (Megabyte) follows the number, the size is approximately
+1,000,000 times that many bytes.
+Commas are used if the number shown is 1,000 or greater.
+The SIZECOMMA field is one character wider than the SIZE field.
+Sizes are rounded when &quot;K&quot; or &quot;M&quot; is present.
+The progression of sizes used looks like:
+
+<P>
+<CENTER><SAMP>0 1 ... 99,999 100K ... 9,999K 10.0M ... 999.9M 1,000M ... 2,000M</SAMP></CENTER>
+<P>
+</DD>
+
+<DT>KSIZE</DT>
+<DD>
+This token represents the total size of the message, expressed in
+kilobytes or megabytes, as most appropriate.
+These are 1,024 byte kilobytes and 1,024 x 1,024 byte megabytes.
+The progression of sizes used looks like:
+
+<P>
+<CENTER><SAMP>0K 1K ... 1023K 1.0M ... 99.9M 100M ... 2047M</SAMP></CENTER>
+<P>
+</DD>
+
+<DT>SIZENARROW</DT>
+<DD>
+This token represents the total size, in bytes, of the message.
+If a &quot;K&quot; (Kilobyte)
+follows the number, the size is approximately 1,000
+times that many bytes.
+If an &quot;M&quot; (Megabyte) follows the number, the size is approximately
+1,000,000 times that many bytes.
+If a &quot;G&quot; (Gigabyte) follows the number, the size is approximately
+1,000,000,000 times that many bytes.
+This field uses only five characters of screen width, including the enclosing
+parentheses.
+The progression of sizes used looks like:
+
+<P>
+<CENTER><SAMP>0 1 ... 999 1K ... 99K .1M ... .9M 1M ... 99M .1G ... .9G 1G 2G</SAMP></CENTER>
+<P>
+</DD>
+
+<DT>DESCRIPSIZE</DT>
+<DD>
+This token is intended to represent a more useful description of the
+message than just its size, but it isn't very useful at this point.
+The plus sign in this view means there are attachments.
+Note that including this token in
+the &quot;<!--#echo var="VAR_index-format"-->&quot; could slow down the
+display a little while Alpine collects the necessary information.
+</DD>
+
+<DT>SUBJKEY</DT>
+<DD>
+This token is the same as the SUBJECT token unless keywords are set for
+the message.
+In that case, a list of keywords enclosed in braces will be prepended to
+the subject of the message.
+Only those keywords that you have defined in your
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option
+in Setup/Config are considered in the list.
+In other words, keywords that have been set by some other means, perhaps
+by another email program, won't show up unless included in
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>.
+Having this set in the <!--#echo var="VAR_index-format"--> will also cause the keywords to be
+prepended to the subject in the MESSAGE TEXT screen.
+If you have given a keyword a nickname
+(<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>), that nickname is displayed
+instead of the actual keyword.
+The <A HREF="h_config_kw_braces"><!--#echo var="VAR_keyword-surrounding-chars"--></A>
+option may be used to modify this token slightly.
+It is also possible to color keywords in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+</DD>
+
+<DT>SUBJKEYINIT</DT>
+<DD>
+This token is the same as the SUBJKEY token except that instead of
+prepending a list of keywords to the subject, a list of first initials
+of keywords will be prepended instead.
+For example, if a message has the keywords <EM>Work</EM> and <EM>Now</EM>
+set (or Work and Now are the Alpine nicknames of keywords that are set)
+then the SUBJKEY token would cause a result like
+<P>
+<CENTER><SAMP>{Work Now} actual subject</SAMP></CENTER>
+<P>
+whereas the SUBJKEYINIT token would give
+<P>
+<CENTER><SAMP>{WN} actual subject</SAMP></CENTER>
+<P>
+Only those keywords that you have defined in your
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option
+in Setup/Config are considered in the list.
+In other words, keywords that have been set by some other means, perhaps
+by another email program, won't show up unless included in
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>.
+The <A HREF="h_config_kw_braces"><!--#echo var="VAR_keyword-surrounding-chars"--></A>
+option may be used to modify this token slightly.
+It is also possible to color keywords in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+</DD>
+
+<DT>SUBJECTTEXT</DT>
+<DD>
+Same as SUBJECT but if there is room in the Subject field for more text,
+the opening part of the text of the message is displayed after the subject.
+The time needed to fetch the text may cause a performance problem
+which can, of course, be avoided by using the SUBJECT version of
+the Subject instead.
+You may color this opening text differently by using the
+<A HREF="h_config_index_opening_color">Index Opening Color</A> option available from
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+You may adjust the characters that are displayed between the Subject and the
+opening text with the option
+<A HREF="h_config_opening_sep"><!--#echo var="VAR_opening-text-separator-chars"--></A>.
+</DD>
+
+<DT>SUBJKEYTEXT</DT>
+<DD>
+Same as SUBJKEY but with the opening message text.
+</DD>
+
+<DT>OPENINGTEXT</DT>
+<DD>
+This is similar to SUBJECTTEXT.
+Instead of combining the Subject and the opening text in a single
+field in the index screen this token allows you to allocate a
+separate column just for the opening text of the message.
+The time needed to fetch this text may cause a performance problem.
+You may color this opening text differently by using the
+<A HREF="h_config_index_opening_color">Index Opening Color</A> option available from
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+</DD>
+
+<DT>OPENINGTEXTNQ</DT>
+<DD>
+This is very similar to OPENINGTEXT.
+The NQ stands for No Quotes.
+The only difference is that quoted text (lines beginning with &gt;) is deleted.
+For some messages this may be confusing.
+For example, a message might have a line preceding some quoted
+text that reads something like &quot;On May 8th person A said.&quot;
+That no longer makes sense after the quoted text is deleted and it
+will appear that person A said whatever the text after the quote
+is, even though that is really person B talking.
+</DD>
+
+<DT>SUBJKEYINITTEXT</DT>
+<DD>
+Same as SUBJKEYINIT but with the opening message text.
+</DD>
+
+<DT>KEY</DT>
+<DD>
+This is a space-delimited list of keywords that are set for the message.
+Only those keywords that you have defined in your
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option
+in Setup/Config are considered in the list.
+In other words, keywords that have been set by some other means, perhaps
+by another email program, won't show up unless included in
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>.
+If you have given a keyword a nickname
+(<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>), that nickname is displayed
+instead of the actual keyword.
+It is also possible to color keywords in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+This token defaults to an arbitrary width of 5.
+You should set it to whatever width suits you using something
+like KEY(17) in the <!--#echo var="VAR_index-format"-->.
+</DD>
+
+<DT>KEYINIT</DT>
+<DD>
+This is a list of keyword initials that are set for the message.
+If you have given a keyword a nickname
+(<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>), the initial of that nickname
+is displayed instead of the initial of the actual keyword.
+It is also possible to color keyword initials in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+This token defaults to an arbitrary width of 2.
+You should set it to whatever width suits you using something
+like KEYINIT(3) in the <!--#echo var="VAR_index-format"-->.
+</DD>
+
+<DT>PRIORITY</DT>
+<DD>
+The X-Priority header is a non-standard header that is used in a
+somewhat standard way by many mail programs.
+Alpine expects the value of this header to be a digit with a value
+from 1 to 5, with 1 being the highest priority and 5 the lowest priority.
+Since this priority is something that the sender sets it is only an indication
+of the priority that the sender attaches to the mail and it is therefore almost
+totally unreliable for use as a filtering criterion.
+This token will display the numeric value of the priority if it is between
+1 and 5.
+It will be suppressed (blank) if the value is 3, which is normal priority.
+This token may be colored with the
+<A HREF="h_config_index_pri_color">Index Priority Symbol Colors</A>.
+</DD>
+
+<DT>PRIORITYALPHA</DT>
+<DD>
+This is a more verbose interpretation of the X-Priority field.
+Once again nothing is displayed unless the value of the field
+is 1, 2, 4, or 5.
+The values displayed for those values are:
+<P>
+<TABLE>
+<TR> <TD>1</TD> <TD>Highest</TD> </TR>
+<TR> <TD>2</TD> <TD>High</TD> </TR>
+<TR> <TD>4</TD> <TD>Low</TD> </TR>
+<TR> <TD>5</TD> <TD>Lowest</TD> </TR>
+</TABLE>
+<P>
+This token may be colored with the
+<A HREF="h_config_index_pri_color">Index Priority Symbol Colors</A>.
+</DD>
+
+<DT>PRIORITY!</DT>
+<DD>
+This is a one character, non-numeric version of the X-Priority field.
+If the value of the X-Priority header is 1 or 2 an exclamation
+point is displayed.
+If the value is 4 or 5 a &quot;v&quot; (think down arrow) is displayed.
+This token may be colored with the
+<A HREF="h_config_index_pri_color">Index Priority Symbol Colors</A>.
+</DD>
+
+<DT>ATT</DT>
+<DD>
+This is a one column wide field that represents the number of attachments
+a message has. It will be blank if there are no attachments, a single
+digit for one to nine attachments, or an asterisk for more than nine.
+Note that including this token in
+the &quot;<!--#echo var="VAR_index-format"-->&quot; could slow down the
+display a little while Alpine collects the necessary information.
+</DD>
+
+<DT>FROMORTO</DT>
+<DD>
+This token represents <EM>either</EM> the personal name (or email address) of
+the person listed in the message's &quot;From:&quot; header
+field, <EM>or</EM>, if that address is yours or one of your
+<A HREF="h_config_alt_addresses">alternate addresses</A>,
+the first person specified in the
+message's &quot;To:&quot; header field
+with the prefix &quot;To: &quot; prepended.
+If the from address is yours and there is also no &quot;To&quot; address,
+Alpine will use the address on the &quot;Cc&quot; line.
+If there is no address there, either, Alpine will look for a newsgroup name
+from the &quot;Newsgroups&quot; header field and put
+that after the &quot;To: &quot; prefix.
+</DD>
+
+<DT>FROMORTONOTNEWS</DT>
+<DD>
+This is almost the same as <EM>FROMORTO</EM>.
+The difference is that newsgroups aren't considered.
+When a message is from you, doesn't have a To or Cc, and does have
+a Newsgroups header; this token will be your name instead of the name
+of the newsgroup (like it would be with FROMORTO).
+</DD>
+
+<DT>TEXT</DT>
+<DD>
+This is a different sort of token.
+It allows you to display a label within each index line.
+It will be the same fixed text for each line.
+It is different from all the other tokens in that there is no space column
+displayed after this token.
+Instead, it is butted up against the following field.
+It also has a different syntax.
+The text to display is given following a colon after the
+word &quot;TEXT&quot;.
+For example,
+<P>
+<CENTER><SAMP>TEXT:abc=</SAMP></CENTER>
+<P>
+would insert the literal text &quot;abc=&quot; (without the quotes)
+into the index display line.
+You must quote the text if it includes space characters, like
+<P>
+<CENTER><SAMP>TEXT:&quot;abc&nbsp;=&nbsp;&quot;</SAMP></CENTER>
+<P>
+</DD>
+
+<DT>HEADER</DT>
+<DD>
+This allows you to display the text from a particular header line in the
+message.
+The syntax for this token is substantially different from all the others
+in order that you might be able to display a portion of the text following
+a particular header.
+The header name you are interested in is given following a colon
+after the word &quot;HEADER&quot;.
+For example,
+<P>
+<CENTER><SAMP>HEADER:X-Spam</SAMP></CENTER>
+<P>
+would display the text of the X-Spam header, if any.
+Like for other index tokens a width field may (and probably should)
+follow this.
+<P>
+<CENTER><SAMP>HEADER:X-Spam(10)</SAMP></CENTER>
+<P>
+displays the first ten characters of the X-Spam header.
+Unlike other index tokens, the syntax for HEADER is more flexible.
+An optional second argument comes after a comma inside the parentheses.
+It specifies the &quot;field&quot; number.
+By default, the field separator is a space character.
+No extra space characters are allowed in the argument list.
+<P>
+<CENTER><SAMP>HEADER:X-Spam(10,2)</SAMP></CENTER>
+<P>
+would display the second field, left-justified, in a 10 character
+wide field.
+The second field would consist of all the text after the first space
+up to the next space or the end of the header.
+The default field number is zero, which stands for the entire line.
+There is also an optional third argument that is a list of field
+separators. It defaults to a space character.
+The example
+<P>
+<CENTER><SAMP>HEADER:X-Spam(10,2,:%&nbsp;)</SAMP></CENTER>
+<P>
+would cause the field separators to be any of colon, percent,
+or space (there is a space character between the percent and the
+right parenthesis).
+The first field runs from the start of the header value up to the first
+colon, percent, or space; the second goes from there to the next; and so on.
+In order to use a comma character as a field separator you must escape
+it by preceding it with a backslash (&#92;).
+The same is true of the backslash character itself.
+There is one further optional argument.
+It is an R or an L to specify right or left adjustment of the text
+within the field.
+The default is to left justify, however if you are displaying numbers
+you might prefer to right justify.
+<P>
+Here's an example of a SpamAssassin header.
+The exact look of the header will vary, but if your incoming mail
+contains headers that look like the following
+<P>
+<CENTER><SAMP>X-Spam-Status: Yes, hits=10.6 tagged_above=-999.0 required=7.0 tests=BAYE...</SAMP></CENTER>
+<P>
+you might want to display the hits value.
+The first field starts with the Y in Yes.
+To get what you're interested in you might use &quot;=&quot; and
+space as the field separators and display the third field, like
+<P>
+<CENTER><SAMP>HEADER:X-Spam-Status(4,3,=&nbsp;)</SAMP></CENTER>
+<P>
+or maybe you would break at the dot instead
+<P>
+<CENTER><SAMP>HEADER:X-Spam-Status(2,2,=.,R)</SAMP></CENTER>
+<P>
+Another example we've seen has headers that look like
+<P>
+<CENTER><SAMP>X-Spam: Gauge=IIIIIII, Probability=7%, Report=...</SAMP></CENTER>
+<P>
+Because there are two equals and a comma before the 7% and a comma
+after it, the token
+<P>
+<CENTER><SAMP>HEADER:X-Spam(3,4,=&#92;,,R)</SAMP></CENTER>
+<P>
+should display the probability (for example 7% or 83%) right justified
+in a 3-wide field.
+</DD>
+
+<DT>ARROW</DT>
+<DD>
+This gives an alternative way to display the current message in the
+MESSAGE INDEX screen.
+Usually the current message is indicated by the line being shown in
+reverse video.
+Instead, if the ARROW token is included in your <!--#echo var="VAR_index-format"-->,
+the current line will include an &quot;arrow&quot; that
+looks like
+<P>
+<CENTER><SAMP>-&gt;</SAMP></CENTER>
+<P>
+in the ARROW token's field.
+For all of the non-current messages, the ARROW field will be filled
+with blanks.
+If you use the fixed-field width feature the length of the &quot;arrow&quot;
+may be adjusted.
+The arrow will be drawn as width-1 dashes followed by a greater than sign.
+For example, if you use ARROW(3) you will get
+<P>
+<CENTER><SAMP>--&gt;</SAMP></CENTER>
+<P>
+and ARROW(1) will give you just
+<P>
+<CENTER><SAMP>&gt;</SAMP></CENTER>
+<P>
+It is also possible to set the color of the ARROW field.
+By default (and for non-current messages) the arrow is colored the same
+as the index line it is part of.
+You may set it to be another color with the
+<A HREF="h_config_index_arrow_color">Index Arrow Color</A> option available from
+the <A HREF="h_color_setup">Setup Kolor</A> screen.
+</DD>
+
+<DT>SCORE</DT>
+<DD>
+This gives the
+<a href="h_rules_score">score</a>
+of each message.
+This will be six columns wide to accomodate the widest possible score.
+You will probably want to use the <!--#echo var="VAR_index-format"--> fixed-field width feature
+to limit the width of the field to the widest score that
+you use (e.g. SCORE(3) if your scores are always between 0 and 999).
+If you have not defined any score rules the scores will all be zero.
+If any of your score rules contain AllText or BodyText patterns
+then including SCORE in the <!--#echo var="VAR_index-format"-->
+may slow down the display of the MESSAGE INDEX screen.
+</DD>
+</DL>
+
+<P>
+<H1><EM>Tokens Available for all but <!--#echo var="VAR_index-format"--></EM></H1>
+
+<DL>
+<DT>CURNEWS</DT>
+<DD>
+This token represents the current newsgroup if there is one.
+For example, &quot;comp.mail.pine&quot;.
+</DD>
+
+<DT>MSGID</DT>
+<DD>
+This token represents the message ID of the message.
+This token does not work with Filter Rule folder names.
+</DD>
+
+<DT>CURDATE</DT>
+<DD>
+This token represents the current date.
+It has the format MMM DD. For example, &quot;Oct 23&quot;.
+</DD>
+
+<DT>CURDATEISO</DT>
+<DD>
+This token represents the current date.
+It has the format YYYY-MM-DD. For example, &quot;1998-10-23&quot;.
+</DD>
+
+<DT>CURDATEISOS</DT>
+<DD>
+This token represents the current date.
+It has the format YY-MM-DD. For example, &quot;98-10-23&quot;.
+</DD>
+
+<DT>CURPREFDATE</DT>
+<DD>
+This token represents the current date.
+It is your operating system's idea of the preferred date representation for the current locale.
+Internally it uses the %x version of the date from the strftime routine.
+</DD>
+
+<DT>CURPREFTIME</DT>
+<DD>
+This token represents the current time.
+It is the preferred time representation for the current locale.
+Internally it uses the %X version of the time from the strftime routine.
+</DD>
+
+<DT>CURPREFDATETIME</DT>
+<DD>
+This token represents the current date and time.
+It is the preferred date and time representation for the current locale.
+Internally it uses the %c version of the time from the strftime routine.
+</DD>
+
+<DT>CURTIME24</DT>
+<DD>
+This token represents the current time.
+It has the format HH:MM. For example, &quot;17:28&quot;.
+</DD>
+
+<DT>CURTIME12</DT>
+<DD>
+This token represents the current time.
+This time is for a 12 hour clock.
+It has the format HH:MMpm.
+For example, &quot;5:28pm&quot; or &quot;11:13am&quot;.
+</DD>
+
+<DT>CURDAY</DT>
+<DD>
+This token represents the current day of the month.
+For example, &quot;23&quot; or &quot;9&quot;.
+</DD>
+
+<DT>CURDAY2DIGIT</DT>
+<DD>
+This token represents the current day of the month.
+For example, &quot;23&quot; or &quot;09&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>CURDAYOFWEEK</DT>
+<DD>
+This token represents the current day of the week.
+For example, &quot;Sunday&quot; or &quot;Wednesday&quot;.
+</DD>
+
+<DT>CURDAYOFWEEKABBREV</DT>
+<DD>
+This token represents the current day of the week.
+For example, &quot;Sun&quot; or &quot;Wed&quot;.
+</DD>
+
+<DT>CURMONTH</DT>
+<DD>
+This token represents the current month.
+For example, &quot;10&quot; or &quot;9&quot;.
+</DD>
+
+<DT>CURMONTH2DIGIT</DT>
+<DD>
+This token represents the current month.
+For example, &quot;10&quot; or &quot;09&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>CURMONTHLONG</DT>
+<DD>
+This token represents the current month.
+For example, &quot;October&quot;.
+</DD>
+
+<DT>CURMONTHABBREV</DT>
+<DD>
+This token represents the current month.
+For example, &quot;Oct&quot;.
+</DD>
+
+<DT>CURYEAR</DT>
+<DD>
+This token represents the current year.
+For example, &quot;1998&quot; or &quot;2001&quot;.
+</DD>
+
+<DT>CURYEAR2DIGIT</DT>
+<DD>
+This token represents the current year.
+For example, &quot;98&quot; or &quot;01&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>LASTMONTH</DT>
+<DD>
+This token represents last month.
+For example, if this is November (the 11th month),
+it is equal to &quot;10&quot; or if this is October (the 10th month),
+it is &quot;9&quot;.
+It is possible that this and the other tokens beginning with LASTMONTH
+below could be useful when used with a Filtering Rule that
+has the &quot;Beginning of Month&quot; option set.
+</DD>
+
+<DT>LASTMONTH2DIGIT</DT>
+<DD>
+This token represents last month.
+For example, if this is November (the 11th month),
+it is equal to &quot;10&quot; or if this is October (the 10th month),
+it is &quot;09&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>LASTMONTHLONG</DT>
+<DD>
+This token represents last month.
+For example, if this is November the value is &quot;October&quot;.
+</DD>
+
+<DT>LASTMONTHABBREV</DT>
+<DD>
+This token represents last month.
+For example, if this is November the value is &quot;Oct&quot;.
+</DD>
+
+<DT>LASTMONTHYEAR</DT>
+<DD>
+This token represents what the year was a month ago.
+For example, if this is October, 1998, it is &quot;1998&quot;.
+If this is January, 1998, it is &quot;1997&quot;.
+</DD>
+
+<DT>LASTMONTHYEAR2DIGIT</DT>
+<DD>
+This token represents what the year was a month ago.
+For example, if this is October, 1998, it is &quot;98&quot;.
+If this is January, 1998, it is &quot;97&quot;.
+</DD>
+
+<DT>LASTYEAR</DT>
+<DD>
+This token represents last year.
+For example, if this is 1998, it equals &quot;1997&quot;.
+It is possible that this
+could be useful when used with a Filtering Rule that
+has the &quot;Beginning of Year&quot; option set.
+</DD>
+
+<DT>LASTYEAR2DIGIT</DT>
+<DD>
+This token represents last year.
+For example, if this is 1998, it equals &quot;97&quot;.
+It is always 2 digits.
+</DD>
+
+<DT>ROLENICK</DT>
+<DD>
+This token represents the nickname of the
+role currently being used. If no role is being used,
+then no text will be printed for this token.
+This token does not work with Filter Rule folder names.
+</DD>
+</DL>
+
+<P>
+<H1><EM>Token Available Only for <!--#echo var="VAR_reply-leadin"--></EM></H1>
+See the help for the
+<A HREF="h_config_reply_intro">&quot;<!--#echo var="VAR_reply-leadin"-->&quot;</A> option
+to see why you might want to use this.
+Since the <!--#echo var="VAR_reply-leadin"--> contains free text this token
+must be surrounded by underscores when used.
+
+<DL>
+<DT>NEWLINE</DT>
+<DD>
+This is an end of line marker.
+</DD>
+</DL>
+
+<P>
+<H1><EM>Token Available Only for Templates and Signatures</EM></H1>
+
+<DL>
+<DT>CURSORPOS</DT>
+<DD>
+This token is different from the others.
+When it is replaced it is replaced with nothing, but it sets an Alpine
+internal variable that tells the composer to start with the cursor
+positioned at the position where this token was.
+If both the template file and the signature file contain
+a &quot;CURSORPOS&quot; token, then the position in the template file
+is used.
+If there is a template file and neither it nor the signature file contains
+a &quot;CURSORPOS&quot; token, then the cursor is positioned
+after the end of the contents of the
+template file when the composer starts up.
+</DD>
+</DL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_reply_token_conditionals =======
+<HTML>
+<HEAD>
+<TITLE>Conditional Inclusion of Text for <!--#echo var="VAR_reply-leadin"-->, Signatures, and Templates</TITLE>
+</HEAD>
+<BODY>
+<H1>Conditional Inclusion of Text for <!--#echo var="VAR_reply-leadin"-->, Signatures, and Templates</H1>
+
+Conditional text inclusion may be used with
+the <A HREF="h_config_reply_intro">&quot;<!--#echo var="VAR_reply-leadin"-->&quot;</A> option,
+in signature files, and in template files used in
+<A HREF="h_rules_roles">&quot;roles&quot;</A>.
+It may <EM>not</EM> be used with the
+<A HREF="h_config_index_format">&quot;<!--#echo var="VAR_index-format"-->&quot;</A> option.
+
+<P>
+There is a limited if-else capability for including text.
+The if-else condition is based
+on whether or not a given token would result in replacement text you
+specify.
+The syntax of this conditional inclusion is
+<P>
+<CENTER><SAMP>_token_(match_this, if_matched [ , if_not_matched ] )</SAMP></CENTER>
+<P>
+The left parenthesis must follow the underscore immediately, with no
+intervening space.
+It means the token is expanded and the results of that expansion are
+compared against the &quot;match_this&quot; argument.
+If there is an exact match, then the &quot;if_matched&quot; text is used
+as the replacement text.
+Otherwise, the &quot;if_not_matched&quot; text is used.
+One of the most useful values for the &quot;match_this&quot; argument is
+the empty string, &quot;&quot;.
+In that case the expansion is compared against the empty string.
+<P>
+Here's an example to make it clearer.
+This text could be included in one of your template files:
+<P>
+<CENTER><SAMP>_NEWS_(&quot;&quot;, &quot;I'm replying to email&quot;, &quot;I'm replying to news&quot;)</SAMP></CENTER>
+<P>
+If that is included in a template file that you are using while replying
+to a message (because you chose to use the role it was part of),
+and that message has a newsgroup header and a newsgroup in that header,
+then the text
+<P>
+<CENTER><SAMP>I'm replying to news</SAMP></CENTER>
+<P>
+will be included in the message you are about to compose.
+On the other hand, if the message you are replying to does not have
+a newsgroup, then the text
+<P>
+<CENTER><SAMP>I'm replying to email</SAMP></CENTER>
+<P>
+would be included instead.
+This would also work in signature files and in
+the &quot;<!--#echo var="VAR_reply-leadin"-->&quot; option.
+If the &quot;match_this&quot;, &quot;if_matched&quot;,
+or &quot;if_not_matched&quot; arguments contain
+spaces, parentheses, or commas;
+they have to be quoted with double quotation marks (like in the example
+above).
+If you want to include a literal quote (&quot;) in the text you must escape the
+quote by preceding it with a backslash (&#92;) character.
+If you want to include a literal backslash character you must escape it
+by preceding it with another backslash.
+<P>
+The comma followed by &quot;if_not_matched&quot; is optional.
+If there is no &quot;if_not_matched&quot;
+present then no text is included if the not_matched case is true.
+Here's another example:
+<P>
+<CENTER><SAMP>_NEWS_(&quot;&quot;, &quot;&quot;, &quot;This msg was seen in group: _NEWS_.&quot;)</SAMP></CENTER>
+<P>
+Here you can see that tokens may appear in the arguments.
+The same is true for tokens with the conditional parentheses.
+They may appear in arguments,
+though you do have to be careful to get the quoting and escaping of
+nested double quotes correct.
+If this was in the signature file being used and you were replying to a message
+sent to comp.mail.pine the resulting text would be:
+<P>
+<CENTER><SAMP>This msg was seen in group: comp.mail.pine.</SAMP></CENTER>
+<P>
+If you were replying to a message that wasn't sent to any newsgroup the
+resulting text would be a single blank line.
+The reason you'd get a blank line is because the end of the line is
+outside of the conditional, so is always included.
+If you wanted to get rid of that blank line you could do so by moving
+the end of line inside the conditional.
+In other words, it's ok to have multi-line
+&quot;if_matched&quot; or &quot;if_not_matched&quot; arguments in your
+template file.
+The text just continues until the next double quotation, even if it's not
+on the same line.
+<P>
+Here's an example for use in the &quot;<!--#echo var="VAR_reply-leadin"-->&quot;:
+<P>
+<CENTER><SAMP>On _DAYDATE_, _FROM__CURNEWS_(&quot;&quot;, &quot;&quot;, &quot;seen in _CURNEWS_,&quot;) wrote</SAMP></CENTER>
+<P>
+If this was in your <!--#echo var="VAR_reply-leadin"--> and you were replying to a message
+while reading the newsgroup comp.mail.pine the resulting text would be:
+<P>
+<CENTER><SAMP>On Sat, 24 Oct 1998, Fred Flintstone, seen in comp.mail.pine, wrote:</SAMP></CENTER>
+<P>
+If you were replying to a message while reading an email folder instead
+of a newsgroup the resulting leadin text would be
+<P>
+<CENTER><SAMP>On Sat, 24 Oct 1998, Fred Flintstone wrote:</SAMP></CENTER>
+<P>
+Here's one more (contrived) example illustrating a matching argument
+that is not the empty string.
+<P>
+<CENTER><SAMP>_SMARTDATE_("Today", _SMARTDATE_, "On _DATE_") _FROM_ wrote:</SAMP></CENTER>
+<P>
+If this was the value of your &quot;<!--#echo var="VAR_reply-leadin"-->&quot; option and you
+were replying to
+a message that was sent today, then the value of the &quot;<!--#echo var="VAR_reply-leadin"-->&quot;
+would be
+<P>
+<CENTER><SAMP>Today Fred Flintstone wrote:</SAMP></CENTER>
+<P>
+But if you were replying to a message sent on Oct. 27 (and that wasn't
+today) you would get
+<P>
+<CENTER><SAMP>On Oct 27 Fred Flintstone wrote:</SAMP></CENTER>
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_cntxt_nick =======
+<HTML>
+<HEAD>
+<TITLE>Collection Nickname Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Collection Edit Help -- Nickname Field</H1>
+
+This field is provided so you can add a short nickname to use when
+referring to this collection within Alpine. Spaces are allowed, and
+you don't need to use double-quotes. However, the double-quote
+character is not allowed.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_folder_server_syntax =======
+<HTML>
+<HEAD>
+<TITLE>Server Name Syntax</TITLE>
+</HEAD>
+<BODY>
+<H1>Server Name Syntax</H1>
+
+This help describes the syntax that may be used for server names
+that may be associated with remote folders or SMTP servers.
+
+<P>
+A server name is the hostname of the server.
+It's a good idea to use the host's fully-qualified network name.
+
+<P>
+<CENTER><SAMP>foo.example.com</SAMP></CENTER>
+<P>
+
+However, IP addresses are allowed if surrounded
+with square-brackets.
+
+<P>
+<CENTER><SAMP>[127.0.0.1]</SAMP></CENTER>
+<P>
+
+An optional network port number may be supplied by appending
+a colon (:) followed by the port number
+to the server name.
+By default, the IMAP port number, 143, is used.
+
+<P>
+<CENTER><SAMP>foo.example.com:port</SAMP></CENTER>
+<P>
+
+Besides server name and optional port number, various other optional
+parameters may be supplied that alter Alpine's interaction with the server.
+A parameter is supplied by appending a slash (/) character followed by
+the parameter's name and,
+depending on the particular parameter, the value assigned to that
+name, to the server name (and optional port number).
+Parameter names are <EM>not</EM> case sensitive.
+Currently supported parameters include:
+
+<DL>
+
+<DT>User</DT>
+<DD>This parameter requires an associated value, and is intended to
+provide the username identifier with which to establish the server
+connection.
+If your SMTP server offers SMTP AUTH authentication, adding this
+parameter to the
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+option will cause Alpine to attempt to authenticate to the server using the
+supplied username.
+Similarly, if your NNTP server offers NNTP &quot;AUTHINFO SASL&quot;
+or &quot;AUTHINFO USER&quot; authentication, adding this parameter to the
+<A HREF="h_config_nntp_server"><!--#echo var="VAR_nntp-server"--></A>
+option (or to the server name for any folder collection using NNTP)
+will cause Alpine to attempt
+to authenticate to the server using the supplied username.
+An example might be:
+
+<P>
+<CENTER><SAMP>/user=katie</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>TLS</DT>
+<DD>
+Normally, when a new connection is made an attempt is made to
+negotiate a secure (encrypted) session using Transport Layer Security (TLS).
+If that fails then a non-encrypted connection will be attempted instead.
+This is a unary parameter indicating communication with the server must
+take place over a TLS connection. If the attempt to use TLS fails then
+this parameter will cause the connection to fail instead of falling
+back to an unsecure connection.
+
+<P>
+<CENTER><SAMP>/tls</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>SSL</DT>
+<DD>
+This is a unary parameter indicating communication with the server should
+take place over a Secure Socket Layer connection. The server must support
+this method, and be prepared to accept connections on the appropriate
+port (993 by default).
+Alpine must be linked with an SSL library for this option to be operational.
+
+<P>
+<CENTER><SAMP>/ssl</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>NoValidate-Cert</DT>
+<DD>Do not validate certificates (for TLS or SSL connections) from the server.
+This is needed if the server uses self-signed certificates or if Alpine
+cannot validate the certificate for some other known reason.
+<P>
+</DD>
+
+<DT>Anonymous</DT>
+<DD>This is a unary parameter (that means it does not have a value)
+indicating that the connection be logged in as
+&quot;anonymous&quot; rather than a specific user.
+Not all servers offer anonymous
+access; those which do generally only offer read-only access to certain
+&quot;public&quot; folders.
+
+<P>
+<CENTER><SAMP>/anonymous</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>Secure</DT>
+<DD>This is a unary parameter indicating that the connection use the
+most secure authentication method mutually supported by Alpine and the
+server.
+Alpine is capable of authenticating connections to
+the server using several methods.
+By default, Alpine will attempt each
+method until either a connection is established or the
+list of methods is exhausted.
+This parameter causes Alpine to instead fail
+the connection if the first (generally most &quot;secure&quot;) method fails.
+
+<P>
+<CENTER><SAMP>/secure</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>Submit</DT>
+<DD>This is a unary parameter for use with the
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A> option.
+It indicates that the connection should be made to the Submit server
+(<A HREF="http://www.ietf.org/rfc/rfc2476.txt">RFC 3676</A>)
+(port 587) instead of the SMTP port (25).
+At the time this help was written the submit option was equivalent to
+specifying port 587.
+
+<P>
+<CENTER><SAMP>/submit</SAMP></CENTER>
+<P>
+or
+<P>
+<CENTER><SAMP>host:587</SAMP></CENTER>
+<P>
+
+</DD>
+
+<DT>Debug</DT>
+<DD>This is a unary parameter indicating that the connection be established
+in a verbose mode. Basically, it causes Alpine to log the communication with
+the server in Alpine's debug file.
+Normally, the pine -d command-line flag would be used instead.
+<P>
+</DD>
+
+<DT>NoRsh</DT>
+<DD>By default, Alpine attempts to login using &quot;rsh&quot;,
+the UNIX remote shell program.
+Including &quot;NoRsh&quot; will cause connections to this server to skip
+the &quot;rsh&quot; attempt.
+This might be useful to avoid long timeouts caused by rsh firewalls, for
+example.
+<P>
+</DD>
+
+<DT>Service</DT>
+<DD>This parameter requires an associated value. The default value is
+&quot;IMAP&quot; which indicates communication with the server based
+on the IMAP4rev1 protocol (defined in RFC 3501 -- see
+<A HREF="http://www.imap.org/docs/rfc3501.html">http://www.imap.org/docs/rfc3501.html</A>).</DD>
+
+Other service values include:
+ <DL>
+ <DT>NNTP</DT>
+ <DD>This value indicates communication with the server takes place via
+the Network News Transfer Protocol. Use this to define a collection
+of newsgroups on a remote news server. So
+
+<P>
+<CENTER><SAMP>/service=NNTP</SAMP></CENTER>
+<P>
+or just
+<P>
+<CENTER><SAMP>/NNTP</SAMP></CENTER>
+<P>
+
+is the way to specify NNTP access.
+<P>
+ </DD>
+
+ <DT>POP3</DT>
+ <DD>This value indicates communication with the server takes place via the
+Post Office Protocol 3 protocol.
+
+<P>
+<CENTER><SAMP>/service=POP3</SAMP></CENTER>
+<P>
+or just
+<P>
+<CENTER><SAMP>/POP3</SAMP></CENTER>
+<P>
+
+Note that there are several important issues
+to consider when selecting this option:
+<OL>
+ <LI> POP3 provides access to only your INBOX. In other words,
+secondary folders such as your &quot;saved-messages&quot; are inaccessible.
+ <LI> Alpine's implementation of POP3 does not follow the traditional POP
+model and will leave your mail on the server. Refer to the
+<A HREF="h_maildrop">Mail Drop</A> functionality for a possible way around this problem.
+ <LI> See the discussion about new-mail checking in <A HREF="h_config_reopen_rule">&quot;<!--#echo var="VAR_folder-reopen-rule"-->&quot;</A>.
+</OL>
+</DD>
+</DL>
+</DL>
+
+<P>
+Note that it is possible to include more than one parameter in a server
+specification by concatenating the parameters. For example:
+
+<P>
+<CENTER><SAMP>foo.example.com:port/user=katie/novalidate-cert/debug</SAMP></CENTER>
+<P>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_cntxt_server =======
+<HTML>
+<HEAD>
+<TITLE>Collection Server: Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Collection Edit Help -- Server Field</H1>
+
+This collection's &quot;Server:&quot; definition indicates the
+hostname of the server providing access to the folders in this
+collection.
+The syntax of this server name is the same as for other server names used
+in remote folder names in
+Alpine and is described
+<A HREF="h_folder_server_syntax">here</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_cntxt_path =======
+<HTML>
+<HEAD>
+<TITLE>Collection Path: Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Collection Edit Help -- Path Field</H1>
+
+The collection's &quot;Path:&quot; definition indicates the location
+of the folders in this collection. If the path or any of its components
+do not exist, Alpine will prompt you for their creation when exiting the
+Add/Change screen.
+
+<P>
+By default the path is interpreted as defining a section of your personal
+folder area. This area and how you specify it are defined by the
+server, if one is specified in the collection, or, typically, the home
+directory if no server is defined.
+
+<P>
+To define a collection outside the default &quot;area&quot;, prefix
+the path with the &quot;namespace&quot; to use when interpreting the
+given path. If a namespace is specified, the Path begins with the
+sharp, &quot;#&quot;, character followed by the name of the namespace
+and then the namespace's path-element-delimiter. Aside from the
+path's format, namespaces can also imply access rights, content
+policy, audience, location, and, occasionally, access methods.
+
+<P>
+Each server exports its own set (possibly of size one) of
+namespaces. Hence, it's likely communication with your server's
+administrator will be required for specific configurations. Some of
+the more common namespaces, however, include:
+
+<DL>
+<DT>#news.</DT>
+<DD>This specifies a set of folders in the newsgroup namespace. Newsgroup
+names are hierarchically defined with each level delimited by a period.
+</DD>
+<DT>#public/</DT>
+<DD>This specifies a folder area that the server may export to the general
+public.
+</DD>
+<DT>#shared/</DT>
+<DD>This specifies a folder area that the folder may export to groups
+of users.
+</DD>
+<DT>#ftp/</DT>
+<DD>This specifies a folder area that is the same as that it may have
+exported via the &quot;File Transfer Protocol&quot;.
+</DD>
+<DT>#mh/</DT>
+<DD>This specifies the personal folder area associated with folders
+and directories that were created using the MH message handling system.
+</DD>
+</DL>
+<P>
+
+In addition, the server may support access to other user's folders,
+provided you have suitable permissions. Common methods use a prefix
+of either &quot;~<VAR>user</VAR>/&quot;, or &quot;/<VAR>user</VAR>/&quot; to
+indicate the root of the other user's folder area.
+
+<P>
+No, nothing's simple.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_cntxt_view =======
+<HTML>
+<HEAD>
+<TITLE>Collection View: Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Collection Edit Help -- View Field</H1>
+
+The collection's &quot;View:&quot; definition provides a way to limit
+the displayed list of folders within a collection. By default, only
+folders that contain the specified characters anywhere in their name
+are shown in the collection's folder list.
+
+<P>
+Additionally, you can use a wildcard character to better control
+the list of folders selected for display. The wildcard specifier is
+the star, &quot;*&quot;, character.
+
+<P>
+So, for example, to define a collection of all folders ending with
+&quot;c&quot;, you'd specify a view of &quot;*c&quot; (without the
+quote characters!). Or, similarly, to define a collection of folders
+whose names start with &quot;a&quot; and end with &quot;z&quot;, you'd
+specify a view of &quot;a*z&quot;.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_add_server =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Server Name Field Explained</TITLE>
+</HEAD>
+<BODY>
+This field should be left blank if the address book is stored in a regular
+file on this system. If it is a remote address book stored on an IMAP
+server then this is the name of that IMAP server.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_add_folder =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook Folder Name Field Explained</TITLE>
+</HEAD>
+<BODY>
+For a remote address book (one for which the Server Name is filled in)
+this is the name of a folder on the remote server. The address book data
+will be stored in this folder. This folder should be used only for
+storing this single address book, not for other address books or for
+other messages.
+<P>
+For a local address book (one for which the Server Name is not filled in)
+this is the name of a file in which the address book will be stored.
+The file is in the same directory as the Alpine configuration file if the
+configuration file is local.
+If the configuration file is remote, then this will be in the home directory
+for Unix Alpine and in the directory specified by the
+&quot;-aux local_directory&quot; command line argument.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_abook_add_nick =======
+<HTML>
+<HEAD>
+<TITLE>Addressbook NickName Field Explained</TITLE>
+</HEAD>
+<BODY>
+This is just an optional nickname for this address book. If present, it
+is used in some of the displays and error messages in the address book
+maintenance screens. It is for your convenience only and serves no
+other purpose.
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_qserv_cn =======
+<HTML>
+<HEAD>
+<TITLE>Directory Query Form Explained</TITLE>
+</HEAD>
+<BODY>
+
+Fill in as many of these fields as you wish to narrow down your
+search. All the fields you fill in must match in order for an entry
+to be returned. You may use the wildcard character &quot;*&quot; in
+any of the fields, it matches any zero or more characters at that
+point in the string. There are no implicit wildcards, so the match is
+exact unless you include wildcards.
+<P>
+
+Note that if an attribute isn't present at all, then the match will fail.
+For example, if a server doesn't support the Locality attribute, then no
+matter what you put in the Locality field (other than leaving it empty)
+the search will fail.
+<P>
+
+This field, the Common Name field, is typically a person's full name.
+<P>
+
+<H1>EDITING and NAVIGATION COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+CURSOR MOTION KEYS----------------------|EDITING KEYS-------------------------
+^B (Left Arrow) Back character | ^D Delete current character
+^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+^P (Up Arrow) Previous line |
+^N (Down Arrow) Next line | F9 Cut marked text or
+^A Beginning of line | delete current line
+^E End of line | F10 Undelete line(s)
+F7 Previous page |
+F8 Next page |-------------------------------------
+^@ (Ctrl-SPACE) Next word | MISCELLANEOUS COMMANDS
+----------------------------------------|
+EXIT COMMANDS | GENERAL COMMANDS | F5 Restore previous search
+F2 Cancel | F1 Get help |
+F3 Search | ^Z Suspend | ^L Redraw Screen
+</PRE>
+<!--chtml else-->
+<PRE>
+CURSOR MOTION KEYS----------------------|EDITING KEYS-------------------------
+^B (Left Arrow) Back character | ^D Delete current character
+^F (Right Arrow) Forward character | ^H (DEL) Delete previous character
+^P (Up Arrow) Previous line |
+^N (Down Arrow) Next line | ^K Cut marked text or
+^A Beginning of line | delete current line
+^E End of line | ^U Undelete line(s)
+^Y Previous page |
+^V Next page |-------------------------------------
+^@ (Ctrl-SPACE) Next word | MISCELLANEOUS COMMANDS
+----------------------------------------|
+EXIT COMMANDS | GENERAL COMMANDS | ^R Restore previous search
+^C Cancel | ^G Get help |
+^X Search | ^Z Suspend | ^L Redraw Screen
+</PRE>
+<!--chtml endif-->
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_composer_qserv_sn =======
+
+The Surname is usually the family name of a person.
+
+<End of help on this topic>
+======= h_composer_qserv_gn =======
+
+This is the part of a person's name that isn't the surname or initials.
+
+<End of help on this topic>
+======= h_composer_qserv_mail =======
+
+This is the email address of a person.
+
+<End of help on this topic>
+======= h_composer_qserv_org =======
+
+This is the organization a person belongs to.
+
+<End of help on this topic>
+======= h_composer_qserv_unit =======
+
+This is the organizational unit a person belongs to.
+
+<End of help on this topic>
+======= h_composer_qserv_country =======
+
+This is the country a person belongs to.
+
+<End of help on this topic>
+======= h_composer_qserv_state =======
+
+This is the state a person belongs to.
+
+<End of help on this topic>
+======= h_composer_qserv_locality =======
+
+This is the locality a person belongs to.
+
+<End of help on this topic>
+======= h_composer_qserv_custom =======
+
+This one is for advanced users only! If you put something in this field,
+then the rest of the fields are ignored.
+
+This field may be set to the string representation of an LDAP search
+filter (see RFC1960). Here are some examples:
+
+To search for an entry with a surname equal to "clinton" you could set
+the custom filter to:
+
+ (sn=clinton)
+
+This is equivalent to putting "clinton" in the SurName field.
+To search for an entry that has a surname that begins with "clint" and
+has a givenname equal to "william" you could use:
+
+ (&(sn=clint*)(givenname=william))
+
+This is equivalent to setting the SurName field to "clint*" and the
+GivenName field to "william".
+To search for an entry where either the common name OR the email address
+contains "abcde" you could use:
+
+ (|(cn=*abcde*)(mail=*abcde*))
+
+That isn't equivalent to anything you can do by setting the other fields
+because of the OR.
+
+<End of help on this topic>
+======= h_composer_qserv_qq =======
+
+This one is a little different from the rest of the categories. It causes
+a search to be formed from the configured search filter that you filled
+in when you added the directory server to your configuration. It can also
+be combined with the other fields if you'd like.
+
+<End of help on this topic>
+======= h_address_format =======
+<HTML>
+<HEAD>
+<TITLE>INTERNET EMAIL ADDRESS FORMAT</TITLE>
+</HEAD>
+<BODY>
+<H1>INTERNET EMAIL ADDRESS FORMAT</H1>
+
+A valid email address on the Internet has a username, an &quot;@&quot; sign,
+and then a domain, with no spaces.
+For example, jsmith@art.example.com might be the email address
+of a person
+with the username &quot;jsmith&quot; who has an account in the domain
+&quot;art.example.com&quot;. The number of dot-separated segments on the
+right of the &quot;@&quot; sign can vary - a shorter example would be
+isabelle@elsewhere.edu (the shortest possible form: here, only the
+organization's domain is specified after the &quot;@&quot; sign); a longer
+example would be
+ jsingh@shakti.edutech.example.com
+(here, the name of the host &quot;shakti&quot; in the domain
+edutech.example.com is also specified).
+<P>
+If you do not know the exact email address of someone you want to write
+to, ask them what it is using other means of communication than email; or
+use the tools for
+finding people's addresses that are available on the Internet.
+<P>
+If you are sending to someone on the same system as you are, you can leave
+the &quot;@&quot; sign and all the information to its right off of the
+address, and Alpine will fill it in automatically,
+unless the feature
+<A HREF="h_config_compose_rejects_unqual">&quot;<!--#echo var="FEAT_compose-rejects-unqualified-addrs"-->&quot;</A> is set in SETUP CONFIGURATION.
+<P>
+
+When an email address you send a message to is not reachable -- either because
+it is simply an incorrect address, or because email can temporarily not be
+delivered to it due to a technical problem on the way to or at the recipient's
+end -- you will almost always get an error notification email message back.
+<P>
+If you encounter problems with, or have questions about, email delivery or
+email address syntax, contact your local network computing consultants.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_user_flag =======
+<HTML>
+<HEAD>
+<TITLE>STATUS FLAG: User Defined Keyword</TITLE>
+</HEAD>
+<BODY>
+<H1>STATUS FLAG: User Defined Keyword</h1>
+
+This is a keyword that is defined for this folder.
+It was most likely defined by the owner of the folder.
+Alpine will not set or clear this flag on its own.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_important =======
+<html>
+<title>STATUS FLAG: Important</title>
+<body>
+<h1>STATUS FLAG: Important</h1>
+
+
+The <EM>Important</EM> flag, indicated by an asterisk in Alpine's
+MESSAGE&nbsp;INDEX
+screen, can only be set by the user, and is intended to be used in
+whatever fashion makes sense to you. You are the only one that can set or
+clear it.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_new =======
+<html>
+<title>STATUS FLAG: New</title>
+<body>
+<h1>STATUS FLAG: New</h1>
+
+
+The <EM>New</EM> flag, indicated by the letter 'N' in Alpine's
+MESSAGE&nbsp;INDEX screen,
+is automatically set when messages are delivered to your Inbox (or other
+folder specified outside of Alpine). Likewise, it is cleared automatically
+the first time you read the message it is associated with.
+
+<P>
+Sometimes it's helpful in prioritizing your mail. For example, perhaps
+a message isn't weighty enough to assign it an <A HREF="h_flag_important">Important</A> flag, but
+you'd like to be reminded of it next time you read mail. This can be done
+easily by <A HREF="h_common_flag">explicitly</A> resetting the <EM>New</EM> flag.
+
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_answered =======
+<html>
+<title>STATUS FLAG: Answered</title>
+<body>
+<h1>STATUS FLAG: Answered</h1>
+
+The <EM>Answered</EM> flag, indicated by the letter 'A' in Alpine's
+MESSAGE&nbsp;INDEX
+screen, is automatically set when you reply to a message. This flag is not
+automatically cleared.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_forwarded =======
+<html>
+<title>STATUS FLAG: Forwarded</title>
+<body>
+<h1>STATUS FLAG: Forwarded</h1>
+
+The <EM>Forwarded</EM> flag, indicated by the letter 'F' in Alpine's
+MESSAGE&nbsp;INDEX
+screen, is automatically set when you forward a message. This flag is not
+automatically cleared.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_flag_deleted =======
+<html>
+<title>STATUS FLAG: Deleted</title>
+<body>
+<h1>STATUS FLAG: Deleted</h1>
+
+The <EM>Deleted</EM> flag, indicated by the letter 'D' in Alpine's
+MESSAGE&nbsp;INDEX
+screen, is set when you use the &quot;"D&nbsp;Delete&quot; command.
+It is cleared
+when you use the &quot;U&nbsp;Undelete&quot; command.
+
+<P>
+Messages marked with this flag will be permanently removed from
+the folder when you issue the <A HREF="h_index_cmd_expunge">Expunge</A>
+command, or
+when you indicate acceptance of their removal upon leaving the folder.
+
+<P>
+Note, there can be other actions implicit in the
+&quot;D&nbsp;Delete&quot; command,
+such as advancing to the next message, that may be momentarily undesirable.
+For this reason, it's sometimes useful to set or clear the <EM>Deleted</EM>
+flag <A HREF="h_common_flag">explicitly</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_timeo ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-check-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-check-timeout"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+Sets the time in seconds that Alpine will
+attempt to open a network connection used for monitoring for Unseen
+messages in Incoming Folders. The default is 5.
+If a connection has not completed within this many seconds Alpine will
+give up and consider it a failed connection.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_interv ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-check-interval"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-check-interval"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+This option specifies, in seconds, how often Alpine will check
+for new mail and state changes in Incoming Folders when Incoming Folders
+Checking is turned on.
+The default is 3 minutes (180).
+This value applies only to folders that are local to the system that
+Alpine is running on or that are accessed using the IMAP protocol.
+The similar option
+<A HREF="h_config_incoming_second_interv"><!--#echo var="VAR_incoming-check-interval-secondary"--></A>
+applies to all other monitored folders.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_second_interv ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-check-interval-secondary"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-check-interval-secondary"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+This option together with the option
+<A HREF="h_config_incoming_interv"><!--#echo var="VAR_incoming-check-interval"--></A>
+specifies, in seconds, how often Alpine will check
+for new mail and state changes in Incoming Folders when Incoming Folders
+Checking is turned on.
+The default for this option is 3 minutes (180).
+For folders that are local to this system or
+that are accessed using the IMAP protocol
+the value of the option
+<A HREF="h_config_incoming_interv"><!--#echo var="VAR_incoming-check-interval"--></A>
+is used.
+For all other monitored folders, the value of this option is used.
+<P>
+The reason there are two separate options is because it is usually
+less expensive to check local and IMAP folders than it is to check
+other types, like POP or NNTP folders.
+You may want to set this secondary value to a higher number than
+the primary check interval.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_list ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-check-list"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-check-list"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+When monitoring the Incoming Message Folders for Unseen messages Alpine will
+normally monitor all Incoming Folders.
+You may use this option to restrict the list of monitored folders to a
+subset of all Incoming Folders.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pers_name ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_personal-name"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_personal-name"--></H1>
+
+This value is used to determine the full name part of the "From" address
+on messages you send.
+<!--chtml if pinemode="os_windows"-->
+ PC-Alpine requires that this be set in order to properly construct the "From" address.
+<!--chtml else-->
+ If unset, Unix Alpine will obtain your full name from
+ the system password file. PC-Alpine, on the other hand, requires that this be set.
+<!--chtml endif-->
+<P>
+If you want to change the value of what gets included in the From header
+in messages you send (other than just the Personal Name)
+look <A HREF="h_config_change_your_from">here</A> for a description.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pruned_folders ======
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_pruned-folders"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_pruned-folders"--></h1>
+
+This variable allows you to define a list of one or more folders that
+Alpine will offer to prune for you in the same way it automatically offers
+to prune your "sent-mail" folder each month.
+Each folder in this list must be a folder in your default folder collection
+(the first folder collection if you have more than one), and it is just
+the relative name of the folder in the collection, not the fully-qualified name.
+It is similar to sent-mail.
+Instead of something like
+<P>
+<CENTER><SAMP><!--#echo var="VAR_pruned-folders"-->={servername}mail/folder</SAMP></CENTER>
+<P>
+the correct value to use would be
+<P>
+<CENTER><SAMP>folder</SAMP></CENTER>
+<P>
+There is an assumption here that your first collection is the folders in
+<P>
+<CENTER><SAMP>{servername}mail</SAMP></CENTER>
+<P>
+
+Once a month, for each folder listed, Alpine will offer to move
+the contents of the folder to a new folder of the same name but with
+the previous month's date appended. Alpine will then look for any such
+date-appended folder names created for a previous month, and offer each
+one it finds for deletion.
+<P>
+
+If you decline the first offer, no mail is moved and no new folder is
+created.
+<P>
+
+The new folders will be created
+in your default folder collection.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_upload_cmd ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_upload-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_upload-command"--></H1>
+
+This option affects the behavior of the Composer's &quot;Read File&quot;
+(^R in the message body) and &quot;Attach File&quot; (^J in the header)
+commands. It specifies
+a Unix program name, and any necessary command line arguments, that Alpine can
+use to transfer files from your personal computer into messages that you are
+composing.<P>
+
+<B>Note:</B> this facility is intended for use with serial line transfer
+protocols, such as kermit, xmodem, or zmodem. It is <B>not</B> intended
+to work with TCP/IP file transfer programs such as ftp.<P>
+
+If a program is specified, the commands listed above are modified to offer a
+subcommand (^Y) to activate the transfer. Obviously, the Unix program
+specified here must match the transfer program or protocol available on the
+personal computer.<P>
+
+Alpine expects to exchange uploaded data via a file on your Unix system. When
+the specified upload program finishes, Alpine expects the uploaded data to be
+contained in this file.<P>
+
+When upload is invoked via the &quot;Read File&quot; subcommand, Alpine
+generates a
+temporary file name that it will pass to the specified Unix program. Alpine
+will read the resulting uploaded text from this file and then delete it when
+the upload command is finished.<P>
+
+When upload is invoked via the &quot;Attach File&quot; subcommand, Alpine will
+prompt
+you for the name of the file that is to contain the uploaded information that
+it is to attach. Alpine will attach this file to the composition, but will
+<B>not</B> delete this file after the upload command is finished.<P>
+
+The special token &quot;_FILE_&quot; may be included among the Unix program's
+command
+line arguments. Alpine will replace this symbol with the name of the file
+being used to exchange the uploaded information. This token allows you to
+position the file name where it is required in the Unix program's command
+line arguments.<P>
+
+If the &quot;_FILE_&quot; token is not present in the specified command, the
+temporary file's name is automatically appended to the specified Unix
+program. In other words, you don't need to use &quot;_FILE_&quot; if it is the
+<B>last</B> command line argument.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_upload_prefix ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_upload-command-prefix"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_upload-command-prefix"--></H1>
+
+This option is used in conjunction with the <!--#echo var="VAR_upload-command"--> option.
+It defines text to be written to the terminal emulator (via standard output)
+immediately prior to starting upload command. This is useful for
+integrated serial line file transfer agents that permit command passing
+(e.g., Kermit's APC method).<P>
+
+The special token &quot;_FILE_&quot; may be included in the string specification.
+That symbol will be replaced with the (Alpine-created) name of the temporary
+file in which Alpine will expect to find the uploaded file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_download_cmd ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_download-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_download-command"--></H1>
+
+This option affects the behavior of the Export command. It specifies a Unix
+program name, and any necessary command line arguments, that Alpine can use to
+transfer the exported message to your personal computer's disk.<P>
+Note: this facility is intended for use with serial line transfer
+protocols, such as kermit, xmodem, or zmodem. It is <B>not</B> intended
+to work with TCP/IP file transfer programs such as ftp.<P>
+If a program is specified, the Export command is modified to offer a
+subcommand (^V) to activate the transfer (in lieu of saving it to
+ the machine where Alpine is running). Obviously, the Unix program
+specified here must match the transfer program or protocol available on the
+personal computer.<P>
+
+When this subcommand is selected and before Alpine invokes the specified Unix
+program, Alpine will create a temporary file containing the text of the
+exported message. Alpine uses this file to pass the exported message text to
+the specified Unix program.<P>
+
+The special token &quot;_FILE_&quot; may be included among the Unix program's command
+line arguments. Alpine will replace this symbol with the temporary file's name
+before executing the Unix program. This token allows you to position the
+file name where it is required in the Unix program's command line arguments.
+<P>
+If the &quot;_FILE_&quot; token is not present in the specified command, the
+temporary file's name is automatically appended to the specified Unix
+program. In other words, you don't need to use &quot;_FILE_&quot; if it is the
+<B>last</B> command line argument.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_download_prefix ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_download-command-prefix"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_download-command-prefix"--></H1>
+
+This option is used in conjunction with the <!--#echo var="VAR_download-command"--> option.
+It defines text to be written to the terminal emulator (via standard output)
+immediately prior to starting the download command. This is useful for
+integrated serial line file transfer agents that permit command passing
+(e.g., Kermit's APC method).
+<P>
+The special token &quot;_FILE_&quot; may be included in the string
+specification.
+That symbol will be replaced with the (Alpine-created) name of the temporary
+file into which Alpine will place the message to be downloaded.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mailcap_path ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_mailcap-search-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_mailcap-search-path"--></H1>
+This variable is used to replace Alpine's default mailcap file search path.
+It takes one or more file names (full paths must be specified) in which to
+look for mail capability data. The default search path can be found in this
+<A HREF="h_news_config">Alpine Configuration</A> help, near the bottom.
+If there is more than one file name listed, list members should be delimited
+by
+<!--chtml if pinemode="os_windows"-->
+a semi-colon (;) under Windows; for example:<PRE>
+ C:&#92;MYCONFIG&#92;MAILCAP.TXT;H:&#92;NETCONFIG&#92;MAILCAP.TXT
+</PRE>
+<!--chtml else-->
+a colon (:) under UNIX; for example:<PRE>
+ ~/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap
+</PRE>
+<!--chtml endif-->
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mimetype_path ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_mimetype-search-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_mimetype-search-path"--></H1>
+
+This variable is used to replace Alpine's default mime.types file search path.
+It takes one or more file names (full paths must be specified) in which to
+look for file-name-extension to MIME type mapping data. The default search
+path can be found in this
+<A HREF="h_news_config">Alpine Configuration</A> help.
+
+<P>
+
+If there is more than one file name listed, list members should be delimited
+by a colon (:) under UNIX and a semi-colon (;) under Windows.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_set_att_ansi ======
+<HTML><HEAD>
+<TITLE>OPTION: Set printer to attached ansi printer</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Set printer to attached ansi printer</H1>
+
+Type "S" to set your printer to "attached-to-ansi".<BR>
+It is OK to include "attached-to-ansi" in your personal list below.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_set_att_ansi2 ======
+<HTML><HEAD>
+<TITLE>OPTION: Set printer to attached ansi printer (no formfeed)</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Set printer to attached ansi printer (no formfeed)</H1>
+
+Type "S" to set your printer to "attached-to-ansi-no-formfeed".<BR>
+It is OK to include "attached-to-ansi-no-formfeed" in your personal
+list below.
+
+<P>
+
+This is the same as the "attached-to-ansi" option except that a
+formfeed character will not be appended to the end of the print job.
+If your printer already ejects the paper by itself at the end of the
+job, you may prefer the "no-formfeed" form of this printer so that you
+don't get an extra blank page between print jobs.
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_set_att_wyse ======
+<HTML><HEAD>
+<TITLE>OPTION: Set printer to attached Wyse60 printer</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Set printer to attached Wyse60 printer</H1>
+
+Type "S" to set your printer to "attached-to-wyse".<BR>
+It is OK to include "attached-to-wyse" in your personal list below.
+<P>
+This is very similar to "attached-to-ansi".
+The only difference is in the control characters sent to turn the printer
+on and off.
+The ansi version of the printer uses ESC LEFT_BRACKET 5 i
+to turn on the printer and ESC LEFT_BRACKET 4 i
+to turn it off.
+The Wyse version uses Ctrl-R for on, and Ctrl-T for off.
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_set_att_wyse2 ======
+<HTML><HEAD>
+<TITLE>OPTION: Set printer to attached Wyse60 printer (no formfeed)</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Set printer to attached Wyse60 printer (no formfeed)</H1>
+
+Type "S" to set your printer to "attached-to-wyse-no-formfeed".<BR>
+It is OK to include "attached-to-wyse-no-formfeed" in your personal
+list below.
+
+<P>
+
+This is the same as the "attached-to-wyse" option except that a
+formfeed character will not be appended to the end of the print job.
+If your printer already ejects the paper by itself at the end of the
+job, you may prefer the "no-formfeed" form of this printer so that you
+don't get an extra blank page between print jobs.
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_set_stand_print ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: Set default printer</TITLE>
+</HEAD>
+<H1>OPTION: Set default printer</H1>
+<BODY>
+Move to the printer you want and type "S" to set it to be your
+default printer. This list is not modifiable by you and has been
+set up by the system administrators. If there is more than one printer
+listed in the Command List, you will be able to cycle through that
+whole list at the time you print, starting with your default.
+It is OK to include entries from this Standard list in your personal
+list below.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_set_custom_print ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: Set default printer</TITLE>
+</HEAD>
+<H1>OPTION: Set default printer</H1>
+<BODY>
+You may add as many print commands as you want to your personal list.
+Specify one of them as your default printer by moving to the printer
+you want and typing "S". If there is more than one printer listed
+in the Command List, you will be able to cycle through that list at
+the time you print, starting with your default. It is OK to include
+entries from the Standard list above or to include the command
+"attached-to-ansi", "attached-to-ansi-no-formfeed", "attached-to-wyse", or
+"attached-to-wyse-no-formfeed" as one of the entries here.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_user_id =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_user-id"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_user-id"--></H1>
+
+This value is used as part of the "From" address on messages you send.
+It is also the default login name for remote IMAP server access. Set this
+to the username part you want to appear on outgoing email.
+<P>
+If you want to change the value of what gets included in the From header
+in messages you send (other than just the User ID)
+look <A HREF="h_config_change_your_from">here</A> for a description.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_user_dom =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_user-domain"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_user-domain"--></H1>
+
+This value specifies the domain part (right-hand side) of your return
+address on outgoing email and is also used as the default domain for email
+composed to a local user.
+<!--chtml if pinemode="os_windows"-->
+ This value is required for PC-Alpine. If you are unsure as to what this should be,
+ contact your local help desk, system administrator, or Internet Service Provider.
+<!--chtml else-->
+ If unset, Unix Alpine will obtain the domain from
+ the system. Often this value will be set for your whole site by the
+ system administrator.<P>
+<!--chtml endif-->
+If you set this, see also the <A HREF="h_config_quell_local_lookup">
+&quot;<!--#echo var="FEAT_quell-user-lookup-in-passwd-file"-->&quot;</A> feature.
+<P>
+If you want to change the value of what gets included in the From header
+in messages you send (other than just the User Domain)
+look <A HREF="h_config_change_your_from">here</A> for a description.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smtp_server =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_smtp-server"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_smtp-server"--></H1>
+This value specifies the name of one or more SMTP
+(Simple Mail Transfer Protocol) servers for sending mail.
+<!--chtml if pinemode="os_windows"-->
+You must have an SMTP server for use with PC-Alpine.
+SMTP servers are
+normally set up by a system administrator for use by all members of a given
+campus or department.
+Contact your local help desk to ask what SMTP
+servers you should use.
+<!--chtml else-->
+Unix Alpine users may not need to set an SMTP server.
+Alpine will attempt to execute the program (usually sendmail) that is used
+to insert mail into the mail system.
+If this works for you, you may leave this option blank.
+If there is an SMTP server running on the Unix host you may be able to
+improve sending performance slightly by setting the SMTP server option
+to &quot;localhost&quot; or to the actual name of the Unix host.
+<P>
+If the Unix host doesn't work the way Alpine was expecting you will need to
+set the value of this option.
+SMTP servers are
+normally set up by a system administrator for use by all members of a given
+campus or department.
+Contact your local help desk to ask what SMTP
+servers you should use.
+<!--chtml endif-->
+<P>
+Your SMTP server may offer SMTP AUTH authentication.
+It may even require it.
+If your SMTP server offers SMTP AUTH authentication you may specify a
+&quot;user&quot; name parameter to cause Alpine to attempt to authenticate.
+This parameter requires an associated value,
+the username identifier with which to establish the server
+connection.
+An example might be:
+
+<P>
+<CENTER><SAMP>smtpserver.example.com/user=katie</SAMP></CENTER>
+<P>
+
+If AUTH authentication is offered by the server, this will cause Alpine to
+attempt to use it.
+If AUTH authentication is not offered by the server, this will cause Alpine
+to fail sending with an error similar to:
+
+<P>
+<CENTER><SAMP>Error: SMTP authentication not available</SAMP></CENTER>
+<P>
+
+Another type of authentication that is used by some ISPs is called
+&quot;POP before SMTP&quot; or &quot;IMAP before SMTP&quot;,
+which means that you have to authenticate
+yourself to the POP or IMAP server by opening a mailbox before you
+can send mail.
+To do this, you usually only have to open your INBOX.
+
+<P>
+You may tell Alpine to use the
+<A HREF="http://www.ietf.org/rfc/rfc2476.txt">Message Submission</A>
+port (587) instead of the SMTP port (25) by including the &quot;submit&quot;
+parameter
+in this option.
+At this time &quot;/submit&quot; is simply equivalent to specifying
+port 587, though it may imply more than that at some point in the future.
+Some ISPs are blocking port 25 in order to reduce the amount of spam
+being sent to their users.
+You may find that the submit option allows you to get around such a block.
+
+<P>
+<CENTER><SAMP>smtpserver.example.com/submit</SAMP></CENTER>
+<P>
+
+To specify any non-standard port number on the SMTP server you may follow
+the hostname with a colon followed by the portnumber.
+
+<P>
+<CENTER><SAMP>smtpserver.example.com:12345</SAMP></CENTER>
+<P>
+
+Normally, when a connection is made to the Smtp-Server Alpine will attempt
+to negotiate a secure (encrypted) session using Transport Layer Security (TLS).
+If that fails then a non-encrypted connection will be attempted instead.
+You may specify that a TLS connection is required if you wish.
+If you append &quot;/tls&quot; to the name then the connection will fail
+instead of falling back to a non-secure connection.
+
+<P>
+<CENTER><SAMP>smtpserver.example.com/tls</SAMP></CENTER>
+<P>
+
+
+For more details about server name possibilities see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_nntp_server =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_nntp-server"--></TITLE></HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_nntp-server"--></H1>
+
+This value specifies the name of one or more NNTP
+(Network News Transfer Protocol)
+servers for reading and posting USENET news.
+NNTP servers are normally
+set up by a system administrator for use by all members of a given campus
+or department.
+Contact your local help desk to ask what NNTP servers you should use.
+<!--chtml if pinemode="os_windows"--><!--chtml else-->
+Often Unix Alpine users will find that this variable has been
+set for the whole system (and they don't have to worry about it).
+<!--chtml endif-->
+When you define an NNTP server here, Alpine implicitly defines a news
+collection for you, assuming that server as the news server and assuming
+that you will use the NNTP protocol and a local newsrc configuration file
+for reading news.
+For more about reading news with Alpine, see
+<A HREF="h_reading_news">how to use Alpine to read news</A>.
+<P>
+Your NNTP server may offer NNTP &quot;AUTHINFO SASL&quot;
+or &quot;AUTHINFO USER&quot; authentication.
+It may even require it.
+If your NNTP server does offer such authentication you may specify a user name
+parameter to cause Alpine to attempt to authenticate.
+The same is true for the server name in a folder collection that uses NNTP.
+This parameter requires an associated value,
+the username identifier with which to establish the server connection.
+An example might be:
+
+<P>
+<CENTER><SAMP>nntpserver.example.com/user=katie</SAMP></CENTER>
+<P>
+
+If authentication is offered by the server, this will cause Alpine to
+attempt to use it.
+If authentication is not offered by the server, this will cause Alpine
+to fail with an error similar to:
+
+<P>
+<CENTER><SAMP>Error: NNTP authentication not available</SAMP></CENTER>
+<P>
+For more details about the server name possibilities see
+<A HREF="h_folder_server_syntax">Server Name Syntax</A>.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_inbox_path =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_inbox-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_inbox-path"--></H1>
+
+This value overrides the default value of your INBOX name/path/location.
+<!--chtml if pinemode="os_windows"-->
+PC-Alpine users must specify an inbox path and it must be a folder on an
+IMAP server.
+<!--chtml else-->
+Unix and VMS Alpine users will often find that this variable
+has been pre-configured by your system administrator.
+<!--chtml endif-->
+You may be able to specify an alternate INBOX that is either a local folder
+or a folder on an IMAP server.
+<P>
+A typical remote <!--#echo var="VAR_inbox-path"--> entry would be: &#123;monet.art.example.com}INBOX
+where &quot;monet.art.example.com&quot; is replaced by the name of your IMAP
+mail server.
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+See <A HREF="h_info_on_mbox">Missing mail and the mbox driver</A> if your
+mail is disappearing.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_change_your_from =====
+<HTML>
+<HEAD>
+<TITLE>How to Change your From Address</TITLE>
+</HEAD>
+<BODY>
+<H1>How to Change your From Address</H1>
+
+If the From address that Alpine includes in mail that you send is not correct,
+you may want to configure a different default value for the From address.
+You may follow these directions to change the default:
+
+<P>
+<UL>
+ <LI> Go to the Main Alpine Menu
+ <LI> From there type the Setup Command
+ <LI> From there type the Config Command
+</UL>
+
+<P>
+You've probably already seen this SETUP CONFIGURATION screen.
+If not, there are many options you may want to set here.
+To set the value of the From header you may use the
+<A href="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A> option.
+Find it by scrolling down a few pages or use the WhereIs command to
+search for &quot;customized&quot;.
+You may want to read the help text associated with the option.
+<P>
+To add a custom From header, type the Add command and enter the
+full header line, including the leading &quot;From:&nbsp;&quot;.
+For example:
+<P>
+<CENTER><SAMP>From: Full Name &lt;user@example.com&gt;</SAMP></CENTER>
+<P>
+Now exit the Setup command and try sending mail to yourself to see
+what the From line looks like.
+<P>
+When you are in the composer you may edit the custom From line by typing
+Ctrl-R while your cursor is in the headers of the message and then moving
+to the From line and editing.
+If you want to leave the default value the same but add the possibility
+of being able to edit the header when you compose, add just the header
+name without a value.
+For example:
+<P>
+<CENTER><SAMP>From:</SAMP></CENTER>
+<P>
+If you change your From address you may also find it useful to add the
+changed From address to the
+<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
+configuration option.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_default_fcc =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_default-fcc"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_default-fcc"--></H1>
+
+This value specifies where a copy of outgoing mail should be saved. If
+this is not a path name, it will be in the default collection for saves.
+Any valid folder specification, local or IMAP, is allowed. This default
+folder carbon copy only applies when the
+<A HREF="h_config_fcc_rule">&quot;<!--#echo var="VAR_fcc-name-rule"-->&quot;</A>
+is set to use the default folder.
+<!--chtml if pinemode="os_windows"-->
+PC-Alpine default is &quot;SENTMAIL&quot; (normally stored as SENTMAIL.MTX)
+<!--chtml else-->
+Unix Alpine default
+is normally &quot;sent-mail&quot; in the default folder collection.
+<!--chtml endif-->
+<P>
+If you access your email through an IMAP server, especially if you often switch between Unix Alpine
+and PC-Alpine, or between various desktop email systems, you may want to set this to a folder on your
+IMAP server (remember that in order to later access this remote folder through Alpine, it
+must be in a folder collection. See <A HREF="h_what_are_collections">Folder Collections Explained</a>
+for more information). An example:<p>
+<CENTER><SAMP>{monet.art.example.com}mail/sent-mail</SAMP></CENTER>
+<P>
+To suppress saving of outgoing mail, set: <!--#echo var="VAR_default-fcc"-->=&quot;&quot;
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_def_save_folder =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_default-saved-msg-folder"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_default-saved-msg-folder"--></H1>
+
+This option determines the default folder name for save-message operations
+(&quot;saves&quot;).
+<P>
+If this is not a path name, it will be in the default collection for saves.
+Any valid folder specification, local or IMAP, is allowed. This default
+folder only applies when the
+<A HREF="h_config_saved_msg_name_rule">&quot;<!--#echo var="VAR_saved-msg-name-rule"-->&quot;</A>
+doesn't override it.
+<!--chtml if pinemode="os_windows"-->
+PC-Alpine default is &quot;SAVEMAIL&quot; (normally stored as SAVEMAIL.MTX).
+<!--chtml else-->
+Unix Alpine default
+is normally &quot;saved-messages&quot; in the default folder collection.
+<!--chtml endif-->
+If you access your email through an IMAP server, especially if you often switch between Unix
+and PC-Alpine, or between various desktop email systems, you may want to set this to a folder on an
+IMAP server (remember that in order to later access this remote folder through Alpine, it
+should be in a folder collection. See <A HREF="h_what_are_collections">Folder Collections Explained</a>
+for more information). An example:<p>
+<CENTER><SAMP>{monet.art.example.com}mail/saved-messages</SAMP></CENTER>
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_postponed_folder =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_postponed-folder"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_postponed-folder"--></H1>
+
+This value overrides the default name for the folder where postponed
+messages are saved. If this is not a path name, it will be in the default
+collection for message Saves. Any valid folder specification, local or
+remote, is allowed.
+PC-Alpine default
+is &quot;POSTPOND&quot; (stored as POSTPOND.MTX).
+The Unix Alpine default is normally &quot;postponed-msgs&quot;
+in the default collection.
+<P>
+Tip: If you are using different installations of (PC-)Alpine -- for example, PC-Alpine on your personal
+computer at home, and Unix Alpine on campus -- you can postpone a composition begun with one Alpine and
+resume it later with the other if you set this option to the <B>same folder on the same IMAP host</B>
+in all Alpine copies you use.
+(Remember that in order to later access this remote folder through Alpine, it must be in a folder
+collection. See <A HREF="h_what_are_collections">Folder Collections Extensions Explained</a>
+for more information). An
+example:<p>
+<CENTER><SAMP>{monet.art.example.com}mail/postponed-msgs</SAMP></CENTER>
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_read_message_folder =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_read-message-folder"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_read-message-folder"--></H1>
+
+By virtue of specifying a folder name here, Alpine will be configured to
+save all messages that you have read during a session into the designated
+&quot;read messages&quot; folder. This allows you to more easily distinguish
+between your really new email (in your INBOX) and those that you have
+already read. Depending on how you define the
+<A HREF="h_config_auto_read_msgs">&quot;auto-move-read-messages&quot;</A>
+setting, you may or may not be asked when you quit
+Alpine if you want read messages to be moved to this folder. In either
+case, moving the messages means they will be deleted from your INBOX.
+<P>
+If this is not a path name, it will be in the default collection for
+saves. Any valid folder specification, local or remote (via IMAP), is
+allowed. There is no default for the name of the read message folder.
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_form_folder =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_form-letter-folder"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_form-letter-folder"--></H1>
+
+A &quot;<!--#echo var="VAR_form-letter-folder"-->&quot; is a mail folder that is intended to
+contain messages that you have composed and that are intended to be
+sent in their original form repeatedly.
+
+<P>
+Setting this variable will alter Alpine's usual behavior when you
+execute the Compose command. Normally, Alpine offers a chance to
+continue a postponed or interrupted message should one or the other
+exist. When this variable is set to a folder name that exists, Alpine
+will also offer the chance to select a message from the folder to
+insert into the composer (much like when continuing a postponed message).
+The difference, however, is that Alpine will not automatically delete
+the selected message from the Form Letter Folder.
+<P>
+Setting this variable will also affect Alpine's behavior when you
+Postpone a message from the composer. Normally, Alpine simply stashes
+the message away in your
+&quot;<A HREF="h_config_postponed_folder"><!--#echo var="VAR_postponed-folder"--></A>&quot;.
+Regardless of the specified folder's existence, Alpine will ask which
+folder you intend the message to be stored in. Choose the
+&quot;F&quot; option to store the message in your Form Letter Folder.
+This is the most common way to add a message to the folder.
+
+<P>
+Another method of adding messages to the folder is via the Alpine
+composer's <SAMP>Fcc:</SAMP> field. If you are sending a message that
+you expect to send in the same form again, you can enter the Form
+Letter Folder's name in this field. Alpine, as usual, will copy the
+message as it's sent. Note, when you later select this message from
+your Form Letter Folder, it will have the same recipients as the original
+message.
+
+<P>
+To delete a message from the Form Letter Folder, you can either select
+the folder from a suitable FOLDER LIST screen, or use the Delete
+command in the MESSAGE INDEX offered when selecting from the folder as
+part of the Compose command. You can delete a Form Letter Folder just
+as any other folder from a suitable FOLDER LIST screen.
+
+<P>
+You may find that the <A HREF="h_rules_roles">&quot;Roles&quot;</A>
+facility can be used
+to replace the Form Letter Folder.
+
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_archived_folders =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-archive-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-archive-folders"--></H1>
+
+This is like
+<A HREF="h_config_read_message_folder"><!--#echo var="VAR_read-message-folder"--></A>,
+only more general. You may archive
+any of the folders in your incoming collection. This is a list of folder
+pairs, with the first separated from the second in the pair by a space.
+The first folder in a pair is the folder you want to archive, and the
+second folder is the folder that read messages from the first should be
+moved to. Depending on how you define the
+<A HREF="h_config_auto_read_msgs">&quot;auto-move-read-messages&quot;</A>
+setting, you may or may not be asked when you
+leave the first folder if you want read messages to be moved to the
+second folder. In either case, moving the messages means they will be
+deleted from the first folder.
+<P>
+The name of the first folder in each pair can be either the technical
+specification of the folder (like what appears in your configuration file)
+or (much easier) the nickname that you gave the folder when you made it
+an incoming folder.
+<p>
+For example:<p>
+<CENTER><SAMP>{monet.art.example.com}inbox {monet.art.example.com}mail/inbox-archive</SAMP></CENTER>
+<p>or, using nicknames:<p>
+<CENTER><SAMP>inbox inbox-archive</SAMP></CENTER>
+<P>
+If these are not path names, they will be in the default collection for
+saves. Any valid folder specification, local or remote (via IMAP), is
+allowed. There is no default.
+<P>
+See the section on <A HREF="h_valid_folder_names">Valid Folder Names</A> for
+details on the syntax of folder definitions.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_newsrc_path ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_newsrc-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_newsrc-path"--></H1>
+
+This option overrides the default name Alpine uses for your "newsrc" news
+status and subscription file. If set, Alpine will take this value as the
+full pathname for the desired newsrc file.<P>
+
+If this option is <B>not</B> set,
+<!--chtml if pinemode="os_windows"-->
+PC-Alpine looks first for $HOME&#92;NEWSRC (where $HOME defaults to the root
+of the current drive, e.g. &quot;C:&#92;&quot;) and then it looks in the same
+directory as your pinerc file for NEWSRC.
+<!--chtml else-->
+Unix Alpine looks for the file ~/.newsrc (that is, the file named .newsrc in
+your account's home directory).
+<!--chtml endif-->
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_literal_sig =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_literal-signature"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_literal-signature"--></H1>
+
+With this option your actual signature, as opposed to
+the name of a file containing your signature,
+is stored in the Alpine configuration file.
+If this is defined it takes precedence over the <!--#echo var="VAR_signature-file"--> option.
+<P>
+
+This is simply a different way to store the signature.
+The signature is stored inside your Alpine configuration file instead of in
+a separate file.
+Tokens work the same way they do with the
+<A HREF="h_config_signature_file"><!--#echo var="VAR_signature-file"--></A> so look there for
+help.
+<P>
+
+The Setup/Signature command on Alpine's MAIN MENU will edit
+the &quot;<!--#echo var="VAR_literal-signature"-->&quot; by default. However, if no
+&quot;<!--#echo var="VAR_literal-signature"-->&quot; is defined and the file named in the
+&quot;<!--#echo var="VAR_signature-file"-->&quot; option exists, then the latter will be used
+instead.
+<P>
+
+The two character sequence &#92;n (backslash followed by
+the character n) will be used to signify a line-break in your signature.
+You don't have to enter the &#92;n, but it will be visible in the
+SETUP CONFIGURATION window after you are done editing the signature.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_signature_file =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_signature-file"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_signature-file"--></H1>
+
+If a <A HREF="h_config_literal_sig"><!--#echo var="VAR_literal-signature"--></A> option is defined,
+then this &quot;<!--#echo var="VAR_signature-file"-->&quot; option will be ignored.
+You can tell that that is the case because the value of the
+&quot;<!--#echo var="VAR_signature-file"-->&quot; will show up as
+<P>
+<CENTER><SAMP>&lt;Ignored: using <!--#echo var="VAR_literal-signature"--> instead&gt;</SAMP></CENTER>
+<P>
+You may either use all Literal Signatures (signatures stored in your
+configuration file) throughout Alpine, or all signature files.
+You can't mix the two.
+<P>
+This is the name of a file that will be automatically inserted into
+outgoing messages.
+It typically contains information such as your
+name, email address and organizational affiliation.
+Alpine adds the
+signature into the message as soon as you enter the composer so you
+can choose to remove it or edit it on a message by message basis.
+Signature file placement in message replies is controlled by the
+&quot;<A HREF="h_config_sig_at_bottom"><!--#echo var="FEAT_signature-at-bottom"--></A>&quot;
+setting in the feature list.
+<P>
+
+The default file name is
+<!--chtml if pinemode="os_windows"-->
+&quot;PINE.SIG&quot; in the same directory as your PINERC file if your
+PINERC file is a local file.
+If your PINERC file is remote, then it will be in the directory specified
+by the &quot;-aux local_directory&quot; command line option.
+<!--chtml else-->
+&quot;.signature&quot;.
+<!--chtml endif-->
+<P>
+
+To create or edit your signature file choose Setup from the MAIN MENU
+and then select S for Signature (Main/Setup/Signature). This puts you
+into the Signature Editor where you can enter a <EM>few</EM> lines of
+text containing your identity and affiliation.
+
+<P>
+If the filename is followed by a vertical bar (|) then instead
+of reading the contents of the file the file is assumed to be a
+program that will produce the text to be used on its standard output.
+The program can't have any arguments and doesn't receive any input from Alpine,
+but the rest of the processing works as if the contents came from a file.
+
+<P>
+Instead of storing the data in a local file, the
+signature data may be stored remotely in an IMAP folder.
+In order to do this,
+you must use a remote name for the file.
+A remote <!--#echo var="VAR_signature-file"--> name might look like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/signature</SAMP></CENTER>
+<P>
+
+The syntax used here is the same as the syntax used for remote configuration
+files from the command line.
+Note that you may not access an existing signature file remotely,
+you have to create a new <EM>folder</EM> that contains the signature data.
+If the name you use here for the signature file is a remote name, then when
+you edit the file from the Setup/Signature command the data will be stored
+remotely in the folder.
+You aren't required to do anything special to create the folder, it
+gets created automatically if you use a remote name.
+
+<P>
+Besides regular text, the signature file may also contain
+(or a signature program may produce) tokens that
+are replaced with text that usually depends on the message you are replying
+to or forwarding.
+For example, if the signature file contains the token
+<P>
+<CENTER><SAMP>_DATE_</SAMP></CENTER>
+<P>
+anywhere in the text, then that token is replaced by the date
+the message you are replying to or forwarding was sent.
+If it contains
+<P>
+<CENTER><SAMP>_CURDATE_</SAMP></CENTER>
+<P>
+that is replaced with the current date.
+The first is an example of a token that depends on the message you
+are replying to (or forwarding) and the second is an example which
+doesn't depend on anything other than the current date.
+You have to be a little careful with this facility since tokens that
+depend on the message you are replying to or forwarding will be replaced
+by nothing in the case where you are composing a new message from scratch.
+The use of <A HREF="h_rules_roles">&quot;roles&quot;</A> may help you
+in this respect.
+It allows you to use different signature files in different cases.
+<P>
+
+The list of tokens available for use in the signature file is
+<A HREF="h_index_tokens">here</A>.
+<P>
+
+Instead of, or along with the use of &quot;roles&quot; to give you
+different signature files in different situations, there is also
+a way to conditionally include text based
+on whether or not a token would result in specific replacement text.
+For example, you could include some text based on whether or not
+the _NEWS_ token would result in any newsgroups if it was used.
+This is explained in detail
+<A HREF="h_reply_token_conditionals">here</A>.
+This isn't for the faint of heart.
+<P>
+In the very unlikely event that you want to include a literal token
+in the signature you must precede it with a backslash character.
+For example,
+<P>
+<CENTER><SAMP>&#92;_DAYDATE_ = _DAYDATE_</SAMP></CENTER>
+<P>
+would produce something like
+<P>
+<CENTER><SAMP>_DAYDATE_ = Sat, 24 Oct 1998</SAMP></CENTER>
+<P>
+It is not possible to have a literal backslash followed by an expanded token.
+<P>
+An alternate method for storing the signature data is available by using the
+<A HREF="h_config_literal_sig"><!--#echo var="VAR_literal-signature"--></A> configuration option.
+This variable will be used by default.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_init_cmd_list =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_initial-keystroke-list"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_initial-keystroke-list"--></H1>
+
+The initial keystroke--or command--list option lets you start Alpine at
+any place you like.
+Whatever keystrokes you specify here will be executed
+by Alpine upon startup as a macro.
+The words SPACE, TAB, DOWN, UP, LEFT, and
+RIGHT indicate the pressing of those keys.
+CR indicates the pressing of the RETURN key.
+F1 through F12 represent the function keys, and ^ followed
+by a character indicates that key pressed along with the control key (in
+other words, ^P means Ctrl-P).
+As a shortcut notation, an element of the list may be several characters
+surrounded by double-quotes (").
+That will be expanded into the individual keystrokes
+(excluding the double-quote characters).
+For example, the quoted-string
+
+<P><CENTER>"ABC"</CENTER>
+
+<P>
+is interpreted the same as the three separate list members
+
+<P><CENTER>A and B and C</CENTER>
+
+<P>
+which is also the same as
+
+<P><CENTER>A,B,C</CENTER>
+
+<P>
+An example: To view message 1 on startup,
+you could use an <!--#echo var="VAR_initial-keystroke-list"--> equal to
+
+<P><CENTER>I,J,1,CR,V</CENTER>
+
+<P>
+An equivalent version of this is
+
+<P><CENTER>"IJ1",CR,V</CENTER>
+
+<P>
+Restrictions: You cannot pre-type into the composer with the initial
+keystroke list, and you cannot mix function key commands with letter
+commands.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_comp_hdrs =====
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_default-composer-hdrs"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_default-composer-hdrs"--></h1>
+
+You can control which headers you want visible when composing outgoing
+email using this option.
+You can specify any of the regular set, any
+<A HREF="h_compose_richhdr">Rich Header</A>,
+or any <A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>
+that you have already defined.
+If you use this setting at all, you must specify all the
+headers you want to see, you can't just add to the regular header set.
+The default set is To:, Cc:, Attchmnt:, and Subject:.<p>
+
+Note that the "Newsgroups:" header will be abbreviated in the Composer
+display, but should be spelled out in full here.<p>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_custom_hdrs =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_customized-hdrs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_customized-hdrs"--></H1>
+
+You may add your own custom headers to outgoing messages.
+Each header you specify here must include the header tag
+(<A HREF="h_composer_reply_to">Reply-To:</A>, Approved:, etc.)
+and may optionally include a value for that header.
+If you want to see these custom headers each time you compose a message,
+you must add them to your
+<A HREF="h_config_comp_hdrs"><!--#echo var="VAR_default-composer-hdrs"--></A> list,
+otherwise they become part
+of the rich header set that you only see when you press the
+<A HREF="h_compose_richhdr">Rich Header</A>
+<!--chtml if pinemode="function_key"-->(F5)
+<!--chtml else-->(Ctrl-R)<!--chtml endif--> command.
+(If you are looking for a way to change which headers are <EM>displayed</EM>
+when you view a message, take a look at the
+<A HREF="h_config_viewer_headers"><!--#echo var="VAR_viewer-hdrs"--></A>
+option instead.)
+Here's an example that shows how you might set your From address
+<P>
+<CENTER><SAMP>From: Full Name &lt;user@example.com&gt;</SAMP></CENTER>
+<P>
+and another showing how you might set a Reply-To address
+<P>
+<CENTER><SAMP>Reply-To: user@example.com</SAMP></CENTER>
+<P>
+You may also set non-standard header values here.
+For example, you could add
+<P>
+<CENTER><SAMP>Organization: My Organization Name</SAMP></CENTER>
+<P>
+or even
+<P>
+<CENTER><SAMP>X-Favorite-Colors: Purple and Gold</SAMP></CENTER>
+<P>
+If you include a value after the colon then that header will be included
+in your outgoing messages unless you delete it before sending.
+If a header in the <!--#echo var="VAR_customized-hdrs"--> list has only a tag but no value, then
+it will not be included in outgoing messages unless you edit a value
+in manually.
+For example, if
+<P>
+<CENTER><SAMP>Reply-To:</SAMP></CENTER>
+<P>
+is in the list, then the Reply-To header will be available for editing
+but won't be included unless a value is added while in the composer.
+<P>
+It's actually a little more complicated than that.
+The values of headers that you set with the <!--#echo var="VAR_customized-hdrs"--> option are
+defaults.
+If the message you are about to compose already has a value for a header,
+that value is used instead of a value from your <!--#echo var="VAR_customized-hdrs"-->.
+For example, if you are Replying to a message the Subject field
+will already be filled in.
+In that case, if the <!--#echo var="VAR_customized-hdrs"--> list contains a Subject line, the
+custom subject will <EM>NOT</EM> be used.
+The subject derived from the subject of the message you are Replying
+to will be used instead.
+<P>
+It is also possible to make header setting even more complicated and more
+automatic by using
+<A HREF="h_rules_roles">Roles</A>,
+but if all you want to do is set a default value for a header, you don't
+need to think about Roles.
+<P>
+If you change your From address you may also find it useful to add the
+changed From address to the
+<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
+configuration option.
+<P>
+Limitation: Because commas are used to separate the list of
+<!--#echo var="VAR_customized-hdrs"-->, it is not possible to have the value of a
+header contain a comma.
+Nor is there currently an &quot;escape&quot; mechanism provided
+to make this work.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_viewer_headers =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_viewer-hdrs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_viewer-hdrs"--></H1>
+
+You may change the default list of headers that are viewed by listing
+the headers you want to view here. If the headers in your
+&quot;<!--#echo var="VAR_viewer-hdrs"-->&quot; list are present in the message, then they
+will be shown. The order of the headers you list will be honored. If
+the special value &quot;all-except&quot; is included as the first
+header in the &quot;<!--#echo var="VAR_viewer-hdrs"-->&quot; list, then all headers in the
+message except those in the list will be shown. The values are all
+case insensitive.
+
+<P>
+Note that once you put anything in the &quot;<!--#echo var="VAR_viewer-hdrs"-->&quot; list,
+then the original default headers are ignored. So, if you just wanted
+to add the header Organization to the list, you would have to list
+Organization plus all of the other headers originally in the default
+list. If you just included Organization and nothing else, then you
+would see only the Organization header, nothing else.
+
+<P>
+The default list of headers includes:
+<UL>
+ <LI>From
+ <LI>Resent-From
+ <LI>To
+ <LI>Resent-To
+ <LI>Cc
+ <LI>Resent-cc
+ <LI>Bcc
+ <LI>Newsgroups
+ <LI>Followup-To
+ <LI>Date
+ <LI>Resent-Date
+ <LI>Subject
+ <LI>Resent-Subject
+ <LI>Reply-To
+</UL>
+
+<P>
+If you are looking for a way to control which headers are included in
+outgoing mail and are visible or not in the composer, take a look at the
+options
+<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>
+and <A HREF="h_config_comp_hdrs"><!--#echo var="VAR_default-composer-hdrs"--></A> instead of
+this option.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_viewer_margin_left =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_viewer-margin-left"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_viewer-margin-left"--></H1>
+
+This variable controls the left-hand vertical margin's width in
+Alpine's Message Viewing screen.
+Its value is the number of space characters preceding each displayed line.
+For consistency with
+<A HREF="h_config_viewer_margin_right"><!--#echo var="VAR_viewer-margin-right"--></A>,
+you may specify the column number to start in
+(column numbering begins with number 1)
+instead of the width of the margin by appending a lower case letter
+&quot;c&quot; to the number.
+For example, a value of &quot;2c&quot; means to start the text in column two,
+which is entirely equivalent to a value of &quot;1&quot;, which means to
+leave a margin of 1 space.
+<P>
+The default is a left margin of 0 (zero).
+Misconfigurations (for example, negative values or values with starting
+left columns greater than the ending right column)
+are silently ignored.
+If the number of columns for text between the <!--#echo var="VAR_viewer-margin-left"--> and
+the <!--#echo var="VAR_viewer-margin-right"--> is fewer than 8, then margins of zero will be used
+instead.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_viewer_margin_right =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_viewer-margin-right"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_viewer-margin-right"--></H1>
+
+This variable controls the right-hand vertical margin's width in
+Alpine's Message Viewing screen.
+Its value is the number of space characters following each displayed line.
+You may specify the column number to end the text in
+(column numbering begins with number 1)
+instead of the width of the margin by appending a lower case letter
+&quot;c&quot; to the number.
+For example, a value of &quot;76c&quot; means to end the text in column 76.
+If the screen is 80 characters wide, this is equivalent to a value
+of &quot;4&quot;, which means to leave a margin of 4 spaces.
+However, if you use different size screens at different times, then these
+two values are not equivalent.
+<P>
+The default right margin is 4.
+Misconfigurations (for example, negative values or values with starting
+left columns greater than the ending right column)
+are silently ignored.
+If the number of columns for text between the
+<A HREF="h_config_viewer_margin_left"><!--#echo var="VAR_viewer-margin-left"--></A> and
+the <!--#echo var="VAR_viewer-margin-right"--> is fewer than 8, then margins of zero will be used
+instead.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quote_suppression =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_quote-suppression-threshold"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_quote-suppression-threshold"--></H1>
+
+This option should be used with care.
+It will cause some of the quoted text to be eliminated from the
+display when viewing a message in the MESSAGE TEXT screen.
+For example, if you set the <!--#echo var="VAR_quote-suppression-threshold"--> to the
+value &quot;5&quot;,
+this will cause quoted text that is longer than five lines to be truncated.
+Quoted text of five or fewer consecutive lines will be displayed in its entirety.
+Quoted text of more than six lines will have the first five lines displayed
+followed by a line that looks something like
+<P>
+<CENTER><SAMP>[ 12 lines of quoted text hidden from view ]</SAMP></CENTER>
+<P>
+As a special case, if exactly one line of quoted text would be hidden, the
+entire quote will be shown instead.
+So for the above example, quoted text that is exactly six lines long will
+will be shown in its entirety.
+(In other words, instead of hiding a single line and adding a line
+that announces that one line was hidden, the line is just shown.)
+<P>
+If the sender of a message has carefully chosen the quotes that he or she
+includes, hiding those quotes may change the meaning of the message.
+For that reason, Alpine requires that when you want to set the value of this
+variable to something less than four lines, you actually have to set it
+to the negative of that number.
+So if you want to set this option to &quot;3&quot;, you actually have to
+set it to &quot;-3&quot;.
+The only purpose of this is to get you to think about whether or not you
+really want to do this!
+If you want to delete all quoted text you set the value of this option
+to the special value &quot;-10&quot;.
+<P>
+The legal values for this option are
+<P>
+<TABLE>
+<TR>
+ <TD> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </TD>
+ <TD> Default, don't hide anything </TD>
+</TR>
+<TR>
+ <TD> &nbsp;-1,-2,-3&nbsp;&nbsp; </TD>
+ <TD> Suppress quote lines past 1, 2, or 3 lines </TD>
+</TR>
+<TR>
+ <TD> &nbsp;4,5,6,...&nbsp; </TD>
+ <TD> Suppress if more than that many lines </TD>
+</TR>
+<TR>
+ <TD> &nbsp;&nbsp;&nbsp;-10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </TD>
+ <TD> Suppress all quoted lines </TD>
+</TR>
+</TABLE>
+<P>
+If you set this option to a non-default value you may sometimes wish to
+view the quoted text that is not shown.
+When this is the case, the
+<A HREF="h_common_hdrmode">HdrMode Command</A>
+may be used to show the hidden text.
+Typing the &quot;H&quot; command once will show the hidden text.
+Typing a second &quot;H&quot; will also turn on Full Header mode.
+The presence or absence of the HdrMode command is determined by the
+<A HREF="h_config_enable_full_hdr">&quot;<!--#echo var="FEAT_enable-full-header-cmd"-->&quot;</A>
+Feature-List option in your Alpine configuration, so you will want to
+be sure that is turned on if you use quote suppression.
+<P>
+For the purposes of this option, a quote is a line that begins with the
+character &quot;&gt;&quot;.
+<P>
+Quotes are only suppressed when displaying a message on the screen.
+The entire quote will be left intact when printing or forwarding or something
+similar.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_saved_msg_name_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_saved-msg-name-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_saved-msg-name-rule"--></H1>
+
+This option determines the default folder name when saving
+a message.
+
+<P>
+The default option is &quot;default-folder&quot;, which is the folder
+called &quot;saved-messages&quot; in Unix Alpine and
+&quot;savemail&quot; in PC-Alpine. To change the default folder, modify
+the Alpine option called
+<A HREF="h_config_def_save_folder">&quot;<!--#echo var="VAR_default-saved-msg-folder"-->&quot;</A>.
+
+<P>
+Choosing any of the &quot;by-&quot; options cause Alpine to attempt to
+get the chosen option's value for the message being saved (or for the
+first message being saved if using an aggregrate save).
+For example, if &quot;by-from&quot; is chosen, Alpine attempts to get the
+value of who the message came from (i.e. the from address).
+Alpine then attempts to save the message to a folder matching that value.
+If &quot;by-from&quot; is chosen and no value is obtained, Alpine uses
+&quot;by-sender&quot;.
+The opposite is also true.
+If &quot;by-recipient&quot; is chosen and the message was posted to a
+newsgroup, Alpine will use the newsgroup name.
+If &quot;by-replyto&quot; is chosen and no value is obtained, Alpine uses
+&quot;by-from&quot;.
+
+<P>
+If any of the &quot;by-realname&quot; options are chosen, Alpine will attempt
+to use the personal name part of the address instead of the mailbox part.
+If any of the &quot;by-nick&quot; options are chosen, the
+address is looked up in your address book and if found, the
+nickname for that entry is used.
+Only simple address book entries are checked, not distribution lists.
+Similarly, if any of the
+&quot;by-fcc&quot; options are chosen, the fcc from the corresponding
+address book entry is used.
+If by-realname, or the by-nick or by-fcc lookups result in no value,
+then if the chosen option ends with the &quot;then-from&quot;,
+&quot;then-sender&quot;, &quot;then-replyto&quot;,
+or &quot;then-recip&quot; suffix, Alpine
+reverts to the same behavior as &quot;by-from&quot;,
+&quot;by-sender&quot;, &quot;by-replyto&quot;, or &quot;by-recip&quot;
+depending on which option was specified.
+If the chosen option doesn't end with one of
+the &quot;then-&quot; suffixes, then Alpine reverts to the default
+folder when no match is found in the address book.
+
+<P>
+Choosing the option called &quot;last-folder-used&quot;, causes Alpine
+to save to the folder that you saved to the last time you saved a
+message. The first time you save a message in an Alpine session, Alpine
+attempts to save the message to the default folder.
+
+<P>
+Here is an example to make some of the options clearer.
+If the message is From
+<P>
+<CENTER><SAMP>Fred Flintstone &lt;flint@bedrock.org&gt;</SAMP></CENTER>
+<P>
+and this rule is set to &quot;by-from&quot;, then the default folder offered
+in the save dialog would be &quot;flint&quot;.
+<P>
+If this rule is set to &quot;by-realname-of-from&quot; then the default would
+be &quot;Fred Flintstone&quot;.
+<P>
+If this rule is set to &quot;by-nick-of-from&quot; then Alpine will search
+for the address &quot;flint@bedrock.org&quot; in your address book.
+If an entry is found and it has a nickname associated with it, that nickname
+will be offered as the default folder.
+If not, the default saved message folder will be offered as the default.
+<P>
+If this rule is set to &quot;by-fcc-of-from&quot; then Alpine will search
+for the address &quot;flint@bedrock.org&quot; in your address book.
+If an entry is found and it has an Fcc associated with it, that Fcc
+will be offered as the default folder.
+If not, the default saved message folder will be offered as the default.
+<P>
+If this rule is set to &quot;by-nick-of-from-then-from&quot; then Alpine will search
+for the address &quot;flint@bedrock.org&quot; in your address book.
+If an entry is found and it has a nickname associated with it, that nickname
+will be offered as the default folder.
+If it is not found (or has no nickname) then the default offered will be
+the same as it would be for the &quot;by-from&quot; rule.
+That is, it would be &quot;flint&quot;
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_fcc_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_fcc-name-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_fcc-name-rule"--></H1>
+
+This option determines the default name for folder carbon copy. Choose
+one:
+
+<DL>
+<DT>default-fcc</DT>
+<DD>This is the normal default, the value of which is set in the
+&quot;<!--#echo var="VAR_default-fcc"-->&quot; variable as specified earlier in this
+configuration.
+</DD>
+
+<DT>last-fcc-used</DT>
+<DD> Causes Alpine to use the folder that was last
+used in the fcc field
+</DD>
+
+<DT>by-nickname</DT>
+<DD>Means that Alpine will use the nickname
+from your address book that matches the first address in the To line.
+If there is no match, it will use the value of the
+&quot;<!--#echo var="VAR_default-fcc"-->&quot; variable.
+</DD>
+
+<DT>by-recipient</DT>
+<DD>Means Alpine will form a folder name
+based on the left hand side of the first address in the To line.
+</DD>
+
+<DT>by-nick-then-recip</DT>
+<DD>Means that it will use the
+matching nickname from your address book if there is one, otherwise it
+will extract the recipient name from the address and use that (like
+by-recipient).
+</DD>
+
+<DT>current-folder</DT>
+<DD>Causes a copy to be written to
+the currently open folder, unless that is the INBOX. In the case
+where the current folder is the INBOX, the &quot;<!--#echo var="VAR_default-fcc"-->&quot; is
+used instead.
+</DD>
+</DL>
+
+<P>
+Note: Whatever the fcc specified by the rule here, it will be
+over-ridden by any fcc entries you have in your address book.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sort_key =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_sort-key"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_sort-key"--></H1>
+
+This option determines the order in which messages will be displayed in
+the MESSAGE INDEX screen. Choose from:
+<P>
+<UL>
+ <LI> <A HREF="h_index_sort_arrival">Arrival</A>
+ <LI> <A HREF="h_index_sort_date">Date</A>
+ <LI> <A HREF="h_index_sort_subj">Subject</A>
+ <LI> <A HREF="h_index_sort_ordsubj">OrderedSubj</A>
+ <LI> <A HREF="h_index_sort_thread">Thread</A>
+ <LI> <A HREF="h_index_sort_from">From</A>
+ <LI> <A HREF="h_index_sort_size">Size</A>
+ <LI> <A HREF="h_index_sort_score">Score</A>
+ <LI> <A HREF="h_index_sort_to">To</A>
+ <LI> <A HREF="h_index_sort_cc">Cc</A>
+</UL>
+
+<P>
+Each type of sort may also be reversed.
+Normal default is by &quot;Arrival&quot;.
+
+<P>
+A possible point of confusion arises when you change the configuration
+of the <!--#echo var="VAR_sort-key"-->.
+The folder will normally be re-sorted when you go back to viewing the
+index.
+However, if you have manually sorted the folder with the
+Sort
+(<!--chtml if pinemode="function_key"-->F7<!--chtml else-->$<!--chtml endif-->)
+command, then it will not be re-sorted until the next time it is opened.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_other_startup =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Set Startup Rule</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Set Startup Rule</H1>
+
+This option determines which message will be the <EM>current message</EM> when
+the folder is first opened.
+It works the same way that the option
+<A HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A>
+works, so look there for help.
+It may be used for any folder, not just incoming folders.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_perfolder_sort =====
+<HTML>
+<HEAD>
+<TITLE>Set Sort Order</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Sort Order</H1>
+
+This option determines the order in which messages will be displayed in
+the MESSAGE INDEX screen when the Current Folder Type set in the
+Pattern is a match. Choose from:
+<P>
+<UL>
+ <LI> <A HREF="h_index_sort_default">Default</A>
+ <LI> <A HREF="h_index_sort_arrival">Arrival</A>
+ <LI> <A HREF="h_index_sort_date">Date</A>
+ <LI> <A HREF="h_index_sort_subj">Subject</A>
+ <LI> <A HREF="h_index_sort_ordsubj">OrderedSubj</A>
+ <LI> <A HREF="h_index_sort_thread">Thread</A>
+ <LI> <A HREF="h_index_sort_from">From</A>
+ <LI> <A HREF="h_index_sort_size">Size</A>
+ <LI> <A HREF="h_index_sort_score">Score</A>
+ <LI> <A HREF="h_index_sort_to">To</A>
+ <LI> <A HREF="h_index_sort_cc">Cc</A>
+</UL>
+
+<P>
+Each type of sort may also be reversed.
+Normal default is by &quot;Arrival&quot;.
+
+<P>
+A possible point of confusion arises when you change the configuration
+of the Sort Order for the currently open folder.
+The folder will normally be re-sorted when you go back to viewing the
+index.
+However, if you have manually sorted the folder with the
+Sort
+(<!--chtml if pinemode="function_key"-->F7<!--chtml else-->$<!--chtml endif-->)
+command, then it will not be re-sorted until the next time it is opened.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_fld_sort_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_folder-sort-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_folder-sort-rule"--></H1>
+
+This option controls the order in which folder list entries will be
+presented in the FOLDER LIST screen. Choose one of the following:
+
+<DL>
+<DT>Alphabetical</DT>
+<DD>sort by alphabetical name independent of type
+</DD>
+
+<DT>Alpha-with-dirs-last</DT>
+<DD>sort by alphabetical name grouping directory entries
+to the end of the list
+</DD>
+
+<DT>Alpha-with-dirs-first</DT>
+<DD>sort by alphabetical name grouping directory entries
+to the start of the list
+</DD>
+</DL>
+
+The normal default is &quot;Alphabetical&quot;.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ab_sort_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_addrbook-sort-rule"--></a></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_addrbook-sort-rule"--></a></H1>
+
+This option controls the order in which address book entries will be
+presented. Choose one of the following:
+
+<DL>
+<DT>fullname</DT>
+<DD>use fullname field, lists mixed in
+</DD>
+
+<DT>fullname-with-lists-last</DT>
+<DD>use fullname field, but put lists at end
+</DD>
+
+<DT>nickname</DT>
+<DD>use nickname field, lists mixed in
+</DD>
+
+<DT>nickname-with-lists-last</DT>
+<DD>use nickname field, but put lists at end
+</DD>
+
+<DT>dont-sort</DT>
+<DD>don't change order of file
+</DD>
+</DL>
+
+<P>
+The normal default is &quot;fullname-with-lists-last&quot;.
+If you use an address book from more than one computer and those
+computers sort the address book differently then the sort order
+will be the order where the last change to the address book was
+made.
+There are two reasons the sorting might be different on different
+systems.
+First, the <!--#echo var="VAR_addrbook-sort-rule"--></a> may be set differently in the two
+places.
+Second, the collation rules on the two computers may be different.
+For example, one system might ignore special characters while the other
+doesn't or one may sort upper and lower case letters together while
+the other doesn't.
+In any case, the order you see is the order on the system where the
+last change was made, for example by an address book edit or a
+Take Address command.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_post_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_posting-character-set"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_posting-character-set"--></H1>
+
+The <!--#echo var="VAR_posting-character-set"--> configuration option is used
+when sending messages.
+
+<P>
+
+When sending a message the text typed in the composer is
+labeled with the character set specified by this option.
+If the composed text is not fully representable in the
+specified <!--#echo var="VAR_posting-character-set"-->, then it is labeled as &quot;UTF-8.&quot
+instead;
+
+<P>
+Attachments are labeled with your
+<A HREF="h_config_key_char_set">&quot;Keyboard Character Set&quot;</A>.
+
+<P>
+Generally, there should be little need to set this option.
+If left unset, the
+default behavior is to label composed text as specifically as
+possible. That is, if the composed text has no non-ASCII characters,
+it is labeled as &quot;US-ASCII.&quot; Similarly, if it is composed of
+only ISO-8859-15 characters, it is labeled as such. Alpine will
+attempt to automatically detect a number of character sets including ISO-8859-15,
+ISO-8859-1, ISO-8859-2, VISCII, KOI8-R, KOI8-U, ISO-8859-7, ISO-8859-6,
+ISO-8859-8, TIS-620, ISO-2022-JP, GB2312, BIG5, and EUC-KR.
+If the message contains a mix of character sets,
+it is labeled as &quot;UTF-8.&quot;
+
+<P>
+
+This setting is provided to allow you to force a particular character set that
+Alpine does not automatically detect. For example, if a message is representable
+in more than one character set then Alpine may choose a different default
+than you want.
+Lastly, by setting this option explicitly to
+&quot;UTF-8&quot; all non-ASCII messages you send will be labeled as
+&quot;UTF-8&quot; instead of something more specific.
+
+<P>
+In the Setup/Config screen you may choose from a list of all the
+character sets Alpine knows about by using the &quot;T&quot; ToCharsets command.
+
+<P>
+The options
+<A HREF="h_config_char_set">&quot;Display Character Set&quot;</A>
+and <A HREF="h_config_key_char_set">&quot;Keyboard Character Set&quot;</A>
+are closely related.
+Setting the feature
+<A HREF="h_config_use_system_translation">&quot;Use System Translation&quot;</A>
+should cause this option to be ignored.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_unk_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_unknown-character-set"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_unknown-character-set"--></H1>
+
+The <!--#echo var="VAR_unknown-character-set"--> configuration option is used
+when reading or replying to messages.
+
+<P>
+
+A text message should either be made up of all US-ASCII characters
+or it should contain a charset label which tells the software which
+character set encoding to use to interpret the message.
+Sometimes a malformed message may be unlabeled but contain non-ascii text.
+This message is outside of the standards so any attempt to read it could fail.
+When Alpine attempts to read such a message it will try to interpret the
+text in the character set you specify here.
+For example, if you have correspondents who send you unlabeled messages that
+are usually made up of characters from the WINDOWS-1251 character set, setting
+this <!--#echo var="VAR_unknown-character-set"--> to <CODE>WINDOWS-1251</CODE> will
+allow you to read those messages.
+Of course, if the unlabeled message is actually in some other character set,
+then you may see garbage on your screen.
+<P>
+Instead of just unlabeled text, this option also affects text which is labeled
+with the charsets &quot;X-Unknown&quot; or &quot;US-ASCII&quot;.
+
+<P>
+In the Setup/Config screen you may choose from a list of all the
+character sets Alpine knows about by using the &quot;T&quot; ToCharsets command.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Display Character Set</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Display Character Set</H1>
+
+The Display Character Set configuration option is used when viewing messages.
+<P>
+Alpine uses Unicode characters internally and
+it is a goal for Alpine to handle email in many different languages.
+Alpine will properly display only left-to-right character sets
+in a fixed-width font. Specifically, Alpine assumes that a fixed-width
+font is in use, in the sense that
+characters are assumed to take up zero, one, or two character cell
+widths from left to right on the screen. This is true even in PC-Alpine.
+<P>
+
+Alpine recognizes some local character sets that are right-to-left
+(Arabic, Hebrew, and Thai) or not representable in a fixed-width font
+(Arabic) and properly converts texts in these character sets to/from
+Unicode; however, there are known display bugs with these character
+sets.
+<P>
+
+There are three possible configuration character settings and some
+environment variable settings that can affect how Alpine
+handles international characters.
+The first two of these are only available in UNIX Alpine.
+The three configuration options are
+Display Character Set,
+Keyboard Character Set, and
+<!--#echo var="VAR_posting-character-set"-->.
+The Keyboard Character Set defaults to being the same value
+as the Display Character Set, and that is usually correct, because
+the keyboard almost always produces characters in the same character set
+as the display displays.
+The Display Character Set is the character set that Alpine
+will attempt to use when sending characters to the display.
+<P>
+
+By default, the Display Character Set variable is not set and UNIX Alpine
+will attempt to get this information from the environment.
+In particular, the <CODE>nl_langinfo(CODESET)</CODE> call is used.
+This usually depends on the setting of the environment variables LANG or LC_CTYPE.
+An explicit configuration setting for Display Character Set will,
+of course, override any default setting.
+<P>
+For PC-Alpine the Display Character Set
+and the Keyboard Character Set
+are always equivalent to <CODE>UTF-8</CODE> and this is not settable.
+<P>
+
+It is probably best to use UNIX Alpine in a terminal emulator
+capable of displaying UTF-8 characters, since that will allow you to
+view just about any received text that is correctly formatted (note,
+however, the above comments about known index display bugs with certain
+character sets). You'll need to have an emulator that uses a UTF-8 font
+and you'll need to set up your environment to use a UTF-8 charmap. For
+example, on a Linux system you might include
+<P>
+<CENTER> <CODE>setenv LANG en_US.UTF-8</CODE> </CENTER>
+<P>
+
+or something similar in your UNIX startup files.
+You'd also have to select a UTF-8 font in your terminal emulator.
+<P>
+
+The types of values that the character set variables may be set to are
+<CODE>UTF-8</CODE>, <CODE>ISO-8859-1</CODE>, or <CODE>EUC-JP</CODE>.
+The <CODE>ISO-2022</CODE> character sets are not supported for input or
+for display, but as a special case, <CODE>ISO-2022-JP</CODE> is supported
+for use only as a <!--#echo var="VAR_posting-character-set"-->.
+In the Setup/Config screen you may choose from a list of all the
+character sets Alpine knows about by using the &quot;T&quot; ToCharsets command.
+Here is a list of many of the possible character sets:
+
+<P>
+<TABLE>
+<TR> <TD>UTF-8</TD> <TD>Unicode</TD> </TR>
+<TR> <TD>US-ASCII</TD> <TD>7 bit American English characters</TD> </TR>
+<TR> <TD>ISO-8859-1</TD> <TD>8 bit European "Latin 1" character set</TD> </TR>
+<TR> <TD>ISO-8859-2</TD> <TD>8 bit European "Latin 2" character set</TD> </TR>
+<TR> <TD>ISO-8859-3</TD> <TD>8 bit European "Latin 3" character set</TD> </TR>
+<TR> <TD>ISO-8859-4</TD> <TD>8 bit European "Latin 4" character set</TD> </TR>
+<TR> <TD>ISO-8859-5</TD> <TD>8 bit Latin and Cyrillic</TD> </TR>
+<TR> <TD>ISO-8859-6</TD> <TD>8 bit Latin and Arabic</TD> </TR>
+<TR> <TD>ISO-8859-7</TD> <TD>8 bit Latin and Greek</TD> </TR>
+<TR> <TD>ISO-8859-8</TD> <TD>8 bit Latin and Hebrew</TD> </TR>
+<TR> <TD>ISO-8859-9</TD> <TD>8 bit European "Latin 5" character set</TD> </TR>
+<TR> <TD>ISO-8859-10</TD> <TD>8 bit European "Latin 6" character set</TD> </TR>
+<TR> <TD>ISO-8859-11</TD> <TD>Latin and Thai</TD> </TR>
+<TR> <TD>ISO-8859-12</TD> <TD>Reserved</TD> </TR>
+<TR> <TD>ISO-8859-13</TD> <TD>8 bit European "Latin 7" character set</TD> </TR>
+<TR> <TD>ISO-8859-14</TD> <TD>8 bit European "Latin 8" character set</TD> </TR>
+<TR> <TD>ISO-8859-15</TD> <TD>8 bit European "Latin 9" character set</TD> </TR>
+<TR> <TD>ISO-8859-16</TD> <TD>8 bit European "Latin 10" character set</TD> </TR>
+<TR> <TD>KOI8-R</TD> <TD>8 bit Latin and Russian</TD> </TR>
+<TR> <TD>KOI8-U</TD> <TD>8 bit Latin and Ukranian</TD> </TR>
+<TR> <TD>WINDOWS-1251</TD> <TD>8 bit Latin and Russian</TD> </TR>
+<TR> <TD>TIS-620</TD> <TD>8 bit Latin and Thai</TD> </TR>
+<TR> <TD>VISCII</TD> <TD>8 bit Latin and Vietnamese</TD> </TR>
+<TR> <TD>GBK</TD> <TD>Latin and Chinese Simplified</TD> </TR>
+<TR> <TD>GB2312</TD> <TD>Latin and Chinese Simplified</TD> </TR>
+<TR> <TD>CN-GB</TD> <TD>Latin and Chinese Simplified</TD> </TR>
+<TR> <TD>BIG5</TD> <TD>Latin and Chinese Traditional</TD> </TR>
+<TR> <TD>BIG-5</TD> <TD>Latin and Chinese Traditional</TD> </TR>
+<TR> <TD>EUC-JP</TD> <TD>Latin and Japanese</TD> </TR>
+<TR> <TD>SHIFT-JIS</TD> <TD>Latin and Japanese</TD> </TR>
+<TR> <TD>EUC-KR</TD> <TD>Latin and Korean</TD> </TR>
+<TR> <TD>KSC5601</TD> <TD>Latin and Korean</TD> </TR>
+</TABLE>
+<P>
+
+When reading incoming email, Alpine understands many different
+character sets and is able to convert the incoming mail into Unicode.
+The Unicode will be converted to the Display Character Set
+for display on your terminal.
+Characters typed at the keyboard will be converted from the
+Keyboard Character Set to Unicode for Alpine's internal
+use.
+You may find that you can read some malformed messages that do not
+contain a character set label by setting the option
+<A HREF="h_config_unk_char_set">&quot;<!--#echo var="VAR_unknown-character-set"-->&quot;</A>.
+<P>
+
+The <!--#echo var="VAR_posting-character-set"--> is used when sending messages.
+The default behavior obtained by leaving this variable unset is usually
+what is wanted. In that default case, Alpine will attempt
+to label the message with the most specific character set from the
+rather arbitrary set
+<P>
+US-ASCII, ISO-8859-15,
+ISO-8859-1, ISO-8859-2, VISCII, KOI8-R, KOI8-U, ISO-8859-7, ISO-8859-6,
+ISO-8859-8, TIS-620, ISO-2022-JP, GB2312, BIG5, EUC-KR, and UTF-8.
+<P>
+
+For example, if the message is made up of only US-ASCII characters, it
+will be labeled US-ASCII. Otherwise, if it is all ISO-8859-15 characters,
+that will be the label. If that doesn't work the same is tried for the
+remaining members of the list.
+
+<P>
+It might make sense to set <!--#echo var="VAR_posting-character-set"--> to an
+explicit value instead.
+For example, if you usually send messages in Greek, setting this
+option to ISO-8859-7 will result in messages being labeled as
+US-ASCII if there are no non-ascii characters, ISO-8859-7 if there
+are only Greek characters, or UTF-8 if there are some characters
+that aren't representable in ISO-8859-7.
+Another possibility is to set this option explicitly to UTF-8.
+In that case
+Alpine labels only ascii messages as US-ASCII and all other
+messages as UTF-8.
+<P>
+
+The options
+<A HREF="h_config_post_char_set">&quot;<!--#echo var="VAR_posting-character-set"-->&quot;</A>
+and <A HREF="h_config_key_char_set">&quot;Keyboard Character Set&quot;</A>
+are closely related to this option.
+Setting the feature
+<A HREF="h_config_use_system_translation">&quot;Use System Translation&quot;</A>
+should cause this option to be ignored.
+
+<P>
+When displaying a message, Alpine compares this setting to the character
+set specified in the message. If not all of the
+characters in the message can be displayed using the Display Character Set
+then Alpine places an editorial
+comment in the displayed text (enclosed in square-brackets) indicating
+that some characters may not be displayed correctly.
+This comment may be eliminated by turning on the option
+<A HREF="h_config_quell_charset_warning"><!--#echo var="FEAT_quell-charset-warning"--></A>.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_key_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Keyboard Character Set</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Keyboard Character Set</H1>
+
+UNIX Alpine only.
+<P>
+The Keyboard Character Set identifies the character set of the characters
+coming from your keyboard.
+It defaults to having the same value as your
+<A HREF="h_config_char_set">&quot;Display Character Set&quot;</A>,
+which in turn defaults to a value obtained from your environment.
+It is unlikely that you will need to use this option, because the keyboard
+almost always produces the same kind of characters as the display displays.
+
+<P>
+This character set is also used when accessing files in your local
+file system.
+The names of the files are assumed to be in the same character set as
+what the keyboard produces, as well as the contents of the files.
+
+<P>
+In the Setup/Config screen you may choose from a list of all the
+character sets Alpine knows about by using the &quot;T&quot; ToCharsets command.
+
+<P>
+The options
+<A HREF="h_config_char_set">&quot;Display Character Set&quot;</A>
+and <A HREF="h_config_post_char_set">&quot;<!--#echo var="VAR_posting-character-set"-->&quot;</A>
+are closely related.
+Setting the feature
+<A HREF="h_config_use_system_translation">&quot;Use System Translation&quot;</A>
+should cause this option to be ignored.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_editor =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_editor"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_editor"--></H1>
+
+<!--#echo var="VAR_editor"--> specifies the program invoked by ^_ in the Composer. This is
+normally an alternative to Alpine's internal composer (Pico). You could use
+this setting to specify an alternate editor to use occasionally or if you
+have a favorite editor and want to use it all the time (see the
+<A HREF="h_config_alt_ed_now">&quot;<!--#echo var="FEAT_enable-alternate-editor-implicitly"-->&quot;</A> setting). <P>
+If you specify multiple editors for this option, ^_ will invoke the first one
+of those specified that exists and is executable. When specifying a program
+for use here, make sure that the format of the text it saves -- which, when
+you exit it, will become the message body in Alpine -- is appropriate
+for the body of an email message; avoid proprietary formats that may result in
+a message body that the recipient of your message will be unable to decipher.
+<P>
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+If you are in doubt about what editors are available on your system, or which
+of them may be appropriate for specification here, ask your local computing
+support staff.
+<!--chtml endif-->
+<P>
+Note that if <a href="h_config_quell_flowed_text"><!--#echo var="FEAT_quell-flowed-text"--></a> is
+unset, outgoing text will be set as flowed. In most cases this will be fine,
+but if the editor has a &quot;flowed text&quot; mode, it would be best to
+use that.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_speller =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_speller"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_speller"--></H1>
+
+UNIX Alpine only.
+<P>
+For PC-Alpine, you must install the aspell library code that you
+may get from
+<A HREF="http://aspell.net/win32/">http://aspell.net/win32/</A>.
+<P>
+This option affects the behavior of the ^T (spell check) command in the
+Composer. It specifies the program invoked by ^T in the Composer.
+By default, Alpine uses
+<P>
+<CENTER><SAMP>aspell --dont-backup --mode=email check</SAMP></CENTER>
+<P>
+if it knows where to find &quot;aspell&quot;.
+If there is no &quot;aspell&quot; command available but the command &quot;ispell&quot; is available
+then the command used is
+<P>
+<CENTER><SAMP>ispell -l</SAMP></CENTER>
+<P>
+Otherwise, the ancient &quot;spell&quot; command is used.
+<P>
+If you specify a value for this command (with full pathname) then that is what
+will be used instead of any of the defaults.
+When invoking this
+spell-checking program, Alpine appends a tempfile name (where the message is
+passed) to the command line. Alpine expects the speller to correct the
+spelling in that file. When you exit from that program Alpine will read the
+tempfile back into the composer.
+<P>
+Don't set this speller option to the standard Unix spell command.
+That won't work because spell works in a different way.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_display_filters =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_display-filters"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_display-filters"--></H1>
+
+This option defines a list of text-filtering commands (programs or
+scripts) that may be used to filter text portions of received messages
+prior to their use (e.g., presentation in the &quot;MESSAGE TEXT&quot;
+display screen, exporting to a text file).
+For security reasons, the full path name of the
+filter command must be specified.
+
+<P>
+The command is executed and the message is piped into its standard input.
+The standard output of the command is read back by Alpine. The
+&quot;_TMPFILE_&quot; token (see below) overrides this default behavior.
+
+<P>
+The filter's use is based on the configured &quot;trigger&quot; string. The
+format of a filter definition is:
+
+<P>
+<CENTER>&lt;trigger&gt; &lt;command&gt; &lt;arguments&gt;</CENTER>
+
+<P>
+You can specify as many filters as you wish, separating them with a comma.
+Each filter can have only one trigger and command. Thus, two trigger
+strings that invoke the same command require separate filter
+specifications.
+
+<P>
+The &quot;trigger&quot; is simply text that, if found in the message,
+will invoke the associated command. If the trigger contains any space
+characters, it must be placed within quotes. Likewise, should you
+wish a filter to be invoked unconditionally, define the trigger as the
+null string, &quot;&quot; (two consecutive double-quote characters). If the
+trigger string is found anywhere in the text of the message the filter
+is invoked. Placing the trigger text within the tokens defined below
+changes where within the text the trigger must be before considering
+it a match.
+
+<P>
+Trigger Modifying Tokens:
+<DL>
+<DT>_CHARSET(<VAR>string</VAR>)_</DT>
+<DD>This token tells Alpine to invoke the supplied command
+if the text is in a character set matching <VAR>string</VAR>
+(e.g., ISO-8859-2 or ISO-2022-JP).
+</DD>
+
+
+<DT>_LEADING(<VAR>string</VAR>)_</DT>
+<DD>This token tells Alpine to invoke the supplied command
+if the enclosed <VAR>string</VAR> is found to be the first
+non-whitespace text.
+<BR>NOTE: Quotes are necessary if <VAR>string</VAR> contains
+the space character.
+</DD>
+
+<DT>_BEGINNING(<VAR>string</VAR>)_</DT>
+<DD>This token tells Alpine to invoke the supplied command
+if the enclosed <VAR>string</VAR> is found at the beginning
+of any line in the text.
+<BR>NOTE: Quotes are necessary if <VAR>string</VAR> contains
+the space character.
+</DD>
+</DL>
+
+<P>
+The &quot;command&quot; and &quot;arguments&quot; portion is simply
+the command line to be invoked if the trigger string is found. Below
+are tokens that Alpine will recognize and replace with special values
+when the command is actually invoked.
+
+<P>
+Command Modifying Tokens:
+
+<DL>
+<DT>_TMPFILE_</DT>
+<DD>When the command is executed, this token is
+replaced with the path and name of the temporary
+file containing the text to be filtered. Alpine
+expects the filter to replace this data with the
+filter's result.
+
+<P>
+NOTE: Use of this token implies that the text to
+be filtered is not piped into standard input of the
+executed command and its standard output is ignored.
+Alpine restores the tty modes before invoking the
+filter in case the filter interacts with the user
+via its own standard input and output.
+</DD>
+
+<DT>_RESULTFILE_</DT>
+<DD>When the command is executed, this token is
+replaced with the path and name of a temporary
+file intended to contain a status message from the
+filter. Alpine displays this in the message status
+field.
+</DD>
+
+<DT>_DATAFILE_</DT>
+<DD>When the command is executed, this token is
+replaced with the path and name of a temporary
+file that Alpine creates once per session and deletes
+upon exit. The file is intended to be used by the
+filter to store state information between instances
+of the filter.
+</DD>
+
+<DT>_PREPENDKEY_</DT>
+<DD>When the command is executed, this token indicates that a random
+number will be passed down the input stream before the message text.
+This number could be used as a session key. It is sent in this way to
+improve security. The number is unique to the current Alpine session
+and is only generated once per session.
+</DD>
+</DL>
+
+<P>
+The feature
+<A HREF="h_config_disable_reset_disp"><!--#echo var="FEAT_disable-terminal-reset-for-display-filters"--></A> is related.
+<P>
+Performance caveat/considerations:
+<BR>
+Testing for the trigger and invoking the filter doesn't come for free.
+There is overhead associated with searching for the trigger string, testing
+for the filter's existence and actually piping the text through the filter.
+The impact can be reduced if the Trigger Modifying Tokens above are
+employed.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sending_filter =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_sending-filters"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_sending-filters"--></H1>
+
+This option defines a list of text-filtering commands (programs and
+scripts) that may be selectively invoked to process a message just before
+it is sent. If set, the Composer's ^X (Send) command will allow you to
+select which filter (or none) to apply to the message before it is sent.
+For security reasons, the full path of the filter program must be
+specified.
+
+<P>
+Command Modifying Tokens:
+
+<DL>
+<DT>_RECIPIENTS_</DT>
+<DD>When the command is executed, this token is replaced
+with the space delimited list of recipients of the
+message being sent.
+</DD>
+
+<DT>_TMPFILE_</DT>
+<DD>
+When the command is executed, this token is
+replaced with the path and name of the temporary
+file containing the text to be filtered. Alpine
+expects the filter to replace this data with the
+filter's result.
+
+<P>
+NOTE: Use of this token implies that the text to
+be filtered is not piped into standard input of the
+executed command and its standard output is ignored.
+Alpine restores the tty modes before invoking the
+filter in case the filter interacts with the user
+via its own standard input and output.
+</DD>
+
+<DT>_RESULTFILE_</DT>
+<DD>When the command is executed, this token is
+replaced with the path and name of a temporary
+file intended to contain a status message from the
+filter. Alpine displays this in the message status
+field.
+</DD>
+
+<DT>_DATAFILE_</DT>
+<DD>When the command is executed, this token is replaced
+in the command line with the path and name of a
+temporary file that Alpine creates once per session
+and deletes upon exit. The file is intended to be
+used by the filter to store state information between
+instances of the filter.
+</DD>
+
+<DT>_PREPENDKEY_</DT>
+<DD>When the command is executed, this token indicates
+that a random number will be passed down the input
+stream before the message text. This number could
+be used as a session key. It is sent in this way
+to improve security. The number is unique to the
+current Alpine session and is only generated once per
+session.
+</DD>
+
+<DT>_INCLUDEALLHDRS_</DT>
+<DD>When the command is executed, this token indicates
+that the headers of the message will be passed down the input stream
+before the message text.
+</DD>
+
+<DT>_MIMETYPE_</DT>
+<DD>When the command is executed, this token is replaced in the
+command name with a temporary file name used to accept any new MIME
+Content-Type information necessitated by the output of the filter.
+Upon the filter's exit, if the file contains new MIME type
+information, Alpine verifies its format and replaces the outgoing
+message's MIME type information with that contained in the file. This
+is basically a cheap way of sending something other than Text/Plain.
+</DD>
+</DL>
+
+<P>
+NOTE: Only the body text, which is visible in the Composer, is piped
+through this filter. Attachments are not sent to the filter.
+<P>
+Sending filters are not used if the feature
+<A HREF="h_config_send_wo_confirm">&quot;<!--#echo var="FEAT_send-without-confirm"-->&quot;</A> is set.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_keywords =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_keywords"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_keywords"--></H1>
+
+You may define your own set of keywords and optionally set them on a
+message by message basis.
+These are similar to the &quot;Important&quot; flag which the user
+may set using the Flag command.
+The difference is that the Important flag is always present for each folder.
+User-defined keywords are chosen by the user.
+You may set up the list of possible keywords here, or you may add keywords
+from the Flag Details screen that you
+can get to after typing the
+Flag (<!--chtml if pinemode="function_key"-->F11<!--chtml else-->*<!--chtml endif-->)
+command.
+After the keywords have been defined,
+then you use the <A HREF="h_common_flag">Flag command</A>
+to set or clear the keywords in each message.
+The behavior of the flag command may be modified by using the
+<A HREF="h_config_flag_screen_default">&quot;<!--#echo var="FEAT_enable-flag-screen-implicitly"-->&quot;</A> option or the
+<A HREF="h_config_flag_screen_kw_shortcut">&quot;<!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"-->&quot;</A> option.
+
+<P>
+Keywords may be used when Selecting messages (Select Keyword).
+Keywords may also be used in the Patterns of Rules (Filters, Indexcolors, etc).
+Filter Rules may be used to set keywords automatically.
+Keywords may be displayed as part of the Subject of a message by using
+the SUBJKEY or SUBJKEYINIT tokens in the
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option.
+The <A HREF="h_config_kw_braces"><!--#echo var="VAR_keyword-surrounding-chars"--></A>
+option may be used to modify the display of keywords using
+SUBJKEY and SUBJKEYINIT slightly.
+Keywords may also be displayed in a column of their own in the MESSAGE INDEX
+screen by using the KEY or KEYINIT tokens.
+It is also possible to color keywords in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+Keywords are not supported by all mail servers.
+
+<P>
+You may give keywords nicknames if you wish.
+If the keyword definition you type in contains a SPACE character, then the
+actual value of the keyword is everything after the last SPACE and the
+nickname for that keyword is everything before the last SPACE.
+For example, suppose you are trying to interoperate with another email program
+that uses a particular keyword with an unpleasant name.
+Maybe it uses a keyword called
+<P>
+<CENTER><SAMP>VendorName.SoftwareName.08</SAMP></CENTER>
+<P>
+but for you that keyword means that the message is work-related.
+You could define a keyword to have the value
+<P>
+<CENTER><SAMP>Work VendorName.SoftwareName.08</SAMP></CENTER>
+<P>
+and then you would use the name &quot;Work&quot; when dealing with
+that keyword in Alpine.
+If you defined it as
+<P>
+<CENTER><SAMP>My Work VendorName.SoftwareName.08</SAMP></CENTER>
+<P>
+the nickname would be everything before the last SPACE, that is the nickname
+would be &quot;My Work&quot;.
+<P>
+Some commonly used keywords begin with dollar signs.
+This presents a slight complication, because the dollar sign is normally used
+to signify
+<A HREF="h_news_config">environment variable expansion</A>
+in the Alpine configuration.
+In order to specify a keyword that begins with a dollar sign you must
+precede the dollar sign with a second dollar sign to escape its special
+meaning.
+For example, if you want to include the keyword
+<P>
+<CENTER><SAMP>$Label1</SAMP></CENTER>
+<P>
+as one of your possible keywords, you must enter the text
+<P>
+<CENTER><SAMP>$$Label1</SAMP></CENTER>
+<P>
+
+instead.
+<P>
+There are a couple limitations.
+First, not all servers support keywords.
+Second, some servers (including the IMAP server included with Alpine)
+have a per folder limit on the number of keywords that may be defined.
+This count commonly includes every keyword you have ever used in the
+folder, even if it is no longer being used.
+In other words, you can add keywords but you cannot remove them easily.
+If you have changed keywords over the life of a folder and find that
+you have reached such a limit, one possible solution might be to copy
+all of the messages to a newly created folder (using Alpine) and then
+delete the original and rename the new folder.
+The reason this might work is that only the keywords currently set in
+any of the messages will be used in the new folder, hopefully putting you
+under the limit.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_alt_addresses =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_alt-addresses"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_alt-addresses"--></H1>
+
+This option provides a place for you to list alternate email addresses
+you may have.
+Each address in the list should be the actual email address part of an
+address, without the full name field or the angle brackets.
+For example:
+
+<P>
+<CENTER><SAMP>user@example.com</SAMP></CENTER>
+<P>
+
+The matching is case-insensitive, so this would match any of
+<SAMP>User@example.com</SAMP>, <SAMP>user@Example.Com</SAMP>, or
+<SAMP>USER@EXAMPLE.COM</SAMP> as well.
+
+<P>
+If set, the option affects the behavior of the Reply
+command and the &quot;+&quot; symbol in the MESSAGE INDEX, which denotes that
+a message has been addressed specifically to you.
+
+<P>
+In the default INDEX display
+the personal name (or email address) of
+the person listed in the message's &quot;From:&quot; header
+field is usually displayed except when that address is yours or one of your
+alternate addresses.
+In that case you will usually see the name of
+the first person specified in the
+message's &quot;To:&quot; header field
+with the prefix &quot;To: &quot; prepended.
+
+<P>
+With respect to Reply, the reply-to-all option will exclude addresses
+listed here.
+
+<P>
+The feature
+<A HREF="h_config_copy_to_to_from"><!--#echo var="FEAT_copy-to-address-to-from-if-it-is-us"--></A>
+is somewhat related to this option.
+
+<P>
+In addition to a list of actual addresses,
+you may use regular expressions (as used with egrep with the ignore case flag)
+to describe the addresses you want to match.
+Alpine will somewhat arbitrarily interpret your entry as a regular
+expression if it contains any of the characters
+*, |, +, ?, {, [, ^, $, or &#92;.
+Otherwise, it will be treated literally.
+The feature
+<a href="h_config_disable_regex"><!--#echo var="FEAT_disable-regular-expression-matching-for-alternate-addresses"--></a>
+may be used to turn off regular expression processing regardless of whether or not
+special characters appear in the entry.
+
+<P>
+A description of how regular expressions work is beyond the
+scope of this help text, but some examples follow.
+
+<P>
+The entry
+
+<P>
+<CENTER><SAMP>.*@example.com</SAMP></CENTER>
+<P>
+
+in the <!--#echo var="VAR_alt-addresses"--> list would mean that any
+address with a domain name of <SAMP>example.com</SAMP> (such as
+<SAMP>fred@example.com</SAMP> or <SAMP>wilma@example.com</SAMP>) will be considered
+one of your alternate addresses.
+Strictly speaking, the dot in <SAMP>example.com</SAMP> ought to be escaped with
+a backslash, as in <SAMP>example&#92;.com</SAMP>, and a dollar sign anchor ought
+to come at the end of the expression to prevent a match of <SAMP>example.com.org</SAMP>.
+Complicating things further, the dollar sign
+is special in the Alpine configuration (it signifies environment variable expansion)
+so the dollar sign should be doubled or backslash escaped for Alpine's sake.
+Quotes around the whole expression will not escape the dollar sign successfully.
+So this example should look like
+
+<P>
+<CENTER><SAMP>.*@example&#92;.com$$</SAMP></CENTER>
+<P>
+
+<P>
+The entry
+
+<P>
+<CENTER><SAMP>^fred[0-9]*@example.com$$</SAMP></CENTER>
+<P>
+
+would match
+<SAMP>fred3@example.com</SAMP> or <SAMP>fred17@example.com</SAMP> as well
+as <SAMP>fred@example.com</SAMP>.
+
+<P>
+You could match all addresses that look like
+<SAMP>fred+stuff@example.com</SAMP> for any value of <SAMP>stuff</SAMP> with the
+entry
+
+<P>
+<CENTER><SAMP>^fred&#92;+.*@example.com$$</SAMP></CENTER>
+<P>
+
+Notice that you have to escape the plus sign with a backslash because plus
+is a special character in regular expressions.
+If you wanted to match plain <SAMP>fred</SAMP> as well as <SAMP>fred+stuff</SAMP>
+the expression
+
+<P>
+<CENTER><SAMP>^fred(()|&#92;+.*)@example.com$$</SAMP></CENTER>
+<P>
+
+would do it, but it would be easier to just add fred@example.com as a
+separate entry.
+
+<P>
+One more example, a match of all first-level subdomains, is given by
+
+<P>
+<CENTER><SAMP>^fred@[[:alnum:]_-]*&#92;.example&#92;.com$$</SAMP></CENTER>
+<P>
+
+<P>
+Because the regular expression matching is based on an old library
+(<SAMP>hs_regex</SAMP>) the regular expressions might not work exactly as you expect,
+but they should be close.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_abook_formats =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_addressbook-formats"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_addressbook-formats"--></H1>
+
+This option specifies the format that address books are displayed in.
+Normally, address books are displayed with the nicknames in the first
+column, the fullnames in the second column, and addresses in the third
+column. The system figures out reasonable defaults for the widths of
+the columns. An address book may be given a different format by
+listing special tokens in the order you want them to display. The
+possible tokens are NICKNAME, FULLNAME, ADDRESS, FCC, and COMMENT.
+So, for example, to get the default behavior you could list
+
+<P>
+<CENTER><!--#echo var="VAR_addressbook-formats"-->=NICKNAME FULLNAME ADDRESS</CENTER>
+
+<P>
+(You can also use the token DEFAULT to get the default behavior for
+an address book format.)
+
+<P>
+The tokens are separated by spaces. &quot;<!--#echo var="VAR_addressbook-formats"-->&quot;
+is a list, so if you have more than one address book you may have a
+separate format for each by putting its format at the corresponding
+location in the &quot;<!--#echo var="VAR_addressbook-formats"-->&quot; list.
+
+<P>
+
+Listed first are the personal address books, then the global address
+books. So, if you have two personal address books and one global
+address book, you may have up to three formats in the
+&quot;<!--#echo var="VAR_addressbook-formats"-->&quot; list. If
+&quot;<!--#echo var="VAR_addressbook-formats"-->&quot; doesn't have as many elements as there
+are address books, the last element is used repeatedly.
+
+<P>
+
+Each of the tokens may also be optionally followed by parentheses with
+either a number or a percentage inside the parentheses. For example,
+<SAMP>FULLNAME(13)</SAMP> means to allocate 13 characters of space to
+the fullnames column, <SAMP>FULLNAME(20%)</SAMP> means to allocate 20%
+of the available space (the screen width minus the space for
+inter-column spaces) to the fullnames column, while plain
+<SAMP>FULLNAME</SAMP> means the system will attempt to figure out a
+reasonable number of columns.
+
+<P>
+There are always 2 spaces between every column, so if you use
+fixed column widths (like 13) you should remember to take that into
+account.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_set_index_format =====
+<HTML>
+<HEAD>
+<TITLE>Set Index Format</TITLE>
+</HEAD>
+<BODY>
+<H1>Set Index Format</H1>
+
+This option is used to customize the content of lines in the
+<A HREF="h_mail_index">MESSAGE INDEX screen</A>.
+This action works exactly like the regular
+&quot;<!--#echo var="VAR_index-format"-->&quot; option in the Setup/Config screen,
+except that you can have a folder-specific value for it if you specify it here.
+Consult the help for
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot;
+for more information.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_format =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_index-format"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_index-format"--></H1>
+
+This option is used to customize the content of lines in the
+<A HREF="h_mail_index">MESSAGE INDEX screen</A>. Each line is intended
+to convey some amount of immediately relevant information about each
+message in the current folder.
+<P>
+
+Alpine provides a pre-defined set of informational fields with
+reasonable column widths automatically computed. You can, however,
+replace this default set by listing special tokens in the order you
+want them displayed.
+<P>
+
+The list of available tokens is
+<A HREF="h_index_tokens">here</A>.
+<P>
+
+Spaces are used to separate listed tokens. Additionally, you can
+specify how much of the screen's width the token's associated data
+should occupy on the index line by appending to the token a pair of
+parentheses enclosing either a number or percentage. For example,
+&quot;SUBJECT(13)&quot; means to allocate 13 characters of space to the subject
+column, and &quot;SUBJECT(20%)&quot; means to
+allocate 20% of the available space
+to the subjects column, while plain &quot;SUBJECT&quot; means the system will
+attempt to figure out a reasonable amount of space.
+<P>
+
+There is always one space between every pair of columns, so if you use fixed
+column widths (like 13) you should remember to take that into account.
+Several of the fields are virtually fixed-width, so it doesn't make
+much sense to specify the width for them. The fields STATUS,
+FULLSTATUS, IMAPSTATUS, MSGNO, the DATE fields, SIZE,
+and DESCRIPSIZE all fall into that category.
+You <EM>may</EM> specify widths for those if you wish, but
+you're probably better off letting the system pick those widths. <P>
+
+<P>
+The default is equivalent to:
+
+<P>
+<CENTER><SAMP>STATUS MSGNO SMARTDATETIME24 FROMORTO(33%) SIZENARROW SUBJKEY(67%)</SAMP></CENTER>
+
+<P>
+This means that the four fields without percentages will be allocated
+first, and then 33% and 67% of the <EM>remaining</EM> space will go to
+the from and subject fields. If one of those two fields is specified
+as a percentage and the other is left for the system to choose, then
+the percentage is taken as an absolute percentage of the screen, not
+of the space remaining after allocating the first four columns. It
+doesn't usually make sense to do it that way. If you leave off all
+the widths, then the subject and from fields (if both are present) are
+allocated space in a 2 to 1 ratio, which is almost exactly the same as
+the default.
+
+<P>
+What you are most likely to do with this configuration option is to
+specify which fields appear at all, which order they appear in, and the
+percentage of screen that is used for the from and subject fields if you
+don't like the 2 to 1 default.
+
+<P>
+If you want to retain the default format that Pine 4.64 had, use
+
+<P>
+<CENTER><SAMP><!--#echo var="VAR_index-format"-->=STATUS MSGNO DATE FROMORTO(33%) SIZE SUBJKEY(67%)</SAMP></CENTER>
+<P>
+<EM>and</EM> set the feature
+<A HREF="h_config_disable_index_locale_dates"><!--#echo var="FEAT_disable-index-locale-dates"--></A>.
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_reply_intro =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_reply-leadin"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_reply-leadin"--></H1>
+
+This option is used to customize the content of the introduction line
+that is included when replying to a message and including the original
+message in the reply.
+The normal default (what you will get if you delete this variable) looks
+something like:
+<P>
+<CENTER><SAMP>On Sat, 24 Oct 1998, Fred Flintstone wrote:</SAMP></CENTER>
+<P>
+where the day of the week is only included if it is available in the
+original message.
+You can replace this default with text of your own.
+The text may contain tokens that are replaced with text
+that depends on the message you are replying to.
+For example, the default is equivalent to:
+<P>
+<CENTER><SAMP>On _DAYDATE_, _FROM_ wrote:</SAMP></CENTER>
+<P>
+
+Since this variable includes regular text mixed with special tokens
+the tokens have to be surrounded by underscore characters.
+For example, to use the token &quot;<SAMP>PREFDATE</SAMP>&quot;
+you would need to use &quot;<SAMP>_PREFDATE_</SAMP>&quot;,
+not &quot;<SAMP>PREFDATE</SAMP>&quot;.
+<P>
+The list of available tokens is
+<A HREF="h_index_tokens">here</A>.
+
+<P>
+By default, the text is all on a single line and is followed by a blank line.
+If your &quot;<!--#echo var="VAR_reply-leadin"-->&quot; turns out to be longer
+than 80 characters when replying to a particular message, it is shortened.
+However, if you use the token
+<P>
+<CENTER><SAMP>_NEWLINE_</SAMP></CENTER>
+<P>
+
+anywhere in the value, no end of line or blank line is appended, and no
+shortening is done.
+The _NEWLINE_ token may be used to get rid of the blank line following
+the text, to add more blank lines, or to form a multi-line
+&quot;<!--#echo var="VAR_reply-leadin"-->&quot;.
+To clarify how _NEWLINE_ works recall that the default value is:
+<P>
+<CENTER><SAMP>On _DAYDATE_, _FROM_ wrote:</SAMP></CENTER>
+<P>
+
+That is equivalent to
+<P>
+<CENTER><SAMP>On _DAYDATE_, _FROM_ wrote:_NEWLINE__NEWLINE_</SAMP></CENTER>
+<P>
+
+In the former case, two newlines are added automatically because
+no _NEWLINE_ token appears in the value of the option (for backwards
+compatibility). In the latter case, the newlines are explicit.
+If you want to remove the blank line that follows the
+&quot;<!--#echo var="VAR_reply-leadin"-->&quot; text use a single
+_NEWLINE_ token like
+<P>
+<CENTER><SAMP>On _DAYDATE_, _FROM_ wrote:_NEWLINE_</SAMP></CENTER>
+<P>
+
+Because of the backwards compatibility problem, it is not possible to
+remove all of the ends of lines, because then there will be no _NEWLINE_ tokens
+and that will cause the automatic adding of two newlines!
+If you want, you may embed newlines in the middle of the text, as well,
+producing a multi-line &quot;<!--#echo var="VAR_reply-leadin"-->&quot;.
+
+<P>
+By default, no attempt is made to localize the date.
+If you prefer a localized form you may find that one of the tokens
+_PREFDATE_ or _PREFDATETIME_ is a satisfactory substitute.
+If you want more control one of the many other date tokens, such as _DATEISO_,
+might be better.
+
+<P>
+For the adventurous, there is a way to conditionally include text based
+on whether or not a token would result in specific replacement text.
+For example, you could include some text based on whether or not
+the _NEWS_ token would result in any newsgroups if it was used.
+It's explained in detail
+<A HREF="h_reply_token_conditionals">here</A>.
+
+<P>
+In the very unlikely event that you want to include a literal token
+in the introduction line you must precede it with a backslash character.
+For example,
+<P>
+<CENTER><SAMP>&#92;_DAYDATE_ = _DAYDATE_</SAMP></CENTER>
+<P>
+would produce something like
+<P>
+<CENTER><SAMP>_DAYDATE_ = Sat, 24 Oct 1998</SAMP></CENTER>
+<P>
+It is not possible to have a literal backslash followed by an expanded token.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_remote_abook_history =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_remote-abook-history"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_remote-abook-history"--></H1>
+
+Sets how many extra copies of
+remote address book
+data will be kept in each remote address book folder.
+The default is three.
+These extra copies are simply old versions of the data. Each time a change
+is made a new copy of the address book data is appended to the folder. Old
+copies are trimmed, if possible, when Alpine exits.
+An old copy can be put back into use by
+deleting and expunging newer versions of the data from the folder.
+Don't delete the first message from the folder. It is a special header
+message for the remote address book and it must be there.
+This is to prevent regular folders from being used as remote address book
+folders and having their data destroyed.
+<P>
+This option is also used to determine how many extra copies of remote
+Alpine configuration files are kept.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_remote_abook_validity =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_remote-abook-validity"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_remote-abook-validity"--></H1>
+
+Sets the minimum number of minutes that a
+remote address book will be considered up to date.
+Whenever an entry contained in a remote address book is used,
+if more than this many minutes have
+passed since the last check the remote server will be queried to see if the
+address book has changed.
+If it has changed, the local copy is updated.
+The default value is five minutes.
+The special value of -1 means never check.
+The special value of zero means only check when the address book is first
+opened.
+<P>
+No matter what the value, the validity check is always done when the
+address book is about to be changed by the user.
+The check can be initiated manually by typing <EM>^L</EM> (Ctrl-L)
+while in the address book maintenance screen for the remote address book.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_user_input_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_user-input-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_user-input-timeout"--></H1>
+
+If this is set to an integer greater than zero, then this is the number
+of <EM>hours</EM> to wait for user input before Alpine times out.
+If Alpine is
+in the midst of composing a message or is waiting for user response to
+a question, then it will not timeout.
+However, if Alpine is sitting idle waiting for
+the user to tell it what to do next and the user does not give any
+input for this many hours, Alpine will exit.
+No expunging or moving of read
+messages will take place.
+It will exit similarly to the way it would exit
+if it received a hangup signal.
+This may be useful for cleaning up unused Alpine sessions that have been
+forgotten by their owners.
+The Alpine developers envision system administrators
+setting this to a value of several hours (24?) so that it won't surprise
+a user who didn't want to be disconnected.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ssh_open_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_ssh-open-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_ssh-open-timeout"--></H1>
+
+Sets the time in seconds that Alpine will
+attempt to open a UNIX secure shell connection.
+The default is 15, the minimum non-zero value is 5,
+and the maximum is unlimited. If this is set to zero ssh connections
+will be completely disabled.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_rsh_open_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_rsh-open-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_rsh-open-timeout"--></H1>
+
+Sets the time in seconds that Alpine will
+attempt to open a UNIX remote shell connection.
+The default is 15, the minimum non-zero value is 5,
+and the maximum is unlimited. If this is set to zero rsh connections
+will be completely disabled.
+This might be useful if rsh connections will never work in your environment
+but are causing delays due to firewalls or some other reason.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tcp_open_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_tcp-open-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_tcp-open-timeout"--></H1>
+
+Sets the time in seconds that Alpine will
+attempt to open a network connection. The default is 30, the minimum is 5,
+and the maximum is system defined (typically 75). If a connection has not
+completed within this many seconds Alpine will give up and consider it a
+failed connection.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tcp_readwarn_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_tcp-read-warning-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_tcp-read-warning-timeout"--></H1>
+
+Sets the time in seconds that Alpine will
+wait for a network read before warning you that things are moving slowly
+and possibly giving you the option to break the connection.
+The default is 15 seconds. The minimum is 5 seconds and the maximumn is
+1000 seconds.
+<P>
+Related option: <A HREF="h_config_tcp_query_timeo"><!--#echo var="VAR_tcp-query-timeout"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tcp_writewarn_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_tcp-write-warning-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_tcp-write-warning-timeout"--></H1>
+
+Sets the time in seconds that Alpine will
+wait for a network write before warning you that things are moving slowly
+and possibly giving you the option to break the connection.
+The default is 0 which means it is unset. If set to a non-zero value, the
+minimum is 5 and the maximum is 1000.
+<P>
+Related option: <A HREF="h_config_tcp_query_timeo"><!--#echo var="VAR_tcp-query-timeout"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tcp_query_timeo =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_tcp-query-timeout"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_tcp-query-timeout"--></H1>
+
+When Alpine times out a network read or write it will normally just display
+a message saying &quot;Still waiting&quot;.
+However, if enough time has elapsed since it started waiting it will offer
+to let you break the connection.
+That amount of time is set by this option, which defaults to 60 seconds,
+has a minimum of 5 seconds, and a maximum of 1000 seconds.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_folders =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-folders"--></H1>
+
+This is a list of one or more folders other than <EM>INBOX</EM> that
+may receive new messages.
+It is related to the
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="FEAT_enable-incoming-folders"-->&quot;</A>
+feature.
+This variable is normally manipulated with the Add, Delete, and Rename
+commands in the FOLDER LIST for the Incoming Message Folders collection.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_folder_spec =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_folder-collections"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_folder-collections"--></H1>
+
+This is a list of one or more collections where saved mail is stored.
+The first collection in this list is the default
+collection for <EM>Save</EM>s,
+including <A HREF="h_config_default_fcc"><!--#echo var="VAR_default-fcc"--></A>.
+<P>
+This variable is normally manipulated using the Setup/collectionList screen.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_news_spec =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_news-collections"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_news-collections"--></H1>
+
+This is a list of collections where news folders are located.
+<P>
+This variable is normally manipulated using the Setup/collectionList screen.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_address_book =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_address-book"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_address-book"--></H1>
+
+A list of personal address books.
+Each entry in the list is an
+optional nickname followed by a pathname or file name relative to the home
+directory.
+The nickname is separated from the rest of the line with whitespace.
+Instead of a local pathname or file name, a remote folder name can be given.
+This causes the address book to
+be a Remote address book.
+<P>
+Use the Setup/AddressBook screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_glob_addrbook =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_global-address-book"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_global-address-book"--></H1>
+
+A list of shared address books. Each entry in the list is an
+optional nickname followed by a pathname or file name relative to the home
+directory.
+A SPACE character separates the nickname from the rest of the line.
+Instead of a local pathname or file name, a remote folder name can be given.
+This causes the address book to
+be a Remote address book.
+Global address books are
+defined to be ReadOnly.
+<P>
+Use the Setup/AddressBook screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_last_vers =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_last-version-used"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_last-version-used"--></H1>
+
+This is set automatically by Alpine.
+It is used to keep track of the last version of Alpine that
+was run by the user.
+Whenever the version number increases, a new version message is printed out.
+This may not be set in the system-wide configuration files.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_printer =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Printer</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Printer</H1>
+
+Your default printer selection.
+<P>
+Use the Setup/Printer screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_cat =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_personal-print-category"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_personal-print-category"--></H1>
+
+This is an internal Alpine variable.
+It will be equal to 1, 2, or 3 depending on whether the default printer is
+attached, standard, or a personal print command.
+<P>
+Use the Setup/Printer screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_command =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_personal-print-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_personal-print-command"--></H1>
+
+List of personal print commands.
+<P>
+Use the Setup/Printer screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_old =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Patterns</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Patterns</H1>
+
+The option Patterns is obsolete in Alpine and in Pine 4.50 and later, replaced by the
+options Patterns-Roles, Patterns-Filters, Patterns-Scores, Patterns-Indexcolors,
+and Patterns-Other.
+Patterns-Scores and Patterns-Filters have been replaced since then by
+Patterns-Scores2 and Patterns-Filters2.
+<P>
+Use the Setup/Rules screens to modify these variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_roles =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_patterns-roles"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_patterns-roles"--></H1>
+
+List of rules used for roles.
+The option Patterns is obsolete in Alpine and in Pine 4.50 and later, replaced by this and
+other options.
+<P>
+Use the Setup/Rules/Roles screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_filts =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_patterns-filters2"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_patterns-filters2"--></H1>
+
+List of rules used for filters.
+<P>
+Use the Setup/Rules/Filters screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_scores =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_patterns-scores2"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_patterns-scores2"--></H1>
+
+List of rules used for scoring.
+<P>
+Use the Setup/Rules/SetScores screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_other =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_patterns-other"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_patterns-other"--></H1>
+
+List of rules used for miscellaneous configuration.
+<P>
+Use the Setup/Rules/Other screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_incols =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: patterns-indexcolors</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: patterns-indexcolors</H1>
+
+List of rules used for coloring lines in the index.
+<P>
+Use the Setup/Rules/Indexcolor screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pat_srch =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: patterns-search</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: patterns-search</H1>
+
+List of rules used only for searching with the Select command in the MESSAGE INDEX.
+<P>
+Use the Setup/Rules/searCh screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_font_name =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Font Name</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Font Name</H1>
+
+PC-Alpine only.
+<P>
+Name of normal font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_font_size =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Font Size</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Font Size</H1>
+
+PC-Alpine only.
+<P>
+Size of normal font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_font_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Font Style</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Font Style</H1>
+
+PC-Alpine only.
+<P>
+Style of normal font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_font_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Font Character Set</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Font Character Set</H1>
+
+PC-Alpine only.
+<P>
+Character set of normal font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_font_name =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Print-Font-Name</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Print-Font-Name</H1>
+
+PC-Alpine only.
+<P>
+Name of printer font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_font_size =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Print-Font-Size</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Print-Font-Size</H1>
+
+PC-Alpine only.
+<P>
+Size of printer font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_font_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Print-Font-Style</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Print-Font-Style</H1>
+
+PC-Alpine only.
+<P>
+Style of printer font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_font_char_set =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Print-Font-Char-Set</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Print-Font-Char-Set</H1>
+
+PC-Alpine only.
+<P>
+Character set of printer font.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_window_position =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Window-Position</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Window-Position</H1>
+
+PC-Alpine only.
+<P>
+Position on the screen of the Alpine window.
+<P>
+Alpine normally maintains this variable itself, and it is set automatically.
+This variable is provided to those who wish to use the same window position
+across different machines from the same configuration.
+<A HREF="h_config_winpos_in_config"><!--#echo var="FEAT_store-window-position-in-config"--></A>
+must also be set for this setting to be used.
+<P>
+The format for this variable is of the form: <CODE>CxR+X+Y</CODE>, where
+C is the number of columns, R is the number of rows, and X and Y specify the
+top left corner of the window.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_cursor_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Cursor Style</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Cursor Style</H1>
+
+PC-Alpine only.
+<P>
+Cursor style.
+<P>
+Use the pulldown Config menu to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ldap_servers =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_ldap-servers"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_ldap-servers"--></H1>
+
+List of LDAP servers and associated data.
+<P>
+Use the Setup/Directory screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sendmail_path =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_sendmail-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_sendmail-path"--></H1>
+
+This names the path to an
+alternative program, and any necessary arguments, to be used in posting
+mail messages. See the Technical notes for more information.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_oper_dir =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_operating-dir"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_operating-dir"--></H1>
+
+This names the root of the
+tree to which you are restricted when reading and writing folders and
+files. It is usually used in the system-wide,
+<EM>fixed</EM> configuration file.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_rshpath =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_rsh-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_rsh-path"--></H1>
+
+Sets the name of the command used to open a UNIX remote shell
+connection. The default is typically <CODE>/usr/ucb/rsh</CODE>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_rshcmd =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_rsh-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_rsh-command"--></H1>
+
+Sets the format of the command used to
+open a UNIX remote shell connection. The default is
+"%s %s -l %s exec /etc/r%sd". All four "%s" entries MUST exist in the
+provided command. The first is for the command's pathname, the second is
+for the host to connnect to, the third is for the user to connect as, and
+the fourth is for the connection method (typically <CODE>imap</CODE>).
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sshpath =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_ssh-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_ssh-path"--></H1>
+
+Sets the name of the command used to open a UNIX secure shell
+connection. The default is typically <CODE>/usr/bin/ssh</CODE>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sshcmd =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_ssh-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_ssh-command"--></H1>
+
+Sets the format of the command used to
+open a UNIX secure shell connection. The default is
+"%s %s -l %s exec /etc/r%sd". All four "%s" entries MUST exist in the
+provided command. The first is for the command's pathname, the second is
+for the host to connnect to, the third is for the user to connect as, and
+the fourth is for the connection method (typically <CODE>imap</CODE>).
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_new_ver_quell =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_new-version-threshold"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_new-version-threshold"--></H1>
+
+When a new version of Alpine is run for the first time it offers a
+special explanatory screen to the user upon startup. This option
+helps control when and if that special screen appears for users that
+have previously run Alpine. It takes as its value an Alpine version
+number. Alpine versions less than the specified value will supress this
+special screen while versions equal to or greater than that specified
+will behave normally.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_drivers =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_disable-these-drivers"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_disable-these-drivers"--></H1>
+
+This variable is a list of mail drivers that will be disabled.
+The candidates for disabling are listed below.
+There may be more in the future if you compile Alpine with
+a newer version of the c-client library.
+<P>
+
+<UL>
+<LI> mbox
+<LI> mbx
+<LI> mh
+<LI> mmdf
+<LI> mtx
+<LI> mx
+<LI> news
+<LI> phile
+<LI> tenex
+<LI> unix
+</UL>
+<P>
+
+The <EM>mbox</EM> driver enables the following behavior: if there is a
+file called <CODE>mbox</CODE>
+in your home directory, and if that file is either empty or in Unix mailbox
+format, then every time you open <EM>INBOX</EM> the <EM>mbox</EM> driver
+will automatically transfer mail from the system mail spool directory into the
+<CODE>mbox</CODE> file and
+delete it from the spool directory. If you disable the <EM>mbox</EM> driver,
+this will not happen.
+<P>
+
+It is not recommended to disable the driver that supports the system default
+mailbox format. On most non-SCO systems, that driver is the
+<EM>unix</EM> driver.
+On most SCO systems, it is the <EM>mmdf</EM> driver.
+The system default driver may be
+configured to something else on your system; check with your system manager
+for additional information.
+<P>
+
+It is most likely not very useful for you to disable any of the drivers other
+than possibly <EM>mbox</EM>.
+You could disable some of the others if you know for
+certain that you don't need them but the performance gain in doing so
+is very modest.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_auths =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_disable-these-authenticators"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_disable-these-authenticators"--></H1>
+
+This variable is a list of SASL (Simple Authentication and Security
+Layer) authenticators that will be disabled.
+SASL is a mechanism for
+authenticating to IMAP, POP3, SMTP, and other network servers.
+<P>
+
+Alpine matches its list of supported authenticators with the server to
+determine the most secure authenticator that is supported by both.
+If no matching authenticators are found, Alpine will revert to plaintext
+login (or, in the case of SMTP, will be unable to authenticate at all).
+<P>
+The candidates for disabling can be found <A HREF="X-Alpine-Config:">here</A>.
+<P>
+
+Normally, you will not disable any authenticators.
+There are two exceptions:
+<P>
+<OL>
+<LI> You use a broken server that advertises an authenticator,
+but does not actually implement it.
+<LI> You have a Kerberos-capable version of Alpine and the server is
+also Kerberos-capable, but you can not obtain Kerberos
+credentials on the server machine, thus you desire to disable
+GSSAPI (which in turn disables Alpine's Kerberos support).
+</OL>
+<P>
+It is never necessary to disable authenticators, since Alpine will try
+other authenticators before giving up.
+However, disabling the relevant authenticator avoids annoying error messages.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_abook_metafile =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_remote-abook-metafile"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_remote-abook-metafile"--></H1>
+
+This is usually set by Alpine and is the name of a file
+that contains data about
+remote address books and remote configuration files.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_composer_wrap_column =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_composer-wrap-column"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_composer-wrap-column"--></H1>
+
+
+This option specifies an aspect of Alpine's Composer. This gives the
+maximum width that auto-wrapped lines will have. It's also the maximum
+width of lines justified using the <A HREF="h_compose_justify">^J
+Justify</A> command. The normal default
+is &quot;74&quot;. The largest allowed setting is normally &quot;80&quot;
+in order to
+prevent very long lines from being sent in outgoing mail. When the mail
+is actually sent, trailing spaces will be stripped off of each line.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_deadlets =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_dead-letter-files"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_dead-letter-files"--></H1>
+
+
+This option affects Alpine's behavior when you cancel a message being
+composed. Alpine's usual behavior is to write the canceled message to
+a file named
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR&quot;,
+<!--chtml else-->
+&quot;dead.letter&quot; in your home directory,
+<!--chtml endif-->
+overwriting any previous message.
+<P>
+If you set this option to a value higher than one, then that many copies
+of dead letter files will be saved.
+For example, if you set this option to &quot;3&quot; then you may have
+files named
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR&quot;,
+&quot;DEADLETR2&quot;, and
+&quot;DEADLETR3&quot;.
+<!--chtml else-->
+&quot;dead.letter&quot;,
+&quot;dead.letter2&quot;, and
+&quot;dead.letter3&quot; in your home directory.
+<!--chtml endif-->
+In this example, the most recently cancelled message will be in
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR&quot;,
+<!--chtml else-->
+&quot;dead.letter&quot;,
+<!--chtml endif-->
+and the third most recently cancelled message will be in
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR3&quot;.
+<!--chtml else-->
+&quot;dead.letter3&quot;.
+<!--chtml endif-->
+The fourth most recently cancelled message will no longer be saved.
+
+<P>
+If you set this option to zero, then NO record of canceled messages is
+maintained.
+<P>
+If the feature
+<A HREF="h_config_quell_dead_letter"><!--#echo var="FEAT_quell-dead-letter-on-cancel"--></A>
+is set, that overrides whatever you set for this option.
+If this option had existed at the time, then the Quell feature would not
+have been added, but it is still there for backwards compatibility.
+So, in order for this option to have the desired effect, make sure the
+Quell feature is turned off.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_maxremstream =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_max-remote-connections"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_max-remote-connections"--></H1>
+
+This option affects low-level behavior of Alpine.
+The default value for this option is <EM>3</EM>.
+If your INBOX is accessed using the IMAP protocol
+from an IMAP server, that connection is kept open throughout the
+duration of your Alpine session, independent of the value of this option.
+The same is true of any
+<A HREF="h_config_permlocked">&quot;<!--#echo var="VAR_stay-open-folders"-->&quot;</A>
+you have defined.
+This option controls Alpine's behavior when connecting to remote IMAP folders
+other than your INBOX or your <!--#echo var="VAR_stay-open-folders"-->.
+It specifies the maximum number of remote IMAP connections (other than
+those mentioned above) that Alpine will use for accessing the rest of your
+folders.
+If you set this option to zero, you will turn off most remote connection
+re-use.
+It's difficult to understand exactly what this option does, and it is usually
+fine to leave it set to its default value.
+It is probably more likely that you will be interested in setting the
+<A HREF="h_config_permlocked">&quot;<!--#echo var="VAR_stay-open-folders"-->&quot;</A> option
+instead of changing the value of this option.
+A slightly longer explanation of what is going on with this option
+is given in the next paragraphs.
+
+<P>
+There are some time costs involved in opening and closing remote IMAP
+folders, the main costs being the time you have to wait for the connection
+to the server and the time for the folder to open.
+Opening a folder may involve not only the time the server takes to do its
+processing but time that Alpine uses to do filtering.
+These times can vary widely.
+They depend on how loaded the server is, how large
+the folder being opened is, and how you set up filtering, among other things.
+Once Alpine has opened a connection to a particular folder, it will attempt
+to keep that connection open in case you use it again.
+In order to do this,
+Alpine will attempt to use the <!--#echo var="VAR_max-remote-connections"--> (the value of
+this option) IMAP connections you have alloted for this purpose.
+<P>
+For example, suppose the value of this option is set to &quot;2&quot;.
+If your INBOX is accessed on a remote server using the IMAP protocol, that
+doesn't count as one of the remote connections but it is always kept open.
+If you then open another IMAP folder, that would be your first
+remote connection counted as one of the <!--#echo var="VAR_max-remote-connections"--> connections.
+If you open a third folder the second will be left open, in case you
+return to it.
+You won't be able to tell it has been left open.
+It will appear to be closed when you leave the folder but the connection
+will remain in the background.
+Now suppose you go back to the second folder (the first folder after the
+INBOX).
+A connection to that folder is still open so you won't have to wait
+for the startup time to open it.
+Meanwhile, the connection to the third folder will be left behind.
+Now, if you open a fourth folder, you will bump into the
+<!--#echo var="VAR_max-remote-connections"--> limit, because this will be the third folder other
+than INBOX and you have the option set to &quot;2&quot;.
+The connection that is being used for
+the third folder will be re-used for this new fourth folder.
+If you go back to the third folder after this, it is no longer already
+connected when you get there.
+You'll still save some time since Alpine will re-use the connection to the
+fourth folder and you have already logged in on that connection,
+but the folder will have to be re-opened from scratch.
+<P>
+If a folder is large and the startup cost is dominated by the time it takes
+to open that folder or to run filters on it, then it will pay to make the
+value of this option large enough to keep it open.
+On the other hand, if you only revisit a handful of folders or if
+the folders are small, then it might
+make more sense to keep this number small so that the reconnect
+time (the time to start up a new connection and authenticate)
+is eliminated instead.
+<P>
+You may also need to consider the impact on the server.
+On the surface, a larger number here may cause a larger impact on the
+server, since you will have more connections open to the server.
+On the other hand, not only will <EM>you</EM> be avoiding the startup costs
+associated with reopening a folder, but the <EM>server</EM> will be
+avoiding those costs as well.
+<P>
+When twenty five minutes pass without any active use of an IMAP connection
+being saved for possible re-use, that connection will be shut down,
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_permlocked =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_stay-open-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_stay-open-folders"--></H1>
+
+This option affects low-level behavior of Alpine.
+There is no default value for this option.
+It is related to the options
+<A HREF="h_config_preopen_stayopens">&quot;<!--#echo var="FEAT_preopen-stayopen-folders"-->&quot;</A>,
+<A HREF="h_config_maxremstream">&quot;<!--#echo var="VAR_max-remote-connections"-->&quot;</A>,
+and <A HREF="h_config_expunge_stayopens">&quot;<!--#echo var="FEAT_offer-expunge-of-stayopen-folders"-->&quot;</A>.
+
+<P>
+Note: changes made to this list take effect the next time you open a
+folder in the list.
+
+<P>
+This is a list of folders that will be permanently kept open once they
+are first opened.
+The names in this list may be either the nickname of an Incoming folder
+or the full technical specification of a folder.
+The folders in this list need not be remote IMAP folders, they could usefully
+be local folders, as well.
+If a folder in the list is a newsgroup or is not accessed either locally
+or via IMAP, then the entry will be ignored.
+For example, folders accessed via NNTP or POP3 will not be kept open, since
+the way that new mail is found with those protocols involves closing and
+reopening the connection.
+<P>
+Once a Stay Open folder has been opened, new-mail checking will continue
+to happen on that folder for the rest of the Alpine session.
+Your INBOX is always implicitly included in this Stay-Open list and doesn't
+need to be added explicitly.
+<P>
+Another difference that you may notice between a Stay Open folder and a
+non-Stay Open folder is which message is selected as the current message
+when you enter the folder index.
+Normally, the starting position for an incoming folder (which most Stay Open
+folders will likely be) is controlled by the
+<A HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A>.
+However, if a folder is a Stay Open folder, when you re-enter the folder
+after the first time the current message will be the same as it was when
+you left the folder.
+An exception is made if you use the TAB command to get to the folder.
+In that case, the message number will be incremented by one from what it
+was when you left the folder.
+<P>
+The above special behavior is thought to be useful.
+However, it is special and different from what you might at first expect.
+The feature
+<A HREF="h_config_use_reg_start_for_stayopen"><!--#echo var="FEAT_use-regular-startup-rule-for-stayopen-folders"--></A>
+may be used to turn off this special treatment.
+<P>
+If the message that was current when you left the folder no longer exists,
+then the regular startup rule will be used instead.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_viewer_overlap =====
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_viewer-overlap"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_viewer-overlap"--></h1>
+
+This option specifies an aspect of Alpine's Message Viewing screen. When
+the space bar is used to page forward in a message, the number of lines
+specified by the &quot;<!--#echo var="VAR_viewer-overlap"-->&quot; variable will be repeated from the
+bottom of the screen. That is, if this was set to two lines, then the
+bottom two lines of the screen would be repeated on the top of the next
+screen. The normal default value is "2".<p>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_scroll_margin =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_scroll-margin"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_scroll-margin"--></H1>
+
+This option controls when Alpine's line-by-line scrolling occurs.
+Typically, when a selected item is at the top or bottom screen edge
+and the UP or DOWN (and Ctrl-P or Ctrl-N) keys are struck, the
+displayed items are scrolled down or up by a single line.
+
+<P>
+This option allows you to tell Alpine the number of lines from the top and
+bottom screen edge that line-by-line paging should occcur. For example,
+setting this value to one (1) will cause Alpine to scroll the display
+vertically when you move to select an item on the display's top or
+bottom edge.
+
+<P>
+By default, this variable is zero, indicating that scrolling happens
+when you move up or down to select an item immediately off the display's
+top or bottom edge.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wordseps =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_composer-word-separators"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_composer-word-separators"--></H1>
+
+This option affects how a &quot;word&quot; is defined in the composer.
+The definition of a word is used when using the Forward Word and Backward
+Word commands in the composer, as well as when using the spell checker.
+Whitespace is always considered a word separator.
+Punctuation (like question marks, periods, commas, and so on) is always
+a word separator if it comes at the end of a word.
+By default, a punctuation character that is in the middle of a word does
+not break up that word as long as the character before and the character
+after it are both alphanumeric.
+If you add a character to this option it will be considered a
+word separator even when it occurs in the middle of an alphanumeric word.
+For example, if you want to skip through each part of an address instead
+of skipping the whole address at once you might want to include &quot;@&quot;
+and &quot;.&quot; in this list.
+If you want the word-skipper to stop on each part of a UNIX filename you
+could add &quot;/&quot; to the list.
+The equal sign and dash are other possibilities you might find helpful.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_reply_indent_string =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_reply-indent-string"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_reply-indent-string"--></H1>
+
+This option specifies an aspect of Alpine's Reply command.
+When a message is replied to and the text of the message is included, the
+included text usually has the string &quot;&gt;&nbsp;&quot; prepended
+to each line indicating it is quoted text.
+(In case you haven't seen this before, &quot;string&quot; is a technical term
+that means chunk of text.)
+
+<P>
+Because of the introduction of <A HREF="h_config_quell_flowed_text">Flowed Text</A>
+in 1999 and its wide-spread adoption since then, you will usually be better off if you
+use one of the standard values,
+&quot;&gt;&nbsp;&quot; or &quot;&gt;&quot;, for this option.
+
+<P>
+This option specifies a different value for that string.
+If you wish to use a string that begins or ends with a space,
+enclose the string in double quotes.
+
+<P>
+Besides simple text, the prepended string can be based
+on the message being replied to.
+The following tokens are substituted for the message's corresponding value:
+
+<DL>
+<DT>_FROM_</DT>
+<DD>This token gets replaced with the message sender's &quot;username&quot;.
+If the name is longer than six characters, only the first six characters are
+used.
+</DD>
+
+<DT>_NICK_</DT>
+<DD>This token gets replaced with the nickname of the message sender's
+address as found in your addressbook.
+If no addressbook entry is found,
+Alpine replaces the characters &quot;_NICK_&quot; with nothing.
+If the nickname is longer than six characters, only the first six characters are
+used.
+</DD>
+
+<DT>_INIT_</DT>
+<DD>This token gets replaced with the initials of the sender of the message.
+</DD>
+
+</DL>
+
+NOTE: When the
+<A HREF="h_config_prefix_editing">&quot;<!--#echo var="FEAT_enable-reply-indent-string-editing"-->&quot;</A>
+feature is enabled, you are given the opportunity to edit the string, whether
+it is the default or one automatically generated using the above tokens.
+<P>
+If you change your <!--#echo var="VAR_reply-indent-string"-->
+so that it is not equal to the default value of &quot;&gt;&nbsp;&quot;, then
+quoted text will not be flowed
+(<A HREF="h_config_quell_flowed_text">Flowed Text</A>)
+when you reply.
+For this reason, we recommend that you leave your <!--#echo var="VAR_reply-indent-string"-->
+set to the default value.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quote_replace_string =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_quote-replace-string"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_quote-replace-string"--></H1>
+
+This option specifies what string to use as a quote when <b>viewing</b> a
+message. The standard way of quoting messages when replying is the string
+&quot;&gt;&nbsp;&quot; (quote space).
+With this variable set, viewing a message will
+replace occurrences of
+&quot;&gt;&nbsp;&quot; and &quot;&gt;&quot; with the replacement string.
+This setting works best when
+<A HREF="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></A>
+or the equivalent setting in your correspondents' mail programs
+is set to the default
+&quot;&gt;&nbsp;&quot;, but it will also work fine with the
+<!--#echo var="VAR_reply-indent-string"--> set to &quot;&gt;&quot;.
+<P>
+By default, this setting will only work on messages that are flowed, which is
+the default way of sending messages for many mail clients including
+Alpine. Enable the feature
+<A HREF="h_config_quote_replace_noflow"><!--#echo var="FEAT_quote-replace-nonflowed"--></A>
+to also have quote-replacement performed on non-flowed messages.
+<P>
+
+Setting this option will replace &quot;&gt;&quot; and
+&quot;&gt;&nbsp;&quot; with the new setting. This string may include trailing
+spaces. To preserve those spaces enclose the full string in double quotes.
+<P>
+No padding to separate the text of the message from the quote string is
+added. This means that if you do not add trailing spaces to the value of
+this variable, text will be displayed right next to the quote string,
+which may be undesirable. This can be avoided by adding a new string
+separated by a space from your selection of quote string replacement. This
+last string will be used for padding. For example, setting this variable to
+<br>&quot;&gt;&quot; &quot; &quot; has the effect of setting
+&quot;&gt;&quot; as the <!--#echo var="VAR_quote-replace-string"-->, with the text padded by
+a space from the last quote string to make it more readable.
+<P>
+One possible setting for this variable could be
+&quot;&nbsp;&nbsp;&nbsp;&nbsp;&quot; (four spaces wrapped in quotes), which
+would have the effect of indenting each level of quoting four spaces and
+removing the &quot;&gt;&quot;'s. Different levels of quoting could be made
+more discernible by setting colors for quoted text.
+<P>
+Replying to or forwarding the viewed message will preserve the original
+formatting of the message, so quote-replacement will not be performed on
+messages that are being composed.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_empty_hdr_msg =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_empty-header-message"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_empty-header-message"--></H1>
+
+When sending, if both the To and Cc fields are empty and you
+are sending the message to a Bcc,
+Alpine will put a special address in the To line. The default value is:
+
+<P>
+<CENTER><SAMP>undisclosed-recipients:&nbsp;;</SAMP></CENTER>
+
+<P>
+The reason for this is to avoid embarrassment caused by some Internet
+mail transfer software that interprets a &quot;missing&quot;
+<SAMP>To:</SAMP> header as an error and replaces it with an
+<SAMP>Apparently-to:</SAMP> header that may contain the addresses you
+entered on the <SAMP>Bcc:</SAMP> line, defeating the purpose of the
+Bcc. You may change the part of this message that comes before the
+&quot;:&nbsp;;&quot; by setting the &quot;<!--#echo var="VAR_empty-header-message"-->&quot;
+variable to something else.
+
+<P>
+The normal default is &quot;undisclosed-recipients&quot;.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_status_msg_delay =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_status-message-delay"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_status-message-delay"--></H1>
+
+This option has evolved over time, causing the possible values to be
+counter-intuitive.
+Read carefully before you set this option.
+First we explain what the option does, then there is a longer discussion
+following that.
+<P>
+If this is set to zero, the default value, it has <EM>no</EM> effect.
+Positive and negative values serve two similar, but different purposes.
+<P>
+If it is set to a positive number, it causes the cursor to move to the
+status line whenever a status message is printed and pause there for this
+many seconds.
+It will probably only be useful if the
+<A HREF="h_config_show_cursor">&quot;<!--#echo var="FEAT_show-cursor"-->&quot;</A> feature is
+also turned on.
+Setting this option to a positive number can only be used to
+<EM>increase</EM> the status message delay.
+This may be useful for Braille displays, or other non-traditional displays.
+<P>
+If it is set to a negative number the interpretation is a bit complicated.
+Negative numbers are used to <EM>decrease</EM> the amount of delay Alpine uses to
+allow you to read important status messages.
+Of course, this may cause you to miss some important messages.
+If you see a message flash by but miss what it says you can use the
+Journal command from the MAIN MENU to read it.
+If you set this option to a negative value, the delay will be
+no more than one second less than the absolute value
+of the value you set.
+So if you set it to -1, the delay will be no more than zero seconds, no
+delay at all.
+If you set it to -2, the delay will be no more than 1 second.
+And so on, -3 is 2 seconds, -4 is 3 seconds, ...
+If the delay that Alpine would have used by default is less than this delay,
+then the smaller delay set by Alpine will be used.
+Setting this option to a negative value can only reduce the amount of
+delay, never increase it.
+<P>
+Here is a more detailed explanation.
+Status messages are the messages that show up spontaneously in the
+status message line, usually the third line from the bottom of the screen.
+By default, Alpine assigns each status message it produces a minimum
+display time.
+Some status messages have a minimum display time of zero.
+You can see an example of such a message by paging up in this help text
+until you reach the top of the screen.
+If you try to page past the top you will see the message
+<P>
+<CENTER><SAMP>[Already at start of help text]</SAMP></CENTER>
+<P>
+in the status line.
+If there is another more important use of the status message line this message
+might be replaced quickly, or it even might not be shown at all.
+However, if there is no reason to get rid of the message, it might stay
+there for several seconds while you read the help.
+An example where it is replaced immediately happens when you page up in
+the help text past the top of the screen, but then type the &quot;WhereIs&quot;
+command right after paging up.
+The message will disappear immediately without causing a delay (unless you
+have set this option to a positive value) to allow you to type input for
+the &quot;WhereIs&quot; command.
+Since it isn't a very important message, Alpine has set its minimum display
+time to zero seconds.
+<P>
+Other messages have minimum display times of three or more seconds.
+These are usually error messages that Alpine thinks you ought to see.
+For example, it might be a message about a failed Save or a failed folder open.
+It is often the case that this minimum display time won't delay you in
+any way because the status message line is not needed for another reason.
+However, there are times when Alpine has to delay what it is doing in
+order to display a status message for the minimum display time.
+This happens when a message is being displayed and Alpine wants to ask
+for input from the keyboard.
+For example, when you Save a message you use the status message line.
+You get a prompt there asking for the name of the folder to save to.
+If there is a status message being displayed that has not
+yet displayed for its minimum
+time Alpine will display that status message surrounded with the characters
+&gt; and &lt; to show you that it is delaying.
+That might happen, for example, if you tried to save to a folder that
+caused an error, then followed that immediately with another Save command.
+You might find yourself waiting for a status message like
+<P>
+<CENTER><SAMP>[&gt;Can't get write access to mailbox, access is readonly&lt;]</SAMP></CENTER>
+<P>
+to finish displaying for three seconds.
+If that is something you find happening to you frequently, you may use
+negative values of this option to decrease or eliminate that delay, at
+the risk of missing the message.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_active_msg_interval =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_busy-cue-rate"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_busy-cue-rate"--></H1>
+
+When Alpine is delayed for some reason it usually shows that
+something is happening with a small animated display in the status
+message line near the bottom of the screen.
+This option sets how frequently the characters (for example, a spinning bar)
+in the active status message lines are updated.
+At most, it can be set to be udpated 20 times per second.
+
+<P>
+Setting this value to zero will prevent display of the animations
+altogether.
+
+<P>
+The option <A HREF="h_config_use_boring_spinner"><!--#echo var="FEAT_busy-cue-spinner-only"--></A>
+can be used to remove the randomness from this animated display.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mailchecknoncurr =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: <!--#echo var="VAR_mail-check-interval-noncurrent"--></TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_mail-check-interval-noncurrent"--></H1>
+
+This option is closely related to the
+<A HREF="h_config_mailcheck">&quot;<!--#echo var="VAR_mail-check-interval"-->&quot;</A>
+option, as well as the
+<A HREF="h_config_quell_checks_comp">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"-->&quot;</A> and
+<A HREF="h_config_quell_checks_comp_inbox">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-inbox"-->&quot;</A> options.
+If the &quot;<!--#echo var="VAR_mail-check-interval"-->&quot; option is set to zero, then automatic
+new-mail checking is disabled and this option will have no effect.
+<P>
+Normally this option is set to zero, which means that the value used will be
+the same as the value for the &quot;<!--#echo var="VAR_mail-check-interval"-->&quot;.
+If you set this option to a value different from zero
+(usually larger than the value for &quot;<!--#echo var="VAR_mail-check-interval"-->&quot;)
+then that is the check interval that will be used
+for folders that are not the currently open folder or the INBOX.
+You may not even have any folders that are noncurrent and not the INBOX.
+If you do, it is likely that they are due to
+<A HREF="h_config_permlocked">&quot;<!--#echo var="VAR_stay-open-folders"-->&quot;</A>
+you have configured.
+This option also affects the rate of mail checking done on cached
+connections to folders you previously had open but are no longer actively
+using.
+You aren't expected to understand that last sentence, but if you are interested
+take a look at
+<A HREF="h_config_maxremstream">&quot;<!--#echo var="VAR_max-remote-connections"-->&quot;</A>
+and the related options.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_fifopath =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: NewMail FIFO Path</TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: NewMail FIFO Path</H1>
+
+This option is only available in UNIX Alpine.
+However, there is a very similar feature built in to PC-Alpine.
+In PC-Alpine's Config menu at the top of the screen
+is an option called &quot;New Mail Window&quot;.
+<P>
+You may have Alpine create a FIFO special file (also called a named pipe) where
+it will send a one-line message each time a new message is received in
+the current folder, the INBOX, or any open
+<A HREF="h_config_permlocked"><!--#echo var="VAR_stay-open-folders"--></A>.
+To protect against two different Alpines both writing to the same FIFO, Alpine
+will only create the FIFO and write to it if it doesn't already exist.
+<P>
+A possible way to use this option would be to have a separate window
+on your screen running the command
+<P>
+<CENTER><SAMP>cat filename</SAMP></CENTER>
+<P>
+where &quot;filename&quot; is the name of the file given for this option.
+Because the file won't exist until after you start Alpine, you must <EM>first</EM>
+start Alpine and <EM>then</EM> run the &quot;cat&quot; command.
+You may be tempted to use &quot;tail -f filename&quot; to view the new
+mail log.
+However, the common implementations of the tail command will not do what you
+are hoping.
+<P>
+The width of the messages produced for the FIFO may be altered with the
+<A HREF="h_config_newmailwidth"><!--#echo var="VAR_newmail-window-width"--></A> option.
+<P>
+On some systems, fifos may only be created in a local filesystem.
+In other words, they may not be in NFS filesystems.
+This requirement is not universal.
+If the system you are using supports it, it should work.
+(It is often the case that your home directory is in an NFS filesystem.
+If that is the case, you might try using a file in the &quot;/tmp&quot;
+filesystem, which is usually a local filesytem.)
+Even when it is possible to use an NFS-mounted filesystem as a place to name
+the fifo (for example, your home directory), it will still be the case that
+the reader (probably the &quot;cat&quot; command) and the
+writer (Alpine) of the fifo must be running on the same system.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_newmailwidth =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: <!--#echo var="VAR_newmail-window-width"--></TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_newmail-window-width"--></H1>
+
+For UNIX Alpine, this option is only useful if you have turned on the
+<A HREF="h_config_fifopath">NewMail FIFO Path</A> option.
+That option causes new mail messages to be sent to a fifo file.
+Those messages will be 80 characters wide by default.
+You can change the width of those messages by changing this option.
+For example, if you are reading those messages in another window you might
+want to set this width to the width of that other window.
+<P>
+If you are using PC-Alpine, it has an option in the Config menu to turn
+on the &quot;New Mail Window&quot;.
+This present option also controls the width of that window.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mailcheck =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: <!--#echo var="VAR_mail-check-interval"--></TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_mail-check-interval"--></H1>
+
+This option specifies, in seconds,
+how often Alpine will check for new mail.
+If set to zero, new-mail checking is disabled.
+(You can always manually force a new-mail check by typing ^L (Ctrl-L), which is also the command to refresh the screen, or by typing the Next command when the
+current message is the last message of the folder.)
+There is a minimum value for this option, normally 15 seconds.
+The default value is normally 150 seconds.
+The higher you set this option, the easier it is on the server.
+<P>
+There are some situations where automatic new-mail checking does not work.
+See the discussion about new-mail checking in
+<A HREF="h_config_reopen_rule">&quot;<!--#echo var="VAR_folder-reopen-rule"-->&quot;</A>.
+<P>
+The new-mail checking will not happen exactly at the frequency that you specify.
+For example, Alpine may elect to defer a non-INBOX mail check if you
+are busy typing.
+Or, it may check more frequently than you have specified if that is
+thought to be necessary to keep the server from closing the connection
+to the folder due to inactivity.
+If Alpine checks for new mail as a side effect of another command, it will reset
+the timer, so that new-mail checking may seem to happen irregularly instead of
+every X seconds like clockwork.
+<P>
+If you are anxious to know about new mail as soon as possible, set the check
+interval low, and you'll know about the new mail by approximately
+that amount of time after it arrives.
+If you aren't so worried about knowing right away, set this option to a
+higher value.
+That will save the server some processing time and may save you some of
+the time you spend waiting for new-mail checks to happen if you are
+dealing with a slow server or slow network connection.
+<P>
+If you suspect that new-mail checking is causing slow downs for you,
+you may want to look into the options
+<A HREF="h_config_quell_checks_comp">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"-->&quot;</A>,
+<A HREF="h_config_quell_checks_comp_inbox">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-inbox"-->&quot;</A> and
+<A HREF="h_config_mailchecknoncurr">&quot;<!--#echo var="VAR_mail-check-interval-noncurrent"-->&quot;</A>,
+which refine when mail checking is done.
+<P>
+If the mailbox being checked uses a <A HREF="h_maildrop">Mail Drop</A> then
+there is a minimum time
+(<A HREF="h_config_maildropcheck">&quot;<!--#echo var="VAR_maildrop-check-minimum"-->&quot;</A>)
+between new-mail checks.
+Because of this minimum you may notice that new mail does not
+appear promptly when you expect it.
+The reason for this is to protect the server from over-zealous opening and
+closing of the Mail Drop folder, since that is a costly operation.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_checks_comp =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"--></H1>
+
+This option is closely related to the
+<A HREF="h_config_mailcheck">&quot;<!--#echo var="VAR_mail-check-interval"-->&quot;</A>
+option, the
+<A HREF="h_config_mailchecknoncurr">&quot;<!--#echo var="VAR_mail-check-interval-noncurrent"-->&quot;</A> option, and
+<A HREF="h_config_quell_checks_comp_inbox">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-inbox"-->&quot;</A>.
+<P>
+If this option is set, then the normal new-mail checking that happens
+while you are composing will not happen for folders other than your
+INBOX (which depends on the setting
+of &quot;<!--#echo var="FEAT_quell-mailchecks-composing-inbox"-->&quot;).
+<P>
+You might want to set this option if you are experiencing delays while
+composing that you think might be related to the speed of the new-mail
+checks.
+<P>
+Even with this option turned on, an occasional new-mail check may be done
+in order to keep the server from killing the connection to the folder.
+For example, IMAP servers may remove a connection to a folder if there
+has been no activity on the connection for 30 minutes or more.
+Instead of letting that happen, Alpine will check for new mail before the
+30 minutes is up even though you have turned on this feature to quell
+those checks.
+<P>
+Besides new-mail checks, checkpoint operations on the folders
+will also be quelled when you set this option.
+The purpose of checkpointing is to write the changes to a folder out to
+disk periodically in order to avoid losing those changes when system or
+software problems occur.
+New-mail checking and checkpointing while you are not composing are not
+affected by this option.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_checks_comp_inbox =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-inbox"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-mailchecks-composing-inbox"--></H1>
+
+This option is closely related to the
+<A HREF="h_config_mailcheck">&quot;<!--#echo var="VAR_mail-check-interval"-->&quot;</A>
+option, the
+<A HREF="h_config_mailchecknoncurr">&quot;<!--#echo var="VAR_mail-check-interval-noncurrent"-->&quot;</A> option, and
+<A HREF="h_config_quell_checks_comp">&quot;<!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"-->&quot;</A>.
+<P>
+If this option is set, then the normal new-mail checking that happens
+while you are composing will not happen for your INBOX.
+Checking of other folders is controlled in a similar way with the
+&quot;<!--#echo var="FEAT_quell-mailchecks-composing-except-inbox"-->&quot; option.
+<P>
+You might want to set this option if you are experiencing delays while
+composing that you think might be related to the speed of the new-mail
+checks.
+<P>
+Even with this option turned on, an occasional new-mail check may be done
+in order to keep the server from killing the connection to the folder.
+For example, IMAP servers may remove a connection to a folder if there
+has been no activity on the connection for 30 minutes or more.
+Instead of letting that happen, Alpine will check for new mail before the
+30 minutes is up even though you have turned on this feature to quell
+those checks.
+<P>
+Besides new-mail checks, checkpoint operations on the INBOX
+will also be quelled when you set this option.
+The purpose of checkpointing is to write the changes to a folder out to
+disk periodically in order to avoid losing those changes when system or
+software problems occur.
+New-mail checking and checkpointing while you are not composing are not
+affected by this option.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_maildropcheck =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: <!--#echo var="VAR_maildrop-check-minimum"--></TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_maildrop-check-minimum"--></H1>
+
+New-mail checking for a
+<A HREF="h_maildrop">Mail Drop</A> is a little different from new
+mail checking for a regular folder.
+One of the differences is that the connection to the Mail Drop is not
+kept open and so the cost of checking
+(delay for you and additional load for the server) may be significant.
+Because of this additional cost we set a minimum time that
+must pass between checks.
+This minimum only applies to the automatic checking done by Alpine.
+If you force a check by typing ^L (Ctrl-L) or by typing the Next command when you are
+at the end of a folder index, then the check is done right away.
+<P>
+This option specifies, in seconds, the <EM>minimum</EM> time between Mail Drop
+new-mail checks.
+You may want to set this minimum high in order to avoid experiencing some
+of the delays associated with the checks.
+Note that the time between checks is still controlled by the regular
+<A HREF="h_config_mailcheck"><!--#echo var="VAR_mail-check-interval"--></A> option.
+When Alpine is about to do an automatic check for new mail (because
+the <!--#echo var="VAR_mail-check-interval"--> has expired) then if the time since the last
+new-mail check
+of any open Mail Drops has been greater than the <!--#echo var="VAR_maildrop-check-minimum"-->,
+the Mail Drop is checked for new mail as well.
+Therefore, it is only useful to set this option to a value that is higher
+than the <!--#echo var="VAR_mail-check-interval"-->.
+<P>
+If this option is set to zero, automatic Mail Drop new-mail
+checking is disabled.
+There is a minimum value, normally 60 seconds.
+The default value is normally 60 seconds as well.
+This applies to the INBOX and to the currently open folder if that is
+different from the INBOX.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_nntprange =====
+<HTML>
+<HEADER>
+<TITLE>OPTION: <!--#echo var="VAR_nntp-range"--></TITLE>
+</HEADER>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_nntp-range"--></H1>
+
+This option applies only to newsgroups accessed using the NNTP protocol.
+It does not, for example,
+apply to newsgroups accessed using an IMAP-to-NNTP proxy.
+
+<P>
+When you open a connection to a News server using the NNTP protocol, you
+normally have access to all of the articles in each newsgroup.
+If a server keeps a large backlog of messages it may speed performance
+some to restrict attention to only the newer messages in a group.
+This option allows you to set how many article numbers should be checked
+when opening a newsgroup.
+You can think of &quot;<!--#echo var="VAR_nntp-range"-->&quot; as specifying the maximum number
+of messages you ever want to see.
+For example, if you only ever wanted to look at the last 500 messages in each
+newsgroup you could set this option to 500.
+In actuality, it isn't quite that.
+Instead, for performance reasons, it specifies the range of article
+numbers to be checked, beginning
+with the highest numbered article and going backwards from there.
+If there are messages that have been canceled or deleted
+their article numbers are still counted as part of the range.
+<P>
+So, more precisely, setting the &quot;<!--#echo var="VAR_nntp-range"-->&quot; will cause article
+numbers
+<P><CENTER>last_article_number - <!--#echo var="VAR_nntp-range"--> + 1 through last_article_number</CENTER>
+<P>
+to be considered when reading a newsgroup.
+The number of messages that show up in your index will be less than or equal
+to the value of &quot;<!--#echo var="VAR_nntp-range"-->&quot;.
+
+<P>
+The purpose of this option is simply to speed up access when reading news.
+The speedup comes because Alpine can ignore all but the last <!--#echo var="VAR_nntp-range"--> article
+numbers, and can avoid downloading any information about the ignored articles.
+There is a cost you pay for this speedup.
+That cost is that there is no way for you to see those ignored articles.
+The articles that come before the range you specify are invisible to you and
+to Alpine, as if they did not exist at all.
+There is no way to see those messages using, for example, an unexclude command
+or something similar.
+The only way to see those articles is to set this option high enough (or
+set it to zero) and then to reopen the newsgroup.
+
+<P>
+If this option is set to 0 (which is also the default),
+then the range is unlimited.
+This option applies globally to all NNTP servers and to all newsgroups
+on those servers.
+There is no way to set different values for different newsgroups or servers.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_news_active =====
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_news-active-file-path"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_news-active-file-path"--></h1>
+
+This option tells Alpine where to look for the "active file" for newsgroups
+when accessing news locally, rather than via NNTP. The default path is
+usually "/usr/lib/news/active".<p>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_news_spool =====
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_news-spool-directory"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_news-spool-directory"--></h1>
+
+This option tells Alpine where to look for the "news spool" for newsgroups
+when accessing news locally, rather than via NNTP. The default path is
+usually "/var/spool/news".<p>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_image_viewer =====
+<html>
+<header>
+<title>OPTION: <!--#echo var="VAR_image-viewer"--></title>
+</header>
+<body>
+<h1>OPTION: <!--#echo var="VAR_image-viewer"--></h1>
+<body>
+This option specifies the program Alpine should call to view MIME
+attachments of type IMAGE (e.g. GIF or TIFF). The Image Viewer setting is
+no longer needed, but remains for backward compatibility. The more
+general method for associating external printing and viewing programs with
+specific MIME data types is to use the system's (or your personal)
+"mailcap" configuration file.<p>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</body>
+</html>
+====== h_config_domain_name =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_use-only-domain-name"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_use-only-domain-name"--></H1>
+
+This option is used only if the
+<A HREF="h_config_user_dom">&quot;<!--#echo var="VAR_user-domain"-->&quot;</A> option is <B>not</B>
+set. If set to &quot;Yes&quot; (and <!--#echo var="VAR_user-domain"--> is not used), then Alpine
+strips the hostname from your return (&quot;From&quot;) address and when
+completing unqualified addresses that you enter into the composer.
+<P>
+If you set this, see also the <A HREF="h_config_quell_local_lookup">
+&quot;<!--#echo var="FEAT_quell-user-lookup-in-passwd-file"-->&quot;</A> feature.
+
+
+<!--chtml if pinemode="os_windows"-->
+<P>This option is not applicable to PC-Alpine.
+<!--chtml else-->
+<P>
+<!--chtml endif-->
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_prune_date =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Last-Time-Prune Question</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Last-Time-Prune Question</H1>
+
+This value records the last time you were asked about deleting old
+sent-mail.
+It is set automatically by Alpine at the beginning of each month.
+In the past, if you wished to suppress the monthly sent-mail
+pruning feature, you could set this to a date in the future.
+This value is relative to the year 1900, so
+to set this, for example, to October 2005, use 105.10.
+<P>
+You can still do that if you wish, or you can use the
+<A HREF="h_config_pruning_rule"><!--#echo var="VAR_pruning-rule"--></A> option, which is probably
+a little more convenient to use.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_goto_default =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_goto-default-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_goto-default-rule"--></H1>
+
+This value affects Alpine's behavior when you use the Goto command.
+Alpine's usual behavior has two parts. If your current folder is
+&quot;Inbox&quot;, Alpine will offer the last open folder as the
+default. If the current folder is other than &quot;Inbox&quot;,
+&quot;Inbox&quot; is offered as the default.
+
+<P>
+The available options include:
+
+<DL>
+
+ <DT>folder-in-first-collection</DT>
+
+ <DD> Alpine will offer the most recently visited folder in the default
+collection found in the &quot;Collection List&quot; screen as the default.
+</DD>
+
+ <DT> inbox-or-folder-in-first-collection</DT>
+
+ <DD> If the current folder is &quot;Inbox&quot;,
+Alpine will offer the most recently visited folder in the
+default collection found in the &quot;Collection List&quot; screen.
+If the current folder is other than &quot;Inbox&quot;,
+&quot;Inbox&quot; is offered as the default.
+</DD>
+
+ <DT> inbox-or-folder-in-recent-collection</DT>
+
+ <DD> This is Alpine's default behavior.
+If the current folder is &quot;Inbox&quot;,
+Alpine will offer the last open
+folder as the default.
+If the current folder is other than &quot;Inbox&quot;,
+&quot;Inbox&quot; is offered as the default.
+</DD>
+
+ <DT> first-collection-with-inbox-default</DT>
+
+ <DD> Instead of offering the most recently visited folder in the default
+collection, the default collection is offered but with &quot;Inbox&quot; as
+the default folder.
+If you type in a folder name it will be in the default collection.
+If you simply accept the default, however, your &quot;Inbox&quot; will be opened.
+</DD>
+
+ <DT> most-recent-folder</DT>
+
+ <DD> The last accepted value simply causes the most recently opened
+folder to be offered as the default regardless of the currently opened
+folder.
+</DD>
+</DL>
+
+<P>
+NOTE: The default while a newsgroup is open remains the same; the last
+open newsgroup.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_lastreply_char =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_threading-lastreply-character"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_threading-lastreply-character"--></H1>
+
+The <!--#echo var="VAR_threading-lastreply-character"--> option has a small effect on the MESSAGE
+INDEX display when using a
+<A HREF="h_config_thread_disp_style"><!--#echo var="VAR_threading-display-style"--></A>
+of &quot;show-thread-structure&quot;, &quot;mutt-like&quot;, or
+&quot;show-structure-in-from&quot;; and sorting by Threads or OrderedSubject.
+The value of this option is a single character.
+This character is used instead of the vertical line character when there are
+no more replies directly to the parent of the current message.
+It can be used to &quot;round-off&quot; the bottom of the vertical line
+by setting it to a character such as a backslash (&#92;) or
+a backquote (&#96;).
+The default value of this option is the backslash character (&#92;).
+This option may not be set to the Empty Value.
+In that case, the default will be used instead.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_indicator_char =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_threading-indicator-character"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_threading-indicator-character"--></H1>
+
+The <!--#echo var="VAR_threading-indicator-character"--> option has a small effect on the MESSAGE
+INDEX display when using a
+<A HREF="h_config_thread_disp_style"><!--#echo var="VAR_threading-display-style"--></A> other
+than &quot;none&quot; and sorting by Threads or OrderedSubject.
+The value of this option is a single character.
+This character is used to indicate that part of a thread (a conversation) is
+hidden beneath a message.
+The message could be expanded
+if desired with the &quot;/&quot; Collapse/Expand command.
+By default, the value of this option is the greater than sign (&gt;).
+<P>
+If this option is set to the Empty Value, then the column (and the following
+blank column) will be deleted from the display.
+
+<P>
+This option is closely related to the
+<A HREF="h_config_thread_exp_char"><!--#echo var="VAR_threading-expanded-character"--></A> option.
+Another similar option that affects the thread display is the
+<A HREF="h_config_thread_lastreply_char"><!--#echo var="VAR_threading-lastreply-character"--></A> option.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_exp_char =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_threading-expanded-character"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_threading-expanded-character"--></H1>
+
+The <!--#echo var="VAR_threading-expanded-character"--> option has a small effect on the MESSAGE
+INDEX display when using a
+<A HREF="h_config_thread_disp_style"><!--#echo var="VAR_threading-display-style"--></A> other
+than &quot;none&quot;.
+The value of this option is a single character.
+This character is used to indicate that part of a thread has been expanded
+and could be collapsed if desired with
+the &quot;/&quot; Collapse/Expand command.
+By default, the value of this option is a dot (.).
+<P>
+If this option is set to the Empty Value, then the column (and the following
+blank column) will be deleted from the display.
+
+<P>
+This option is closely related to the
+<A HREF="h_config_thread_indicator_char"><!--#echo var="VAR_threading-indicator-character"--></A> option.
+Another similar option that affects the thread display is the
+<A HREF="h_config_thread_lastreply_char"><!--#echo var="VAR_threading-lastreply-character"--></A> option.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_index_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_threading-index-style"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_threading-index-style"--></H1>
+
+When a folder is sorted by Threads or OrderedSubject,
+this option will affect the INDEX displays.
+<P>
+
+The possible values for this option are:
+
+<DL>
+<DT>regular-index-with-expanded-threads</DT>
+<DD>This is the default display.
+If the configuration option
+<A HREF="h_config_thread_disp_style">&quot;<!--#echo var="VAR_threading-display-style"-->&quot;</A>
+is set to something other than &quot;none&quot;, then this setting
+will cause Alpine to start off with a MESSAGE INDEX with all of
+the threads expanded.
+That is, each message will have a line in the MESSAGE INDEX display.
+The Collapse/Expand command (/) may be used to manually collapse or
+expand a thread or subthread (see also <A HREF="h_config_slash_coll_entire">&quot;<!--#echo var="FEAT_slash-collapses-entire-thread"-->&quot;</A>).
+<P>
+This setting affects the display when the folder is first threaded.
+The collapsed state may also be re-initialized by re-sorting the folder manually
+using the SortIndex command ($).
+After re-sorting the threads will once again all be expanded, even if you
+have previously collapsed some of them.
+<P>
+If &quot;<!--#echo var="VAR_threading-display-style"-->&quot; is set to &quot;none&quot;, then
+the display will be the regular default Alpine MESSAGE INDEX, but sorted
+in a different order.
+</DD>
+
+<DT>regular-index-with-collapsed-threads</DT>
+<DD>If the configuration option
+<A HREF="h_config_thread_disp_style">&quot;<!--#echo var="VAR_threading-display-style"-->&quot;</A>
+is set to something other than &quot;none&quot;, then this setting
+will cause Alpine to start out with all of the threads collapsed instead of
+starting out with all of the threads expanded.
+The Collapse/Expand command (/) may be used to manually collapse or
+expand a thread or subthread (see also <A HREF="h_config_slash_coll_entire">&quot;<!--#echo var="FEAT_slash-collapses-entire-thread"-->&quot;</A>).
+<P>
+This setting affects the display when the folder is first threaded.
+The collapsed state may also be re-initialized by re-sorting the folder manually
+using the SortIndex command ($).
+After re-sorting the threads will once again all be collapsed, even if you
+have previously expanded some of them.
+</DD>
+
+<DT>separate-index-screen-always</DT>
+<DD>With this setting and the next, you will see an index of threads
+instead of an
+index of messages, provided you have sorted by Threads or OrderedSubject.
+<P>
+The THREAD INDEX contains a '*' in the first column if any message in the thread
+is marked Important.
+If not, it contains a '+' if any message in the thread is to you.
+The second column is blank. The third column contains a 'D' if all of the
+messages in the thread are deleted.
+Otherwise, it contains an 'N' if any of the messages in the thread are New.
+<P>
+When you view a particular thread from the THREAD INDEX you will be
+in the MESSAGE INDEX display
+but the index will only contain messages from the thread you are viewing.
+</DD>
+
+<DT>separate-index-screen-except-for-single-messages</DT>
+<DD>This is very similar to the option above.
+When you are in the THREAD INDEX, one of the available commands
+is &quot;ViewThd&quot;.
+With the setting &quot;separate-index-screen-always&quot; (the option above)
+when you view a particular thread you will be in the
+MESSAGE INDEX display and the index will only contain messages from
+the thread you are viewing.
+If the thread you are viewing consists of a single message, the MESSAGE INDEX
+will be an index with only one message in it.
+If you use this &quot;separate-index-screen-except-for-single-messages&quot;
+setting instead, then that index that contains a single message
+will be skipped and you will go directly from the THREAD INDEX into the
+MESSAGE TEXT screen.
+</DD>
+
+</DL>
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_disp_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_threading-display-style"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_threading-display-style"--></H1>
+
+When a folder is sorted by Threads or OrderedSubject,
+this option will affect the MESSAGE INDEX display.
+By default, Alpine will display the MESSAGE INDEX in the
+&quot;show-thread-structure&quot; style if a folder is sorted
+by Threads or OrderedSubject.
+<P>
+
+The possible values for this option are:
+
+<DL>
+<DT>none</DT>
+<DD>Regular index display.
+The same index line as would be displayed without threading is used.
+The only difference will be in the order of the messages.
+</DD>
+
+<DT>show-thread-structure</DT>
+<DD>Threaded Subjects will be indented and vertical bars and horizontal
+lines will be added to make it easier to see the relationships among
+the messages in a thread (a conversation).
+</DD>
+
+<DT>mutt-like</DT>
+<DD>This is the same as the option above except that the Subject
+is suppressed (is blank) if it matches the previous Subject in the thread.
+The name comes from the email client <A HREF="http://www.mutt.org/">Mutt</A>.
+Here is an example of what a mutt-like index might look like.
+In this example, the first column represents the message number, the
+<A HREF="h_config_thread_index_style"><!--#echo var="VAR_threading-index-style"--></A>
+is set to &quot;regular-index-with-expanded-threads&quot;, and the
+<A HREF="h_config_thread_lastreply_char"><!--#echo var="VAR_threading-lastreply-character"--></A>
+is set to a backslash:
+<PRE>
+&nbsp;&nbsp;&nbsp;1 Some topic
+&nbsp;&nbsp;&nbsp;&nbsp;2 . Subject original message in thread
+&nbsp;&nbsp;&nbsp;&nbsp;3 |-> reply to 2
+&nbsp;&nbsp;&nbsp;&nbsp;4 . |-> another reply to 2
+&nbsp;&nbsp;&nbsp;&nbsp;5 . | &#92;-> reply to 4
+&nbsp;&nbsp;&nbsp;&nbsp;6 . | &#92;-> reply to 5
+&nbsp;&nbsp;&nbsp;&nbsp;7 | &#92;-> reply to 6
+&nbsp;&nbsp;&nbsp;&nbsp;8 |-> another reply to 2
+&nbsp;&nbsp;&nbsp;&nbsp;9 . |->New subject another reply to 2 but with a New subject
+&nbsp;&nbsp;&nbsp;10 | |-> reply to 9
+&nbsp;&nbsp;&nbsp;11 | &#92;-> another reply to 9
+&nbsp;&nbsp;&nbsp;12 | &#92;-> reply to 11
+&nbsp;&nbsp;&nbsp;13 &#92;-> final reply to 2
+&nbsp;&nbsp;&nbsp;14 Next topic
+</PRE>
+</DD>
+
+<DT>indent-subject-1</DT>
+<DD>Threaded Subjects will be indented one space per level of the conversation.
+The bars and lines that show up in the show-thread-structure display will
+not be there with this style.
+</DD>
+
+<DT>indent-subject-2</DT>
+<DD>Same as above but indent two spaces per level instead of one space.
+</DD>
+
+<DT>indent-from-1</DT>
+<DD>Similar to indent-subject-1, except that instead of indenting the
+Subject field one space the From field of a thread will be indented one
+space per level of the conversation.
+</DD>
+
+<DT>indent-from-2</DT>
+<DD>Same as above but indent two spaces per level instead of one space.
+</DD>
+
+<DT>show-structure-in-from</DT>
+<DD>The structure of the thread is illustrated with indenting, vertical bars,
+and horizontal lines just like with the show-thread-structure option, but
+the From field is used to show the relationships instead of the Subject field.
+</DD>
+
+</DL>
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pruning_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_pruning-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_pruning-rule"--></H1>
+
+By default, Alpine will ask at the beginning of each month whether or not
+you want to rename your sent-mail folder to a name like sent-mail-month-year.
+(See the feature <A HREF="h_config_prune_uses_iso"><!--#echo var="FEAT_prune-uses-yyyy-mm"--></A> to
+change the format of the folder to sent-mail-yyyy-mm.)
+It will also ask whether you would like to delete old sent-mail folders.
+If you have defined
+<A HREF="h_config_read_message_folder"><!--#echo var="VAR_read-message-folder"--></A>
+or
+<A HREF="h_config_pruned_folders"><!--#echo var="VAR_pruned-folders"--></A>
+Alpine will also ask about pruning those folders.
+<P>
+
+With this option you may provide an automatic answer to these questions.
+The default value is to ask you what you'd like to do.
+<P>
+
+The six possible values for this option are:
+
+<DL>
+<DT>ask about rename, ask about deleting</DT>
+<DD>This is the default.
+Alpine will ask whether you want to rename the folders and whether you
+want to delete each of the old folders.
+</DD>
+
+<DT>ask about rename, don't delete</DT>
+<DD>Alpine will ask whether you want to rename the folders, but won't
+ask about or delete old folders.
+</DD>
+
+<DT>always rename, ask about deleting</DT>
+<DD>This means you want to always answer yes and have Alpine automatically
+rename the folder if possible.
+You will also be asked about deleting old folders.
+</DD>
+
+<DT>always rename, don't delete</DT>
+<DD>This means you want to always answer yes and have Alpine automatically
+rename the folder if possible.
+There will be no deleting of old folders.
+</DD>
+
+<DT>don't rename, ask about deleting</DT>
+<DD>This means you want to always answer no.
+Alpine will not rename the folder.
+You will be asked about deleting old folders.
+</DD>
+
+<DT>don't rename, don't delete</DT>
+<DD>This means you want to always answer no.
+Alpine will not rename the folder.
+There will be no deleting of old folders, either.
+</DD>
+</DL>
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_reopen_rule =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_folder-reopen-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_folder-reopen-rule"--></H1>
+
+Alpine normally checks for new mail in the currently open folder
+and in the INBOX every few <A HREF="h_config_mailcheck">minutes</A>.
+
+<P>
+There are some situations where automatic new-mail checking does not work.
+For example, if a mail folder is opened using the POP protocol or a newsgroup
+is being read using the NNTP protocol, then new-mail checking is disabled.
+
+<P>
+It may be possible to check for new mail in these cases by reopening the
+folder.
+Alpine does not do this for you automatically, but you may do the commands
+manually to cause this to happen.
+You reopen by going back to the folder list screen from the message
+index screen with the &quot;&lt;&quot; command,
+and then going back into the message index screen with
+the &quot;&gt;&quot; command.
+(Actually, any method you would normally use to open a folder will work the
+same as the &quot;&lt;&quot; followed by &quot;&gt;&quot; method.
+For example, the GoTo Folder command will work, or you may use L to go to the
+Folder List screen and Carriage Return to reopen the folder.)
+
+<P>
+There are some cases where Alpine knows that reopening the folder should
+be useful as a way to discover new mail.
+At the time of this writing, connections made using the POP protocol,
+news reading using the NNTP protocol, local news reading, and local
+ReadOnly folders that are in the traditional UNIX or the MMDF format all
+fall into this category.
+There are other cases where it <EM>may</EM> be a way to discover new mail, but Alpine
+has no way of knowing, so it might also just be an exercise in futility.
+All remote, ReadOnly folders other than those listed just above fall into this
+category.
+The setting of this option together with the type of folder
+controls how Alpine will react to the apparent attempt to reopen a folder.
+
+<P>
+If you don't reopen, then you will just be back in
+the message index with no change.
+You left the index and came back, but the folder remained &quot;open&quot;
+the whole time.
+However, if you do reopen the folder, the folder is closed and then reopened.
+In this case, the current state of the open folder is lost.
+The New status, Important and Answered flags,
+selected state, Zoom state, collapsed or expanded state of threads,
+current message number,
+and any other temporary state is all lost when the reopen happens.
+For POP folders (but not NNTP newsgroups) the Deleted flags are also lost.
+
+<P>
+In the possibilities listed below, the text says &quot;POP/NNTP&quot; in
+several places.
+That really implies the case where Alpine knows it is a good way to discover
+new mail, which is more than just POP and NNTP, but POP and NNTP are
+the cases of most interest.
+This option probably has more possible values than it deserves. They are:
+
+<DL>
+<DT>Always reopen</DT>
+<DD>Alpine will not ask whether you want to reopen but will just do the reopen
+whenever you type a command that implies a reopen, regardless of the
+access method.
+In other words, it is assumed you would always answer Yes if asked
+about reopening.
+</DD>
+
+<DT>Yes for POP/NNTP, Ask about other remote [Yes]</DT>
+<DD>Alpine will assume a Yes answer if the access method is POP or NNTP, but
+will ask you whether to reopen other remote folders,
+with a default answer of Yes.
+</DD>
+
+<DT>Yes for POP/NNTP, Ask about other remote [No]</DT>
+<DD>Alpine will assume a Yes answer if the access method is POP or NNTP, but
+will ask you whether to reopen other remote folders,
+with a default answer of No.
+</DD>
+
+<DT>Yes for POP/NNTP, No for other remote</DT>
+<DD>Alpine will assume a Yes answer if the access method is POP or NNTP, and
+will assume a No answer for all other remote folders.
+</DD>
+
+<DT>Always ask [Yes]</DT>
+<DD>Alpine will not differentiate based on access method.
+It will always ask for all remote folders, with a default answer of Yes.
+</DD>
+
+<DT>Always ask [No]</DT>
+<DD>Alpine will not differentiate based on access method.
+It will always ask for all remote folders, with a default answer of No.
+</DD>
+
+<DT>Ask about POP/NNTP [Yes], No for other remote</DT>
+<DD>Alpine will ask if the access method is POP or NNTP, with a default answer
+of Yes.
+It will never attempt to reopen other remote folders.
+</DD>
+
+<DT>Ask about POP/NNTP [No], No for other remote</DT>
+<DD>This is the default.
+Alpine will ask if the access method is POP or NNTP, with a default answer
+of No.
+It will never attempt to reopen other remote folders.
+</DD>
+
+<DT>Never reopen</DT>
+<DD>Alpine will never attempt to reopen already open folders.
+</DD>
+</DL>
+
+<P>
+Remember, wherever it says POP or NNTP above it really means POP or NNTP or
+any of the other situations where it is likely that reopening is a good way
+to discover new mail.
+
+<P>
+There is an alternative that may be of useful in some situations.
+Instead of manually checking for new mail you can set up a
+<A HREF="h_maildrop">Mail Drop</A>
+and automatically check for new mail.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_inc_startup =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_incoming-startup-rule"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_incoming-startup-rule"--></H1>
+
+This value affects Alpine's behavior when opening the &quot;INBOX&quot; or
+one of the &quot;INCOMING MESSAGE FOLDERS&quot;.
+It determines which message will be the <EM>current message</EM> when
+the folder is first opened.
+The default value is &quot;first-unseen&quot;.
+
+<P>
+The seven possible values for this option are:
+
+<DL>
+<DT>first-unseen</DT>
+<DD>The current message is set to the first
+unseen message that has not been marked deleted, or the last message if
+all of the messages have been seen previously.
+Messages which have not been seen or which have been seen but re-marked
+as New are considered unseen messages.
+See the note at the bottom of this help about newsgroups.
+</DD>
+
+<DT>first-recent</DT>
+<DD>Similar to the default, but rather than starting on the first
+unseen message Alpine starts on the first <EM>recent</EM> message.
+A message is recent if it arrived since the last time the folder was
+open. This value causes the current message to be set to the first
+recent message if there is one, otherwise to the last
+message in the folder.
+</DD>
+
+<DT>first-important</DT>
+<DD>This will result in the current message being set to the first
+message marked Important (but not Deleted).
+If no messages are marked Important, then it will be the last message.
+Messages are marked Important by <EM>you</EM>, not by the sender, using
+the
+<A HREF="h_common_flag">Flag command</A>.
+Or they may be marked Important by an Alpine
+<A HREF="h_mainhelp_filtering">Filter</A>
+that you have set up.
+</DD>
+
+<DT>first-important-or-unseen</DT>
+<DD>This selects the first of the first unseen and the first important
+messages.
+</DD>
+
+<DT>first-important-or-recent</DT>
+<DD>This selects the first of the first recent and the first important
+messages.
+</DD>
+
+<DT>first</DT>
+<DD>Simply starts you on the <EM>first</EM> undeleted message in the folder.
+If all messages are deleted you start on the last message.
+</DD>
+
+<DT>last</DT>
+<DD>Simply starts you on the <EM>last</EM> undeleted message in the folder
+If all messages are deleted you start on the last message.
+</DD>
+</DL>
+
+<P>
+NOTE: For newsgroups in the incoming collection, &quot;first-unseen&quot; and
+&quot;first-recent&quot; are the same and are affected by whether or not the
+feature
+<A HREF="h_config_news_uses_recent">&quot;<!--#echo var="FEAT_news-approximates-new-status"-->&quot;</A>
+is turned on.
+Also, there is no permanent storage in news for an Important flag.
+This means that no messages will be marked Important when a newsgroup is
+first opened.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_browser =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_url-viewers"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_url-viewers"--></H1>
+<!--chtml if pinemode="os_windows"-->
+PC-Alpine users do not need to enter anything here, unless:<UL>
+<LI> they want to override, for use with Alpine, the application defined
+in the Windows operating system for handling URLs; or
+<LI> they are (planning on) using the same configuration file with
+Unix Alpine.
+</UL>
+<P>
+Note that if using a viewer that has a space in its path, you should
+use the DOS name for that directory or file. Example:
+<PRE>
+url-viewer=C:&#92;Progra~1&#92;mozilla&#92;mozilla.exe
+</PRE>
+<HR><P>
+<!--chtml endif-->
+This option affects Alpine's handling of URLs that are found in
+messages you read. Normally, only URLs Alpine can handle directly
+are automatically offered for selection in the &quot;Message
+Text&quot; screen. When one or more applications
+capable of deciphering URLs on their command line are added here, Alpine
+will choose the first available to display URLs it cannot handle directly.
+A viewer's availability is based on its being specified with a <B>full
+directory path</B> and the evaluation of any optionally supplied
+parameters described below.
+
+<P>
+Additionally, to support various connection methods and applications, each
+entry in this list can optionally begin with one or more of
+the following special tokens. The allowed tokens include:
+
+<P>
+<DL>
+<DT>_TEST(<VAR>test-string</VAR>)_</DT>
+<DD>
+The <VAR>test-string</VAR> is a shell command that Alpine will run to
+evaluate a viewer's availability. The command specified by the test
+string is run and if its resulting exit status is non-zero, Alpine will
+not consider the associated viewer for use.
+</DD>
+
+<DT>_SCHEME(<VAR>scheme-list</VAR>)_</DT>
+<DD>
+The <VAR>scheme-list</VAR> is a list of one or more (comma-delimited)
+URL schemes that are to be used with the associated viewer. This is
+the way to configure Alpine to recognize URLs other than the built-in set.
+<P>
+It can also be used to override Alpine's built-in handlers.
+For example, you could specify &quot;news&quot; in the <VAR>scheme-list</VAR>,
+and Alpine would use (provided it passed all other criteria) the associated
+viewer when it encounterd a URL of the form &quot;news:comp.mail.pine&quot;.
+
+</DD>
+</DL>
+
+<P>
+By default, Alpine will simply append a space character followed by the
+selected URL prior to launching the command in your specified SHELL. You can
+optionally specify where in the command the selected URL should appear
+by using the &quot;_URL_&quot; token. All occurrences found in the command
+will be replaced with the selected URL before the command is handed
+to the shell. If such replacement occurs, the default appending of the
+selected URL does not take place.
+
+<P>
+NOTE: If the viewer you specify has any command-line arguments,
+including the &quot;_URL_&quot; token, you will need to add a
+double-quote character before the command path and after the last
+argument (see the &quot;lynx&quot; example below).
+
+<P>
+So, here are some example entries:
+<PRE>
+url-viewers = _TEST(&quot;test -n '$&#123;DISPLAY}'&quot;)_ /usr/local/bin/netscape
+ &quot;/usr/local/bin/lynx _URL_&quot;
+ C:&#92;BIN&#92;NETSCAPE.BAT
+</PRE>
+<P>
+This example shows that for the first viewer in the list to be used
+the environment variable &quot;DISPLAY&quot; must be defined. If it
+is, then the path and file &quot;/usr/local/bin/netscape&quot; must exist.
+If neither condition is met,
+then the path and file &quot;/usr/local/bin/lynx&quot; must exist.
+If it does, then the &quot;_URL_&quot; token is replaced by the selected URL.
+If the path to &quot;lynx&quot; is invalid,
+then the final path and file C:&#92;BIN&#92;NETSCAPE.BAT must exist.
+Note that the last
+entry is a DOS/Windows path. This is one way to support Alpine running
+on more than one architecture with the same configuration file.<P>
+<P>
+<!--chtml if pinemode="os_windows"-->
+<!--chtml else-->
+Note that depending on the type of browser used and the method of
+its invocation (such as whether it will open in a separate window) from
+the MESSAGE TEXT screen, the browser may &quot;supplant&quot;
+the MESSAGE TEXT screen, and you will have to quit the browser to return to
+it (for example, when using Lynx; to exit Lynx, use the &quot;Q&quot; command).
+In other words, launching the browser from Alpine may make Alpine
+&quot;disappear&quot; (although it is still &quot;running&quot;)
+until you close the browser again.<P>
+<UL><LI><A HREF="h_config_browser_xterm">Defining <!--#echo var="VAR_url-viewers"--> in an X windows
+environment: for advanced users and systems administrators</A>
+</UL>
+<!--chtml endif-->
+<P>If you are unsure what browsers are available on your system or how to
+specify them in Alpine's <!--#echo var="VAR_url-viewers"--> option for best usability, contact your
+local computing support staff.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_browser_xterm =====
+<HTML>
+<HEAD>
+<TITLE><!--#echo var="VAR_url-viewers"--> and X windows applications</TITLE>
+</HEAD>
+<BODY>
+<H1>Defining <!--#echo var="VAR_url-viewers"--> in an X windows
+environment: for advanced users and systems administrators</H1>
+If you are using Alpine with an X-terminal (emulator) and want to define an
+X windows-based application in <!--#echo var="VAR_url-viewers"-->,
+you may want to do so in a manner that causes any <B>already</B>
+invoked viewer application to be used for viewing URLs you select from Alpine
+messages, and a <B>new</B> URL-viewer process to be
+started <B>only</B> if the same application has <B>not already</B>
+been launched -- for one reason, to avoid file-locking contentions among
+multiple invocations of the same URL-viewer application.
+(The example entries set in the help screen for the &quot;<!--#echo var="VAR_url-viewers"-->&quot;
+option does not do this.) A method of doing that would be:<OL>
+<LI> use
+the _TEST(<VAR>test-string</VAR>)_ token in the <B>first</B> entry to
+check (using commands appropriate for your Unix shell
+in place of <VAR>test-string</VAR>) for the presence of a
+lockfile created by the URL-viewer application -- which implies that the
+application is already running, though this is not foolproof.
+Following that in the same <!--#echo var="VAR_url-viewers"--> entry, specify the
+application with its appropriate command line option(s) to
+show the URL selected from the Alpine message in an already open window of
+that application, or perhaps in a new window of that application.
+
+<LI> In the
+<B>second</B> entry for the <!--#echo var="VAR_url-viewers"--> option, specify the same
+application without those command line options, but this time using the
+_TEST(...)_ token to check whether the environment variable &quot;DISPLAY&quot;
+is defined.
+<LI> If you will be using Alpine (with the same .pinerc file) outside of the X
+windows environment (for instance, using VT-100 terminal emulation), you
+may wish to specify a non-X windows URL-viewer application such as Lynx
+as the last entry.
+</OL><BR>
+How exactly you define your <!--#echo var="VAR_url-viewers"--> entries to do this will depend on
+the command shell, the URL-viewer application(s), and possibly the specific
+version of the latter, you are using.
+<P>
+Relevant command
+line options for the Netscape browser for showing URLs (selected from Alpine)
+when Netscape is already running are discussed in the document
+&quot;Remote Control of UNIX Netscape&quot;
+found at the URL (as of 12 Aug. 1998):
+<P>
+
+<CENTER><A HREF="http://home.netscape.com/newsref/std/x-remote.html">http://home.netscape.com/newsref/std/x-remote.html</A></CENTER>
+
+<P>(If the URL-viewer application is
+<B>not</B> running on the same host as Alpine, but being launched from an
+applications server, you may not be able to use the command line options for
+using an existing invocation of the application in Alpine's <!--#echo var="VAR_url-viewers"--> entry.)
+<P>
+<!--chtml if this-method="shown-to-work"-->
+An example using the Korn shell and the Netscape browser (first entry wrapped
+because of its length, but should all appear on one line):
+<P>
+url-viewers = _TEST("test -L /myhomedir/.netscape/lock")_ &quot;/usr/local/bin/netscape -remote 'openURL(_URL_, new-window)' &amp;&quot;<BR>
+
+_TEST(&quot;test -n '$&#123;DISPLAY}'&quot;)_ &quot;/usr/local/bin/netscape &amp;&quot;<BR>
+ &quot;/usr/local/bin/lynx '_URL_'&quot;
+<P>
+<!--chtml endif-->
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_full_hdr =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-full-header-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-full-header-cmd"--></H1>
+
+This feature enables the &quot;H Full Headers&quot; command which toggles between
+the display of all headers in the message and the normal edited view of
+headers. The Full Header command also controls which headers are included
+for Export, Pipe, Print, Forward, and Reply functions. (For Reply, the
+Full Header mode will respect the
+<A HREF="h_config_include_header">&quot;Include-Headers-in-Reply&quot;</A>
+feature setting.)
+<P>
+If Full Header mode is turned on and you Forward a message, you will
+be asked if you'd like to forward the message as an attachment, as opposed
+to including the text of the message in the body of your new message.
+<P>
+If you have also turned on the
+<A HREF="h_config_quote_suppression">&quot;Quote Suppression&quot;</A>
+option then the Full Headers command actually rotates through three states
+instead of just two.
+The first is the normal view with long quotes suppressed.
+The second is the normal view but with the long quotes included.
+The last enables the display of all headers in the message.
+When using Export, Pipe, Print, Forward, or Reply the quotes are
+never suppressed, so the first two states are identical.
+<P>
+Normally, the Header Mode will reset
+to the default behavior when moving to a new message.
+The mode can be made to persist from message to message by setting the feature
+<A HREF="h_config_quell_full_hdr_reset"><!--#echo var="FEAT_quell-full-header-auto-reset"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_full_hdr_and_text =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-full-header-and-text"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-full-header-and-text"--></H1>
+
+This feature affects how the &quot;H Full Headers&quot; command displays
+message text. If set, the raw message text will be displayed. This
+especially affects MIME formatted email, where the entire MIME format
+will be displayed. This feature similarly affects how messages are
+included for the Export, Pipe, Print, Forward, and Reply functions.
+<P>
+When viewing a raw message that has attachments with this feature set,
+you will not be able to view attachments without first leaving full
+headers mode. This is because MIME parsing is not done on the raw message.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_pipe =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-unix-pipe-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-unix-pipe-cmd"--></H1>
+
+This feature enables the "| Pipe" command that sends the current message
+to the specified command for external processing.
+<P>
+
+A short description of how the pipe command works is given
+<A HREF="h_pipe_command">here</A>.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_full_hdr_reset =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-full-header-auto-reset"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-full-header-auto-reset"--></H1>
+
+The <A HREF="h_common_hdrmode">HdrMode Command</A>
+normally resets to the default state when switching to a new message.
+For example, if you've used the &quot;H&quot; command to turn on Full
+Headers for a message you are viewing, and then you type the Next command
+to look at the next message, the full headers will no longer be shown.
+Setting this feature disables that reset.
+Instead, the Header Mode remains the same from message to message.
+
+<P>
+The presence or absence of the HdrMode command is determined by the
+<A HREF="h_config_enable_full_hdr">&quot;<!--#echo var="FEAT_enable-full-header-cmd"-->&quot;</A>
+Feature-List option.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_tab_complete =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-tab-completion"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-tab-completion"--></H1>
+
+This feature enables the TAB key when at a prompt for a filename. In this
+case, TAB will cause the partial name already entered to be automatically
+completed, provided the partial name is unambiguous.
+This feature is on by default.
+<P>
+Similarly, this feature also enables TAB completion of address book
+nicknames when at a prompt for a nickname,
+or when typing in an address field in the composer.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quit_wo_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quit-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quit-without-confirm"--></H1>
+
+This feature controls whether or not Alpine will ask for confirmation when a
+Quit command is received.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quote_replace_noflow =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quote-replace-nonflowed"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quote-replace-nonflowed"--></H1>
+
+This feature, which is only active when
+<A HREF="h_config_quote_replace_string"><!--#echo var="VAR_quote-replace-string"--></A> is
+also set,
+enables quote-replacement on non-flowed messages. It is off
+by default because a non-flowed message is more dependent on its format,
+and thus quote-replacement may cause less-than-pleasing results.
+Setting this feature will cause quote-replacement similar to that of flowed
+messages, but with the added possibility of long lines being wrapped
+into new lines if the Quote-Replacement-String is longer than the string
+it is replacing, which is &quot;&gt;&nbsp;&quot;.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_jump =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-jump-shortcut"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-jump-shortcut"--></H1>
+
+When this feature is set you may enter a number (followed by RETURN)
+and jump to that message number, when in the MESSAGE INDEX or MESSAGE TEXT
+screens. In other words, it obviates the need for typing the "J" for the
+Jump command.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_alt_ed =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-alternate-editor-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-alternate-editor-cmd"--></H1>
+
+If this feature is set (the default), and the
+<A HREF="h_config_editor">&quot;<!--#echo var="VAR_editor"-->&quot;</A> option
+<B>is not</B> set, entering
+the ^_ (Ctrl-underscore) key while composing a message will prompt you
+for the name of the editor you would like to use.
+<P>
+If the environment variable $EDITOR is set, its value will be offered as
+a default.
+<P>
+If the <A HREF="h_config_editor">&quot;<!--#echo var="VAR_editor"-->&quot;</A> option
+<B>is</B> set, the ^_ key will activate the specified
+editor without prompting, in which case it is not necessary to
+set the &quot;<!--#echo var="FEAT_enable-alternate-editor-cmd"-->&quot; feature.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_alt_ed_now =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-alternate-editor-implicitly"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-alternate-editor-implicitly"--></H1>
+
+If this feature and the <A HREF="h_config_editor">&quot;<!--#echo var="VAR_editor"-->&quot;</A>
+variable are both set, Alpine will
+automatically activate the specified editor when the cursor is moved from
+the header of the message being composed into the message text. For
+replies, the alternate editor will be activated immediately. If this
+feature is set but the &quot;<!--#echo var="VAR_editor"-->&quot; variable is not set, then Alpine will
+automatically ask for the name of an alternate editor when the cursor
+is moved out of the header being composed, or if a reply is being done.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_bounce =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-bounce-cmd"--></TITLE>
+</HEAD>
+<H1>FEATURE: <!--#echo var="FEAT_enable-bounce-cmd"--></H1>
+<BODY>
+
+Setting this feature enables the "B Bounce" command, which will prompt
+for an address and *remail* the message to the new recipient. This command
+is used to re-direct messages that you have received in error, or need to
+be redirected for some other reason (e.g. list moderation). The final
+recipient will see a header indicating that you have Resent the msg, but
+the message's From: header will show the original author of the message,
+and replies to it will go back to that author, and not to you.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_agg_ops =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-aggregate-command-set"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-aggregate-command-set"--></H1>
+
+When this feature is set you may use the commands and subcommands that relate to
+performing operations on more than one message at a time. We call these
+&quot;aggregate operations&quot;. In particular, the
+<!--chtml if pinemode="function_key"-->&quot;F5
+<!--chtml else-->&quot;;
+<!--chtml endif--> Select&quot;,
+
+<!--chtml if pinemode="function_key"-->
+&quot;F6
+<!--chtml else-->
+&quot;A
+<!--chtml endif-->
+Apply&quot;, and
+<!--chtml if pinemode="function_key"-->
+&quot;F4
+<!--chtml else-->
+&quot;Z
+<!--chtml endif-->
+Zoom&quot; commands are enabled by this feature. Select is used to
+&quot;tag&quot; one or more messages meeting the specified criteria. Apply can
+then be used to apply any message command to all of the selected/tagged
+messages. Further, the Zoom command allows you to toggle the MESSAGE INDEX
+view between just those Selected and all messages in the folder.
+<P>
+This feature also enables the
+<!--chtml if pinemode="function_key"-->
+&quot;F7&quot;
+<!--chtml else-->
+&quot;^X&quot;
+<!--chtml endif-->
+
+subcommand in the MESSAGE INDEX
+WhereIs command that causes all messages matching the WhereIs argument to
+become selected; and the Select, Select Current, and ZoomMode commands in the
+<A HREF="h_folder_maint">FOLDER LIST screen</A>.
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_auto_unzoom"><!--#echo var="FEAT_auto-unzoom-after-apply"--></A>,
+<LI> <A HREF="h_config_auto_unselect"><!--#echo var="FEAT_auto-unselect-after-apply"--></A>.
+<LI> <A HREF="h_config_auto_zoom"><!--#echo var="FEAT_auto-zoom-after-select"--></A>, and
+<LI> <A HREF="h_config_select_wo_confirm"><!--#echo var="FEAT_select-without-confirm"--></A>.
+</UL>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+
+====== h_config_enable_flag =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-flag-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-flag-cmd"--></H1>
+
+Setting this feature enables the
+<A HREF="h_common_flag">&quot;* Flag&quot;</A>
+command that allows you to
+manipulate the status flags associated with a message. By default, Flag
+will set the "Important" flag, which results in an asterisk being
+displayed in column one of the MESSAGE INDEX for such messages.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_flag_screen_default =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-flag-screen-implicitly"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-flag-screen-implicitly"--></H1>
+
+The feature modifies the behavior of the
+<a href="h_common_flag">Flag</a>
+command (provided it too is
+<A HREF="h_config_enable_flag">enabled</A>).
+By default, when the "* Flag" command is selected,
+Alpine offers a prompt to set one of several flags and also offers the
+option of entering the detailed flag manipulation screen via the "^T"
+key. Enabling this feature causes Alpine to immediately enter the detailed
+flag screen rather than first offer the simple prompt.
+The
+<A HREF="h_config_flag_screen_kw_shortcut"><!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"--></A> option offers a slightly different way of setting keywords.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_flag_screen_kw_shortcut =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-flag-screen-keyword-shortcut"--></H1>
+
+This feature modifies the behavior of the
+<a href="h_common_flag">Flag</a> command
+and the <A HREF="h_index_cmd_select">Select</A> command.
+This feature is set by default.
+When this feature is not set, when the "* Flag" command is selected,
+Alpine offers a prompt to set one of several flags and also offers the
+option of entering the detailed flag manipulation screen via the "^T"
+key.
+If you have
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>
+defined, then enabling this feature adds a shortcut way to set or unset
+keywords.
+You use &quot;*&quot; followed by the first letter of a keyword (or the nickname of
+a keyword if you've given it a nickname) and that will set the keyword.
+<P>
+An example is easier to understand than the explanation.
+The flag command can always be used to set the system flags.
+For example, to set the Answered flag you would type
+<P>
+<CENTER><SAMP>* A</SAMP></CENTER>
+<P>
+Now suppose you have defined a keyword &quot;Work&quot; using the <!--#echo var="VAR_keywords"-->
+option in the Config screen.
+By default, to set a keyword like &quot;Work&quot; you would usually
+have to go to the Flag Details screen using
+the &quot;^T To Flag Details&quot; command.
+Instead, if you have enabled this feature, you may type
+<P>
+<CENTER><SAMP>* W</SAMP></CENTER>
+<P>
+to set the Work flag, or
+<P>
+<CENTER><SAMP>* ! W</SAMP></CENTER>
+<P>
+to unset it.
+Just like for the other flag setting commands, the case of the letter does
+not matter, so &quot;w&quot; or &quot;W&quot; both set the &quot;Work&quot;
+keyword.
+<P>
+Notice that you can only use this trick for one keyword that begins
+with &quot;W&quot;.
+If you happen to have a &quot;Work&quot; keyword and another keyword that is
+&quot;WIFI&quot; the &quot;* W&quot; command will set the first one in
+your list of keywords.
+Also, there are five letters that are reserved for system
+flags and the NOT command.
+If you type &quot;* A&quot; it will always set the Answered flag, not
+your &quot;Aardvark&quot; keyword.
+In order to set the &quot;Aardvark&quot; keyword you'll still have to use
+the Flag Details screen.
+<P>
+Because enabling the
+<A HREF="h_config_flag_screen_default"><!--#echo var="FEAT_enable-flag-screen-implicitly"--></A>
+option causes Alpine to skip directly to the Flag Details screen when the
+Flag command is used,
+setting it will cause this feature to have no effect at all.
+<P>
+Similarly, when Selecting by Keyword, setting this option will allow you
+to use Keyword initials instead of full keywords.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_can_suspend =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-suspend"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-suspend"--></H1>
+
+Setting this feature will allow you to type ^Z (Control Z) to
+<!--chtml if pinemode="os_windows"-->
+minimize Alpine into its icon, bringing into focus whatever
+application is running behind the PC-Alpine window.
+<!--chtml else-->
+temporarily suspend Alpine.
+
+<P>
+This does not exit Alpine, but puts it in the background to watch
+for new mail and such. Normally, you type a command, such
+as &quot;fg&quot; at your system prompt to return to your Alpine session.
+
+<P>
+The <A HREF="h_config_suspend_spawns"><!--#echo var="FEAT_use-subshell-for-suspend"--></A> feature
+adjusts whether Alpine is placed into the background of the shell its
+running in or starts a news shell.
+<!--chtml endif-->
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_take_lastfirst ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-take-last-comma-first"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-take-last-comma-first"--></H1>
+
+Normally, when TakeAddr is used to copy an address from a message into
+an address book entry, Alpine will attempt to rewrite the full name of the
+address in the form
+<P>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Last, First<P>
+
+instead of<P>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;First Last
+
+<P>
+It does this because many people find it useful to sort by Last name
+instead of First name. If this feature is set, then the TakeAddr command
+will not attempt to reverse the name in this manner.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_disable_regex ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-regular-expression-matching-for-alternate-addresses"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-regular-expression-matching-for-alternate-addresses"--></H1>
+
+Normally, the
+<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
+option is interpreted as a regular expression.
+One type of address that might cause trouble is an address that
+contains a plus sign.
+If you want to have an address with a plus as one of your
+<!--#echo var="VAR_alt-addresses"-->
+and you don't want to use regular expressions, then setting this
+feature will cause Alpine to treat the addresses you list literally instead.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_take_fullname ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-take-fullname-in-addresses"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-take-fullname-in-addresses"--></H1>
+
+Normally, when TakeAddr is used to copy an address or addresses
+from a message into an address book entry, Alpine will try to preserve
+the full name associated with each address in the list of addresses.
+The reason for this is so that if the entry is a list or later becomes a
+list, then information about the individual addresses in the list
+is preserved.
+If you would rather just have the simple addresses in the list of addresses,
+set this feature. For example, with the default setting you might
+see something like this in the ADDRESS BOOK editor after you type TakeAddr
+<P>
+<PRE>
+ Nickname : nick
+ Fullname : Bedrock Elders
+ Fcc :
+ Comment :
+ Addresses : Fred Flintstone &lt;flint@bedrock.org&gt;,
+ Barney Rubble &lt;rubble@bedrock.org&gt;
+</PRE>
+<P>
+but with this feature set it would look like
+<P>
+<PRE>
+ Nickname : nick
+ Fullname : Bedrock Elders
+ Fcc :
+ Comment :
+ Addresses : flint@bedrock.org,
+ rubble@bedrock.org
+</PRE>
+<P>
+instead. Note the difference in the Addresses field.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_print_from ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_print-includes-from-line"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_print-includes-from-line"--></H1>
+
+If this feature is set, then the Berkeley-mail style From line is included
+at the start of each message that is printed. This line looks something
+like the following, with the address replaced by the address from the
+From line of the message being printed:
+<P>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;From user@domain.somewhere.com Mon May 13
+14:11:06 1998
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expanded_distlists ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expanded-view-of-distribution-lists"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expanded-view-of-distribution-lists"--></H1>
+If this feature is set, then distribution lists in the address book
+screen will always be expanded automatically.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_compose_news_wo_conf ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-sets-newsgroup-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-sets-newsgroup-without-confirm"--></H1>
+This feature controls one aspect of Alpine's Composer. If you enter the
+composer while reading a newsgroup, you will normally be prompted to
+determine whether you intend the new message to be posted to the current
+newsgroup or not. If this feature is set, Alpine will not prompt you
+in this situation, and will assume that you do indeed wish to post
+to the newsgroup you are reading.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_compose_rejects_unqual ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-rejects-unqualified-addrs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-rejects-unqualified-addrs"--></H1>
+
+This feature controls one aspect of the message composer; in particular,
+what happens when an unqualified name is entered into an address header.
+If set, unqualified names entered as addresses will be treated as errors
+unless they match an addressbook nickname. Alpine will not attempt to turn
+them into complete addresses by adding your local domain.<P>
+
+A complete (fully qualified) address is one containing a username followed
+by an &quot;@&quot; (&quot;at&quot;) symbol, followed by a domain name (e.g.
+&quot;jsmith@example.com&quot;). An unqualified name is one <B>without</B>
+the &quot;@&quot; symbol and domain name (e.g. &quot;jsmith&quot;).
+
+(See also <A HREF="h_address_format">Explanation of Address formats</A>.)
+
+<P>
+
+When you enter a fully qualified address, Alpine does not interpret or
+modify it, but simply passes it on to the mail-transport-agent (MTA) for
+your system. Alpine conforms to the Internet standards governing message
+headers and will not send an unqualifed name to the MTA. Therefore, when
+you enter an unqualified name, Alpine will normally attempt to turn it into
+a fully qualified address, first by checking to see if you have entered a
+matching nickname in your addressbook, or failing that, by simply adding
+your own domain to the name entered. So if your address is
+&quot;jsmith@example.com&quot; and you enter &quot;fred&quot;, then (assuming
+&quot;fred&quot; is not a nickname in your addressbook), Alpine will turn
+that into &quot;fred@example.com&quot;.<P>
+
+There are situations where it is not desirable for Alpine to interpret such
+unqualified names as valid (local) addresses. For example, if &quot;fred&quot;
+turned out to be a typo (intended to be an addressbook nickname), but
+there actually was a &quot;fred&quot; in your local domain, the message might
+be mis-delivered without your realizing it. In order to reduce the likelihood
+of such accidents, setting this feature will cause Alpine to treat such
+addresses as errors, and require that you explicitly enter the full local
+address (e.g. &quot;fred@example.com&quot;) or correct the name so that it
+matches an address book nickname.<P>
+
+Consider this a safety feature against mis-directed mail.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_local_lookup ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-user-lookup-in-passwd-file"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-user-lookup-in-passwd-file"--></H1>
+
+This feature controls an aspect of Alpine's Composer, and if needed, will
+usually be set by your system manager in Alpine's system-wide configuration
+file. Specifically, if this feature is set, Alpine will not attempt to look
+in the system password file to find a Full Name for the entered address.
+<P>
+Normally, names you enter into address fields (e.g. To: or Cc:) are
+checked against your address book(s) to see if they match an address book
+nickname. Failing that, (in Unix Alpine) the name is then checked against
+the Unix password file. If the entered name matches a username in the
+system password file, Alpine extracts the corresponding Full Name information
+for that individual, and adds that to the address being entered.
+<P>
+However, password file matching can have surprising (incorrect) results if
+other users of the system do not receive mail at the domain you are using.
+That is, if either the
+<A HREF="h_config_user_dom">&quot;<!--#echo var="VAR_user-domain"-->&quot;</A> or
+<A HREF="h_config_domain_name">&quot;<!--#echo var="VAR_use-only-domain-name"-->&quot;</A>
+option
+is set such that the administrative domain of other users on the system
+isn't accurately reflected, Alpine should be told that a passwd file match
+is coincidental, and Full Name info will be incorrect. For example, a
+personal name from the password file could get falsely paired with the
+entered name as it is turned into an address in the configured domain.
+<P>
+If you are seeing this behavior, enabling this feature will prevent Unix
+Alpine from looking up names in the password file to find the Full Name
+for incomplete addresses you enter.<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tab_checks_recent ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_tab-checks-recent"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_tab-checks-recent"--></H1>
+
+In a FOLDER LIST screen, the TAB key usually just changes which
+folder is highlighted.
+If this feature is set, then the TAB key will cause the number of
+recent messages and the total number of messages in the highlighted folder
+to be displayed instead.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_maildrops_preserve_state ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_maildrops-preserve-state"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_maildrops-preserve-state"--></H1>
+
+This feature affects the way <A HREF="h_maildrop">Mail Drops</A> work.
+Normally, when mail is moved from a Mail Drop folder to a destination
+folder, it is delivered as new mail.
+Any Seen/New, Answered, Important/Flagged state that has changed will be
+ignored.
+All of the mail will be considered unSeen, unAnswered, and unImportant after
+it is moved.
+<P>
+If this feature is set, then the state changes that have been made
+to the messages in the Mail Drop folder will be preserved.
+<P>
+In any case, messages that are already marked Deleted when the
+mail is to be moved from the Mail Drop will be ignored.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_preopen_stayopens ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_preopen-stayopen-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_preopen-stayopen-folders"--></H1>
+
+This feature is related to the option
+<A HREF="h_config_permlocked">&quot;<!--#echo var="VAR_stay-open-folders"-->&quot;</A>.
+Normally, Stay Open folders are only opened on demand, when the user
+asks to open them.
+From then on they are kept open for the duration of the session.
+However, if this feature is set, then the Stay Open folders will all be
+opened at startup, at the same time that the INBOX is opened.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expunge_inbox ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_offer-expunge-of-inbox"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_offer-expunge-of-inbox"--></H1>
+
+The INBOX is normally treated differently from regular folders in several
+ways.
+One of the differences is that the normal &quot;close&quot; sequence of
+events is deferred until Alpine is exited, instead of happening when you
+leave the INBOX to view another folder.
+The &quot;close&quot; sequence normally includes the Expunging
+of deleted messages
+(either automatically or after a prompt, controlled by the features
+<A HREF="h_config_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm"-->&quot;</A>,
+<A HREF="h_config_full_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm-everywhere"-->&quot;</A>, and
+<A HREF="h_config_expunge_manually"><!--#echo var="FEAT_expunge-only-manually"--></A>), and the
+handling of the
+<A HREF="h_config_read_message_folder"><!--#echo var="VAR_read-message-folder"--></A>.
+
+<P>
+If this feature is set the &quot;close&quot; sequence handling will take
+place every time you leave the INBOX.
+The INBOX will still be kept open, but the offer to Expunge and the archiving
+to the <!--#echo var="VAR_read-message-folder"-->
+will take place each time you leave the INBOX instead of only once at the
+end of the session.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expunge_stayopens ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_offer-expunge-of-stayopen-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_offer-expunge-of-stayopen-folders"--></H1>
+
+This feature is related to the option
+<A HREF="h_config_permlocked">&quot;<!--#echo var="VAR_stay-open-folders"-->&quot;</A>.
+Stay Open folders are treated differently from regular folders in several
+ways.
+One of the differences is that the normal &quot;close&quot; sequence of
+events is deferred until Alpine is exited, instead of happening when you
+leave the folder to view another folder.
+The &quot;close&quot; sequence normally includes the Expunging
+of deleted messages
+(either automatically or after a prompt, controlled by the features
+<A HREF="h_config_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm"-->&quot;</A>,
+<A HREF="h_config_full_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm-everywhere"-->&quot;</A>, and
+<A HREF="h_config_expunge_manually"><!--#echo var="FEAT_expunge-only-manually"--></A>), and the
+handling of
+<A HREF="h_config_archived_folders"><!--#echo var="VAR_incoming-archive-folders"--></A>.
+
+<P>
+If this feature is set the &quot;close&quot; sequence handling will take
+place when you leave the Stay Open folder.
+The folder will still be kept open, but the offer to Expunge and the archiving
+will take place each time you leave the folder instead of only once at the
+end of the session.
+This feature does not affect the INBOX, which will still only be processed
+when you exit Alpine.
+However, there is a similar feature that affects only the INBOX called
+<A HREF="h_config_expunge_inbox">&quot;<!--#echo var="FEAT_offer-expunge-of-inbox"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_preserve_start_stop ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_preserve-start-stop-characters"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_preserve-start-stop-characters"--></H1>
+
+This feature controls how special control key characters, typically
+Ctrl-S and Ctrl-Q, are interpreted when input to Alpine. These characters
+are known as the "stop" and "start" characters and are sometimes used in
+communications paths to control data flow between devices that operate at
+different speeds.
+
+<P>
+
+By default, Alpine turns the system's handling of these special characters
+off except during printing. However, if you see Alpine reporting input errors
+such as:
+<P>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ Command "^Q" not defined for this screen.]
+<P>
+and, at the same time, see your display become garbled, then it is likely
+that setting this option will solve the problem. Be aware, though, that
+enabling this feature will also cause Alpine to ostensibly "hang"
+whenever the Ctrl-S key combination is entered as the system is now
+interpreting such input as a "stop output" command. To "start
+output" again, simply type Ctrl-Q.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_incoming ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-incoming-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-incoming-folders"--></H1>
+
+Alpine's Incoming Message Folders collection
+provides a convenient way to access multiple incoming folders.
+It is also useful if you have accounts on multiple computers.
+<P>
+
+If set, this feature defines a pseudo-folder collection called
+&quot;INCOMING MESSAGE FOLDERS&quot;. Initially, the only folder included
+in this collection will be your INBOX, which will no longer show up in
+your Default folder collection.
+<P>
+
+You may add more folders to the Incoming Message Folders collection by
+using the
+<!--chtml if pinemode="function_key"-->
+&quot;F10
+<!--chtml else-->
+&quot;A
+<!--chtml endif-->
+Add&quot; command in the FOLDER LIST screen. You will be prompted for
+the host the folder is stored on (which defaults to the same host used
+for your INBOX), a nickname, and the actual folder name. Once a set
+of Incoming Message Folders are defined, the TAB key (in MESSAGE INDEX
+or MESSAGE TEXT screens) may be used to scan the folders for those
+with Recent messages. If you add more folders to
+your <!--#echo var="VAR_incoming-folders"--> collection, turning this feature back off will have
+no effect.
+<P>
+NOTE: Normally the software that actually delivers mail (the stuff that happens
+before Alpine is involved) is in a better position to do delivery filtering
+than is Alpine itself.
+If possible, you may want to look at programs such as
+&quot;filter&quot; or &quot;procmail&quot;, which are examples of delivery
+filtering programs.
+If you'd prefer to have Alpine do the filtering for you, you may set that
+up.
+Look <A HREF="h_rules_filter">here</A> for help with Alpine filtering.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_incoming_checking ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-incoming-folders-checking"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-incoming-folders-checking"--></H1>
+
+This feature is only operational if you have enabled the optional
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A> collection.
+If you do have Incoming Message Folders and you also set this feature,
+then the number of Unseen messages in each folder will be displayed
+in the FOLDER LIST screen for the Incoming Message Folders.
+The number of Unseen messages in a folder will be displayed in parentheses
+to the right of the name of each folder.
+If there are no Unseen messages in a folder then only the name
+is displayed, not a set of parentheses with zero inside them.
+A redraw command, Ctrl-L, can be used in the FOLDER LIST screen for
+the Incoming Message Folders to cause an immediate update.
+<P>
+If a check for Unseen messages fails for a particular folder then Alpine
+will no longer attempt to check that folder for the duration of the
+session and this will be indicated by a question mark inside the
+parentheses.
+<P>
+The features
+<A HREF="h_config_incoming_checking_total"><!--#echo var="FEAT_incoming-checking-includes-total"--></A>,
+<A HREF="h_config_incoming_checking_recent"><!--#echo var="FEAT_incoming-checking-uses-recent"--></A>,
+<A HREF="h_config_incoming_list"><!--#echo var="VAR_incoming-check-list"--></A>,
+<A HREF="h_config_incoming_interv"><!--#echo var="VAR_incoming-check-interval"--></A>,
+<A HREF="h_config_incoming_second_interv"><!--#echo var="VAR_incoming-check-interval-secondary"--></A>, and
+<A HREF="h_config_incoming_timeo"><!--#echo var="VAR_incoming-check-timeout"--></A>
+all affect how this feature behaves.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_checking_total ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_incoming-checking-includes-total"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_incoming-checking-includes-total"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+When incoming folder checking is turned on the default is to display
+the number of unseen messages in each folder.
+More precisely, it is the number of undeleted unseen messages.
+Using this option you may also display the total number of messages
+in each folder.
+Instead of a single number representing the number of unseen messages
+you will get two numbers separated by a slash character.
+The first is the number of unseen messages and the second is the
+total number of messages.
+<P>
+You may also use the recent message count instead of the unseen message
+count by turning on the feature
+<A HREF="h_config_incoming_checking_recent"><!--#echo var="FEAT_incoming-checking-uses-recent"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incoming_checking_recent ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_incoming-checking-uses-recent"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_incoming-checking-uses-recent"--></H1>
+
+This option has no effect unless the feature
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is set, which in turn has no effect unless
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+is set.
+<P>
+When incoming folder checking is turned on the default is to display
+the number of unseen messages in each folder.
+More precisely, it is the number of undeleted unseen messages.
+Using this option you may display the number of recent messages instead
+of the number of unseen messages.
+A message is only counted as recent if this is the first session to
+see it, so the recent count might be less than the unseen count.
+The difference between the two would be accounted for by the unseen messages
+in the folder which were there previously but have not been looked at yet.
+<P>
+If you simultaneously run more than one email client at a time
+(for example, you run more than one Alpine in parallel) then turning
+this feature on can cause some confusion.
+The confusion stems from the fact that each message is only considered to be
+recent in one session.
+That means that the counts of new messages may be different in the two
+Alpines running side by side, because each incoming message will only be
+counted as recent in one of the two sessions.
+<P>
+You may also display the total number of messages
+in each folder by using the
+<A HREF="h_config_incoming_checking_total"><!--#echo var="FEAT_incoming-checking-includes-total"--></A>
+option.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_attach_in_reply ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_include-attachments-in-reply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_include-attachments-in-reply"--></H1>
+
+This feature controls an aspect of Alpine's Reply command. If set, any MIME
+attachments that were part of the original message will automatically be
+included in the Reply.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_include_header =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_include-header-in-reply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_include-header-in-reply"--></H1>
+
+This feature controls an aspect of Alpine's Reply command. If set, and the
+original message is being included in the reply, then headers from that
+message will also be part of the reply.
+<P>
+&lt;End of help on this topic&gt;
+</HEAD>
+</HTML>
+====== h_config_sig_at_bottom =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_signature-at-bottom"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_signature-at-bottom"--></H1>
+
+This feature controls an aspect of Alpine's Reply command. If this feature
+is set, and the original message is being included in the reply, then the
+contents of your signature file (if any) will be inserted after the included
+message.
+<P>
+This feature does not affect the results of a Forward command.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sigdashes =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-sigdashes"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-sigdashes"--></H1>
+
+This feature enables support for the common USENET news convention
+of preceding a message signature with the special line consisting of
+the three characters &quot;--&nbsp;&quot; (i.e., dash, dash, and space).
+
+<P>
+When enabled and a
+&quot;<A HREF="h_config_signature_file"><!--#echo var="VAR_signature-file"--></A>&quot; exists,
+Alpine will insert the special line before including the file's text (unless
+the special line already exists somewhere in the file's text).
+
+<P>
+In addition, when you Reply or Followup to a message containing one of
+these special lines and choose to include its text, Alpine will observe
+the convention of not including text beyond the special line in your
+reply.
+If <A HREF="h_config_enable_full_hdr">&quot;Full Header&quot;</A>
+mode is enabled and turned on, then Alpine <EM>will</EM>
+include the text beyond the special line regardless of the setting of
+this feature.
+
+<P>
+See also &quot;<a href="h_config_strip_sigdashes"><!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></a>&quot;
+for a related feature.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_new_thread_blank_subject =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_new-thread-on-blank-subject"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_new-thread-on-blank-subject"--></H1>
+
+When this feature is enabled (the default) Alpine will create a new thread
+every time that the subject line becomes empty at any time during composition.
+
+<P>
+This behavior is particularly useful in case you are replying to a message.
+Replying to a message causes the message to be in the same thread than the
+original message that is being replied to. However, many authors want to create
+a new message (in a different thread) while replying to a message, and they do
+this by changing the full subject, by first deleting the original subject and
+typing the new subject of the current message.
+
+<P>
+Enabling this feature causes that any time that the subject is deleted, the
+message being composed will be considered the first message of a new thread.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_strip_sigdashes =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></H1>
+
+This feature doesn't do anything if the feature
+&quot;<A HREF="h_config_sigdashes"><!--#echo var="FEAT_enable-sigdashes"--></A>&quot; is turned on.
+However, if the &quot;<!--#echo var="FEAT_enable-sigdashes"-->&quot; feature is not turned on,
+then turning on this feature enables support for the convention
+of not including text beyond the sigdashes line when Replying or Following
+up to a message and including the text of that message.
+If <A HREF="h_config_enable_full_hdr">&quot;Full Header&quot;</A>
+mode is enabled and turned on, then Alpine <EM>will</EM>
+include the text beyond the special line regardless of the setting of
+this feature.
+<P>
+In other words, this is a way to turn on the signature stripping behavior
+without also turning on the dashes-adding behavior.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_forward_as_attachment =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_forward-as-attachment"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_forward-as-attachment"--></H1>
+
+This feature affects the way forwarded message text is handled. When set, rather than
+include the text of the forwarded message below any additional text you provide in the
+composer, the forwarded message is attached in its entirety to the message you send.
+<P>
+This is useful in that it keeps the text you provide in the composer distinct from the
+text of the forwarded message. Similarly, it allows the recipient to
+conveniently operate on the forwarded message. For example, they might reply directly to
+the sender of the forwarded message, or process it as part of a spam report.
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sub_lists =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-partial-match-lists"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-partial-match-lists"--></H1>
+
+This feature affects the subcommands available when Saving,
+or when Opening a new folder. If set, the subcommand ^X ListMatches will be
+available. This command allows you to type in a substring of the folder
+you are looking for and when you type ^X it will display all folders
+that contain that substring in their names.
+This feature is set by default.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_scramble_message_id =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_scramble-message-id"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_scramble-message-id"--></H1>
+
+Normally the Message-ID header that Alpine generates when sending a message
+contains the name of the computer from which the message is being sent.
+Some believe that this hostname could be used by spammers or could
+be used by others for nefarious purposes.
+If this feature is set, that name will be transformed with a simple
+Rot13 transformation.
+The result will still have the correct syntax for a Message-ID but the
+part of the MessageID that is often a domain name will not be an actual
+domain name because the letters will be scrambled.
+<P>
+It is possible (but unlikely?) that some spam detection
+software will use that as a reason to reject the mail as spam.
+It has also been reported that some spam detection software uses the
+fact that there are no dots after the &quot;@&quot; as a reason to reject
+messages.
+If your PC-Alpine Message-ID is using a name without a dot that is because
+that is what Windows thinks is your &quot;Full computer name&quot;.
+The method used to set this varies from one type of Windows to another but
+check under Settings -> Control Panel -> System and
+look for Network Identification or Computer Name or something similar.
+How to set it is beyond the scope of Alpine.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_downgrade_multipart_to_text =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_downgrade-multipart-to-text"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_downgrade-multipart-to-text"--></H1>
+
+<P>
+This feature affects Alpine's behavior when sending mail. Internet
+standards require Alpine to translate all non-ASCII characters in
+messages that it sends using MIME encoding. This encoding can be
+ostensibly broken for recipients if any agent between Alpine and the
+recipient, such as an email list expander, appends text to the
+message, such as list information or advertising. When sending such
+messages Alpine attempts to protect such encoding by placing extra
+MIME boundaries around the message text.
+<P>
+These extra boundaries are invisible to recipients that
+use MIME-aware email programs (the vast majority). However, if
+you correspond with users of email programs that are not MIME-aware,
+or do not handle the extra boundaries gracefully, you can
+use this feature to prevent Alpine from including the extra
+MIME information. Of course, it will increase the likelihood
+that non-ASCII text you send may appear corrupt to the recipient.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_show_sort =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_show-sort"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_show-sort"--></H1>
+
+If this feature is set and there is sufficient space on the screen,
+a short indication of the current sort order will be
+added in the titlebar (the top line on the screen), before the name
+of the folder.
+For example, with the default Arrival sort in effect,
+the display would have the characters
+
+<P><CENTER>[A]</CENTER><P>
+
+added between the title of the screen and the folder name.
+The letters are the same as the letters you may type to manually
+sort a folder with the SortIndex command ($).
+The letters in the table below are the ones that may show
+up in the titlebar line.
+<P>
+<TABLE>
+<TR> <TD> A </TD> <TD> <EM>A</EM>rrival </TD> </TR>
+<TR> <TD> S </TD> <TD> <EM>S</EM>ubject </TD> </TR>
+<TR> <TD> F </TD> <TD> <EM>F</EM>rom </TD> </TR>
+<TR> <TD> T </TD> <TD> <EM>T</EM>o </TD> </TR>
+<TR> <TD> C </TD> <TD> <EM>C</EM>c </TD> </TR>
+<TR> <TD> D </TD> <TD> <EM>D</EM>ate </TD> </TR>
+<TR> <TD> Z </TD> <TD> si<EM>Z</EM>e </TD> </TR>
+<TR> <TD> O </TD> <TD> <EM>O</EM>rderedsubject </TD> </TR>
+<TR> <TD> E </TD> <TD> scor<EM>E</EM> </TD> </TR>
+<TR> <TD> H </TD> <TD> t<EM>H</EM>read </TD> </TR>
+</TABLE>
+<P>
+If the sort order is Reversed, the letter above will be preceded by the letter
+&quot;R&quot;, for example
+
+<P><CENTER>[RS]</CENTER><P>
+
+means that a Reverse Subject sort is in effect.
+For the case where the sort is in Reverse Arrival order, the &quot;A&quot; is
+left out, and just an &quot;R&quot; is shown.
+
+<P><CENTER>[R]</CENTER>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_reset_disp =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-terminal-reset-for-display-filters"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-terminal-reset-for-display-filters"--></H1>
+
+UNIX Alpine only.
+<P>
+This feature affects Alpine's behavior when using
+<A HREF="h_config_display_filters"><!--#echo var="VAR_display-filters"--></A>.
+Normally, before the display filter is run, the terminal mode is reset
+to what it was before you started Alpine.
+This may be necessary if the filter requires the use of the terminal.
+For example, it may need to interact with you.
+If you set this feature, then the terminal mode will not be reset.
+One thing that turning on this feature should fix is the coloring of
+<A HREF="h_config_quote_color">quoted text</A> in the message view, which
+breaks because the terminal reset resets the color state of the terminal.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_sender =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-sender"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-sender"--></H1>
+
+This feature affects Alpine's generation of the &quot;Sender:&quot; or
+<A HREF="h_config_use_sender_not_x">&quot;X-X-Sender&quot;</A>
+header fields.
+By default, Alpine will generate such a header in situations where the
+username or domain are not the same as
+the &quot;From:&quot; header on the message.
+With this feature set,
+no &quot;Sender:&quot; or &quot;X-X-Sender&quot; header will be generated.
+This may be desirable on a system that is virtually hosting many domains,
+and the sysadmin has other methods available for tracking a message to
+its originator.
+<P>
+See also <A HREF="h_config_allow_chg_from">&quot;<!--#echo var="FEAT_allow-changing-from"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_sender_not_x =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-sender-not-x-sender"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-sender-not-x-sender"--></H1>
+
+Normally Alpine adds a header line
+labeled &quot;X-X-Sender&quot;, if the sender is
+different from the From: line.
+The standard specifies that this header
+line should be labeled &quot;Sender&quot;, not &quot;X-X-Sender&quot;.
+Setting this feature causes
+&quot;Sender&quot; to be used instead of &quot;X-X-Sender&quot;.
+<P>
+See also <A HREF="h_config_disable_sender">&quot;<!--#echo var="FEAT_disable-sender"-->&quot;</A>.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_fk =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-function-keys"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-function-keys"--></H1>
+
+This feature specifies that Alpine will respond to function keys instead of
+the normal single-letter commands. In this mode, the key menus at the
+bottom of each screen will show function key designations instead of the
+normal mnemonic key.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_cancel_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-cancel-confirm-uses-yes"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-cancel-confirm-uses-yes"--></H1>
+
+This feature affects what happens when you type ^C to cancel a composition.
+By default, if you attempt to cancel a composition by typing ^C, you will be
+asked to confirm the cancellation by typing a &quot;C&quot;
+for <EM>C</EM>onfirm.
+It logically ought to be a &quot;Y&quot; for <EM>Y</EM>es, but that is
+risky because the &quot;^C Y&quot; needed to cancel a message
+is close (on the keyboard) to the &quot;^X Y&quot; needed to send a message.
+<P>
+If this feature is set the confirmation asked for
+will be a &quot;<EM>Y</EM>es&quot;
+instead of a &quot;<EM>C</EM>onfirm&quot; response.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_compose_maps_del =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-maps-delete-key-to-ctrl-d"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-maps-delete-key-to-ctrl-d"--></H1>
+
+This feature affects the behavior of the DELETE key.
+If set, Delete will be equivalent to ^D, and delete
+the current character. Normally Alpine defines the Delete key
+to be equivalent to ^H, which deletes the <EM>previous</EM>
+character.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_compose_bg_post =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-background-sending"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-background-sending"--></H1>
+
+This feature affects the behavior of Alpine's mail sending. If set, this
+feature enables a subcommand in the composer's "Send?" confirmation
+prompt. The subcommand allows you to tell Alpine to handle the actual
+posting in the background. While this feature usually allows posting
+to appear to happen very fast, it has no affect on the actual delivery
+time it takes a message to arrive at its destination.
+
+<P>
+Please Note:
+<OL>
+ <LI>This feature will have no effect if the feature
+ <A HREF="h_config_send_wo_confirm">&quot;<!--#echo var="FEAT_send-without-confirm"-->&quot;</A>
+ is set.
+ <LI>This feature isn't supported on all systems. All DOS and Windows,
+ as well as several Unix ports, do not recognize this feature.
+ <LI>Error handling is significantly different when this feature is
+ enabled. Any message posting failure results in the message
+ being appended to your &quot;Interrupted&quot; mail folder. When you
+ type the <A HREF="h_common_compose">C</A>ompose command,
+ Alpine will notice this folder and
+ offer to extract any messages contained. Upon continuing a
+ failed message, Alpine will display the nature of the failure
+ in the status message line.
+ <LI> <EM>WARNING</EM>: Under extreme conditions, it is possible
+ for message data to
+ get lost. <EM>Do</EM> <EM>not</EM> enable this feature
+ if you typically run close to any sort of disk-space limits or quotas.
+</OL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_compose_dsn =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-delivery-status-notification"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-delivery-status-notification"--></H1>
+
+This feature affects the behavior of Alpine's mail sending. If set, this
+feature enables a subcommand in the composer's &quot;Send?&quot; confirmation
+prompt. The subcommand allows you to tell Alpine to request the type of
+Delivery Status Notification (DSN) that you would like. Most users will
+be happy with the default, and need not enable this feature.
+<P>
+If the feature
+<A HREF="h_config_send_wo_confirm">&quot;<!--#echo var="FEAT_send-without-confirm"-->&quot;</A> is set,
+then this feature has no effect and the type of DSN is not selectable.
+
+<P>
+Turning on this feature and then turning on the DSNOpts from the send
+prompt reveals four on-off toggles at the bottom of the screen.
+The &quot;X&quot; command toggles between NoErrRets and ErrRets. NoErrRets requests
+that no notification be returned to you, even if there is a delivery
+failure. The &quot;D&quot; key toggles between Delay and NoDelay. This tells the
+server that you are willing (or not) to receive delay notifications, which
+happen when there is an unusual delay at some mail server (in that mail
+server's opinion). The &quot;S&quot; key toggles between Success and NoSuccess.
+Success requests that you be sent a DSN message when the message is
+successfully delivered to the recipients mailbox. Setting NoErrRets will
+automatically turn off Delay and Success notification, and will flip the
+toggles to show that. Similarly, turning on Delay and/or Success will
+automatically toggle the &quot;X&quot; key to ErrRets. The fourth command, the
+&quot;H&quot; key, toggles between RetHdrs and RetFull. RetFull requests that
+the full message be returned in any failed DSN. RetHdrs requests that
+only the headers be returned in any failed DSN. Notice that this command
+applies only to failed delivery status reports. For delay or success
+reports, the full message is never returned, only the headers are returned.
+
+<P>
+If you don't enable the DSN feature or if you don't turn it on for a
+particular message, the default is that you will be notified about failures,
+you might be notified about delays, and you won't be notified about
+successes. You will usually receive the full message back when there is
+a failure.
+
+<P>
+If you turn on the DSNOpts the default is to return as much information as
+possible to you. That is, by default, the Success and Delay options are
+turned on and the full message will be returned on failure.
+
+<P>
+The sending prompt will display the current DSN request (if any) in a
+shorthand form. It will be:
+
+<P><CENTER>[Never]</CENTER>
+
+<P>
+if you have requested NoErrRets. Otherwise, it will look something like:
+
+<P><CENTER>[FDS-Hdrs]</CENTER>
+
+<P>
+The &quot;F&quot; will always be there, indicating that you will be notified
+of failures. (Alpine doesn't provide a way to request no failure notification
+and at the same time request either success or delay notification. The only
+way to request no failure notifications is to request no notifications at
+all with NoErrRets.) The &quot;D&quot; and/or &quot;S&quot; will be present if you have
+requested Delay and/or Success notification. If one of those is missing,
+that means you are requesting no notification of the corresponding type.
+After the dash it will say either Hdrs or Full. Hdrs means to return only
+the headers and Full means to return the full message (applies to
+failure notifications only).
+
+<P>
+NOTE: This feature relies on your system's mail transport agent or
+configured
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+having the negotiation mechanism introduced in
+&quot;Extended SMTP&quot; (ESMTP) and the specific extension called
+&quot;Delivery Status Notification&quot; (DSN). If the mail tranport agent you
+are using doesn't support DSN, a short warning will be shown to you on
+the message line at the bottom of the screen after you send your message,
+but your message will have been sent anyway.
+
+<P>
+Note that DSNs don't provide a mechanism to request read receipts. That
+is, if you request notification on success you are notified when the
+message is delivered to the mailbox, not when the message is read.
+
+<P>
+ESMTP allows for graceful migration to upgraded mail transfer agents, but
+it is possible that this feature might cause problems for some servers.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_zoom =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_auto-zoom-after-select"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_auto-zoom-after-select"--></H1>
+
+This feature affects the behavior of the Select command.
+If set, the select command will automatically perform a zoom
+after the select is complete.
+This feature is set by default.
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>,
+<LI> <A HREF="h_config_auto_unzoom"><!--#echo var="FEAT_auto-unzoom-after-apply"--></A>, and
+<LI> <A HREF="h_config_auto_unselect"><!--#echo var="FEAT_auto-unselect-after-apply"--></A>.
+<LI> <A HREF="h_config_select_wo_confirm"><!--#echo var="FEAT_select-without-confirm"--></A>.
+</UL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_unzoom =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_auto-unzoom-after-apply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_auto-unzoom-after-apply"--></H1>
+
+This feature affects the behavior of the Apply command. If set, and if
+you are currently looking at a Zoomed Index view of selected messages,
+the Apply command will do the operation you specify, but then will
+implicitly do an &quot;UnZoom&quot;, so that you will automatically be back in
+the normal Index view after the Apply.
+This feature is set by default.
+
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>,
+<LI> <A HREF="h_config_auto_unselect"><!--#echo var="FEAT_auto-unselect-after-apply"--></A>.
+<LI> <A HREF="h_config_auto_zoom"><!--#echo var="FEAT_auto-zoom-after-select"--></A>, and
+<LI> <A HREF="h_config_select_wo_confirm"><!--#echo var="FEAT_select-without-confirm"--></A>.
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_unselect =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_auto-unselect-after-apply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_auto-unselect-after-apply"--></H1>
+
+This feature affects the behavior of the Apply command. If set,
+the Apply command will do the operation you specify, but then will
+implicitly do an &quot;UnSelect All&quot;, so that you will automatically be back in
+the normal Index view after the Apply.
+
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>,
+<LI> <A HREF="h_config_auto_zoom"><!--#echo var="FEAT_auto-zoom-after-select"--></A>, and
+<LI> <A HREF="h_config_auto_unzoom"><!--#echo var="FEAT_auto-unzoom-after-apply"--></A>, and
+<LI> <A HREF="h_config_select_wo_confirm"><!--#echo var="FEAT_select-without-confirm"--></A>.
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_fast_recent =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-fast-recent-test"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-fast-recent-test"--></H1>
+
+This feature controls the behavior of the TAB key when traversing folders
+in the optional
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+collection or in optional <!--#echo var="VAR_news-collections"-->.
+
+<P>
+When the TAB
+(<A HREF="h_common_nextnew">NextNew</A>)
+key is pressed, the default behavior is to
+explicitly examine the status of the folder for the number of recent
+messages (messages delivered since the last time it was viewed).
+Depending on the size and number of messages in the folder, this test
+can be time consuming.
+
+<P>
+Enabling this feature will cause Alpine to only test for the existence of
+any recent messages rather than to obtain the count. This is much faster
+in many cases. The downside is that you're not given the number of recent
+messages when prompted to view the next folder.
+If the feature
+<A HREF="h_config_tab_uses_unseen">&quot;<!--#echo var="FEAT_tab-uses-unseen-for-next-folder"-->&quot;</A>
+is turned on, then the present feature will have no effect.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_arrow_nav =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-arrow-navigation"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-arrow-navigation"--></H1>
+
+This feature controls the behavior of the left and right arrow keys.
+If set, the left and right arrow keys will operate like the usual
+navigation keys &lt; and &gt;.
+This feature is set by default.
+
+<P>
+If you set this feature, and do not like the changed behavior of the up/down
+arrow
+keys when navigating through the FOLDER LIST screen --
+<B>first</B> from column to column, if more than one folder is
+displayed per row,
+and <B>then</B> from row to row -- you may either also wish to set the feature
+&quot;<A HREF="h_config_relaxed_arrow_nav"><!--#echo var="FEAT_enable-arrow-navigation-relaxed"--></A>&quot;,
+&quot;<A HREF="h_config_single_list"><!--#echo var="FEAT_single-column-folder-list"--></A>&quot;, or
+use the ^P/^N (instead of up/down arrow) keys to move up/down the list of
+folders in each column.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_relaxed_arrow_nav =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-arrow-navigation-relaxed"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-arrow-navigation-relaxed"--></H1>
+
+This feature controls the behavior of the left, right, up and down
+arrow keys in the FOLDER LIST screen when the &quot;<A
+HREF="h_config_arrow_nav"><!--#echo var="FEAT_enable-arrow-navigation"--></A>&quot; feature is
+set.
+This feature is set by default.
+
+<P>
+
+When this feature is set, the left and right
+arrow keys in the FOLDER LIST screen
+move the highlight bar to the left or right, and the up and
+down arrows move it up or down.
+
+<P>
+When the &quot;<!--#echo var="FEAT_enable-arrow-navigation"-->&quot; feature is set and this
+feature is not set;
+the left and right arrow keys in the Folder List screen strictly
+track the commands bound to the '&lt;' and '&gt;' keys, and the up
+and down arrow keys move the highlight bar to the previous and next
+folder or directory name.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_alt_compose_menu =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_alternate-compose-menu"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_alternate-compose-menu"--></H1>
+
+This feature controls the menu that is displayed when Compose is selected.
+If set, a list of options will be presented, with each option representing
+the type of composition that could be used. This feature is most useful for
+users who want to avoid being prompted with each option separately, or who
+want to avoid the checking of remote postponed or form letter folders.
+The possible types of composition are:
+
+<P>
+New, for starting a new composition. Note that if New is selected and roles
+are set, roles are checked for matches and applied according to the setting
+of the matching role.
+
+<P>
+Interrupted, for continuing an interrupted composition. This option is only
+offered if an interrupted message folder is detected.
+
+<P>
+Postponed, for continuing postponed compositions. This option is offered
+if a <!--#echo var="VAR_postponed-folder"--> is set in the config REGARDLESS OF whether or not
+the postponed folder actually exists. This option is especially handy
+for avoiding having to check for the existence of a remote postponed folder.
+
+<P>
+Form, for using form letters. This option is offered if the <!--#echo var="VAR_form-letter-folder"-->
+is set in the config, and is not checked for existence for reasons similar
+to those explained by the postponed option.
+
+<P>
+setRole, for selecting a role to apply to a composition.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_alt_role_menu =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_alternate-role-menu"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_alternate-role-menu"--></H1>
+
+Normally the <A HREF="h_common_role">Role Command</A> allows you to choose
+a role and compose a new message using that role.
+When this feature is set, the role command will first ask whether you want to
+Compose a new message, Forward the current message, Reply to the
+current message, or Bounce the current message.
+If you are not in the MESSAGE INDEX and are not viewing a message,
+then there is no current message and the question will be skipped.
+After you have chosen to Compose, Forward, Reply, or Bounce you will
+then choose the role to be used.
+<P>
+When Bouncing the &quot;Set From&quot; address is used for the
+Resent-From header, the &quot;Set Fcc&quot; value is used for the Fcc
+provided that the option
+<A HREF="h_config_fcc_on_bounce">&quot;<!--#echo var="FEAT_fcc-on-bounce"-->&quot;</A> is turned on,
+and the &quot;Use SMTP Server&quot; value is used for the SMTP server, if
+set.
+Other actions of the role are ignored when Bouncing.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_always_spell_check =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_spell-check-before-sending"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_spell-check-before-sending"--></H1>
+<P>
+When this feature is set, every composed message will be spell-checked before
+being sent.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_asterisks =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_suppress-asterisks-in-password-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_suppress-asterisks-in-password-prompt"--></H1>
+<P>
+When you are running Alpine you will sometimes be asked for a password
+in a prompt on the third line from the bottom of the screen.
+Normally each password character you type will cause an asterisk to echo
+on the screen. That gives you some feedback to know that your typing is
+being recognized.
+There is a very slight security risk in doing it this way because someone
+watching over your shoulder might be able to see how many characters there
+are in your password.
+If you'd like to suppress the echoing of the asterisks set this feature.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_flowed_text =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-flowed-text"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-flowed-text"--></H1>
+<P>
+Alpine generates flowed text where possible.
+The method for generating flowed text is defined by
+<A HREF="http://www.ietf.org/rfc/rfc3676.txt">RFC 3676</A>,
+the benefit of doing so is
+to send message text that can properly be viewed both on normal width displays
+and on displays with smaller or larger than normal screen widths.
+With flowed text, a space at the end of a line tells the receiving mail
+client that the following line belongs to the same paragraph.
+Quoted text will also be affected, with only the innermost
+level of &quot;&gt;&quot; quoting being followed by a space.
+However, if you have changed the
+<A HREF="h_config_reply_indent_string">&quot;<!--#echo var="VAR_reply-indent-string"-->&quot;</A>
+so that it is not equal to the default value of &quot;&gt;&nbsp;&quot;, then
+quoted text will not be flowed.
+For this reason, we recommend that you leave your
+&quot;<!--#echo var="VAR_reply-indent-string"-->&quot; set to the default.
+<P>
+This feature turns off the generation of flowed text, as it might be
+desired to more tightly control how a message is displayed on the receiving end.
+<P>
+If this feature is <EM>not</EM> set, you can control on a message by message
+basis whether or not flowed text is generated.
+You do this by typing ^V at the Send confirmation prompt that you get
+after typing ^X to send a message.
+^V is a toggle that turns flowing off and back on if typed again.
+If for some reason flowing cannot be done on a particular message, then the
+^V command will not be available.
+This would be the case, for example, if this feature was set, or if your
+&quot;<!--#echo var="VAR_reply-indent-string"-->&quot; was set to a non-default value.
+If the feature
+<A HREF="h_config_send_wo_confirm">&quot;<!--#echo var="FEAT_send-without-confirm"-->&quot;</A> is set,
+then the opportunity to control on a message by message basis
+whether or not flowed text is generated is lost.
+<P>
+When this feature is not set and you have typed ^V to turn off flowing,
+the Send confirmation prompt will change to look like
+<P>
+<CENTER><SAMP>Send message (not flowed)?</SAMP></CENTER>
+<P>
+<A HREF="h_config_strip_ws_before_send">&quot;<!--#echo var="FEAT_strip-whitespace-before-send"-->&quot;</A> will
+also turn off the sending of flowed text messages, but it differs in that
+it also trims all trailing white space from a message before sending it.
+<P>
+If alternate editors are used extensively, be aware that a message will still
+be sent flowed if this feature is unset. In most cases this will be fine,
+but if the editor has a &quot;flowed text&quot; mode, it would be best to
+use that.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_strip_ws_before_send =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_strip-whitespace-before-send"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_strip-whitespace-before-send"--></H1>
+<P>
+By default, trailing whitespace is not stripped from
+a message before sending. Trailing whitespace should have no effect on an
+email message, and in flowed text can aid in delimiting paragraphs.
+However, the old behavior of stripping trailing whitespace was in place
+to better deal with older clients that couldn't handle certain types of
+text encodings. This feature restores the old behavior
+<P>
+Trailing whitespace is of aid to flowed-text-formatted messages, which are
+generated by default but can be turned off via the
+<A HREF="h_config_quell_flowed_text">&quot;<!--#echo var="FEAT_quell-flowed-text"-->&quot;</A> feature.
+<!--#echo var="FEAT_strip-whitespace-before-send"--> also has the effect of turning off sending
+of flowed text.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_del_from_dot =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-cut-from-cursor"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-cut-from-cursor"--></H1>
+
+This feature controls the behavior of the Ctrl-K command in the composer.
+If set, ^K will cut from the current cursor position to the end of the line,
+rather than cutting the entire line.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_print_index =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_print-index-enabled"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_print-index-enabled"--></H1>
+
+This feature controls the behavior of the Print command when in the
+MESSAGE INDEX screen. If set, the print command will give you a prompt
+asking if you wish to print the message index, or the currently highlighted
+message. If not set, the message will be printed.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_allow_talk =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_allow-talk"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_allow-talk"--></H1>
+
+UNIX Alpine only.
+<P>
+By default, permission for others to &quot;talk&quot; to your terminal is turned
+off when you are running Alpine. When this feature is set, permission is
+instead turned on. If enabled, you may see unexpected messages in the
+middle of your Alpine screen from someone attempting to contact you via the
+&quot;talk&quot; program.
+
+<P>
+NOTE: The &quot;talk&quot; program has nothing to do with Alpine or email. The
+talk daemon on your system will attempt to print a message on your screen
+when someone else is trying to contact you. If you wish to see these
+messages while you are running Alpine, you should enable this feature.
+
+<P>
+If you do enable this feature and see a &quot;talk&quot; message, you must
+suspend or quit Alpine before you can respond.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_send_filter_dflt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_compose-send-offers-first-filter"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_compose-send-offers-first-filter"--></H1>
+If you have <A HREF="h_config_sending_filter">&quot;<!--#echo var="VAR_sending-filters"-->&quot;</A>
+configured, setting this feature will cause
+the first filter in the <!--#echo var="VAR_sending-filters"--> list to be offered as the default
+instead of unfiltered, the usual default.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_custom_print =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_print-offers-custom-cmd-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_print-offers-custom-cmd-prompt"--></H1>
+
+When this feature is set, the print command will have an additional
+subcommand called "C CustomPrint". If selected, you will have
+the opportunity to enter any system print command --instead of being
+restricted to using those that have been previously configured in the
+printer setup menu.
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_dot_files =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-dot-files"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-dot-files"--></H1>
+
+When this feature is set, files beginning with dot (".") will be
+visible in the file browser. For example, you'll be able to select them
+when using the browser to add an attachment to a message.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_dot_folders =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-dot-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-dot-folders"--></H1>
+
+When this feature is set, folders beginning with dot (".") may be added
+and viewed.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_ff_between_msgs =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_print-formfeed-between-messages"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_print-formfeed-between-messages"--></H1>
+
+Setting this feature causes a formfeed to be printed between messages when
+printing multiple messages (with Apply Print command).
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_blank_keymenu =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-keymenu"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-keymenu"--></H1>
+
+If this feature is set the command key menu that normally appears on the
+bottom two lines of the screen will not usually be there. Asking for
+help with ^G or ? will cause the key menu to appear instead of causing
+the help message to come up. If you want to actually see the help text,
+another ^G or ? will show it to you. After the key menu has popped
+up with the help key it will remain there for an O for Other command but
+disappear if any other command is typed.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_mouse =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-mouse-in-xterm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-mouse-in-xterm"--></H1>
+
+This feature controls whether or not an X terminal mouse can be used with
+Alpine. If set, and the $DISPLAY variable indicates that an X terminal is
+being used, the left mouse button on the mouse can be used to select text
+or commands.
+Clicking on a command at the bottom of the screen will behave as if you had
+typed that command.
+Clicking on an index line will move the current message highlight to
+that line.
+Double-clicking on an index line will view the message.
+Double-clicking on a link will view the link.
+<P>
+This type of mouse support will also work in some terminal emulators which are
+not actually X terminals, but which have extra code to support the xterm
+style mouse.
+For those emulators you not only need to turn this feature on but you also
+have to set the $DISPLAY environment variable even though it isn't needed
+for your terminal.
+That will cause Alpine to think that it is an xterm and to properly interpret the
+escape sequences sent by the mouse.
+<P>
+Note: if this feature is set, the behavior of X terminal cut-and-paste is
+also modified. It is sometimes possible to hold the shift key down while clicking
+left or middle mouse buttons for the normal xterm cut/paste operations.
+There is also an Alpine command to toggle this mode on or off.
+The command is Ctrl-&#92; (Control-backslash).
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_xterm_newmail =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-newmail-in-xterm-icon"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-newmail-in-xterm-icon"--></H1>
+
+This feature controls whether or not Alpine will attempt to announce new
+mail arrival when it is running in an X terminal window and that window
+is iconified. If set, and the $DISPLAY variable indicates that an X
+terminal is being used, Alpine will send appropriate escape sequences to
+the X terminal to modify the label on Alpine's icon to indicate that new
+mail has arrived. Alpine will also modify the Alpine window's title to
+indicate new mail.
+See also <a href="h_config_enable_newmail_short_text"><!--#echo var="FEAT_enable-newmail-short-text-in-icon"--></a>.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_enable_newmail_short_text =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-newmail-short-text-in-icon"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-newmail-short-text-in-icon"--></H1>
+
+This feature controls the text to be displayed in an icon in the event
+of a new message arrival. Normally, the message will
+be the one that is displayed on the screen. This feature shortens the
+message to a count of the number of new messages in brackets. This may be
+more useful for those who use the window's title bar in the task bar as a
+new mail indicator. This feature is only useful if the
+<A HREF="h_config_enable_xterm_newmail"><!--#echo var="FEAT_enable-newmail-in-xterm-icon"--></A>
+feature is also set. Like the <!--#echo var="FEAT_enable-newmail-in-xterm-icon"-->
+feature, this feature is only relevant when run in an xterm environment.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_copy_to_to_from =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_copy-to-address-to-from-if-it-is-us"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_copy-to-address-to-from-if-it-is-us"--></H1>
+
+This feature affects the From address used when Replying to a message.
+It is probably only useful if you have some
+<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
+defined.
+When enabled, it checks to see if any of the addresses in the To or Cc
+fields of the message you are replying to is one of your addresses.
+If it is, and there is only one of them, then that address is used as
+the From address in the message you are composing.
+In other words, you will be using a From address that is the same
+as the To address that was used to get the mail to you in the first place.
+
+<P>
+If a role is being used and it has a From address defined, that From address will
+be used rather than the one derived from this feature.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_prefix_editing =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-reply-indent-string-editing"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-reply-indent-string-editing"--></H1>
+
+This feature affects the Reply command's &quot;Include original message
+in Reply?&quot; prompt. When enabled, it causes the
+&quot;Edit Indent String&quot; sub-command to appear which allows
+you to edit the string Alpine would otherwise use to denote included
+text from the message being replied to.<P>
+
+Thus, you can change Alpine's default message quote character (usually
+an angle bracket) on a per message basis. So you could change your quoted message to
+look, for example, like this:<p>
+
+<pre>On Tues, 26 Jan 1999, John Q. Smith wrote:
+
+John: I just wanted to say hello and to congratulate you
+John: on a job well done!</pre><p>
+
+The configuration option
+<A HREF="h_config_reply_indent_string">&quot;<!--#echo var="VAR_reply-indent-string"-->&quot;</A>
+may be used to change what appears as the default string to be edited.
+<P>
+NOTE: Edited <!--#echo var="VAR_reply-indent-string"--> only apply to the message
+currently being replied to.
+<P>
+If you change your <!--#echo var="VAR_reply-indent-string"-->
+so that it is not equal to the default value of &quot;&gt;&nbsp;&quot;, then
+quoted text will not be flowed
+(<A HREF="h_config_quell_flowed_text">Flowed Text</A>)
+when you reply.
+For this reason, we recommend that you leave your <!--#echo var="VAR_reply-indent-string"-->
+set to the default value.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_search_and_repl =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-search-and-replace"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-search-and-replace"--></H1>
+
+This feature modifies the behavior of Alpine's composer. Setting this
+feature causes Alpine to offer the "^R Replace" subcommand, which
+allows you to search and replace text strings in a message you are composing,
+inside the "^W Where is" command.
+
+<P>
+
+To search and replace text, first enter the text to be replaced at the
+"Search: " prompt. Then, rather than pressing Enter to just search for that
+text, press ^R, which turns the prompt into
+
+<P>
+
+Search (to replace):
+
+<P>
+
+and then press Enter. The cursor will highlight the first occurrence
+of the text string you entered, and the prompt will show:
+
+<P>
+
+Replace "<your text string>" with :
+
+<P>
+
+where <your text string> is what you entered at the previous prompt;
+here, enter the replacement text. To only replace the highlighted
+occurrence, simply press Enter now; to replace all occurrences in the
+message, press ^X (Repl All), then Enter. You will then be asked to confirm
+each replacement.
+
+<P>
+
+The command ^R toggles between "Replace" and "Don't Replace"; its subcommand
+^X toggles between "Replace All" and "Replace One."
+
+<P>
+
+If you previously searched for text in a message, it will be offered for
+re-use as part of the prompt, shown in [ ] brackets.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_view_attach =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-attachments"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-attachments"--></H1>
+
+This feature modifies the behavior of Alpine's MESSAGE TEXT screen.
+Setting this feature causes Alpine to present attachments in boldface.
+The first available attachment is displayed in inverse. This is the
+"selected" attachment. Pressing RETURN will cause Alpine to display
+the selected attachment. Use the arrow keys to change which of the
+attachments displayed in boldface is the current selection.
+
+<P>
+
+Speaking of arrow keys, the Up and Down Arrows will select the next
+and previous attachments if one is available on the screen for selection.
+Otherwise, they will simply adjust the viewed text one line up or down.
+
+<P>
+
+Similarly, when selectable items are present in a message, the Ctrl-F key
+can be used to select the next item in the message independent of which
+portion of the viewed message is currently displayed. The Ctrl-B key can
+be used to select the previous item in the same way.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_y_print =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-print-via-y-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-print-via-y-command"--></H1>
+
+This feature modifies the behavior of Alpine's Print command.
+<P>
+By default, Alpine's print command is available by pressing the "%" key.
+(This command is a substantial change from Pine versions before 4.00 --
+where the print command was "Y" -- based on numerous complaints about
+printing being invoked inadvertently, since Y also means "Yes.")
+
+<P>
+
+This feature is supplied to mitigate any disruption or anxiety users
+might feel as a result of this change.
+
+<P>
+
+Enabling this feature will cause Alpine to recognize both the old
+command, "Y" for Prynt, as well the new "%" method for invoking
+printing. Note, key menu labels are not changed as a result of
+enabling this feature.
+
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_lessthan_exit =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-exit-via-lessthan-command"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-exit-via-lessthan-command"--></H1>
+
+If this feature is set, then on screens where there is an Exit command
+but no < command, the < key will perform the same function as the Exit
+command.
+This feature is set by default.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_view_url =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-urls"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-urls"--></H1>
+This feature modifies the behavior of Alpine's MESSAGE TEXT screen.
+When this feature is set (the default) Alpine will select possible URLs from the
+displayed text and display them in boldface for selection.
+<P>
+The first available URL is displayed in inverse. This is the
+&quot;selected&quot; URL. Pressing RETURN will cause Alpine to display
+the selected URL via either built-in means as with mailto:, imap:,
+news:, and nntp:, or via an external application as defined
+by the <A HREF="h_config_browser">&quot;url-viewer&quot;</A>
+variable.
+<P>
+Use the arrow keys to change which of the URLs displayed in boldface
+is the current selection.
+<P>
+Speaking of arrow keys, the Up and Down Arrows will select the next
+and previous URL if one is available on the screen for selection (unless
+you have set the feature
+<A HREF="h_config_enable_view_arrows">&quot;<!--#echo var="FEAT_enable-msg-view-forced-arrows"-->&quot;</A>).
+Otherwise, they will simply adjust the viewed text one line up or down.
+<P>
+Similarly, when selectable items are present in a message, the Ctrl-F
+key can be used to select the next item in the message independent
+of which portion of the viewed message is currently displayed. The
+Ctrl-B key can be used to select the previous item in the same way.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_view_web_host =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-web-hostnames"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-web-hostnames"--></H1>
+
+This feature modifies the behavior of Alpine's MESSAGE TEXT screen.
+When this feature is set (the default) Alpine will select possible web hostnames
+from the displayed text and display them in boldface for selection.
+This can be useful when you receive messages referencing World Wide Web
+sites without the use of complete URLs; for example, specifying only
+&quot;www.washington.edu/alpine/&quot; (which will <B>not</B> become a
+selectable
+item by setting <A HREF="h_config_enable_view_url">&quot;<!--#echo var="FEAT_enable-msg-view-urls"-->&quot;</A>)
+rather than explicitly
+&quot;http://www.washington.edu/alpine/&quot;.
+<P>
+The first available hostname is displayed in inverse. This is the
+&quot;selected&quot; hostname. Pressing RETURN will cause Alpine to display
+the selected hostname via an external application as defined
+by the <A HREF="h_config_browser">&quot;url-viewer&quot;</A>
+variable.
+<P>
+Use the arrow keys (unless you have set the feature
+<A HREF="h_config_enable_view_arrows">&quot;<!--#echo var="FEAT_enable-msg-view-forced-arrows"-->&quot;</A>)
+to change which of the hostnames displayed in
+boldface is the current selection.
+<P>
+Similarly, when selectable web hostnames are present in a message, the Ctrl-F
+key can be used to select the next web hostname in the message independent
+of which portion of the viewed message is currently displayed. The
+Ctrl-B key can be used to select the previous web hostnames in the same way.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_view_addresses =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-addresses"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-addresses"--></H1>
+
+This feature modifies the behavior of Alpine's MESSAGE TEXT screen.
+Setting this feature causes Alpine to select possible email addresses
+from the displayed text and display them in boldface for selection.
+
+<P>
+The first available email address is displayed in inverse. This is the
+&quot;selected&quot; address. Pressing RETURN will cause Alpine to enter
+the message composition screen with the To: field filled in with the
+selected address.
+<P>
+Use the arrow keys (unless you have set the feature
+<A HREF="h_config_enable_view_arrows">&quot;<!--#echo var="FEAT_enable-msg-view-forced-arrows"-->&quot;</A>)
+to change which of the hostnames displayed in
+boldface is the current selection.
+<P>
+Similarly, when selectable web hostnames are present in a message, the Ctrl-F
+key can be used to select the next web hostname in the message independent
+of which portion of the viewed message is currently displayed. The
+Ctrl-B key can be used to select the previous web hostnames in the same way.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_view_arrows =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-forced-arrows"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-forced-arrows"--></H1>
+
+This feature modifies Up and Down arrow key behavior in Alpine's
+MESSAGE TEXT screen when selectable Attachments, URL's, or
+web-hostnames are presented. Alpine's usual behavior is to move to
+the next or previous selectable item if currently displayed or
+simply to adjust the screen view by one line.
+
+<P>
+
+Setting this feature causes the UP and Down arrow key to behave as
+if no selectable items were present in the message.
+
+<P>
+
+Note, the Ctrl-F (next selectable item) and Ctrl-B (previous selectable
+item) functionality is unchanged.
+<P>
+
+&lt;End of help on this topic&gt;
+</BODY>
+<HTML>
+====== h_config_quell_charset_warning =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-charset-warning"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-charset-warning"--></H1>
+
+By default, if the message you are viewing contains characters that are
+not representable in your
+<A HREF="h_config_char_set">&quot;Display Character Set&quot;</A>
+then Alpine will
+add a warning to the start of the displayed text.
+If this option is set, then that editorial message will be suppressed.
+<P>
+Setting this feature also suppresses the comment about the character set
+in header lines.
+For example, when viewing a message you might see
+<P>
+<CENTER><SAMP>From: &quot;[ISO-8859-2] Name&quot; &lt;address&gt;</SAMP></CENTER>
+<P>
+in the From header if your Character-Set is something other than ISO-8859-2.
+If you set this feature, the comment about the character set will
+no longer be there.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_host_after_url =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-server-after-link-in-html"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-server-after-link-in-html"--></H1>
+
+By default, links in HTML text are displayed with the host the link
+references appended, within square brackets, to the link text. Alpine
+does this to help indicate where a link will take you, particularly when
+the link text might suggest a different destination.
+
+<P>
+Setting this feature will prevent the server name from being appended
+to the displayed text.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_prefer_plain_text =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_prefer-plain-text"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_prefer-plain-text"--></H1>
+
+A message being viewed may contain alternate versions of the same content.
+Those alternate versions are supposed to be ordered by the sending software such that the
+first alternative is the least preferred and the last alternative is the
+most preferred. Alpine will normally display the most-preferred version that
+it knows how to display. This is most often encountered where the two
+alternate versions are a plain text version and an HTML version, with the
+HTML version listed last as the most preferred.
+<P>
+If this option is set, then any plain text version will be preferred to
+all other versions.
+<P>
+When viewing a message there is a command &quot;A TogglePreferPlain&quot;,
+which will temporarily change the sense of this option.
+If this option is set you will first see the plain text version of a
+message.
+If you then type the &quot;A&quot; command, you will see the most preferred version,
+most likely HTML, instead.
+Typing the &quot;A&quot; command a second time will switch it back.
+Alternatively, if the present option is not set you will originally see
+the most preferred version of the message and typing &quot;A&quot; will switch to
+the plain text version.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pass_control =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_pass-control-characters-as-is"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_pass-control-characters-as-is"--></H1>
+
+It is probably not useful to set this option.
+This is a legacy option left behind &quot;just in case&quot;.
+Multi-byte characters that have an octet that has the same
+value as a control character are permitted through whether or not
+this option is turned on.
+<P>
+This feature controls how certain characters contained in messages are
+displayed.
+If set, all characters in a message will be sent to the
+screen. Normally, control characters are displayed as shown below to
+avoid a garbled screen and to
+avoid inadvertently changing terminal setup parameters.
+Control characters are usually displayed as two character sequences like
+<P><CENTER><SAMP> ^C </SAMP></CENTER><P>
+for Control-C,
+<P><CENTER><SAMP> ^[ </SAMP></CENTER><P>
+for ESCAPE,
+<P><CENTER><SAMP> ^? </SAMP></CENTER><P>
+for DELETE, and
+<P><CENTER><SAMP> ~E </SAMP></CENTER><P>
+for the character with value 133 (0x85).
+(The DEL character is displayed as ^?, regular control characters are displayed
+as the character ^ followed by the character obtained by adding the
+five low-order bits of the character to 0x40, and the C1
+control characters 0x80 - 0x9F are displayed as the character ~ followed by the
+character obtained by adding the
+five low-order bits of the character to 0x40.)
+Sometimes, in cases where changing a single control character into a
+two-character sequence would confuse Alpine's display routines,
+a question mark is substituted for the control character.
+<P>
+If you wish to filter out regular control characters but pass the
+so-called C1 control characters (0x80 <= char < 0xA0) through unchanged, then
+you may leave this feature unset and set the feature <A HREF="h_config_pass_c1_control"><!--#echo var="FEAT_pass-c1-control-characters-as-is"--></A> instead.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_pass_c1_control =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_pass-c1-control-characters-as-is"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_pass-c1-control-characters-as-is"--></H1>
+
+It is probably not useful to set this option.
+This is a legacy option left behind &quot;just in case&quot;.
+Multi-byte characters that have an octet that has the same
+value as a control character are permitted through whether or not
+this option is turned on.
+<P>
+If the feature <A HREF="h_config_pass_control"><!--#echo var="FEAT_pass-control-characters-as-is"--></A>
+is set, then this feature has no effect.
+However, if you wish to filter out regular control characters but pass the
+so-called C1 control characters (0x80 <= char < 0xA0) through unchanged, then
+you may leave <A HREF="h_config_pass_control"><!--#echo var="FEAT_pass-control-characters-as-is"--></A>
+unset and set this feature.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_fcc_on_bounce =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_fcc-on-bounce"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_fcc-on-bounce"--></H1>
+
+This feature controls an aspect of Alpine's behavior when bouncing a
+message. If set, normal FCC ("File Carbon Copy") processing will be
+done, just as if you had composed a message to the address you are
+bouncing to. If not set, no FCC of the message will be saved.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_show_cursor =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_show-cursor"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_show-cursor"--></H1>
+
+This feature controls an aspect of Alpine's displays. If set, the system
+cursor will move to convenient locations in the displays. For example,
+to the beginning of the status field of the highlighted index line, or
+to the highlighted word after a successful WhereIs command. It is intended
+to draw your attention to an "interesting" spot on the screen.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sort_fcc_alpha =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_sort-default-fcc-alpha"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_sort-default-fcc-alpha"--></H1>
+
+This feature controls an aspect of Alpine's FOLDER LIST screen.
+If set, the default Fcc folder will be sorted alphabetically with the other
+folders instead of appearing right after the INBOX.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_sort_save_alpha =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_sort-default-save-alpha"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_sort-default-save-alpha"--></H1>
+
+This feature controls an aspect of Alpine's FOLDER LIST screen.
+If set, the default save folder will be sorted alphabetically with the other
+folders instead of appearing right after the INBOX (and default FCC folder).
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_single_list =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_single-column-folder-list"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_single-column-folder-list"--></H1>
+
+This feature controls an aspect of Alpine's FOLDER LIST screen. If set,
+the folders will be listed one per line instead of several per line
+in the FOLDER LIST display.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_vertical_list =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_vertical-folder-list"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_vertical-folder-list"--></H1>
+
+This feature controls an aspect of Alpine's FOLDER LIST screen. If set,
+the folders will be listed alphabetically down the columns rather
+than across the columns as is the default.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_verbose_post =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-verbose-smtp-posting"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-verbose-smtp-posting"--></H1>
+This feature controls an aspect of Alpine's message sending. When enabled,
+Alpine will send a VERB (i.e., VERBose) command early in the posting process
+intended to cause the SMTP server to provide a more detailed account of
+the transaction. This feature is typically only useful to system
+administrators and other support personel as an aid in troublshooting
+problems.
+<P>
+Note, this feature relies on a specific capability of the system's mail
+transport agent or configured
+<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>.
+It is possible that this
+feature will cause problems for some tranport agents, and may result in
+sending failure. In addition, as the verbose output comes from the mail
+transport agent, it is likely to vary from one system to another.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_reply_to =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_reply-always-uses-reply-to"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_reply-always-uses-reply-to"--></H1>
+
+This feature controls an aspect of Alpine's Reply command. If set, Alpine
+will not prompt when a message being replied to contains a "Reply-To:"
+header value, but will simply use its value (as opposed to using the
+"From:" field's value).
+
+<P>
+
+Note: Using the "Reply-To:" address is usually the preferred behavior,
+however, some mailing list managers choose to place the list's address in
+the "Reply-To:" field of any message sent out to the list. In such
+cases, this feature makes it all too easy for personal replies to be
+inadvertently sent to the entire mail list, so be careful!
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_del_skips_del =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_delete-skips-deleted"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_delete-skips-deleted"--></H1>
+
+This feature controls an aspect of Alpine's Delete command. If set, this
+feature will cause the Delete command to advance past following messages that
+are marked deleted. In other words, pressing "D" will both mark the
+current message deleted and advance to the next message that is not marked
+deleted.
+This feature is set by default.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_expunge_manually =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expunge-only-manually"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expunge-only-manually"--></H1>
+
+Normally, when you close a folder that contains deleted messages you are
+asked if you want to expunge those messages from the folder permanently.
+If this feature is set, you won't be asked and the deleted messages will
+remain in the folder.
+If you choose to set this feature you will have to expunge the
+messages manually using the eXpunge command, which you can use while
+in the MESSAGE INDEX screen.
+If you do not expunge deleted messages the size of your
+folder will continue to increase until you are out of disk space.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_expunge =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expunge-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expunge-without-confirm"--></H1>
+
+This features controls an aspect of Alpine's eXpunge command. If set, you
+will <B>not</B> be prompted to confirm your intent before the expunge takes
+place.
+Actually, this is only true for the INBOX folder and for folders in the
+Incoming Folders collection. See the feature
+<A HREF="h_config_full_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm-everywhere"-->&quot;</A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_full_auto_expunge =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expunge-without-confirm-everywhere"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expunge-without-confirm-everywhere"--></H1>
+
+This features controls an aspect of Alpine's eXpunge command. If set, you
+will <B>not</B> be prompted to confirm your intent before the expunge
+takes place. This feature sets this behavior for all folders, unlike the
+<A HREF="h_config_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm"-->&quot;</A>
+feature that works only for incoming folders.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_read_msgs =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_auto-move-read-msgs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_auto-move-read-msgs"--></H1>
+This feature controls an aspect of Alpine's behavior upon quitting. If set,
+and the
+<A HREF="h_config_read_message_folder">&quot;<!--#echo var="VAR_read-message-folder"-->&quot;</A>
+option is also set, then Alpine will
+automatically transfer all read messages to the designated folder and mark
+them as deleted in the INBOX. Messages in the INBOX marked with an
+&quot;N&quot; (meaning New, or unseen) are not affected.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_fcc_only =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_fcc-only-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_fcc-only-without-confirm"--></H1>
+This features controls an aspect of Alpine's composer.
+The only time this feature will be used is if you attempt to send mail
+that has no recipients but does have an Fcc.
+Normally, Alpine will ask if you really mean to copy the message only to
+the Fcc.
+That is, it asks if you really meant to have no recipients.
+If this feature is set, you
+will <B>not</B> be prompted to confirm your intent to make only a copy
+of a message with no recipients.
+<P>
+This feature is closely related to
+<A HREF="h_config_warn_if_no_to_or_cc"><!--#echo var="FEAT_warn-if-blank-to-and-cc-and-newsgroups"--></A>.
+The difference between this feature and that feature is that this feature
+considers a Bcc to be a recipient while that feature will ask for confirmation
+even if there is a Bcc when there is no To, Cc, or Newsgroup.
+The default values also differ. This feature defaults to asking the question
+and you have to turn it off.
+The <!--#echo var="FEAT_warn-if-blank-to-and-cc-and-newsgroups"--> feature defaults to not asking
+unless you turn it on.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mark_fcc_seen =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_mark-fcc-seen"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_mark-fcc-seen"--></H1>
+
+This features controls the way Fccs (File carbon copies) are
+made of the messages you send.
+
+<P>
+Normally, when Alpine saves a copy of a message you sent as an Fcc, that
+copy will be marked as Unseen.
+When you look at the folder it was saved in the message will appear to
+be a New message until you read it.
+When this feature is enabled, the message will be marked as having
+been Seen.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_no_fcc_attach =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_fcc-without-attachments"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_fcc-without-attachments"--></H1>
+
+This features controls the way Fcc's (File carbon copies) are
+made of the messages you send.
+
+<P>
+Normally, Alpine saves an exact copy of your message as it was sent.
+When this feature is enabled, the &quot;body&quot; of the message
+you send (the text you type in the composer) is preserved in the
+copy as before, however all attachments are replaced with text
+explaining what had been sent rather than the attachments themselves.
+
+<P>
+This feature also affects Alpine's &quot;Send ?&quot; confirmation prompt
+in that a new &quot;^F Fcc Attchmnts&quot; option becomes available which
+allows you to interactively set whether or not attachments are saved
+to the Fcc'd copy.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_read_in_newsrc_order =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_news-read-in-newsrc-order"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_news-read-in-newsrc-order"--></H1>
+
+This feature controls the order in which newsgroups will be presented. If
+set, they will be presented in the same order as they occur in your
+<!--chtml if pinemode="os_windows"-->
+&quot;NEWSRC&quot;
+<!--chtml else-->
+&quot;.newsrc&quot;
+<!--chtml endif-->
+file (the default location of which can be changed with the
+<A HREF="h_config_newsrc_path">&quot;<!--#echo var="VAR_newsrc-path"-->&quot;</A> option).
+<P>
+If not set, the newsgroups will be presented in alphabetical order.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_tz_comment =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-timezone-comment-when-sending"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-timezone-comment-when-sending"--></H1>
+
+Normally, when Alpine generates a Date header for outgoing mail,
+it will try to include the symbolic timezone at the end of the
+header inside parentheses.
+The symbolic timezone is often three characters long, but on
+some operating systems, it may be longer.
+Apparently there are some SMTP servers in the world that will reject an
+incoming message if it has a Date header longer than about 80 characters.
+If this feature is set, the symbolic timezone normally generated by
+Alpine will not be included.
+You probably don't need to worry about this feature unless you run into
+the problem described above.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_post_wo_validation =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_news-post-without-validation"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_news-post-without-validation"--></H1>
+
+This feature controls whether the NNTP server is queried as newsgroups
+are entered for posting. Validation over slow links (e.g. dialup using
+SLIP or PPP) can cause delays. Set this feature to eliminate such delays.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_send_wo_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_send-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_send-without-confirm"--></H1>
+
+By default, when you send or post a message you will be asked to confirm
+with a question that looks something like:
+
+<P>
+<CENTER><SAMP>Send message?</SAMP></CENTER>
+<P>
+
+If this feature is set, you
+will <B>not</B> be prompted to confirm your intent to send
+and your message will be sent.
+<P>
+If this feature is set it disables some possibilities and renders some
+other features meaningless.
+You will not be able to use
+<A HREF="h_config_sending_filter">Sending Filters</A>,
+Verbose sending mode,
+<A HREF="h_config_compose_bg_post">Background Sending</A>,
+<A HREF="h_config_compose_dsn">Delivery Status Notifications</A>,
+or ^V to turn off the generation of flowed text for this message.
+These options are normally available as suboptions in the Send prompt, but
+with no Send prompt the options are gone.
+
+<P>
+A somewhat related feature is
+<A HREF="h_config_quell_post_prompt">&quot;<!--#echo var="FEAT_quell-extra-post-prompt"-->&quot;</A>,
+which may be used to eliminate the extra confirmation
+question when posting to a newsgroup.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_filtering_done_message =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-filtering-done-message"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-filtering-done-message"--></H1>
+
+If you use Filter Rules that move messages or set status of messages
+you sometimes see a message from Alpine that looks like
+
+<P>
+<CENTER><SAMP>filtering done</SAMP></CENTER>
+<P>
+
+If this feature is set, this message will be suppressed.
+If the feature
+<A HREF="h_config_quell_filtering_messages"><!--#echo var="FEAT_quell-filtering-messages"--></A>
+is set then this message will be suppressed regardless.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_filtering_messages =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-filtering-messages"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-filtering-messages"--></H1>
+
+If you use Filter Rules that move messages or set status of messages
+you sometimes see messages from Alpine that look like
+
+<P>
+<CENTER><SAMP>&lt;filter name&gt;: Moving 2 filtered messages to &lt;folder name&gt;</SAMP></CENTER>
+<P>
+
+or
+
+<P>
+<CENTER><SAMP>&lt;filter name&gt;: Setting flags in 5 messages</SAMP></CENTER>
+<P>
+
+or
+
+<P>
+<CENTER><SAMP>Processing filter &lt;filter name&gt;</SAMP></CENTER>
+<P>
+
+If this feature is set, these messages will be suppressed.
+The feature
+<A HREF="h_config_quell_filtering_done_message"><!--#echo var="FEAT_quell-filtering-done-message"--></A>
+is related.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_post_prompt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-extra-post-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-extra-post-prompt"--></H1>
+
+By default, when you post a message to a newsgroup you are asked to confirm
+that you want to post with the question
+
+<P>
+<CENTER><SAMP>Posted message may go to thousands of readers. Really post?</SAMP></CENTER>
+<P>
+
+If this feature is set, you
+will <B>not</B> be prompted to confirm your intent to post to a newsgroup
+and your message will be posted.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_check_mail_onquit =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_check-newmail-when-quitting"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_check-newmail-when-quitting"--></H1>
+
+If this feature is set, Alpine will check for new mail after you give the
+Quit command.
+If new mail has arrived since the previous check, you will be notified
+and given the choice of quitting or not quitting.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_inbox_no_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_return-to-inbox-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_return-to-inbox-without-confirm"--></H1>
+
+Normally, when you use the TAB <A HREF="h_common_nextnew">NextNew</A>
+command and there are no more folders or newsgroups to visit, you are asked
+if you want to return to the INBOX.
+If this feature is set you will not be asked.
+It will be assumed that you do want to return to the INBOX.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_dates_to_local =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_convert-dates-to-localtime"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_convert-dates-to-localtime"--></H1>
+
+Normally, the message dates that you see in the
+MESSAGE INDEX and MESSAGE VIEW are displayed in the timezone they were sent from.
+For example, if a message was sent to you from a few timezones to the east
+it might appear that it was sent from the future;
+or if it was sent from somewhere to the west it might appear
+as if it is from yesterday even though it was sent only a few minutes ago.
+If this feature is set an attempt will be made to convert the dates
+to your local timezone to be displayed.
+<P>
+Note that this does not affect the results of Select by Date or of
+anything else other than these displayed dates.
+When viewing the message you may look at the original unconverted value of the Date
+header by using the <A HREF="h_common_hdrmode">HdrMode Command</A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tab_no_prompt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_continue-tab-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_continue-tab-without-confirm"--></H1>
+
+Normally, when you use the TAB <A HREF="h_common_nextnew">NextNew</A>
+command and there is a problem checking a folder, you are asked
+whether you want to continue with the search in the following folder or not.
+This question gives you a chance to stop the NextNew processing.
+(The checking problem might be caused by the fact that the folder does not
+exist, or by an authentication problem, or by a server problem
+of some sort.)
+<P>
+
+If this feature is set you will not be asked.
+It will be assumed that you do want to continue.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_input_history =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-save-input-history"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-save-input-history"--></H1>
+
+Many of the prompts that ask for input in the status line near the
+bottom of the screen will respond to Up Arrow and Down Arrow
+with the history of previous entries.
+For example, in the MESSAGE INDEX screen when you use the WhereIs
+command the text you entered will be remembered and can be recalled
+by using the Up Arrow key.
+Another example, when saving a message the folders saved to will
+be remembered and can be recalled using the arrow keys.
+<P>
+In the Save prompt, some users prefer that the Up and Down arrow keys
+be used for the Previous Collection and Next Collection commands
+instead of for a history of previous saves.
+If this option is set the Up and Down arrow keys will become synonyms for the
+Previous Collection and Next Collection (^P and ^N) commands in the
+prompt for the name of a folder to Save to or in the prompt for the
+name of a folder to GoTo.
+When this feature is not set (the default), ^P and ^N will change the
+collection and the arrow keys will show the history.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_confirm_role =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_confirm-role-even-for-default"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_confirm-role-even-for-default"--></H1>
+
+If you have roles, when you Reply to or Forward a message, or Compose
+a new message, Alpine
+will search through your roles for one that matches.
+Normally, if no matches are found you will be placed into the composer
+with no opportunity to select a role.
+If this feature is set, then you will be asked to confirm that you don't
+want a role.
+This will give you the opportunity to select a role (with the ^T command).
+If you confirm no role with a Return, you will be placed in
+the composer with no role.
+You may also confirm with either an &quot;N&quot; or a &quot;Y&quot;.
+These behave the same as if you pressed the Return.
+(The &quot;N&quot; and &quot;Y&quot; answers are available because they
+match what you might type if there was a role match.)
+<P>
+If you are using the alternate form of the Compose command called
+&quot;Role&quot;, then all of your roles will be available to you,
+independent of the value of this feauture and of the values set for all of
+Reply Use, Forward Use, and Compose Use.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_news_cross_deletes =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_news-deletes-across-groups"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_news-deletes-across-groups"--></H1>
+
+This feature controls what Alpine does when you delete a message in a
+newsgroup that appears in more than one newsgroup. Such a message
+is sometimes termed a &quot;crossposting&quot; in that it was posted
+across several newsgroups.
+
+<P>
+Alpine's default behavior when you delete such a message is to remove
+only the copy in the current newsgroup from view when you use the
+&quot;Exclude&quot; command or the next time you visit the newsgroup.
+
+<P>
+Enabling this feature causes Alpine to remove every occurrence of the
+message from all newsgroups it appears in and to which you are
+subscribed.
+
+<P>
+NOTE: As currently implemented, enabling this feature may increase the
+time it takes the Expunge command and newsgroup closing to complete.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_news_catchup =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_news-offers-catchup-on-close"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_news-offers-catchup-on-close"--></H1>
+
+This feature controls what Alpine does as it closes a newsgroup.
+When set, Alpine will offer to delete all messages from the newsgroup
+as you are quitting Alpine or opening a new folder.
+
+<P>
+This feature is useful if you typically read all the interesting messages
+in a newsgroup each time you open it. This feature saves you from
+having to delete each message in a newsgroup as you read it or from
+selecting all the messages and doing an
+<A HREF="h_config_enable_agg_ops">aggregate delete</A> before you
+move on to the next folder or newsgroup.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_next_thrd_wo_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_next-thread-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_next-thread-without-confirm"--></H1>
+
+This feature controls an aspect of Alpine's Next and Prev commands in
+the case where you are using one of the
+&quot;separate-index-screen&quot; styles for the configuration option
+<A HREF="h_config_thread_index_style">&quot;<!--#echo var="VAR_threading-index-style"-->&quot;</A>
+and currently have the folder sorted by a Threaded or OrderedSubject sort.
+When you are Viewing a particular thread you have a
+MESSAGE INDEX of only the messages in that thread.
+If you press the Next command with the last message in the thread highlighted
+you will normally be asked if you want to &quot;View next thread?&quot;,
+assuming there is a next thread to view.
+If this feature is set it will be assumed that you always want to view the
+next thread and you won't be asked to confirm that.
+Similarly, if the first message of the thread is highlighted and you
+press the Prev command, this feature will prevent the question
+&quot;View previous thread&quot;.
+<P>
+This feature only has an effect in the MESSAGE INDEX screen.
+If you then view a particular message from that screen and press the
+Next command, you will be sent to the next thread without being asked,
+independent of the setting of this feature.
+<P>
+The feature
+<A HREF="h_config_auto_open_unread">&quot;<!--#echo var="FEAT_auto-open-next-unread"-->&quot;</A> also has some similar effects.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_kw_braces =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_keyword-surrounding-chars"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_keyword-surrounding-chars"--></H1>
+
+This option controls a minor aspect of Alpine's MESSAGE INDEX and MESSAGE
+TEXT screens.
+If you have modified the
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot; option
+so that either the &quot;SUBJKEY&quot; or &quot;SUBJKEYINIT&quot; tokens
+are used to display keywords or their initials along with the Subject; then
+this option may be used to modify the resulting display slightly.
+By default, the keywords or initials displayed for these tokens will be
+surrounded with curly braces ({ and }) and a trailing space.
+For example, if keywords &quot;Work&quot; and &quot;Now&quot; are set for
+a message, the &quot;SUBJKEY&quot; token will normally look like
+<P>
+<CENTER><SAMP>{Work Now} actual subject</SAMP></CENTER>
+<P>
+and the SUBJKEYINIT token would look like
+<P>
+<CENTER><SAMP>{WN} actual subject</SAMP></CENTER>
+<P>
+The default character before the keywords is the left brace ({) and the
+default after the keywords is the right brace followed by a space (} ).
+<P>
+This option allows you to change that.
+You should set it to two values separated by a space.
+The values may be quoted if they include space characters.
+So, for example, the default value could be specified explicitly by setting this
+option to
+<P>
+<CENTER><SAMP><!--#echo var="VAR_keyword-surrounding-chars"-->="{" "}&nbsp;"</SAMP></CENTER>
+<P>
+The first part wouldn't need to be quoted (but it doesn't hurt).
+The second part does need the quotes because it includes a space character.
+If you wanted to change the braces to brackets you could use
+<P>
+<CENTER><SAMP><!--#echo var="VAR_keyword-surrounding-chars"-->="[" "]&nbsp;"</SAMP></CENTER>
+<P>
+Inside the quotes you can use backslash quote to mean quote, so
+<P>
+<CENTER><SAMP><!--#echo var="VAR_keyword-surrounding-chars"-->="&#92;"" "&#92;" "</SAMP></CENTER>
+<P>
+would produce
+<P>
+<CENTER><SAMP>"Work Now" actual subject</SAMP></CENTER>
+<P>
+It is also possible to color keywords in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>).
+<P>
+It is not possible to change the fact that a space character is used to
+separate the keywords if more than one keyword is set for a message.
+It is also not possible to change the fact that there are no separators
+between the keyword initials if more than one keyword is set.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_opening_sep =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_opening-text-separator-chars"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_opening-text-separator-chars"--></H1>
+
+This option controls a minor aspect of Alpine's MESSAGE INDEX screen.
+With some setups the text of the subject is followed
+by the opening text of the message if there is any room available in the index line.
+If you have configured your
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option
+to include one of the Subject tokens that causes this behavior
+(SUBJECTTEXT, SUBJKEYTEXT, or SUBJKEYINITTEXT), then this option may be used
+to modify what is displayed slightly.
+By default, the Subject is separated from the opening text of the message by
+the three characters space dash space;
+<P>
+<CENTER><SAMP>&quot;&nbsp;-&nbsp;&quot;</SAMP></CENTER>
+<P>
+Use this option to set it to something different.
+The value must be quoted if it includes any space characters.
+For example, the default value could be specified explicitly by setting this
+option to
+<P>
+<CENTER><SAMP><!--#echo var="VAR_opening-text-separator-chars"-->="&nbsp;-&nbsp;"</SAMP></CENTER>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_select_wo_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_select-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_select-without-confirm"--></H1>
+
+This feature controls an aspect of Alpine's Save, Export, and Goto commands.
+These commands all take text input to specify the name of the folder or
+file to be used, but allow you to press ^T for a list of possible names.
+If set, the selected name will be used immediately, without further
+opportunity to confirm or edit the name.
+<P>
+Some related help topics are
+<UL>
+<LI> <A HREF="h_mainhelp_aggops">Aggregate Operations</A>
+<LI> <A HREF="h_index_cmd_select">Selecting: Select and WhereIs/Select</A>,
+<LI> <A HREF="h_config_enable_agg_ops"><!--#echo var="FEAT_enable-aggregate-command-set"--></A>,
+<LI> <A HREF="h_config_auto_unselect"><!--#echo var="FEAT_auto-unselect-after-apply"--></A>.
+<LI> <A HREF="h_config_auto_unzoom"><!--#echo var="FEAT_auto-unzoom-after-apply"--></A>, and
+<LI> <A HREF="h_config_auto_zoom"><!--#echo var="FEAT_auto-zoom-after-select"--></A>.
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_save_part_wo_confirm =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_save-partial-msg-without-confirm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_save-partial-msg-without-confirm"--></H1>
+
+This feature controls an aspect of Alpine's Save command.
+By default, when you Save a message that has some deleted parts, you will
+be asked to confirm that you want to Save with a prompt that looks like:
+<P>
+<CENTER><SAMP>Saved copy will NOT include entire message! Continue?</SAMP></CENTER>
+<P>
+If this feature is set, you will not be asked.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_resentto =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-resent-to-in-rules"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-resent-to-in-rules"--></H1>
+
+This feature is turned off by default because turning it on causes problems
+with some deficient IMAP servers.
+In Alpine <A HREF="h_mainhelp_filtering">Filters</A> and other types of Rules, if the
+<A HREF="h_rule_patterns">Pattern</A>
+contains a To header pattern and this feature is turned on,
+then a check is made in the message to see
+if a Resent-To header is present, and that is used instead of the To header.
+If this feature is not turned on, then the regular To header will always
+be used.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_reg_start_for_stayopen =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-regular-startup-rule-for-stayopen-folders"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-regular-startup-rule-for-stayopen-folders"--></H1>
+
+This feature affects which message is selected as the current message
+when you enter a
+<A HREF="h_config_permlocked">Stay Open</A> folder.
+<P>
+Normally, the starting position for an incoming folder (which most Stay Open
+folders will likely be) is controlled by the
+<A HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A>.
+However, if a folder is a Stay Open folder, when you re-enter the folder
+after the first time the current message will be the same as it was when
+you left the folder.
+An exception is made if you use the TAB command to get to the folder.
+In that case, the message number will be incremented by one from what it
+was when you left the folder.
+<P>
+The above special behavior is thought to be useful.
+However, it is special and different from what you might at first expect.
+If this feature is set, then Stay Open folders will not be treated specially
+as far as the startup rule is concerned.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_current_dir =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-current-dir"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-current-dir"--></H1>
+
+This feature controls an aspect of several commands.
+If set, your &quot;current working directory&quot;
+<!--chtml if pinemode="running"-->
+(which, at least for your current Alpine &quot;session,&quot;
+is &quot;<!--#echo var="CURRENT_DIR"-->&quot;)
+<!--chtml endif-->
+will be used instead of your home directory
+<!--chtml if pinemode="running"-->
+(which, in the present configuration of your system, is
+ &quot;<!--#echo var="HOME_DIR"-->&quot;)
+<!--chtml endif-->
+for all of the following operations:<UL>
+ <LI> Export in the MESSAGE INDEX and MESSAGE TEXT screens
+ <LI> Attachment Save in the MESSAGE TEXT and ATTACHMENT TEXT screens
+ <LI> <!--chtml if pinemode="function_key"-->F4
+ <!--chtml else-->Ctrl-R
+ <!--chtml endif--> file inclusion in the COMPOSER
+ <LI> <!--chtml if pinemode="function_key"-->F5
+ <!--chtml else-->Ctrl-J
+ <!--chtml endif--> file attachment in the COMPOSER
+</UL>
+<!--chtml if pinemode="os_windows"-->
+<P>
+If you are starting PC-Alpine from a desktop icon or the Start menu,
+you can set the &quot;current drive&quot;
+by specifying it in the &quot;Start in:&quot;
+box found in the Shortcut tab of the Properties.
+<!--chtml endif-->
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_save_wont_delete =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_save-will-not-delete"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_save-will-not-delete"--></H1>
+
+This feature controls one aspect of the Save command. If set, Save will
+not mark the message &quot;deleted&quot; (its default behavior) after
+it has been copied to the designated folder.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_boring_spinner =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_busy-cue-spinner-only"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_busy-cue-spinner-only"--></H1>
+
+When Alpine is delayed for some reason it usually shows that
+something is happening with a small animated display in the status
+message line near the bottom of the screen.
+Setting this feature will cause that animation to be the same
+each time instead of having Alpine choose a random animation.
+You may turn the animation off altogether by setting the
+<A HREF="h_config_active_msg_interval"><!--#echo var="VAR_busy-cue-rate"--></A>
+option to zero.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_unsel_wont_advance =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_unselect-will-not-advance"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_unselect-will-not-advance"--></H1>
+
+This feature controls one aspect of the Unselect Current message command.
+Normally, when the Unselect current message command (:) is typed when the
+current message is selected, the message will be unselected and the next
+message will become the current message.
+If this feature is set, the cursor will not advance to the next message.
+Instead, the current message will remain the current message after
+unselecting.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_prune_uses_iso =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_prune-uses-yyyy-mm"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_prune-uses-yyyy-mm"--></H1>
+
+By default, Alpine asks monthly whether or not you would like to rename
+some folders to a new name containing the date.
+It also asks whether or not you would like to delete some old folders.
+See the <A HREF="h_config_pruning_rule"><!--#echo var="VAR_pruning-rule"--></A> option for an
+explanation.
+
+<P>
+By default, the name used when renaming a folder looks like
+<P>
+<CENTER><SAMP>&lt;foldername&gt;-&lt;month&gt;-&lt;year&gt;</SAMP></CENTER>
+<P>
+For example, the first time you run Alpine in May of 2004,
+the folder &quot;sent-mail&quot; might be renamed to
+<P>
+<CENTER><SAMP>sent-mail-apr-2004</SAMP></CENTER>
+<P>
+If this feature is set, the name used will be of the form
+<P>
+<CENTER><SAMP>&lt;foldername&gt;-&lt;yyyy&gt;-&lt;mm&gt;</SAMP></CENTER>
+<P>
+where &quot;yyyy&quot; is the year and &quot;mm&quot; is the two-digit
+month (01, 02, ..., 12).
+For the April, 2004 example above, it would instead be
+<P>
+<CENTER><SAMP>sent-mail-2004-04</SAMP></CENTER>
+<P>
+because April is the 4th month of the year.
+A reason you might want to set this feature is so that the folders
+will sort in chronological order.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_save_advances =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_save-will-advance"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_save-will-advance"--></H1>
+
+This feature controls one aspect of the Save command. If set, Save will
+(in addition to copying the current message to the designated folder) also
+advance to the next message.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_force_arrow =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_force-arrow-cursor"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_force-arrow-cursor"--></H1>
+
+This feature affects Alpine's MESSAGE INDEX display routine.
+If set, the normal inverse-video cursor will be
+replaced by a simple &quot;arrow&quot; cursor, which normally occupies the
+second column of the index display.
+<P>
+This is the same index cursor you get if you turn on
+<A HREF="h_config_force_low_speed"><!--#echo var="FEAT_assume-slow-link"--></A>, but the index
+line coloring will still be present if this feature is turned on and
+<!--#echo var="FEAT_assume-slow-link"--> is off.
+<P>
+An alternative version of the Arrow cursor is available by including the
+<A HREF="h_config_index_arrow_color">ARROW</A>
+token in the
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option.
+<P>
+It ought to be the case that this feature also affects the ATTACHMENT INDEX,
+but that is not implemented.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_force_low_speed =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_assume-slow-link"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_assume-slow-link"--></H1>
+
+UNIX Alpine only.
+<P>
+This feature affects Alpine's display routines. If set, the normal
+inverse-video cursor (used to highlight the current item in a list) will be
+replaced by an &quot;arrow&quot; cursor and other
+screen update optimizations for
+low-speed links (e.g. 2400 bps dialup connections) will be activated.
+One of the optimizations is that colored index lines (set up with Indexcolor
+Rules) will not be colored.
+If you are just turning this feature on because you like using
+the &quot;arrow&quot; cursor you may have an arrow cursor with index line
+coloring by turning this feature off and the
+<A HREF="h_config_force_arrow"><!--#echo var="FEAT_force-arrow-cursor"--></A> on.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_show_delay_cue =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-mail-check-cue"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-mail-check-cue"--></H1>
+
+If set, this feature will cause an asterisk to appear in the upper
+left-hand corner of the screen whenever Alpine checks for new mail.
+Two asterisks whenever Alpine saves (checkpoints) the state of the current
+mailbox to disk.
+
+<!--chtml if pinemode="os_windows"-->
+<P>
+In addition, PC-Alpine will display a less-than symbol, '&lt;', when
+it is trying to open a network connection (e.g, to open your INBOX
+on an IMAP
+server) or read from the network connection. A greater-than symbol,
+will be displayed when PC-Alpine is trying to write to the network
+connection (e.g, sending a command to your IMAP server).
+<!--chtml endif-->
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_color_style =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Color Style</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Color Style</H1>
+
+UNIX Alpine only.
+<P>
+If the terminal or terminal emulator you are using is capable of displaying
+colors, this option controls whether or not color will be used in Alpine.
+If you turn color on and things are set up correctly,
+you should see color appear on the screen immmediately.
+Modern terminal emulators are usually capable of displaying colors.
+<P>
+The available options include:
+<P>
+
+<DL>
+<DT>no-color</DT>
+<DD>Don't use color.
+</DD>
+
+<DT>use-termdef</DT>
+<DD>In order to decide if your terminal is capable of color, Alpine looks in
+the terminal capabilities database, TERMINFO or TERMCAP, depending on
+how Alpine was compiled.
+This is a good option to choose if you switch between a color and a non-color
+terminal with the same Alpine configuration.
+Alpine will know to use color on the color terminal because it is described
+in the termcap entry, and Alpine will know to use black and white on the
+non-color terminal.
+The Alpine Technical Notes
+<CENTER><SAMP><A HREF="http://www.washington.edu/alpine/tech-notes/">http://www.washington.edu/alpine/tech-notes/</A></SAMP></CENTER>
+have more information on configuring a TERMCAP or TERMINFO
+entry for color Alpine.
+This is usually something a system administrator does.
+</DD>
+
+<DT>force-ansi-8color</DT>
+<DD>This is probably the setting that most people should use.
+Because setting up a termcap entry is confusing and because the
+terminal capabilities database is often not correctly configured for color,
+this choice and the next may be easier for you to use.
+If your terminal emulator responds to ANSI color escape sequences, which
+many do, this option will cause Alpine to believe your terminal will respond
+to the escape sequences that produce eight different foreground and background
+colors.
+The escape sequences used to set the foreground colors are
+
+ <P><CENTER>ESC&nbsp;[&nbsp;3&nbsp;&lt;color_number&gt;&nbsp;m</CENTER><P>
+
+where the color_number is an ASCII digit between 0 and 7.
+The numbers 0 through 7 should correspond to the colors black, red, green,
+yellow, blue, magenta, cyan, and white.
+Some terminal emulators use a pre-ANSI scheme that swaps
+the colors blue and red and the colors yellow and cyan.
+This will cause the default colors to be different, but other than that
+things should work fine.
+There is also a 9th color available, the last one shown, which is the default
+color from the terminal emulator.
+When used as a background color some people refer to this color as
+&quot;transparent&quot;, which is why the letters &quot;TRAN&quot; are
+shown in the color swatch of the SETUP COLOR screen.
+The foreground transparent color is shown as
+the color of the &quot;TRAN&quot; text.
+(The transparent color will not work correctly in a PC-Alpine configuration.)
+The escape sequences used to set the background colors are the same
+as for the foreground colors except a &quot;4&quot; replaces the &quot;3&quot;.
+The escape sequences for foreground and background default colors (transparent)
+are 39m and 49m.
+<P>
+Note: With the Tera Term terminal emulator this setting works well.
+You should also have the Tera Term &quot;Full color&quot; option turned OFF.
+You may find the &quot;Full color&quot; option in Tera Term's &quot;Setup&quot;
+menu, in the &quot;Window&quot; submenu.
+</DD>
+
+<DT>force-ansi-16color</DT>
+<DD>Many terminal emulators know about the same eight colors above
+plus eight more.
+This option attempts to use all 16 colors.
+The same escape sequences as for the eight-color terminal are used
+for the first eight colors.
+The escape sequences used to set foreground colors 8-15 are the same as
+for 0-7 except the &quot;3&quot; is replaced with a &quot;9&quot;.
+The background color sequences for colors 8-15 are the same as for 0-7
+except the &quot;4&quot; is replaced with &quot;10&quot;.
+You can tell if the 16 colors are working by turning on this option
+and then going into one of the color configuration screens, for example,
+the configuration screen for Normal Color.
+If you see 16 different colors to select from (plus a 17th for
+the transparent color), it's working.
+</DD>
+
+<DT>force-xterm-256color</DT>
+<DD>Some versions of xterm (and some other terminal emulators)
+have support for 256 colors.
+The escape sequences used to set the foreground colors are
+
+ <P><CENTER>ESC&nbsp;[&nbsp;38&nbsp;;&nbsp;5&nbsp;;&nbsp;&lt;color_number&gt;&nbsp;m</CENTER><P>
+
+where the color_number is an ASCII digit between 0 and 255.
+Background colors are the same with the 38 replaced with a 48.
+The numbers 0 through 15 are probably similar to the 16 color version
+above, then comes a 6x6x6 color cube, followed by 24 colors of gray.
+The terminal default (transparent) color is the 257th color at the bottom.
+Some terminal emulators will misinterpret these escape sequences causing
+the terminal to blink or overstrike characters or to do something else
+undesirable.
+<P>
+The PuTTY terminal emulator has an option called &quot;Allow terminal to
+use xterm 256-colour mode&quot; which allows PuTTY to work well with
+this 256-color setting.
+
+</DD>
+</DL>
+
+<P>
+The normal default is &quot;no-color&quot;.
+<P>
+
+Once you've turned on color you may set the
+colors of many objects on the screen individually.
+For example, you may add colors to the status letters on the MESSAGE
+INDEX page.
+Most categories of color that Alpine supports are configurable here.
+For example, &quot;Normal Color&quot;
+is the color used to display most of the text in Alpine, and
+&quot;Reverse Color&quot; is used to display highlighted text, such as the
+current message in the MESSAGE INDEX.
+<P>
+Lines in the MESSAGE INDEX may also be colored.
+Use Setup Rules to get to the Indexcolor configuration screen.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_index_locale_dates =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-index-locale-dates"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-index-locale-dates"--></H1>
+
+This feature affects the display of dates in the MESSAGE INDEX.
+Normally an attempt is made to localize the dates
+used in the MESSAGE INDEX display to your locale.
+This is controlled with the
+LC_TIME locale setting on a UNIX system.
+On Windows the Regional Options control panel may be used to set the date format.
+At the programming level, Alpine is using the strftime routine
+to print the parts of a date.
+<P>
+If this feature is set, dates are displayed in English and
+with the conventions of the United States.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_open_unread =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_auto-open-next-unread"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_auto-open-next-unread"--></H1>
+
+This feature controls the behavior of the TAB key when traversing folders
+in the optional
+<A HREF="h_config_enable_incoming">&quot;<!--#echo var="VAR_incoming-folders"-->&quot;</A>
+collection or in optional <!--#echo var="VAR_news-collections"-->.
+<P>
+When the TAB
+(<A HREF="h_common_nextnew">NextNew</A>)
+key is pressed, and there
+are no more unseen messages in the current (incoming message or news)
+folder, Alpine will search the list of folders in the current collection for
+one containing New or Recent (new since the last time the folder was
+opened) messages.
+This behavior may be modified slightly with the
+<A HREF="h_config_tab_uses_unseen">&quot;<!--#echo var="FEAT_tab-uses-unseen-for-next-folder"-->&quot;</A>
+feature that causes Alpine to look for Unseen messages instead of Recent
+messages.
+Normally, when such a folder is found, Alpine will ask
+whether you wish to open the folder. If this feature is set, Alpine will
+automatically open the folder without prompting.
+<P>
+This feature also affects some other similar situations.
+If you have a
+<A HREF="h_config_thread_index_style"><!--#echo var="VAR_threading-index-style"--></A>
+that is equal to one of the &quot;separate-&quot; values, and you are
+viewing a thread; then when you type the NextNew command and are at the
+end of the current thread you will automatically go to the next thread
+if this feature is set.
+By default, you would be asked if you want to view the next thread.
+You will also be asked at times whether or not you want to view the next
+thread after you delete the last message in the thread.
+Setting this feature will also cause that question to be skipped.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_auto_include_reply =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_include-text-in-reply"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_include-text-in-reply"--></H1>
+
+This feature controls an aspect of Alpine's Reply command. Normally, Alpine
+will ask whether you wish to include the original message in your reply.
+If this feature is set and the feature
+<A HREF="h_config_prefix_editing">&quot;<!--#echo var="FEAT_enable-reply-indent-string-editing"-->&quot;</A>
+is <EM>not</EM> set, then the original message will be included in the reply
+automatically, without prompting.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_select_in_bold =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_show-selected-in-boldface"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_show-selected-in-boldface"--></H1>
+
+This feature controls an aspect of Alpine's
+<A HREF="h_config_enable_agg_ops">&quot;aggregate operation&quot;</A>
+commands; in
+particular, the Select and WhereIs commands. Select and WhereIs (with the
+^X subcommand) will search the current folder for messages meeting a
+specified criteria, and &quot;tag&quot; the resulting messages with an
+&quot;X&quot; in the
+first column of the applicable lines in the MESSAGE INDEX. If this feature
+is set, instead of using the &quot;X&quot; to denote a selected message,
+Alpine will
+attempt to display those index lines in boldface. Whether this is
+preferable to the &quot;X&quot; will depend on personal taste and the type of
+terminal being used.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_alt_auth =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_try-alternative-authentication-driver-first"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_try-alternative-authentication-driver-first"--></H1>
+
+This feature affects how Alpine connects to IMAP servers.
+It's utility has largely been overtaken by events,
+but it may still be useful in some circumstances.
+If you only connect to modern IMAP servers that support
+&quot;TLS&quot; you can ignore this feature.
+
+<P>
+Details:
+
+<P>
+By default, Alpine will attempt to connect to an IMAP server on the
+normal IMAP service port (143), and if the server offers &quot;Transport Layer
+Security&quot; (TLS) and Alpine has been compiled with encryption capability,
+then a secure (encrypted) session will be negotiated.
+
+<P>
+With this feature enabled, before connecting on the normal IMAP port, Alpine
+will first attempt to connect to an alternate IMAP service port (993) used
+specifically for encrypted IMAP sessions via the Secure Sockets Layer
+(SSL) method.
+If the SSL attempt fails, Alpine will then try the default
+behavior described in the previous paragraph.
+
+<P>
+TLS negotiation on the normal port is preferred, and supersedes the use of
+SSL on port 993, but older servers may not provide TLS support.
+This feature may be convenient when accessing IMAP servers that do not support
+TLS, but do support SSL connections on port 993.
+However, it is important to understand that with this feature enabled,
+Alpine will <EM>attempt</EM> to make a secure connection if that is possible,
+but it will proceed to make an insecure connection if that is the only
+option offered by the server, or if the Alpine in question has been built
+without encryption capability.
+
+<P>
+Note that this feature specifies a per-user (or system-wide) default
+behavior, but host/folder specification flags may be used to control the
+behavior of any specific connection.
+This feature interacts with some of
+the possible host/folder path specification flags as follows:
+
+<P>
+The <SAMP>/tls</SAMP> host flag, for example,
+
+<P>
+<CENTER><SAMP>{foo.example.com/tls}INBOX</SAMP></CENTER>
+<P>
+will over-ride this feature for the specified host by bypassing the
+SSL connection attempt.
+Moreover, with <SAMP>/tls</SAMP> specified,
+the connection attempt will fail if the
+service on port 143 does not offer TLS support.
+
+<P>
+The <SAMP>/ssl</SAMP> host flag, for example,
+
+<P>
+<CENTER><SAMP>{foo.example.com/ssl}INBOX</SAMP></CENTER>
+<P>
+will insist on an SSL connection for the specified host,
+and will fail if the SSL service on port 993 is not available.
+Alpine will not subsequently retry a connection
+on port 143 if <SAMP>/ssl</SAMP> is specified.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_file_dir ======
+<HTML>
+<HEAD>
+<TITLE>OPTION: File Directory</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: File Directory</H1>
+
+PC-Alpine only.
+<P>
+This value affects the Composer's &quot;^J&nbsp;Attach&quot; command,
+the Attachment Index Screen's &quot;S&nbsp;Save&quot; command, and the
+Message Index's &quot;E&nbsp;Export&quot; command.
+
+<P>
+Normally, when a filename is supplied that lacks a leading &quot;path&quot;
+component, Alpine assumes the file exists in the user's home directory.
+Under Windows operating systems, this definition isn't always clear. This
+feature allows you to explictly set where Alpine should look for files
+without a leading path.
+
+<P>
+NOTE: this feature's value is ignored if either
+<A HREF="h_config_use_current_dir"><!--#echo var="FEAT_use-current-dir"--></A> feature
+is set or the PINERC has a value for the &quot;<!--#echo var="VAR_operating-dir"-->&quot; variable.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quote_all_froms =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_save-will-quote-leading-froms"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_save-will-quote-leading-froms"--></H1>
+
+This feature controls an aspect of the Save command (and also the way
+outgoing messages are saved to an FCC folder). If set, Alpine will add
+a leading &quot;>&quot; character in front of message lines beginning with &quot;From&quot;
+when they are saved to another folder, including lines syntactically
+distinguishable from the type of message separator line commonly used on
+Unix systems.
+
+<P>
+The default behavior is that a &quot;>&quot; will be prepended only to lines
+beginning with &quot;From &quot; that might otherwise be confused with a message
+separator line on Unix systems. If pine is the only mail program you use,
+this default is reasonable. If another program you use has trouble
+displaying a message with an unquoted &quot;From &quot; saved by Alpine, you should
+enable this feature. This feature only applies to the common Unix mailbox
+format that uses message separator lines beginning with &quot;From &quot;. If
+Alpine has been configured to use a different mailbox format (possibly
+incompatible with other mail programs), then this issue does not arise,
+and the feature is irrelevant.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_normal_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Normal Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Normal Color</H1>
+
+Sets the color Alpine normally uses.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default this color is black characters on a white background.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_reverse_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Reverse Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Reverse Color</H1>
+
+Sets the color Alpine uses for reverse video characters.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_title_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Title Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Title Color</H1>
+
+Sets the color Alpine uses for the titlebar (the top line on the screen).
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Title Color is black characters on a yellow background.
+<P>
+The actual titlebar color may be different from the Title Color if
+the option
+<A HREF="h_config_titlebar_color_style">&quot;<!--#echo var="VAR_titlebar-color-style"-->&quot;</A>
+is set to some value other than the default.
+It may also be different if the current folder is closed and the
+<A HREF="h_config_titleclosed_color">Title Closed Color</A>
+color is set to something different from the Title Color.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_titleclosed_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Title Closed Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Title Closed Color</H1>
+
+Sets the color Alpine uses for the titlebar (the top line on the screen)
+when the current folder is closed.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Title Color Closed Color is white characters on a red background.
+<P>
+By setting this color to something noticeable you will be alerted to the
+fact that the current folder is closed, perhaps unexpectedly.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_status_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Status Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Status Color</H1>
+
+Sets the color Alpine uses for status messages written to the message
+line near the bottom of the screen.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Status Color is the same as the Reverse Color.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_opening_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index Opening Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index Opening Color</H1>
+
+With some setups the text of the subject is followed
+by the opening text of the message if there is any room available in the index line.
+If you have configured your
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option
+to include one of the Subject tokens that causes this behavior
+(SUBJECTTEXT, SUBJKEYTEXT, or SUBJKEYINITTEXT), you may set the color of
+this opening text with this option.
+This coloring takes place for all but the current index line, and the Opening
+Color appears to be in front of any color from an Index Color Rule.
+<P>
+By default the Index Opening Color is gray characters on a white background.
+
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_pri_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index Priority Symbol Colors</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index Priority Symbol Colors</H1>
+
+The X-Priority header is a non-standard header that is used in a
+somewhat standard way by many mail programs.
+Alpine expects the value of this header to be a digit with a value
+from 1 to 5, with 1 being the highest priority and 5 the lowest priority.
+Alpine can be made to display an indication of this priority in
+messages by use of one of the tokens
+(<A HREF="h_index_tokens">Tokens for Index and Replying</A>)
+PRIORITY, PRIORITYALPHA, or PRIORITY! in the
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option.
+
+<P>
+You may set the color used to draw these tokens by use of the colors
+Index High Priority Symbol Color and Index Low Priority Symbol Color.
+This coloring takes place for all but the current index line, and the Priority
+Color appears to be in front of any color from an Index Color Rule.
+If the priority has a value of 1 or 2 the High Priority color will be
+used,
+and if the value is 4 or 5 the Low Priority color will be used.
+<P>
+If you don't set these colors the index line will be colored in the same color as
+the bulk of the index line.
+
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_subject_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index Subject Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index Subject Color</H1>
+
+You may set the color used to draw the Subject part of the index line.
+This coloring takes place for all but the current index line, and the Subject
+Color appears to be in front of any color from an Index Color Rule.
+<P>
+If you don't set this color it will be colored in the same color as
+the bulk of the index line.
+
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_from_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index From Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index From Color</H1>
+
+You may set the color used to draw the From part of the index line.
+This coloring takes place for all but the current index line, and the From
+Color appears to be in front of any color from an Index Color Rule.
+<P>
+If you don't set this color it will be colored in the same color as
+the bulk of the index line.
+
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_arrow_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index Arrow Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index Arrow Color</H1>
+
+If you have configured your
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option
+to include the &quot;ARROW&quot; token, you may set the color of
+the arrow displayed with this option.
+If you don't set the color it will be colored in the same color as
+the bulk of the index line.
+
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_index_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Index Colors</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Index Colors</H1>
+
+You may add color to the single character symbols that give the status
+of each message in the MESSAGE INDEX.
+By default the characters &quot;+&quot;, &quot;*&quot;, &quot;D&quot;,
+&quot;A&quot;, and &quot;N&quot; show up near the left hand side of the
+screen depending on whether the message is addressed to you, and whether
+the message is marked Important, is Deleted, is Answered, or is New.
+The color for each of those characters may be specified by setting the
+&quot;Index-to-me&quot; Symbol Color,
+the &quot;Index-important&quot; Symbol Color,
+the &quot;Index-deleted&quot; Symbol Color,
+the &quot;Index-answered&quot; Symbol Color,
+and the &quot;Index-new&quot; Symbol Color.
+There are also two other symbol colors called &quot;Index-recent&quot;
+and &quot;Index-unseen&quot;.
+These two colors will only be used if you have configured your
+&quot;<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>&quot; option
+to include the &quot;IMAPSTATUS&quot; or &quot;SHORTIMAPSTATUS&quot; token.
+<P>
+The default colors for these symbols are:
+<TABLE>
+<TR> <TD> &nbsp;&nbsp;Index-to-me&nbsp;&nbsp;&nbsp;&nbsp; </TD> <TD> black on cyan </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-important </TD> <TD> white on bright red </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-deleted&nbsp;&nbsp; </TD> <TD> same as Normal Color </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-answered&nbsp; </TD> <TD> bright red on yellow </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-new&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </TD> <TD> white on magenta </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-recent&nbsp;&nbsp;&nbsp; </TD> <TD> same as Normal Color </TD> </TR>
+<TR> <TD> &nbsp;&nbsp;Index-unseen&nbsp;&nbsp;&nbsp; </TD> <TD> same as Normal Color </TD> </TR>
+</TABLE>
+<P>
+Besides coloring the message status symbols, you may also color the
+entire index line.
+This is done by using the
+<A HREF="h_rules_incols">SETUP INDEX LINE COLORS</A> screen, which you
+may get to with the commands <EM>S</EM>etup/<EM>R</EM>ules/<EM>I</EM>ndexcolor.
+When the entire line is colored that color will be &quot;behind&quot; the
+status symbol colors talked about in the paragraph above.
+<P>
+You may also color
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>
+in the index using the
+Setup/Kolor screen (<A HREF="h_config_kw_color">Keyword Colors</A>);
+the <A HREF="h_config_index_arrow_color">ARROW</A> cursor;
+the Subject using
+<A HREF="h_config_index_subject_color">Index Subject Color</A>;
+the From field using
+<A HREF="h_config_index_from_color">Index From Color</A>;
+and the
+<A HREF="h_config_index_opening_color">Index Opening</A> text.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_metamsg_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Meta-Message Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Meta-Message Color</H1>
+
+Sets the color Alpine uses in the MESSAGE TEXT screen for messages to you
+that aren't part of the message itself.
+For example, an attachment that isn't shown might produce a meta
+message something like:
+<P>
+<CENTER><SAMP> [ Part 2, &quot;comment&quot; Text/PLAIN (Name: &quot;file&quot;) ]</SAMP></CENTER>
+<P>
+If you set the
+<A HREF="h_config_quote_suppression"><!--#echo var="VAR_quote-suppression-threshold"--></A>
+option you might see
+<P>
+<CENTER><SAMP>[ 12 lines of quoted text hidden from view ]</SAMP></CENTER>
+<P>
+Warnings about suspicious looking URLs in HTML will also be colored
+with this color.
+<P>
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Meta-Message Color is black characters on a yellow background.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_keylabel_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: KeyLabel Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: KeyLabel Color</H1>
+
+Sets the color Alpine uses for the labels of the keys in the two-line
+menu at the bottom of the screen.
+For example, some of the screens have a &quot;P PrevMsg&quot; command.
+This option sets the color used when displaying &quot;PrevMsg&quot;.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the KeyLabel Color is the same as the Normal Color.
+<P>
+WARNING: Some terminal emulators have the property that the screen will scroll
+down one line whenever a character is written to the character cell in the
+lower right corner of the screen.
+Alpine can usually avoid writing a character in that corner of the screen.
+However, if you have defined a KeyLabel Color then Alpine does have to write
+a character in that cell in order to color the cell correctly.
+If you find that your display sometimes scrolls up a line this could be
+the problem.
+The most obvious symptom is probably that the titlebar at the top of the
+screen scrolls off the screen.
+Try setting KeyLabel Color to Default to see if that fixes the problem.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_keyname_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: KeyName Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: KeyName Color</H1>
+
+Sets the color Alpine uses for the names of the keys in the two-line
+menu at the bottom of the screen.
+For example, some of the screens have a &quot;P PrevMsg&quot; command.
+This option sets the color used when displaying the &quot;P&quot;.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the KeyName Color is the same as the Reverse Color.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_slctbl_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Selectable Item Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Selectable Item Color</H1>
+
+Sets the color Alpine uses for selectable items, such as URLs.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Selectable Item Color is the same as the Normal Color,
+except that it is bold.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quote_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Quote Colors</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Quote Colors</H1>
+
+Sets the colors Alpine uses for coloring quoted text in the MESSAGE TEXT
+screen.
+If a line begins with a &gt; character (or space followed by &gt;)
+it is considered a quote.
+That line will be given the Quote1 Color (first level quote).
+If there is a second level of quoting then the Quote2 Color will be used.
+Alpine considers there to be a second level of quoting if that first &gt; is
+followed by another &gt; (or space followed by &gt;).
+If there are characters other than whitespace and &gt; signs, then it isn't
+considered another level of quoting.
+Similarly, if there is a third level of quoting the Quote3 Color will be
+used.
+If there are more levels after that the Quote Colors are re-used.
+If you define all three colors then it would repeat like Color1, Color2, Color3,
+Color1, Color2, Color3, ...
+If you only define the first two it would be
+Color1, Color2, Color1, Color2, ...
+If you define only the Quote1 Color, then the entire quote would be that
+color regardless of the quoting levels.
+By default, the Quote1 Color is black characters on a greenish-blue background;
+the Quote2 Color is black characters on a dull yellow background; and
+the Quote3 Color is black characters on a green background.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incunseen_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Incoming Unseen Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Incoming Unseen Color</H1>
+
+If the option
+<A HREF="h_config_enable_incoming_checking"><!--#echo var="FEAT_enable-incoming-folders-checking"--></A>
+is turned on it is possible to highlight the folders that contain
+unseen messages by coloring them with this color.
+By default, this is the same as the Normal Color and no highlighting is done.
+<P>
+Usually the &quot;current&quot; folder (the folder the cursor is on)
+is highlighted using reverse video.
+If the current folder is colored because it contains unseen messages then
+the color used to show that it is also the current folder is controlled
+by the
+<A HREF="h_config_index_color_style"><!--#echo var="VAR_current-indexline-style"--></A>
+feature at the top of the SETUP COLOR screen.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_signature_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Signature Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Signature Color</H1>
+
+Sets the color Alpine uses for coloring the signature in the MESSAGE TEXT
+screen. According to USENET conventions, the signature is defined as the
+paragraph following the &quot;sigdashes&quot;, that is, the special line
+consisting of the three characters
+&quot;--&nbsp;&quot; (i.e., dash, dash, and space). Alpine allows for one
+empty line right after the sigdashes to be considered as part of the
+signature.
+By default, the Signature Color is blue characters on a white background.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_prompt_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Prompt Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Prompt Color</H1>
+
+Sets the color Alpine uses for confirmation prompts and questions that
+appear in the status line near the bottom of the screen.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, the Prompt Color is the same as the Reverse Color.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_header_general_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Header General Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Header General Color</H1>
+
+Sets the color Alpine uses for the headers of a message in the MESSAGE TEXT
+screen.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+By default, this is the same as the Normal Color.
+<P>
+It is also possible to set the colors for specific header fields, for
+example the Subject, using
+<A HREF="h_config_customhdr_color"><!--#echo var="VAR_viewer-hdr-colors"--></A>.
+If both a Header General Color and a specific Viewer Header Color are set
+the specific color will override the general color, as you would expect.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_incol =====
+<HTML>
+<HEAD>
+<TITLE>Index Line Color</TITLE>
+</HEAD>
+<BODY>
+<H1>Index Line Color</H1>
+
+This option is used to set the color of a line in the index when the
+message for that line matches the Pattern.
+This colors the whole index line, except possibly the status letters,
+which may be colored separately using the
+<A HREF="h_color_setup">Setup Kolor</A> screen.
+The foreground color is the color of the actual characters and the
+background color is the color of the area behind the characters.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_usetransparent_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Use Transparent Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Use Transparent Color</H1>
+
+This is a special color supported by some terminal emulators.
+It is intended to result in the default foreground or background color
+from the terminal emulator.
+This is the color the terminal was displaying characters in before you started Alpine.
+The reason it is called Transparent is because you could set the foreground color
+to some specific color, like Red, and then set the background color to the
+Transparent Color. If it works as expected, the background color from the terminal
+window in which Alpine is running will show through but with the Red characters
+in the foreground.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_usenormal_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Use Normal Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Use Normal Color</H1>
+
+When you use this color value, the actual color used will be the same
+as the corresponding Normal Color.
+For example if your Normal Color is black on white and you set both
+the foreground and background colors here to use the Normal Color, you'll
+get black on white. If you later change the Normal Color to red on blue
+this color will also change to red on blue.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_usenone_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Use None Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Use None Color</H1>
+
+This is a special color that simply means to leave the color alone.
+It is useful for Index symbols and for Keyword Colors used in the Subject
+field of an index line.
+The most likely use is to set an explicit foreground color and then set
+the background color to the None Color.
+That will cause the symbol or keyword to be drawn in the foreground color
+with a background equal to whatever color the rest of the index line is already
+drawn in.
+You will see no visible effect unless you have assigned Indexcolor Rules to
+color index lines or you have set an actual color for the Reverse Color.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_dflt_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Default Color</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Default Color</H1>
+
+Setting default will cause the color to be the default color.
+Unsetting default is normally done by choosing a color, but in some cases
+you may want to declare the current default color to be your non-default
+choice.
+For example, the default Keyname Color is the same as the Reverse Color.
+Whenever the Reverse Color changes the Keyname Color will also change, unless
+you've changed it or unset the default box.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_bold_slctbl =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Bold</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Bold</H1>
+
+The color for this particular section may have the Bold attribute turned
+on or off.
+Setting bold will cause the characters to be bold.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_kw_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Keyword Colors</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Keyword Colors</H1>
+
+Sets the colors Alpine uses for Keyword fields in the MESSAGE INDEX screen.
+Keywords are displayed as part of the Subject by default.
+They are also displayed as part of the Subject if the tokens
+&quot;SUBJKEY&quot;, &quot;SUBJKEYTEXT&quot;, &quot;SUBJKEYINIT&quot;, or &quot;SUBJKEYINITTEXT&quot; are used in the
+<A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> option.
+Keywords may also be displayed in a column of their own in the MESSAGE INDEX
+screen by using the &quot;KEY&quot; or &quot;KEYINIT&quot; tokens.
+<P>
+For example, you might have set up a Keyword
+&quot;Work&quot; using the
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A> option in the Setup/Config screen.
+You could cause that Keyword to show up as a special color
+by setting up the Keyword Color using this option, and then including it
+in the MESSAGE INDEX screen using one of the tokens listed above in the
+<!--#echo var="VAR_index-format"-->.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_customhdr_color =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_viewer-hdr-colors"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_viewer-hdr-colors"--></H1>
+
+Sets the colors Alpine uses for specific header fields in the MESSAGE TEXT screen.
+For example, you may set the color of the Subject header or the From header.
+The foreground color is the color of the actual character and the
+background color is the color of the area behind the character.
+<P>
+In addition to setting the colors for particular headers (like the Subject)
+you may also set a color to be used for all headers unless overridden by a
+more specific Viewer Header Color.
+To do this use the
+<A HREF="h_config_header_general_color">Header General Color</A>.
+<P>
+For Header Colors,
+there is an additional line on the
+screen labeled &quot;Pattern to match&quot;.
+If you leave that blank, then the whole field for that header will
+be colored.
+However, if you give a pattern to match, the coloring will only take place
+if there is a match for that pattern in the value of the field.
+For example, if you are working on a color for the Subject header and
+you fill in a pattern of &quot;important&quot;, then only Subjects that
+contain the word &quot;important&quot; will be colored.
+<P>
+If the pattern you enter is a comma-separated list of patterns, then coloring
+happens if any of those patterns matches.
+<P>
+<A HREF="h_color_setup">Descriptions of the available commands</A>
+<P>
+Look <A HREF="h_edit_nav_cmds">here</A>
+to see the available Editing and Navigation commands.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_customhdr_pattern =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: Viewer Header Color Pattern</TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: Viewer Header Color Pattern</H1>
+
+If you leave this blank, then the whole field for the header will
+be colored.
+If you give a pattern to match, the coloring will only take place
+if there is a match for that pattern in the value of the field.
+For example, if you are working on a color for the Subject header and
+you fill in a pattern of &quot;important&quot;, then only Subjects that
+contain the word &quot;important&quot; will be colored.
+<P>
+For address headers (like From and To) and for the Newsgroups header,
+a pattern match will cause only the matched addresses or newsgroups to be
+colored.
+If there is no pattern to match, then all of the addresses or newsgroups
+in the relevant header will be colored.
+<P>
+The matching pattern may be a comma-separated list of patterns to match
+instead of a single pattern.
+For example, you could use the pattern &quot;important,urgent&quot; which would
+cause a match if either the word &quot;important&quot; or the word
+&quot;urgent&quot; appeared in the value of the header.
+You could list several comma-separated email addresses in the Header
+From Color pattern so that those addresses will be colored when any of
+them appear in the From header.
+<P>
+To add a new matching pattern or change the existing pattern use the
+<!--chtml if pinemode="function_key"-->
+&quot;F4&quot;
+<!--chtml else-->
+&quot;C&quot;
+<!--chtml endif-->
+&quot;Change&quot; command that is available when the &quot;Pattern to
+match&quot; line is highlighted.
+The
+<!--chtml if pinemode="function_key"-->
+&quot;F10&quot;
+<!--chtml else-->
+&quot;D&quot;
+<!--chtml endif-->
+&quot;Delete&quot; command may be used to quickly remove all patterns
+for a particular header.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_color_setup =====
+<HTML>
+<HEAD>
+<TITLE>SETUP COLOR COMMANDS</TITLE>
+</HEAD>
+<BODY>
+<H1>SETUP COLOR COMMANDS</H1>
+<!--chtml if pinemode="function_key"-->
+<PRE>
+Available Commands -- Group 1
+-------------------------------
+F1 Display this help text
+F2 Show other available commands
+F3 Exit to MAIN MENU
+F4 Select the highlighted foreground or background color
+F5 Move to previous line
+F6 Move to next line
+F7 Previous page
+F8 Next page
+F9 Add a config section for a header field
+F10 Restore all default colors (for all sections)
+F11 Print color configuration screen
+F12 Whereis (search for word)
+
+Available Commands -- Group 2
+-------------------------------
+F1 Display this help text
+F2 Show other available commands
+F5 Delete config section for highlighted header field
+F6 Shuffle the order of Header Color sections
+</PRE>
+<!--chtml else-->
+<PRE>
+General commands
+-------------------------------------------------
+ ? Display this help text E Exit back to MAIN MENU
+ P Previous Line N Next Line
+ - Previous page Spc (space bar) Next page
+ W WhereIs (search for word) % Print color configuration screen
+
+Color Setup Commands
+------------------------------------------------
+ * Select the highlighted foreground or background color
+ A Add a config section for a header field
+ D Delete config section for highlighted header field
+ R Restore all default colors (for all sections)
+ $ Shuffle the order of Header Color sections
+</PRE>
+<!--chtml endif-->
+
+<H2>Description of the Setup Color Screen</H2>
+
+From this screen you may turn on color and set the colors of
+various parts of the Alpine display.
+For help on turning on color move your cursor into the Color Style section
+at the top of the Setup Color screen and ask for help.
+
+<P>
+There are several sections in the Setup Color Screen.
+At the top are some settings that handle the style of color used
+with your terminal emulator (UNIX only), and some settings that
+control how the current indexline and the titlebar are colored.
+After that comes a long section called GENERAL COLORS that allows
+you to set the color of many elements in the Alpine screens.
+For example, the color of the titlebar, status messages,
+selectable links, quotes and signatures in messages, and so on.
+After that is a section called INDEX COLORS that allows you to
+set the colors of various pieces of the displayed index lines in
+the MESSAGE INDEX screen.
+The next section is HEADER COLORS. This is for coloring headers of
+messages in the MESSAGE TEXT screen in just about any way you would like.
+Finally, the KEYWORD COLORS section allows you to highlight
+<A HREF="h_config_keywords"><!--#echo var="VAR_keywords"--></A>
+in the MESSAGE INDEX screen.
+
+<P>
+To change a color, highlight the color you want to change (for example,
+the Status Color) by moving
+the cursor into it.
+You may want to read the help text for the color to see a brief desription
+of what you are coloring.
+Then press &quot;C&quot; for Change to set the color to something new.
+That will put you into a screen with two columns of colors, one for
+the foreground color and one for the background color.
+The foreground color is just the color you want the actual characters
+to be and the background color is the color of the rest of the rectangle
+behind the characters.
+Select the foreground and background colors desired by using the Next and
+Prev keys to highlight the color, and the * command to select it.
+<P>
+To set a color to its default value, set the X in the Default line at
+the bottom of the list of colors.
+<P>
+
+The HEADER COLORS section is a little bit different from the others.
+Besides coloring the specific fields that Alpine knows about, you may also
+color specific header fields when viewing a message in the MESSAGE TEXT
+screen.
+For example, you may color the Subject header a particular color.
+There are a few commands for use with headers.
+The &quot;AddHeader&quot; command adds a section to the color
+configuration screen that allows you to set the color for that header.
+You'll be asked for the name of the header field you want to color.
+If you wanted to color the Subject, you would answer
+with the word &quot;subject&quot;.
+Once you've added a header field, the color setting works just like the
+other color fields, except that there is an additional line on the
+configuration screen labeled &quot;Pattern to match&quot;.
+If you leave that blank, then the whole field for that header will always
+be colored.
+However, if you give a pattern to match, the coloring will only take place
+if there is a match for that pattern in the value of the field.
+For example, if you are working on a color for the Subject header and
+you fill in a pattern of &quot;important&quot;, then only Subjects that
+contain the word &quot;important&quot; will be colored.
+<P>
+The &quot;DeleteHdr&quot; command removes a header section from the
+configuration altogether.
+The &quot;Shuffle&quot; command changes the order of header sections.
+This is only necessary if you use header sections with pattern fields.
+For example, if you have two Subject sections, one with one pattern and
+another with another pattern, and the subject for a particular message
+happens to match both, then the color from the first match is used.
+<P>
+
+The command &quot;RestoreDefs&quot; will restore all of the default colors.
+Each section will change to the default value used for that section when
+color is first enabled.
+When you restore all default colors the color settings for the Header Colors
+will be unset (since that's the default), but the header fields you've
+added will remain so that you may easily reset them.
+In order to get rid of them completely you'd have to use
+the &quot;DeleteHdr&quot; command.
+
+<P>
+Remember that <A HREF="h_rules_incols">Index Line Colors</A>
+may be set with matching rules and that is configured separately from
+the rest of the color settings described here.
+It is configured in the Setup/Rules/Indexcolors section of the configuration screen
+instead of in the Setup/Kolor section.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_news_uses_recent ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_news-approximates-new-status"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_news-approximates-new-status"--></H1>
+
+This feature causes certain messages to be marked as "New" in the
+MESSAGE INDEX of newsgroups.
+This feature is set by default.
+
+<P>
+
+When opening a newsgroup, Alpine will consult your "newsrc" file and
+determine the last message you have previously disposed of via the "D"
+key. If this feature is set, any subsequent messages will be shown in the
+Index with an "N", and the first of these messages will be highlighted.
+Although this is only an approximation of true "New" or "Unseen"
+status, it provides a useful cue to distinguish more-or-less recent
+messages from those you have seen previously, but are not yet ready to
+mark deleted.
+
+<P>
+
+Background: your "newsrc" file (used to store message status information
+for newsgroups) is only capable of storing a single flag, and Alpine uses
+this to record whether or not you are "done with" a message, as
+indicated by marking the message as "Deleted". Unfortunately, this
+means that Alpine has no way to record exactly which messages you have
+previously seen, so it normally does not show the "N" status flag for
+any messages in a newsgroup. This feature enables a starting
+*approximation* of seen/unseen status that may be useful.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_expose_hidden_config =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_expose-hidden-config"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_expose-hidden-config"--></H1>
+
+If set, this causes configuration options and features that are normally
+hidden from view to be editable in the Setup/Config screen.
+
+<P>
+The purpose of this feature is to allow you to change configuration
+features and variables that are normally hidden.
+This is particularly useful if you are using a remote configuration,
+where it is difficult to edit the contents manually, but it may also be used
+on a local pinerc configuration file.
+<P>
+If set, several configuration variables and features that are normally
+hidden from view will show up in the Setup/Configuration screen.
+They will be at the bottom of the configuration screen.
+You can find them by searching for the words &quot;hidden configuration&quot;.
+<P>
+
+Note that this is an advanced feature that should be used with care.
+The reason that this part of the configuration is normally hidden is because
+there is a significant potential for causing problems if you change these
+variables.
+If something breaks after a change try changing it back to see if that is
+what is causing the problem.
+There are also some variables that are normally hidden because they are
+manipulated through Alpine in other ways.
+For example, colors are normally set using the Setup/Kolors screen and
+the &quot;<!--#echo var="VAR_address-book"-->&quot; variable is normally set using
+the Setup/AddressBooks screen, so there is little reason to edit these directly.
+The &quot;<!--#echo var="VAR_incoming-folders"-->&quot; variable is normally changed by using
+the Add, Delete, and Rename commands in the FOLDER LIST screen,
+and the &quot;<!--#echo var="VAR_last-time-prune-questioned"-->&quot; variable is normally used
+internally by Alpine and not set directly by the user.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_signature_edit =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-signature-edit-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-signature-edit-cmd"--></H1>
+
+If set, this disables the editing of signature files from within
+the Setup/Config screen.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_roles_templateedit =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-roles-template-edit"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-roles-template-edit"--></H1>
+
+If set, this disables the editing of template files within the
+Role setup screen.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_roles_sigedit =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-roles-sig-edit"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-roles-sig-edit"--></H1>
+
+If set, this disables the editing of signature files within the
+Role setup screen.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_roles_setup =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-roles-setup-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-roles-setup-cmd"--></H1>
+
+If set, this disables the Setup/Rules/Roles command.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_pipes_in_templates =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-pipes-in-templates"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-pipes-in-templates"--></H1>
+
+By default, if a template file name is followed by a vertical bar (|) then
+that causes the file to be executed to produce the text for the template.
+If this feature is set, then this is not allowed.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_pipes_in_sigs =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-pipes-in-sigs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-pipes-in-sigs"--></H1>
+
+By default, if a signature file name is followed by a vertical bar (|) then
+that causes the file to be executed to produce the text for the signature.
+If this feature is set, then this is not allowed.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_password_cmd =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-password-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-password-cmd"--></H1>
+
+If set, then the Setup/Newpassword command is disabled.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_password_caching =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-password-caching"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-password-caching"--></H1>
+
+Normally, loginname/password combinations are cached in Alpine so that
+you do not have to enter the same password more than once in a session.
+A disadvantage to this approach is that the password must be stored in
+the memory image of the running Alpine in order that it can be re-used.
+In the event that Alpine crashes and produces a core dump, and that core
+dump is readable by others, the loginname and password could be read
+from the core dump.
+<P>
+If this feature is set, then the passwords will not be cached and you
+will have to retype the password whenever Alpine needs it.
+Even with this feature set there is still some chance that the core
+file will contain a password, so care should be taken to make the
+core files unreadable.
+<P>
+NOTE: If PASSFILE caching is enabled, this does not disable it.
+That is a separate and independent feature.
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_kb_lock =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-keyboard-lock-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-keyboard-lock-cmd"--></H1>
+
+If set, then the Keyboard Lock command is removed from the MAIN MENU.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_config_cmd =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-config-cmd"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-config-cmd"--></H1>
+
+If set, then the Setup/Config screen is disabled.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_allow_chg_from =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_allow-changing-from"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_allow-changing-from"--></H1>
+
+This feature affects Alpine's handling of the &quot;From:&quot; header field
+in the "<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>" configuration
+option.
+<P>
+If this feature is set then the From line can be changed just like
+all the other header fields that can be changed.
+This feature defaults to <EM>ON</EM>.
+<P>
+Even with this feature turned ON (the default) you will not be able
+to change the From header unless you add it to your list of
+<A HREF="h_config_custom_hdrs"><!--#echo var="VAR_customized-hdrs"--></A>.
+You may also want to change the
+<A HREF="h_config_comp_hdrs"><!--#echo var="VAR_default-composer-hdrs"--></A>
+if you want the From header to always show up in the composer without
+having to type the Rich Headers command first.
+<P>
+Note that many sites restrict the use of this feature in order to
+reduce the chance of falsified addresses and misdirected mail.
+If you want to change the value of what gets included in the From header
+in messages you send
+look <A HREF="h_config_change_your_from">here</A> for a description.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_collate =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-setlocale-collate"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-setlocale-collate"--></H1>
+
+This is a hard to understand feature that should only be used in rare cases.
+Normally, the C function call
+<P>
+<CENTER><SAMP>setlocale(LC_COLLATE, &quot;&quot;)</SAMP></CENTER>
+<P>
+is used by Alpine.
+If you want to try turning it off,
+setting this feature will turn it off.
+This part of the locale has to do with the sort order
+of characters in your locale.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_attach_extra_prompt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-attachment-extra-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-attachment-extra-prompt"--></H1>
+
+By default, when you attempt to view an attachment externally
+from the &quot;Attachment View&quot; screen, you are asked if you
+really want to view the selected attachment.
+
+<P>
+If this feature is set, you will <B>not</B> be prompted to confirm
+your selection. Prior to Alpine and to Pine 4.50, the default behavior was to not
+prompt. This feature was added for those wanting to preserve that
+behavior (along with
+<A HREF="h_config_quell_attach_ext_warn"><!--#echo var="FEAT_quell-attachment-extension-warn"--></A>).
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_attach_ext_warn =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-attachment-extension-warn"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-attachment-extension-warn"--></H1>
+
+<P>
+This feature suppresses the extra warning you can get when trying
+to view an attachment for which there is no mime-type match. Turning
+on this feature will just run the program according to extension
+instead of first warning the user that it will run according to the
+file's extension.
+<P>
+This feature can be used along side
+<A HREF="h_config_quell_attach_extra_prompt"><!--#echo var="FEAT_quell-attachment-extra-prompt"--></A>
+to preserve the behavior exhibited in Pine versions prior to Pine 4.50.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mailcap_params =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-mailcap-param-substitution"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-mailcap-param-substitution"--></H1>
+
+If set, this will allow mailcap named parameter substitution to occur
+in mailcap entries.
+By default, this is turned off to prevent security problems that may occur
+with some incorrect mailcap configurations.
+For more information, see RFC1524 and look for "named parameters" in the
+text of the RFC.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_disable_shared =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_disable-shared-namespaces"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_disable-shared-namespaces"--></H1>
+
+If this feature is set, the automatic search for namespaces &quot;ftp&quot;,
+&quot;imapshared&quot;, and &quot;imappublic&quot; by the underlying library
+will be disabled.
+The reason this feature exists is because there are some implementations
+of system password lookup routines that are very slow when presented with
+a long loginname that does not exist.
+This feature could be set to prevent the delay at startup time when the
+names above are searched for in the password file.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_hide_nntp_path =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_hide-nntp-path"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_hide-nntp-path"--></H1>
+
+Normally the Path header that Alpine generates when posting to a newsgroup
+contains the name of the computer from which the message is being sent and
+the user name.
+Some believe that this information is used by spammers.
+If this feature is set, that information will be replaced with the text
+<P>
+<CENTER><SAMP>not-for-mail</SAMP></CENTER>
+<P>
+instead.
+<P>
+It should be noted that many servers being connected to will still reveal
+the information that this feature attempts to protect.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_no_bezerk_zone =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-berkeley-format-timezone"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-berkeley-format-timezone"--></H1>
+
+POSIX mandates a timezone in UNIX mailbox format folder delimiters
+(the line that begins with From <SPACE>).
+Some versions of Berkeley mail have trouble with this, and don't recognize
+the line as a message delimiter.
+If this feature is set, the timezone will be left off the delimiter line.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_domain_warn =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-maildomain-warning"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-maildomain-warning"--></H1>
+
+When your configuration is set up so that your domain name contains no dots,
+it is usually a configuration error.
+By default, Alpine will warn you about this when you start it up.
+You will see a warning message that looks like
+
+<P>
+<CENTER><SAMP>Incomplete maildomain &quot;&lt;domain&gt;&quot;.</SAMP></CENTER>
+
+<P>
+If this feature is set, the warning is turned off.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_imap_env =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-imap-envelope-update"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-imap-envelope-update"--></H1>
+
+In the MESSAGE INDEX screen, if the open folder is being accessed
+using IMAP, Alpine normally tries to paint the index lines on the screen
+as soon as the information arrives from the IMAP server.
+This means that the index information makes it onto the screen more quickly
+than it otherwise would.
+This sometimes results in behavior that bothers some users.
+For example, when paging to a new page of the index, it may be possible for
+the lines to be painted on the screen in a random order, rather than from
+top to bottom.
+<P>
+
+Setting this feature causes Alpine to wait for all of the information
+to be gathered before it paints the index screen.
+Once it collects all of the information, the screen will be painted quickly
+from top to bottom.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_news_env =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-news-envelope-update"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-news-envelope-update"--></H1>
+
+In the MESSAGE INDEX screen, if the open folder is being accessed
+using NNTP (News), Alpine normally tries to paint the index lines on the screen
+as soon as the information arrives from the NNTP server.
+This means that the index information makes it onto the screen more quickly
+than it otherwise would.
+This sometimes results in behavior that bothers some users.
+For example, when paging to a new page of the index, it may be possible for
+the lines to be painted on the screen in a random order, rather than from
+top to bottom.
+<P>
+
+Setting this feature causes Alpine to wait for all of the information
+to be gathered before it paints the index screen.
+Once it collects all of the information, the screen will be painted quickly
+from top to bottom.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_content_id =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-content-id"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-content-id"--></H1>
+
+This feature changes the behavior of Alpine when sending messages.
+It is intended to work around a bug in Microsoft's Outlook XP mail user
+agent.
+As of this writing, Microsoft has acknowledged the bug but
+has not added it to the Knowledge Base.
+We have been told that there will be a post-SP1 hotfix for Outlook XP.
+This particular bug has bug fix number OfficeQFE:4781.
+The nature of the bug is that messages with attachments that
+contain a Content-ID header (which standard Alpine attachments do)
+do not show the attachment indicator (a paperclip) when viewed with
+Outlook XP.
+So the user has no indication that the message contains an attachment.
+
+<P>
+If this feature is set then Alpine will remove most Content-ID headers
+before sending a message.
+If an attachment is of type MESSAGE, then the existing Content-ID headers
+inside the message will be left intact.
+This would only happen with Alpine if a message was forwarded as an attachment
+or if a message with a message attached was forwarded.
+Similarly if an attachment of type MULTIPART/ALTERNATIVE is forwarded,
+the Content-ID headers of the alternative parts will not be removed.
+
+<P>
+Because the Content-ID header is a standard part of MIME it is possible
+that setting this feature will break something.
+For example, if an attachment has a Content-ID header that is necessary
+for the correct functioning of that attachment, it is possible that Alpine
+may remove that header when the attachment is forwarded.
+However, it seems fairly safe at this time.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_winpos_in_config =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_store-window-position-in-config"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_store-window-position-in-config"--></H1>
+
+PC-Alpine only.
+<P>
+
+Normally, PC-Alpine will store its window size and position in the
+Windows Registry.
+This is convenient if you want to use the same remote
+configuration from more than one PC.
+If you use multiple configuration files to start PC-Alpine, you may want
+to store the window size and position in the configuration file instead
+of in the Registry.
+Setting this feature causes the value to be stored in
+<A HREF="h_config_window_position">Window-Position</A>.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_ssl_largeblocks =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-ssl-largeblocks"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-ssl-largeblocks"--></H1>
+
+PC-Alpine only.
+<P>
+This feature changes the behavior of fetching messages
+and attachments so that the message data is fetched in chunks no larger
+than 12K bytes.
+This works around a bug in Microsoft's SSL/TLS support.
+Some versions of Microsoft SSL are not able to read full-sized (16K)
+SSL/TLS packets.
+Some servers will send such packets and this will
+cause PC-Alpine to crash with the error
+
+<P>
+<CENTER><SAMP>incomplete SecBuffer exceeds maximum buffer size</SAMP></CENTER>
+
+<P>
+Microsoft is aware of the problem and has developed a hotfix for it, it is
+discussed in article 300562 in the Microsoft Knowledge Base.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_partial =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-partial-fetching"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-partial-fetching"--></H1>
+
+Partial fetching is a feature of the IMAP protocol.
+By default, Alpine
+will use partial fetching when copying the contents of a message or attachment
+from the IMAP server to Alpine.
+This means that the fetch will be done in many
+small chunks instead of one big chunk. The main benefit of this approach is
+that the fetch becomes interruptible. That is, the user can type <EM>^C</EM>
+to stop the fetch early. In some cases partial fetching may cause a performance
+problem so that the fetching of data takes significantly longer when partial
+fetching is used. Turning on this feature will turn off partial fetching.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_personal_name_prompt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-personal-name-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-personal-name-prompt"--></H1>
+
+PC-Alpine only. This feature quells the prompting for a
+<A HREF="h_config_pers_name">personal name</A>. This
+prompt normally happens before composing a message, and only happens when
+there is no personal name already set.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_user_id_prompt =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-user-id-prompt"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-user-id-prompt"--></H1>
+
+PC-Alpine only. This feature quells the prompting for a
+<A HREF="h_config_user_id"><!--#echo var="VAR_user-id"--></A>
+if the information can be obtained from the login name used
+to open the INBOX. Normally, this prompt happens before composing
+a message, and only happens when there is no user-id already set
+in the configuration.
+<P>
+With this feature set, composing a message is only possible after
+establishing a connection to the INBOX.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_save_aggregates =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_save-aggregates-copy-sequence"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_save-aggregates-copy-sequence"--></H1>
+
+This feature will optimize an aggregate copy operation, if
+possible, by issuing a single IMAP <EM>COPY</EM> command with a
+list of the messages to be copied.
+This feature is set by default.
+This may reduce network traffic and elapsed time for the Save.
+<EM>However, many IMAP servers (including the UW IMAP server) do
+not preserve the order of messages when this optimization is applied.</EM>
+If this feature is not set,
+Alpine will copy each message individually and the order of the messages
+will be preserved.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_use_system_translation =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: Use System Translation</TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: Use System Translation</H1>
+
+UNIX Alpine only.
+<P>
+Alpine normally uses its own internal software to convert between the multi-byte
+representation of characters and the Unicode representation of those
+same characters.
+It converts from the multi-byte characters your keyboard produces to Unicode,
+and from Unicode to the multi-byte characters your display expects.
+Alpine also uses its own internal software to decide how much space on
+the screen a particular Unicode character will occupy.
+
+<P>
+Setting this feature tells Alpine to use the system-supplied routines to
+perform these tasks instead.
+In particular there are three tasks and three system routines that will
+be used for these tasks.
+
+<P>
+To convert from multi-byte to Unicode the routine
+
+<P>
+<CENTER><SAMP>mbstowcs</SAMP></CENTER>
+<P>
+
+is used.
+To convert from Unicode to multi-byte the routine
+
+<P>
+<CENTER><SAMP>wcrtomb</SAMP></CENTER>
+<P>
+
+is used.
+And to find the screen width a particular Unicode character will
+occupy the routine used is
+
+<P>
+<CENTER><SAMP>wcwidth</SAMP></CENTER>
+<P>
+
+This feature has been only lightly tested.
+The internal routines should normally be used unless you run into
+a problem that you think may be solved by using the system routines.
+Note that your environment needs to be set up for these
+routines to work correctly.
+In particular, the LANG or LC_CTYPE variable in your environment will
+need to be set.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_suspend_spawns =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_use-subshell-for-suspend"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_use-subshell-for-suspend"--></H1>
+
+This feature affects Alpine's behavior when process suspension is enabled
+and then activated via the Ctrl-Z key. Alpine suspension allows one to
+temporarily interact with the operating system command &quot;shell&quot;
+without
+quitting Alpine, and then subsequently resume the still-active Alpine session.
+<P>
+
+When the <A HREF="h_config_can_suspend">&quot;<!--#echo var="FEAT_enable-suspend"-->&quot;</A> feature
+is set and subsequently the Ctrl-Z key
+is pressed, Alpine will normally suspend itself and return temporary control
+to Alpine's parent shell process. However, if this feature is set, Alpine
+will instead create an inferior subshell process. This is useful when the
+parent process is not intended to be used interactively. Examples include
+invoking Alpine via the -e argument of the Unix &quot;xterm&quot; program,
+or via a menu system.<P>
+
+Note that one typically resumes a suspended Alpine by entering the Unix
+&quot;fg&quot; command, but if this feature is set, it will be necessary to
+enter the &quot;exit&quot; command instead.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_8bit_smtp =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-8bit-esmtp-negotiation"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-8bit-esmtp-negotiation"--></H1>
+
+This feature affects Alpine's behavior when sending mail.
+By default, this feature is set.
+Internet standards
+require that all electronic mail messages traversing the global Internet
+consist of 7bit ASCII characters unless a pair of cooperating mail
+transfer agents explicitly agree to allow 8bit messages. In general,
+then, exchanging messages in non-ASCII characters requires MIME encoding.
+<P>
+However, there are now Internet standards that allow for unencoded 8bit
+exchange of messages between cooperating systems. When this feature is set
+Alpine will try to negotiate unencoded 8bit transmission during the
+sending process. Should the negotiation fail, Alpine will fall back to its
+ordinary encoding rules.
+<P>
+Note, this feature relies on your system's mail transport agent or
+configured <A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A>
+having the negotiation mechanism introduced in
+&quot;Extended SMTP&quot; (ESMTP) and the specific extension called
+&quot;8BITMIME&quot;.
+<P>
+ESMTP allows for graceful migration to upgraded mail transfer agents, but
+it is possible that this feature might cause problems for some servers.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_8bit_nntp =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-8bit-nntp-posting"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-8bit-nntp-posting"--></H1>
+
+This feature affects Alpine's behavior when posting news.
+
+<P>
+
+The Internet standard for exchanging USENET news messages (RFC-1036)
+specifies that USENET messages should conform to Internet mail standards
+and contain only 7bit characters, but much of the news transport software
+in use today is capable of successfully sending messages containing 8bit
+characters. Hence, many people believe that it is appropriate to send 8bit
+news messages without any MIME encoding.
+
+<P>
+
+Moreover, there is no Internet standard for explicitly negotiating 8bit
+transfer, as there is for Internet email. Therefore, Alpine provides the
+option of posting unencoded 8bit news messages, though not as the default.
+Setting this feature will turn OFF Alpine's MIME encoding of newsgroup
+postings that contain 8bit characters.
+
+<P>
+
+Note, articles may cross a path or pass through news transport software
+that is unsafe or even hostile to 8bit characters. At best this will only
+cause the posting to become garbled. The safest way to transmit 8bit
+characters is to leave Alpine's MIME encoding turned on, but recipients
+who lack MIME-aware tools are often annoyed when they receive MIME-encoded
+messages.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mark_for_cc =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_mark-for-cc"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_mark-for-cc"--></H1>
+
+This feature affects Alpine's MESSAGE INDEX display.
+By default, a '+' is displayed in the first column if the
+message is addressed directly to you.
+When this feature is set and the message is not addressed to you, then a
+'-' character is displayed if the message is instead Cc'd directly
+to you.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tab_uses_unseen =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_tab-uses-unseen-for-next-folder"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_tab-uses-unseen-for-next-folder"--></H1>
+
+This feature affects Alpine's behavior when using the TAB
+<A HREF="h_common_nextnew">NextNew Command</A>
+to move from one folder to the next.
+Alpine's usual behavior is to search for folders
+with <EM>Recent</EM> messages in them.
+Recent messages are messages that have arrived since the last time the
+folder was opened.
+
+<P>
+Setting this feature causes Alpine to search for <EM>Unseen</EM>
+messages instead of Recent messages.
+Unseen messages remain Unseen until you view them (or flag then as Seen with
+the <A HREF="h_common_flag">Flag Command</A>).
+Setting this feature allows you to locate messages you have not read
+instead of only recently received messages.
+When this feature is set, the feature
+<A HREF="h_config_fast_recent">&quot;<!--#echo var="FEAT_enable-fast-recent-test"-->&quot;</A>
+will have no effect, so the checking may be slower.
+
+<P>
+Another reason why you might want to use this feature is that Alpine sometimes
+opens folders implicitly behind the scenes, and this clears the
+Recent status of all messages in the folder.
+One example where this happens is when Saving or filtering a
+message to another folder.
+If that message has some <A HREF="h_config_keywords">keywords</A>
+set, then because of some shortcomings
+in the IMAP specification, the best way to ensure that those keywords are
+still set in the saved copy of the message is to open the folder and
+set the keywords explicitly.
+Because this clears the Recent status of all messages in that folder the
+folder will not be found by the NextNew command unless this feature is set.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_tab_new_only =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_tab-visits-next-new-message-only"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_tab-visits-next-new-message-only"--></H1>
+
+This feature affects Alpine's behavior when using the TAB key to move from
+one message to the next. Alpine's usual behavior is to select the next
+unread message or message flagged as "Important".
+
+<P>
+
+Setting this feature causes Alpine to skip the messages flagged as important,
+and select unread messages exclusively. Tab behavior when there are no
+new messages left to select remains unchanged.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_warn_if_subj_blank =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_warn-if-blank-subject"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_warn-if-blank-subject"--></H1>
+
+This feature affects Alpine's behavior when you send a message being
+composed.
+If this option is set, Alpine will check to see if the message about to be sent
+has a subject or not.
+If not, you will be asked if you want to send the message anyway.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_warn_if_fcc_blank =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_warn-if-blank-fcc"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_warn-if-blank-fcc"--></H1>
+
+This feature affects Alpine's behavior when you send a message being
+composed.
+If this option is set, Alpine will check to see if the message about to be sent
+has an Fcc or not.
+If not, you will be asked if you want to send the message anyway.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_warn_if_no_to_or_cc =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_warn-if-blank-to-and-cc-and-newsgroups"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_warn-if-blank-to-and-cc-and-newsgroups"--></H1>
+
+This feature affects Alpine's behavior when you send a message being
+composed.
+If this option is set, Alpine will check to see if the message about to be sent
+has either a To address, a Cc address, or a Newsgroup.
+If none of these is set,
+you will be asked if you want to send the message anyway.
+<P>
+
+This feature is closely related to
+<A HREF="h_config_auto_fcc_only"><!--#echo var="FEAT_fcc-only-without-confirm"--></A>.
+Alpine will normally ask if you want to copy a message only to the Fcc.
+This feature also applies to cases where there is a Bcc but still no To, Cc,
+or Newsgroup.
+If the <!--#echo var="FEAT_fcc-only-without-confirm"--> feature is set and you are sending a
+message with only an Fcc, then you won't be asked about sending with
+a blank To and Cc and Newsgroups header even if this feature is set.
+Similarly, if you have already been asked if you want to send to the Fcc
+only and you have answered Yes, then you won't be asked again about sending with
+blank To, Cc, and Newsgroups headers even if this feature is set.
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_dead_letter =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-dead-letter-on-cancel"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-dead-letter-on-cancel"--></H1>
+
+This feature affects Alpine's behavior when you cancel a message being
+composed. Alpine's usual behavior is to write the canceled message to
+a file named
+<!--chtml if pinemode="os_windows"-->
+&quot;DEADLETR&quot;,
+<!--chtml else-->
+&quot;dead.letter&quot; in your home directory,
+<!--chtml endif-->
+overwriting any previous message. Under
+some conditions (some routine), this can introduce a noticeable delay.
+Setting this feature will cause Alpine NOT to write canceled compositions
+into the file.
+<P>
+NOTE: Enabling this feature means NO record of canceled messages is
+maintained.
+<P>
+This feature affects the newer option
+<A HREF="h_config_deadlets"><!--#echo var="VAR_dead-letter-files"--></A>, which specifies the
+number of dead letter files to keep around.
+If this feature is set, then the <!--#echo var="VAR_dead-letter-files"--> option has no effect.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_beeps =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-status-message-beeping"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-status-message-beeping"--></H1>
+
+This feature affects Alpine's behavior when it displays status message
+(e.g., Error complaints, New mail warnings, etc). Setting this feature
+will not affect the display of such messages, but will cause those that
+emit a beep to become silent.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_suppress_user_agent =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_suppress-user-agent-when-sending"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_suppress-user-agent-when-sending"--></H1>
+
+If this feature is set then Alpine will not generate a
+<CODE>User-Agent</CODE> header in outgoing messages.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_lock_failure_warnings =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-lock-failure-warnings"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-lock-failure-warnings"--></H1>
+
+This feature affects Alpine's behavior when it encounters a problem
+acquiring a mail folder lock. Typically, a secondary file associated
+with the mail folder being opened is created as part of the locking
+process. On some systems, such file creation has been administratively
+precluded by the system configuration.
+<P>
+Alpine issues a warning when such failures occur, which can become bothersome
+if the system is configured to disallow such actions. Setting this
+feature causes Alpine to remain silent when this part of lock creation fails.
+<P>
+WARNING: systems that have been configured in a way that precludes locking
+introduce some risk of mail folder corruption when more than one program
+attempts to modify the mail folder. This is most likely to occur to one's
+INBOX or other incoming message folder.
+<P>
+See also <A HREF="h_info_on_locking">&quot;What Systems Managers Need to Know about Alpine File Locking&quot;</A>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_role_take ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-rules-under-take"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-rules-under-take"--></H1>
+
+Normally, the Take command takes addresses from a message and helps you
+put them into your Address Book.
+If you use Rules for Indexcolors, Roles, Filtering, or Scoring;
+you may find it useful
+to be able to Take information from a message's headers and put it into
+a new Rule.
+When this feature is set, you will be given an extra prompt that gives
+you the choice to Take into the Address Book or Take into a rule.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_take_export ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-take-export"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-take-export"--></H1>
+
+Normally, the Take command takes addresses from a message and helps you
+put them into your Address Book.
+When this feature is set, you will be given an extra prompt that gives you
+the choice to Take addresses into a file instead of your Address
+Book.
+Only the user@domain_name part of the address is put in the file.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_folder_internal_msg ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-folder-internal-msg"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-folder-internal-msg"--></H1>
+
+This feature determines whether or not Alpine will create
+&quot;pseudo messages&quot; in folders that are in standard Unix or
+MMDF format. <P>
+
+Alpine will normally create these pseudo messages when they are not already
+present in a standard Unix or MMDF folder. Their purpose is to record
+certain mailbox state data needed for correct IMAP and POP server
+operation, and also for Alpine to be able to mark messages as Answered when
+the Reply has been postponed.<P>
+
+Sites that do not use IMAP/POP for remote mail access, and that need to
+support mail tools that are adversely affected by the presence of the
+pseudo-messages (e.g. some mail notification tools) may enable this
+feature to tell Alpine not to create them. Note that Alpine's
+&quot;Answered&quot; flag
+capability will be adversely affected if this is done.<P>
+
+Note too that, even if this feature is enabled, Alpine will not remove
+pseudo-messages when it encounters them (e.g. those created by UW's imapd
+or ipopd servers.) This feature has no effect on folders that are not in
+standard Unix or MMDF format, as pseudo-messages are not needed in the
+other formats to record mailbox state information.
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_mulnews_as_typed ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_mult-newsrc-hostnames-as-typed"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_mult-newsrc-hostnames-as-typed"--></H1>
+
+This feature will be of little use to most users.
+It has no effect unless the feature
+<A HREF="h_config_enable_mulnewsrcs"><!--#echo var="FEAT_enable-multiple-newsrcs"--></A>
+is set.
+
+When the <!--#echo var="FEAT_enable-multiple-newsrcs"--> feature is set
+then the setting of this feature may have an effect on the names of the
+newsrc files used.
+Normally, the name of the news server will be canonicalized before it is
+used in the newsrc file name.
+For example, if you type the news server name
+
+<P>
+<CENTER><SAMP>servername</SAMP></CENTER>
+<P>
+
+it is likely that the canonical name will be something like
+
+<P>
+<CENTER><SAMP>servername.example.com</SAMP></CENTER>
+<P>
+
+Or it may be the case that
+
+<P>
+<CENTER><SAMP>servername.example.com</SAMP></CENTER>
+<P>
+
+is really an alias (a DNS CNAME) for
+
+<P>
+<CENTER><SAMP>othername.example.com</SAMP></CENTER>
+<P>
+
+If this feature is not set, then the canonicalized names will be used.
+If this feature is set, then the name you typed in (or put in your
+configuration) will be used.
+
+<P><UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL>
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_quell_empty_dirs ======
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_quell-empty-directories"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_quell-empty-directories"--></H1>
+
+This feature causes Alpine to remove from the display any directories
+that do not contain at least one file or directory. This can be useful
+to prevent overly cluttered folder lists when a collection is stored on
+a server that treats all names as both a folder and a directory.
+
+<P>
+Note, enabling this feature can cause surprising behavior! For example,
+you can still use Add to create a directory, but unless you immediately
+enter that directory and create a folder, that newly created directory
+may not be displayed next time you enter the folder list.
+
+<P>
+The description above is not quite correct.
+Only directories which potentially may hold messages are hidden if empty.
+That is, a directory which is really just a directory and is not selectable
+as a folder will not be hidden.
+Such directories can occur on servers that treat most names as both a folder
+and a directory.
+These directories are typically created implicitly when a folder is created
+inside a directory that does not yet exist.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_termcap_wins =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_termdef-takes-precedence"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_termdef-takes-precedence"--></H1>
+
+This feature may affect Alpine's low-level input routines. Termcap (or
+terminfo, depending on how your copy of Alpine was compiled and linked)
+is the name of the database that describes terminal capabilities. In
+particular, it describes the sequences of characters that various keys
+will emit.
+
+<P>
+An example would be the Up Arrow key on the keyboard. Up
+Arrow is not a distinct character on most Unix systems. When you press
+the Up Arrow key a short sequence of characters are produced. This
+sequence is supposed to be described in the termcap database by the
+&quot;ku&quot; capability (or by the &quot;kcuu1&quot; capability if you
+are using terminfo instead of termcap).
+
+<P>
+By default, Alpine defines some terminal
+escape sequences that are commonly used. For example, the sequence
+&quot;ESC&nbsp;O&nbsp;A&quot; is recognized as an Up Arrow key. The sequence
+&quot;ESC&nbsp;[&nbsp;A&quot;
+is also recognized as an Up Arrow key. These are chosen because common
+terminals like VT100's or ANSI standard terminals produce these
+sequences when you press the Up Arrow key.
+
+<P>
+If your system's termcap
+(terminfo) database assigns some other function to the sequence
+&quot;ESC&nbsp;O&nbsp;A&quot;
+it is usually ignored by Alpine. Also, if your termcap (terminfo)
+database assigns a sequence that doesn't begin with an escape
+character (<SAMP>ESC</SAMP>) it is usually ignored by Alpine.
+This usually works fine
+because most terminals emit the escape sequences that Alpine has defined
+by default. We have also found that it is usually better to have these
+defaults take precedence over the definitions contained in the database
+because the defaults are more likely to be correct than the database.
+
+<P>
+There are some terminals where this breaks down. If you want Alpine to
+believe the definitions given in your termcap (terminfo) database in
+preference to the defaults the Alpine itself sets up, then you may turn
+this feature on. Then, sequences of characters that are defined in
+both termcap (terminfo) and in Alpine's set of defaults will be
+interpreted the way that termcap (terminfo) says they should be
+interpreted. Also, if your terminal capabilities database assigns a
+sequence that doesn't begin with escape, it will not be ignored.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_cruise_mode =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-cruise-mode"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-cruise-mode"--></H1>
+
+This feature affects Alpine's behavior when you hit the
+&quot;Space&nbsp;Bar&quot; at
+the end of a displayed message. Typically, Alpine complains that the end
+of the text has already been reached. Setting this feature causes such
+keystrokes to be interpreted as if the &quot;Tab&quot; key had been hit, thus
+taking you to the next &quot;interesting&quot; message,
+or scanning ahead to the
+next incoming folder with &quot;interesting&quot; messages.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_cruise_mode_delete =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-cruise-mode-delete"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-cruise-mode-delete"--></H1>
+
+This feature modifies the behavior of Alpine's
+<A HREF="h_config_cruise_mode">&quot;<!--#echo var="FEAT_enable-cruise-mode"-->&quot;</A> feature.
+Setting this feature causes Alpine to implicitly delete read
+messages when it moves on to display the next &quot;interesting&quot; message.
+<P>
+NOTE: Beware when enabling this feature AND the
+<A HREF="h_config_auto_expunge">&quot;<!--#echo var="FEAT_expunge-without-confirm"-->&quot;</A>
+feature.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_slash_coll_entire =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_slash-collapses-entire-thread"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_slash-collapses-entire-thread"--></H1>
+
+The slash (/) command is available from the MESSAGE INDEX screen when
+the folder is sorted by either Threads or OrderedSubject, and the
+<A HREF="h_config_thread_disp_style"><!--#echo var="VAR_threading-display-style"--></A>
+is set to something other than &quot;none&quot;.
+Normally, the slash command Collapses or Expands the subthread that
+starts at the currently highlighted message, if any.
+If this option is set, then the slash command Collapses or Expands the
+<EM>entire</EM> current thread instead of just the subthread.
+The current thread is simply the top-level thread that contains the
+current message.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_color_thrd_import =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_thread-index-shows-important-color"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_thread-index-shows-important-color"--></H1>
+
+This option affects only the THREAD INDEX screen.
+Whether or not you ever see a THREAD INDEX screen depends on the setting
+of the configuration option
+<A HREF="h_config_thread_index_style">&quot;<!--#echo var="VAR_threading-index-style"-->&quot;</A>
+and on the sort order of the index.
+
+<P>
+If a message within a thread is flagged as Important
+and this option is set, then
+the entire line in the THREAD INDEX will be colored the color of the
+Index-important Symbol, which can be set using the
+<A HREF="h_color_setup">Setup Kolor</A> screen.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_allow_goto =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-goto-in-file-browser"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-goto-in-file-browser"--></H1>
+
+This feature modifies the behavior of Alpine's file browser. Setting this
+feature causes Alpine to offer the "G Goto" command in the file browser.
+That is the default.
+
+<P>
+
+The Goto command allows you to explicitly type in the desired directory.
+<P>
+&lt;End of help on this topic&gt;
+</BODY></HTML>
+====== h_config_add_ldap =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_ldap-result-to-addrbook-add"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_ldap-result-to-addrbook-add"--></H1>
+
+If both the Directory option
+<A HREF="h_config_ldap_opts_impl">&quot;Use-Implicitly-From-Composer&quot;</A>
+and this feature are set,
+then when an implicit directory lookup is done from the
+composer you will automatically be prompted to add the result of the
+directory lookup to your address book.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_patterns_compat_behavior =====
+<HTML>
+<HEAD>
+<TITLE>Rules Behavior Changes in Pine 4.50</TITLE>
+</HEAD>
+<BODY>
+<H1>Rules Behavior Changes in Pine 4.50</H1>
+
+In Alpine, Rules that contain unrecognized elements
+are ignored.
+In most cases, the unrecognized elements will be something that was
+added as a new Rules feature in a later version of Alpine.
+In versions of Pine <EM>prior</EM> to 4.50, Pine did <EM>not</EM>
+ignore rules that contained unrecognized elements.
+For example, a new element of Rules that was added in Pine 4.50 is
+Age interval.
+Suppose you add an Indexcolor rule, using version Pine 4.50 or later, that colors
+all messages older than a week red.
+Now, if you run Pine 4.44 using that same configuration file, it will not
+recognize the Age interval and so will just ignore it.
+That means that all messages will match that rule so all messages will
+be colored red when using Pine version 4.44.
+
+<P>
+This behavior was considered a bug so it is fixed in Alpine and Pine 4.50 and later.
+However, since the behavior still exists in versions prior to Pine 4.50 and
+since Filtering is a potentially destructive operation, another measure
+was taken to attempt to avoid unintentional Filtering of messages.
+The first time that you run Alpine or a Pine that is version 4.50 or greater,
+the rules in your Filters configuration variable (&quot;Patterns-Filters&quot;)
+will be copied to a new Filters configuration variable
+with a different name (&quot;Patterns-Filters2&quot;).
+From then on, Alpine will continue to use the new
+variable.
+Of course, Pine version 4.44 or lower will continue to use the old
+variable.
+That means that if you are using Alpine
+and also using a version of Pine that is older than 4.50, they will not
+share the configuration information about Filters.
+If you make a change in one version you won't see it in the other version.
+
+<P>
+Since Scoring can be used to trigger Filtering, the same thing has been
+done for Score rules.
+The old configuration variable name is (&quot;Patterns-Scores&quot;)
+and the new name is (&quot;Patterns-Scores2&quot;).
+The same is not true of Role, Indexcolor, and Other rules that are
+thought to be less harmful when a mistake is made.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_opts_sentdate =======
+<HTML>
+<HEAD>
+<TITLE>PATTERN FEATURE: Use-Date-Header-For-Age</TITLE>
+</HEAD>
+<BODY>
+<H1>PATTERN FEATURE: Use-Date-Header-For-Age</H1>
+
+By default, the Age interval of a Pattern uses a message's time of
+arrival to compute the age of the message.
+If this feature is set, the date in the message's Date header will
+be used instead.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_opts_notdel =======
+<HTML>
+<HEAD>
+<TITLE>FILTER FEATURE: Move-Only-if-Not-Deleted</TITLE>
+</HEAD>
+<BODY>
+<H1>FILTER FEATURE: Move-Only-if-Not-Deleted</H1>
+
+If this option is set then a message will be moved into the
+specified folder only if it is not marked for deletion.
+This is useful if you have multiple Alpine sessions running
+simultaneously and you don't want messages to be filtered into a
+folder more than once.
+It is also useful if you want to filter
+only the &quot;undeleted&quot; messages in a newsgroup into a folder.
+This method is not foolproof.
+There may be cases where a message
+gets marked deleted and so it is never filtered into the folder.
+For example, if you deleted it in another Alpine session or another mail
+program that didn't use the filtering rule.
+<P>
+This option has no effect if the Filter Action is not set to Move.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_filt_opts_nonterm =======
+<HTML>
+<HEAD>
+<TITLE>FILTER FEATURE: Dont-Stop-Even-if-Rule-Matches</TITLE>
+</HEAD>
+<BODY>
+<H1>FILTER FEATURE: Dont-Stop-Even-if-Rule-Matches</H1>
+
+If this option is set then this is a non-terminating rule.
+Usually, for each message, Alpine searches through the Filter Rules until
+a match is found and then it performs the action associated with that rule.
+Rules following the match are not considered.
+If this option is set then the search for matches will continue at the next
+rule.
+<P>
+If a non-terminating rule matches then the actions associated with
+that rule, except for any implied deletion of the message, are performed
+before the match for the next rule is checked.
+For example, if the non-terminating rule sets the Important status, then that
+status will be set when the next rule is considered.
+However, if the non-terminating rule Moves the message, the message will
+actually be copied instead of copied and deleted so that it is still there
+for the next rule.
+A moved message is deleted after all the relevant rules have been checked.
+The name of the &quot;Move&quot; action is confusing in this case because
+a single message can be moved to more than one folder.
+It turns the Move into a Copy instead, but it is still followed by a deletion
+at the end.
+<P>
+This option may be useful if you want to have a single message filtered to
+two different folders because it matches two different Patterns.
+For example, suppose you normally filter messages to a particular mailing
+list into one folder, and messages addressed directly to you into a second
+folder.
+If a message is sent to both you and the list (and you can tell that by
+looking at the headers of the message) this option may give you a convenient
+way to capture a copy to each folder.
+(It may also cause you to capture two copies to each folder,
+depending on whether your mail system delivers one or two copies of the
+message to you and on how the list works.)
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+===== h_mainhelp_smime ======
+<HTML>
+<HEAD>
+<TITLE>S/MIME Overview</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME Overview</H1>
+
+S/MIME is a standard for the public key encryption and signing of email.
+UNIX Alpine contains a basic implementation of S/MIME based on
+the <A HREF="http://www.openssl.org/">OpenSSL</A> libraries.
+To check if this version of Alpine supports S/MIME look at
+<A HREF="X-Alpine-Config:">Supported Options in this Alpine</A> and look
+for &quot;S/MIME&quot; under the &quot;Encryption&quot; heading.
+<P>
+Some limitations:
+<UL>
+ <LI> There is no PC-Alpine implementation.
+ <LI> There is no provision for checking for CRLs
+ (Certificate Revocation Lists) in Alpine.
+ <LI> This built-in S/MIME implementation is not compatible with and does not help with PGP.
+ <LI> There is no mechanism available for feeding either an entire incoming
+ or an entire outgoing message to an external
+ filter and using that external filter to do S/MIME or PGP processing.
+ <LI> Because the implementation currently uses OpenSSL, there is only a very
+ limited integration with the Mac OS Keychain (the storing and access of
+ public certificates).
+ <LI> There is no way to view or manipulate the lists of certificates from
+ within Alpine.
+</UL>
+<P>
+The S/MIME configuration screen is reached by going to the Main Menu and typing
+the &quot;S&nbsp;Setup&quot; command followed by &quot;M&nbsp;S/MIME&quot;.
+<P>
+
+<H2>S/MIME BASICS</H2>
+
+In order to digitally sign messages you send you must have a public/private key-pair.
+This may be obtained from a public Certificate Authority (CA) such as Thawte, Verisign, Comodo,
+or GoDaddy; or from a smaller CA such as a university which provides certificates for its
+users or a company which provides certificates for its workers.
+These certificates are bound to an email address, so the identity being verified is the
+email address not a person's name.
+<P>
+Mail is signed by using the sender's private key, which only the owner of the private key
+has access to.
+The signature is verified using the signer's public key, which anyone can
+have access to.
+With Alpine, the first time you receive a signed message the public key of the
+sender will be stored for future use.
+
+<P>
+Mail is encrypted using the recipient's public key and decrypted by
+the recipient with their private key.
+
+<P>
+You need a key of your own in order to sign outgoing messages and to have others
+encrypt messages sent to you.
+You do not need a key of your own to verify signed messages sent by others or to
+encrypt messages sent to others.
+
+<H2>ALPINE S/MIME CERTIFICATE STORAGE</H2>
+
+By default UNIX Alpine stores the certificates it uses in a directory in your
+home directory.
+The directory name is
+<P>
+<CENTER><SAMP>.alpine-smime</SAMP></CENTER>
+<P>
+Within that directory are three subdirectories.
+Each of the three subdirectories contains files with PEM-encoded contents,
+the default format for OpenSSL.
+The &quot;<SAMP>public</SAMP>&quot; directory contains public certificates.
+The files within that directory have names that are email addresses with the
+suffix &quot;<SAMP>.crt</SAMP>&quot; appended.
+An example filename is
+<P>
+<CENTER><SAMP>user@example.com.crt</SAMP></CENTER>
+<P>
+The &quot;<SAMP>private</SAMP>&quot; directory contains private keys, probably just one for
+your private key.
+These are also email addresses but with the suffix &quot;<SAMP>.key</SAMP>&quot; instead.
+The third directory is &quot;<SAMP>ca</SAMP>&quot; and it contains certificates for any Certificate
+Authorities that you want to trust but that aren't contained in the set of system CAs.
+Those files may have arbitrary names as long as they end with the
+suffix &quot;<SAMP>.crt</SAMP>&quot;.
+
+<H2>HOW TO SIGN AND ENCRYPT</H2>
+
+If you have a certificate you may sign outgoing messages.
+After typing the Ctrl-X command to send a message you will see the prompt
+<P>
+<CENTER><SAMP>Send message?</SAMP></CENTER>
+<P>
+Available subcommands include &quot;G&nbsp;Sign&quot; and &quot;E&nbsp;Encrypt&quot;.
+Typing the &quot;G&quot; command will change the prompt to
+<P>
+<CENTER><SAMP>Send message (Signed)?</SAMP></CENTER>
+<P>
+Typing the &quot;E&quot; command will change the prompt to
+<P>
+<CENTER><SAMP>Send message (Encrypted)?</SAMP></CENTER>
+<P>
+You may even type both to get
+<P>
+<CENTER><SAMP>Send message (Encrypted, Signed)?</SAMP></CENTER>
+<P>
+
+<H2>HOW TO READ SIGNED OR ENCRYPTED MESSAGES</H2>
+
+The reading of a signed message should not require any special action on
+your part.
+There should be an editorial addition at the start of the message which
+says either
+<P>
+<CENTER><SAMP>This message was cryptographically signed.</SAMP></CENTER>
+<P>
+or
+<P>
+<CENTER><SAMP>This message was cryptographically signed but the signature could not be verified.</SAMP></CENTER>
+<P>
+If an encrypted message is sent to you the encrypted text will not
+be shown.
+You will have to type the &quot;Ctrl-D&nbsp;Decrypt&quot; command (from the screen where
+you are viewing the message) and supply your passphrase when asked.
+<P>
+For a signed or encrypted message there is also a &quot;Ctrl-E&nbsp;Security&quot; command
+which gives you some information about the certificate used to sign or encrypt the message.
+
+<H2>MISCELLANEOUS</H2>
+
+You may have access to a private certificate in the PKCS12 format,
+which would sometimes be in a file with a &quot;.p12&quot; suffix.
+The UNIX shell command
+<P>
+<CENTER><SAMP>openssl pkcs12 -in file.p12 -out file.pem</SAMP></CENTER>
+<P>
+may work to convert that from the PKCS12 format to the PEM format.
+Then that file could be placed in the &quot;<SAMP>private</SAMP>&quot;
+directory with a filename of your email address followed by the
+suffix &quot;<SAMP>.key</SAMP>&quot;.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_pubcertdir =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME OPTION: <!--#echo var="VAR_smime-public-cert-directory"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME OPTION: <!--#echo var="VAR_smime-public-cert-directory"--></H1>
+
+UNIX Alpine only.
+<P>
+If the option
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>
+is set then this option will have no effect.
+<P>
+Normally, Public Certificates for use with S/MIME will be stored in the directory
+which is the value of this option.
+Those certificates will be stored in PEM format, one certificate per file.
+The name of the file for the certificate corresponding to
+<P>
+<CENTER><SAMP>emailaddress</SAMP></CENTER>
+<P>
+should be
+<P>
+<CENTER><SAMP>emailaddress.crt</SAMP></CENTER>
+<P>
+For example, a file for user@example.com would be in the file
+<P>
+<CENTER><SAMP>user@example.com.crt</SAMP></CENTER>
+<P>
+in this directory.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+Typically, the public certificates that you have will come from S/MIME signed
+messages that are sent to you.
+Alpine will extract the public certificate from the signed message and store
+it in the certificates directory.
+These PEM format public certificates look something like:
+<PRE>
+-----BEGIN CERTIFICATE-----
+MIIFvTCCBKWgAwIBAgIQD4fYFHVI8T20yN4nus097DANBgkqhkiG9w0BAQUFADCB
+rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+...
+2b9KGqDyMWW/rjNnmpjzjT2ObGM7lRA8lke4FLOLajhrz4ogO3b4DFfAAM1VSZH8
+D6sOwOLJZkLY8FRsfk63K+2EMzA2+qAzMKupgeTLqXIf
+-----END CERTIFICATE-----
+</PRE>
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_pubcertcon =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_smime-public-cert-container"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_smime-public-cert-container"--></H1>
+
+UNIX Alpine only.
+<P>
+If this option is set it will be used instead of
+<A HREF="h_config_smime_pubcertdir"><!--#echo var="VAR_smime-public-cert-directory"--></A>.
+<P>
+This option gives you a way to store certificates remotely on an IMAP server
+instead of storing the certificates one per file locally.
+In order to do that you just give this option a remote folder name for a folder
+which does not yet exist.
+The name is similar to the name you might use for a remote configuration file.
+A remote folder name might look something like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/publiccerts</SAMP></CENTER>
+<P>
+See
+<A HREF="h_valid_folder_names">Valid Folder Names</A> for more information
+about the syntax of folder names.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_privkeydir =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_smime-private-key-directory"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_smime-private-key-directory"--></H1>
+
+UNIX Alpine only.
+<P>
+In order to sign outgoing S/MIME messages you will need a
+personal digital ID certificate.
+You will usually get such a certificate from a certificate authority such as
+Thawte or CAcert.
+(In order to encrypt outgoing messages you don't need a personal digital ID, you
+need the public certificate of the recipient instead.)
+If the option
+<A HREF="h_config_smime_privkeycon"><!--#echo var="VAR_smime-private-key-container"--></A>
+is set then this option will have no effect.
+<P>
+Normally, Private Keys for use with S/MIME will be stored in the directory
+which is the value of this option.
+Those certificates will be stored in PEM format, one certificate per file.
+The name of the file for the certificate corresponding to your
+<P>
+<CENTER><SAMP>emailaddress</SAMP></CENTER>
+<P>
+should be
+<P>
+<CENTER><SAMP>emailaddress.key</SAMP></CENTER>
+<P>
+For example, if your address is user@example.com the name of the file would be
+<P>
+<CENTER><SAMP>user@example.com.key</SAMP></CENTER>
+<P>
+in this directory.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+Typically, the private key that you have will come from a Certificate
+Authority.
+The private key should be stored in a PEM format file that
+looks something like:
+<PRE>
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,2CBD328FD84CF5C6
+
+YBEXYLgLU9NJoc1V+vJ6UvcF08RX54S6jXsmgL0b5HGkudG6fhnmHkH7+UCvM5NI
+SXO/F8iuZDfs1VGG0NyitkFZ0Zn2vfaGovBvm15gx24b2xnZDLRB7/bNZkurnK5k
+VjAjZ2xXn2hFp2GJwqRdmxYNqsKGu52B99oti5HUWuZ2GFRaWjn5hYOqeApZE2uA
+...
+oSRqfI51UdSRt0tmGhHeTvybUVrHm9eKft8TTGf+qSBqzSc55CsmoVbRzw4Nfhix
+m+4TJybNGNfAgOctSkEyY/OCb49fRRQTCBZVIhzLGGmpYmkO55HbIA==
+-----END RSA PRIVATE KEY-----
+</PRE>
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_privkeycon =====
+<HTML>
+<HEAD>
+<TITLE>OPTION: <!--#echo var="VAR_smime-private-key-container"--></TITLE>
+</HEAD>
+<BODY>
+<H1>OPTION: <!--#echo var="VAR_smime-private-key-container"--></H1>
+
+UNIX Alpine only.
+<P>
+If this option is set it will be used instead of
+<A HREF="h_config_smime_privkeydir"><!--#echo var="VAR_smime-private-key-directory"--></A>.
+<P>
+This option gives you a way to store keys remotely on an IMAP server
+instead of storing the keys one per file locally.
+In order to do that you just give this option a remote folder name for a folder
+which does not yet exist.
+The name is similar to the name you might use for a remote configuration file.
+A remote folder name might look something like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/privatekeys</SAMP></CENTER>
+<P>
+See
+<A HREF="h_valid_folder_names">Valid Folder Names</A> for more information
+about the syntax of folder names.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_cacertdir =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME OPTION: <!--#echo var="VAR_smime-cacert-directory"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME OPTION: <!--#echo var="VAR_smime-cacert-directory"--></H1>
+
+UNIX Alpine only.
+<P>
+If the option
+<A HREF="h_config_smime_cacertcon"><!--#echo var="VAR_smime-cacert-container"--></A>
+is set then this option will have no effect.
+<P>
+CACert is a shorthand name for certification authority certificate.
+Normally Alpine will use the CACerts that are located in the standard system
+location for CACerts.
+It may be the case that one of your correspondents has a Digital ID which has
+been signed by a certificate authority that is not in the regular set of system certificate
+authorities.
+You may supplement the system list by adding further certificates of your own.
+These should be stored in the directory
+which is the value of this option.
+The certificates will be stored in PEM format, one certificate per file.
+The names of the files can be anything ending in &quot;.crt&quot;.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+These PEM format CA certificates look very similar to your public
+certificates for particular email addresses
+(<A HREF="h_config_smime_pubcertdir"><!--#echo var="VAR_smime-public-cert-directory"--></A>).
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_cacertcon =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME OPTION: <!--#echo var="VAR_smime-cacert-container"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME OPTION: <!--#echo var="VAR_smime-cacert-container"--></H1>
+
+UNIX Alpine only.
+<P>
+If this option is set it will be used instead of
+<A HREF="h_config_smime_cacertdir"><!--#echo var="VAR_smime-cacert-directory"--></A>.
+<P>
+This option gives you a way to store certificates remotely on an IMAP server
+instead of storing the certificates one per file locally.
+In order to do that you just give this option a remote folder name for a folder
+which does not yet exist.
+The name is similar to the name you might use for a remote configuration file.
+A remote folder name might look something like:
+<P>
+<CENTER><SAMP>{myimaphost.myschool.k12.wa.us}mail/cacerts</SAMP></CENTER>
+<P>
+See
+<A HREF="h_valid_folder_names">Valid Folder Names</A> for more information
+about the syntax of folder names.
+<P>
+Use the Setup/SMIME screen to modify this variable.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_config_smime_sign_by_default ==========
+<HTML>
+<HEAD>
+<TITLE>S/MIME FEATURE: <!--#echo var="FEAT_smime-sign-by-default"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME FEATURE: <!--#echo var="FEAT_smime-sign-by-default"--></H1>
+
+UNIX Alpine only.
+<P>
+This feature only has an effect if your version of Alpine includes
+support for S/MIME.
+It affects Alpine's behavior when you send a message.
+If this option is set, the &quot;Sign&quot; option will default to ON when sending messages.
+<P>
+Only the default value is affected.
+In any case, you may still toggle the Signing option on or off before sending
+with the &quot;G Sign&quot; command (provided you have a personal digital ID
+certificate).
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_config_smime_pubcerts_in_keychain ==========
+<HTML>
+<HEAD>
+<TITLE>S/MIME FEATURE: <!--#echo var="FEAT_publiccerts-in-keychain"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME FEATURE: <!--#echo var="FEAT_publiccerts-in-keychain"--></H1>
+
+UNIX Alpine only.
+<P>
+If this feature is set the Mac OS X default keychain will be used as the place
+to store public certificates instead of a
+<A HREF="h_config_smime_pubcertdir"><!--#echo var="VAR_smime-public-cert-directory"--></A>
+or a
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_config_smime_dont_do_smime ==========
+<HTML>
+<HEAD>
+<TITLE>S/MIME FEATURE: <!--#echo var="FEAT_smime-dont-do-smime"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME FEATURE: <!--#echo var="FEAT_smime-dont-do-smime"--></H1>
+
+UNIX Alpine only.
+<P>
+Setting this feature turns off all of Alpine's S/MIME support.
+You might want to set this if you are having trouble due to the S/MIME support.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_config_smime_encrypt_by_default ==========
+<HTML>
+<HEAD>
+<TITLE>S/MIME FEATURE: <!--#echo var="FEAT_smime-encrypt-by-default"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME FEATURE: <!--#echo var="FEAT_smime-encrypt-by-default"--></H1>
+
+UNIX Alpine only.
+<P>
+This feature only has an effect if your version of Alpine includes
+support for S/MIME.
+It affects Alpine's behavior when you send a message.
+If this option is set, the &quot;Encrypt&quot; option will default to ON when sending messages.
+<P>
+Only the default value is affected.
+In any case, you may still toggle the Encrypt option on or off before sending
+with the &quot;E Encrypt&quot; command (provided you have a the public digital ID
+for the recipient).
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+========== h_config_smime_remember_passphrase ==========
+<HTML>
+<HEAD>
+<TITLE>S/MIME FEATURE: <!--#echo var="FEAT_smime-remember-passphrase"--></TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME FEATURE: <!--#echo var="FEAT_smime-remember-passphrase"--></H1>
+
+UNIX Alpine only.
+<P>
+This feature only has an effect if your version of Alpine includes
+support for S/MIME.
+If this option is set, you will only have to enter your passphrase for your private key
+once during an Alpine session.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_pub_to_con =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Public Certs to Container</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Public Certs to Container</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the public certificates in your configured
+<A HREF="h_config_smime_pubcertdir"><!--#echo var="VAR_smime-public-cert-directory"--></A>
+to the container in your configured
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>.
+This might be useful if you decide to switch from using a cert directory to a cert
+container.
+<P>
+Warning: Any previous contents in the container will be lost.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_pub_to_dir =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Public Certs to Directory</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Public Certs to Directory</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the public certificates in your configured
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>
+to the directory in your configured
+<A HREF="h_config_smime_pubcertdir"><!--#echo var="VAR_smime-public-cert-directory"--></A>.
+This might be useful if you decide to switch from using a cert container to a cert
+directory.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_priv_to_con =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Private Keys to Container</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Private Keys to Container</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the private keys in your configured
+<A HREF="h_config_smime_privkeydir"><!--#echo var="VAR_smime-private-key-directory"--></A>.
+to the container in your configured
+<A HREF="h_config_smime_privkeydir"><!--#echo var="VAR_smime-private-key-container"--></A>.
+This might be useful if you decide to switch from using a key directory to a key
+container.
+<P>
+Warning: Any previous contents in the container will be lost.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_priv_to_dir =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Private Keys to Directory</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Private Keys to Directory</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the private keys in your configured
+<A HREF="h_config_smime_privkeydir"><!--#echo var="VAR_smime-private-key-container"--></A>.
+to the directory in your configured
+<A HREF="h_config_smime_privkeydir"><!--#echo var="VAR_smime-private-key-directory"--></A>.
+This might be useful if you decide to switch from using a key container to a key
+directory.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_cacert_to_con =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer CA Certs to Container</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer CA Certs to Container</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the CA certificates in your configured
+<A HREF="h_config_smime_cacertdir"><!--#echo var="VAR_smime-cacert-directory"--></A>
+to the container in your configured
+<A HREF="h_config_smime_cacertcon"><!--#echo var="VAR_smime-cacert-container"--></A>.
+This might be useful if you decide to switch from using a CA cert directory to a CA cert
+container.
+<P>
+Warning: Any previous contents in the container will be lost.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_cacert_to_dir =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer CA Certs to Directory</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer CA Certs to Directory</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the CA certificates in your configured
+<A HREF="h_config_smime_cacertcon"><!--#echo var="VAR_smime-cacert-container"--></A>.
+to the directory in your configured
+<A HREF="h_config_smime_cacertdir"><!--#echo var="VAR_smime-cacert-directory"--></A>.
+This might be useful if you decide to switch from using a CA cert container to a CA cert
+directory.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_pubcon_to_key =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Public Certs to Keychain</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Public Certs to Keychain</H1>
+
+Mac OS X Alpine only.
+<P>
+The Transfer command will copy the public certificates in your configured
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>
+to your default Mac OS X Keychain.
+This might be useful if you decide to switch from using a cert container to using
+the Keychain to store your public certs, which you may do by using the
+feature
+<A HREF="h_config_smime_pubcerts_in_keychain">S/MIME FEATURE: <!--#echo var="FEAT_publiccerts-in-keychain"--></A>.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_smime_transfer_pubkey_to_con =====
+<HTML>
+<HEAD>
+<TITLE>S/MIME: Transfer Public Certs to Keychain</TITLE>
+</HEAD>
+<BODY>
+<H1>S/MIME: Transfer Public Certs to Keychain</H1>
+
+UNIX Alpine only.
+<P>
+The Transfer command will copy the public certificates in your configured
+<A HREF="h_config_smime_pubcertcon"><!--#echo var="VAR_smime-public-cert-container"--></A>
+to your default Mac OS X Keychain.
+This might be useful if you decide to switch from using a cert container to using
+the Keychain to store your public certs.
+<P>
+<UL>
+<LI><A HREF="h_mainhelp_smime">General S/MIME help</A>
+</UL><P>
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_lame_list_mode =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-lame-list-mode"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-lame-list-mode"--></H1>
+
+This feature modifies the method Alpine uses to ask your IMAP
+server for folder names to display in the FOLDER LIST screen.
+It is intended to compensate for a small set of IMAP servers that
+are programmed to ignore a part of the request, and thus respond
+to Alpine's query with nonsensical results.
+<P>
+
+If you find that Alpine is erroneously displaying blank folder lists,
+try enabling this feature.
+<P>
+
+NOTE: Enabling this feature has consequences for the Goto and Save
+commands. Many servers allow access to folders outside the area
+reserved for your personal folders via some reserved character,
+typically '#' (sharp), '~' (tilde) or '/' (slash). This mechanism
+allows, at the Goto and Save prompts, quick access to folders
+outside your personal folder collection without requiring a specific
+collection definition. This behavior will generally not be available
+when this feature is enabled.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_mulnewsrcs =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_enable-multiple-newsrcs"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_enable-multiple-newsrcs"--></H1>
+
+This feature makes it so Alpine can use multiple newsrcs based on
+the news server being connected to, which allows for separate lists
+of subscribed-to newsgroups. When this feature is not set, there is only
+one list of newsgroups.
+<P>
+Under this feature, the name of a newsrc is based on the news server.
+For example, if your <a href="h_config_newsrc_path"><!--#echo var="VAR_newsrc-path"--></a>
+is set to &quot;.newsrc&quot;, and the news server you are connecting to is
+news.example.com, then the newsrc to be used is .newsrc-news.example.com.
+Setting this feature for the first time will allow for the option of using
+your old newsrc the next time you read news.
+<P>
+If this feature is set, then the feature
+<A HREF="h_config_mulnews_as_typed"><!--#echo var="FEAT_mult-newsrc-hostnames-as-typed"--></A>
+also may affect the name of the newsrc file that is used.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_ab_export_vcard =======
+<HTML>
+<HEAD>
+<TITLE>Address Book Export Format</TITLE>
+</HEAD>
+<BODY>
+<H1>Address Book Export Format</H1>
+
+You are exporting address book data from Alpine to a file outside of Alpine.
+You are being asked to choose the format of the export.
+Here are the choices:
+
+<DL>
+<DT><EM>A</EM>ddress List</DT>
+<DD>
+The addresses from the address book entries you are saving
+from will be saved one address per line.
+Address book lists (those with more than one address) will have
+all of their addresses saved separately.
+</DD>
+
+<DT><EM>V</EM>Card</DT>
+<DD>
+The entries will be saved in
+<A HREF="h_whatis_vcard">vCard</A> format.
+</DD>
+
+<DT><EM>T</EM>ab Separated</DT>
+<DD>
+The entries will be saved in tab-separated columns.
+There will be just 4 columns of data that correspond to Alpine's
+Nickname field, Full Name field, Address field, and Comment field.
+It might prove useful to Select only the Simple, non-List address book
+entries before Saving.
+</DD>
+
+<DT><EM>^C</EM> Cancel</DT>
+<DD>
+Cancel out of the Save.
+</DD>
+
+</DL>
+
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_predict_nntp_server =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_predict-nntp-server"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_predict-nntp-server"--></H1>
+
+This feature allows Alpine to assume that the open NNTP server at the
+time of composition is the NNTP server to which the message should be
+posted. This is especially recommended when there are multiple News
+collections. If this feature is not set, Alpine will try to post to the first server in
+the <a href="h_config_nntp_server"><!--#echo var="VAR_nntp-server"--></a> variable. Setting
+this feature also negates the need to add News collection servers to
+the <!--#echo var="VAR_nntp-server"--> variable.
+<P>
+This feature can be especially handy when used in conjunction with
+<a href="h_config_enable_mulnewsrcs"><!--#echo var="FEAT_enable-multiple-newsrcs"--></a>.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_nntp_search_uses_overview =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_nntp-search-uses-overview"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_nntp-search-uses-overview"--></H1>
+
+This feature should probably be turned on unless it causes trouble.
+The results of the NNTP overview command (XOVER) may be used to help
+with some searches in news groups.
+It should result in quicker response time.
+Turning this feature on apparently causes search results which are
+different from what you would get with the feature turned off on some
+servers.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_thread_sorts_by_arrival =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_thread-sorts-by-arrival"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_thread-sorts-by-arrival"--></H1>
+
+This feature affects how a threading sort arranges threads. The default way
+to arrange threads is by the date of the earliest message in the thread.
+This feature arranges threads by the last message to arrive in a thread.
+<P>
+This feature causes old threads that get recent messages to sort to the bottom,
+where previously a message arrival to a thread would not rearrange the order of
+that thread.
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_textplain_int =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: <!--#echo var="FEAT_show-plain-text-internally"--></TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: <!--#echo var="FEAT_show-plain-text-internally"--></H1>
+
+This feature modifies the method Alpine uses to display Text/Plain
+MIME attachments from the Attachment Index screen. Normally, the
+&quot;View&quot; command searches for any externally defined (usually
+via the
+&quot;<A HREF="h_config_mailcap_path">Mailcap</A>&quot; file) viewer,
+and displays the selected text within that viewer.
+
+<P>
+Enabling this feature causes Alpine to ignore any external viewer
+settings and always display text with Alpine's internal viewer.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wp_columns =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_wp-columns"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_wp-columns"--></H1>
+
+Web Alpine only.
+<P>
+This configuration setting specifies the number of horizontal characters
+used to format various WebAlpine pages. Smaller values will tend to reduce
+the amount of horizontal scrolling required to view pages within narrow
+browsers, such as those found on PDAs, and larger values will tend to
+spread more information across the page.
+
+<P>
+The Message List page uses the width to determine how many characters
+to assign each field. Note, a smaller value may result in a disproportionate
+amount of blank space between fields on each line. Similarly, a large
+value may result in cramped fields or horizontal scrolling.
+
+<P>
+The Message View page uses this value to determine when to wrap lines
+in displayed message text. Note, a smaller value may result in jagged
+right margins or confusing quoting. A larger value may cause lines of text to
+run beyond the browser's right edge, requiring horizontal scrolling.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wp_state =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_wp-state"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_wp-state"--></H1>
+
+Web Alpine only.
+<P>
+Various aspects of cross-session state.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wp_aggstate =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_wp-aggstate"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_wp-aggstate"--></H1>
+
+Web Alpine only.
+<P>
+Aggregate operations tab state.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wp_indexlines =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_wp-indexlines"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_wp-indexlines"--></H1>
+
+Web Alpine only.
+<P>
+Number of index lines in table.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_wp_indexheight =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_wp-indexheight"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_wp-indexheight"--></H1>
+
+Web Alpine only.
+<P>
+Index table row height.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_rss_news =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_rss_news"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_rss-news"--></H1>
+
+Web Alpine only.
+<P>
+RSS News feed.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_rss_weather =====
+<HTML>
+<HEAD>
+<TITLE>WEB ALPINE OPTION: <!--#echo var="VAR_rss-weather"--></TITLE>
+</HEAD>
+<BODY>
+<H1>WEB ALPINE OPTION: <!--#echo var="VAR_rss-weather"--></H1>
+
+Web Alpine only.
+<P>
+RSS Weather feed.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_send_confirms_only_expanded =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: send-confirms-only-expanded</TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: send-confirms-only-expanded (Web Alpine Only)</H1>
+
+This Web Alpine option specifies whether or not a Send confirmations
+happens when a composed message is readied for sending or not. The
+default behavior is to not confirm that the nicknames were expanded to
+the intended addresses.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_jump_command =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: enable-jump-command</TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: enable-jump-command (Web Alpine Only)</H1>
+
+This Web Alpine option specifies whether or not a Jump command is
+offered in the Message List and Message View pages. The command is
+implemented as an input field in the left column of the List and View
+screens.
+
+<P>
+When enabled and a number is entered in the input field while the
+Message List is displayed, the Message List is reframed with the
+specified message. While viewing a message, the message associated
+with the specified message number is displayed.
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_enable_newmail_sound =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: enable-newmail-sound</TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: enable-newmail-sound (Web Alpine Only)</H1>
+
+This Web Alpine option specifies whether or not a sound file is sent
+to the web browser along with the newmail notification message.
+
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_render_html_internally =====
+<HTML>
+<HEAD>
+<TITLE>FEATURE: render-html-internally</TITLE>
+</HEAD>
+<BODY>
+<H1>FEATURE: render-html-internally (Web Alpine Only)</H1>
+
+By default, Web Alpine will pass cleansed HTML text you receive in messages
+to the browser for display (rendering). This feature causes Web Alpine to convert
+the HTML text into plain text in the same way Unix and PC-Alpine do.
+
+
+<P>
+<UL>
+<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+</UL><P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+====== h_config_role_undo =====
+Yes, remember changes and exit back to list of roles; No, discard changes
+made in this screen; ^C, cancel exit and stay in this config screen.
+====== h_exit_editor =====
+S, save changes and exit from the editor; D, do not save changes but
+do exit from the editor; or ^C, cancel exit and stay in the editor.
+====== h_config_undo =====
+Yes, save changes and exit; No, exit without saving any changes made since
+entering this CONFIGURATION screen; ^C, cancel exit and stay in config screen.
+====== h_os_index_whereis =====
+Enter ^V or ^Y to go immediately to the last or first message in the index.
+Or, enter the match string followed by RETURN.
+====== h_os_index_whereis_agg =====
+Enter ^V or ^Y to go immediately to the last or first message in the index,
+Or, enter the match string followed by RETURN (or ^X to select all matches).
+=========== h_oe_add_full ==================
+Type the full name of the person being added and press the RETURN key.
+Press ^C to cancel addition.
+=========== h_oe_add_nick ==================
+Type a short nickname and press RETURN. A nickname is a short easy-to-
+remember word, name or initials like "joe", or "wcfields." ^C to cancel.
+========== h_oe_add_addr ================
+Type the e-mail address and press RETURN.
+Press ^C to cancel addition.
+========== h_oe_crlst_full ==============
+Type a long name or description for the list that you are creating and
+press RETURN. Press ^C to cancel creation of list.
+=========== h_oe_crlst_nick =============
+Type a nickname (short, easy-to-remember name or single word) for the list
+you are creating and press RETURN. Press ^C to cancel.
+========== h_oe_crlst_addr ==============
+Type an e-mail address, or a nickname already in the address book that you
+want to be part of this list and press RETURN.
+========== h_oe_adlst_addr =============
+Type an e-mail address or a nickname already in the address book that you
+want to add to this list and press RETURN.
+========== h_oe_editab_nick ============
+Change the nickname using the arrow keys and delete key. Press RETURN
+when done. Press ^C to cancel editing and leave the nickname as it was.
+========== h_oe_editab_full ============
+Change the full name using the arrow keys and delete key. Press RETURN
+when done. Press ^C to cancel editing and leave the full name as it was.
+========== h_oe_editab_addr ============
+Change the address using the arrow keys and delete key. Press RETURN
+when done. Press ^C to cancel editing and leave the address as it was.
+========== h_oe_editab_fcc ============
+Change the fcc using the arrow keys and delete key. Press RETURN when
+done. Press ^C to cancel editing and leave the fcc as it was.
+========== h_oe_editab_comment ============
+Change the comment field using the arrow keys and delete key. Press RETURN
+when done. Press ^C to cancel editing and leave the comment as it was.
+====== h_ab_forward =====
+Yes, expand nicknames and qualify local names with your current domain name;
+No, leave nicknames and local names as is; ^C, cancel.
+========== h_ab_export ==========
+Type the name of a file to write the addresses into and
+press RETURN. You may also specify an absolute path. Use ^C to cancel.
+========== h_ab_edit_a_field ==========
+Edit any of the fields of the currently selected entry by typing one of the
+letters at the bottom of the screen. Press ^C to cancel edit.
+====== h_ab_del_data_revert =====
+Press B to completely delete addrbook and revert to default, C to delete config
+and revert while leaving data, or D to only delete data (make it empty).
+====== h_ab_del_data_modify =====
+Press B to completely delete addrbook, C to delete configuration while leaving
+data, or D to delete data (make it empty) but leave config. ^C to cancel.
+====== h_ab_del_config_modify =====
+Yes, remove this address book from my configuration.
+No, make no changes now.
+====== h_ab_del_config_revert =====
+Yes, remove this address book from my config and revert to default.
+No, make no changes now.
+====== h_ab_del_default =====
+Yes, remove this default address book from my configuration.
+No, make no changes now.
+====== h_ab_really_delete =====
+Yes, delete the actual contents of the address book, not just the
+configuration. No, don't delete the data after all, cancel and start over.
+====== h_ab_del_ignore =====
+Press I to ignore all the default address books for this category. Press R to
+remove this one address book and add the others to your personal list.
+====== h_ab_del_dir_ignore =====
+Press I to ignore all the default directory servers for this category.
+Press R to remove this one server and add the others to your personal list.
+====== h_ab_copy_dups =====
+Yes, overwrite the existing entry.
+No, skip duplicates but save the rest. Press ^C to cancel.
+====== h_confirm_cancel =====
+Type C to Confirm that you want to abandon the message you are composing.
+Type N or ^C to cancel out of the cancel and keep composing.
+====== h_ab_text_or_vcard =====
+Text, start composer with displayed text already included.
+VCard, start composer with address book entry attached as a vCard. ^C cancels.
+====== h_ab_backup_or_ldap =====
+Backup, copy email address from entry and allow editing of it.
+LDAP, copy LDAP search criteria, do not allow editing of it. ^C cancels.
+====== h_ldap_text_or_vcard =====
+Text: export displayed text for selected entry. Address: export only the
+email address. VCard: export entry in vCard format. ^C cancels.
+====== h_ab_save_exp =====
+Save, save entry or entries to an address book.
+Export, save to file outside of pine. ^C cancels save.
+====== h_ab_add =====
+A, add a brand new entry to this address book.
+E, edit the entry that is currently highlighted. ^C to cancel.
+====== h_ab_shuf =====
+U, swap order of highlighted address book and the one above it.
+D, swap order of highlighted address book and the one below it. ^C to cancel.
+====== h_ab_shuf_up =====
+U, swap order of highlighted address book and the one above it.
+Press ^C to cancel.
+====== h_ab_shuf_down =====
+D, swap order of highlighted address book and the one below it.
+Press ^C to cancel.
+====== h_folder_prop =====
+Count is # of messages in the folder, Unseen means messages that have not
+been read, New means messages that were Recently added to the folder.
+====== h_role_shuf =====
+U, swap order of highlighted rule and the one above it.
+D, swap order of highlighted rule and the one below it. ^C to cancel.
+====== h_role_shuf_up =====
+U, swap order of highlighted rule and the one above it.
+Press ^C to cancel.
+====== h_role_shuf_down =====
+D, swap order of highlighted rule and the one below it.
+Press ^C to cancel.
+====== h_incoming_shuf =====
+B, swap order of highlighted directory and the one before it.
+F, swap order of highlighted directory and the one after it. ^C to cancel.
+====== h_incoming_shuf_up =====
+B, swap order of highlighted directory and the one before it.
+Press ^C to cancel.
+====== h_incoming_shuf_down =====
+F, swap order of highlighted directory and the one after it.
+Press ^C to cancel.
+====== h_dir_shuf =====
+U, swap order of highlighted directory and the one above it.
+D, swap order of highlighted directory and the one below it. ^C to cancel.
+====== h_dir_shuf_up =====
+U, swap order of highlighted directory and the one above it.
+Press ^C to cancel.
+====== h_dir_shuf_down =====
+D, swap order of highlighted directory and the one below it.
+Press ^C to cancel.
+====== h_hdrcolor_shuf =====
+U, swap order of highlighted Header Color and the one above it.
+D, swap order of highlighted Header Color and the one below it. ^C to cancel.
+====== h_hdrcolor_shuf_up =====
+U, swap order of highlighted Header Color and the one above it.
+Press ^C to cancel.
+====== h_hdrcolor_shuf_down =====
+D, swap order of highlighted Header Color and the one below it.
+Press ^C to cancel.
+========== h_oe_editab_al ============
+Change the address using the arrow keys and delete key. Press RETURN
+when done. Press ^C to cancel editing and leave the address as it was.
+========== h_dir_comp_search ===============
+Type a string to look for just like you would in the composer. Your configured
+rules for the servers with the implicit flag set will be used.
+========== h_oe_searchab ===============
+Type the word or name you want to search for and press RETURN. If you press
+RETURN without entering anything the word in [] will be searched for.
+========== h_oe_chooseabook ==========
+Choose the address book you want to save the new entry in.
+Use ^N or ^P to change address books. ^C to cancel.
+========== h_oe_takeaddr ==========
+Edit the e-mail address using the arrow and delete keys. Press RETURN
+when done. Press ^C to cancel adding this entry to the address book.
+========== h_oe_take_replace ==========
+Press R to replace the old entry with this new data. You will still have
+another chance to cancel. N to enter another nickname. ^C to cancel now.
+========== h_oe_take_replace_or_add ==========
+Press R to replace the old entry. Press A to add the selected addresses to
+the old existing list. N to enter another nickname. ^C to cancel now.
+========== h_oe_takename ==========
+Edit the full name to be correct using the arrow and delete keys. Press RETURN
+when done. Press ^C to cancel adding this entry to the address book.
+========== h_oe_takenick ==========
+Type a nickname (short easy-to-remember name, initials or single word) for this
+entry in the address book and press RETURN. Press ^C to cancel addition.
+========== h_oe_jump ==========
+Type the message number you want to jump to and press RETURN. The word "end"
+represents the last message. Press ^C to cancel jumping to another message.
+========== h_oe_jump_thd ==========
+Type the thread number you want to jump to and press RETURN. The word "end"
+represents the last thread. Press ^C to cancel jumping to another thread.
+========== h_oe_debuglevel ==========
+Higher number shows more debugging details.
+Press ^C if you want to cancel the change.
+========== h_oe_broach ==========
+Type the name of the folder you want to open and press RETURN. Press ^P/^N
+to go to the previous/next collections in the list. Press ^C to cancel goto.
+========== h_oe_foldsearch ==========
+Type the text you want to search for in foldernames and press RETURN. If you
+press RETURN without entering anything, any text in [] will be searched for.
+========== h_oe_foldrename ==========
+Change the old name of the folder to the new name using the arrow and
+delete keys and press RETURN. Press ^C to cancel rename.
+========== h_oe_login ==========
+Enter your login name for the host you are opening the mailbox on. Just press
+RETURN to use your login from this host as is, or edit it with delete key.
+========== h_oe_passwd ==========
+Type your password for the host and login shown as part of the prompt.
+Press ^C to cancel opening folder.
+========== h_oe_choosep ==========
+Enter the number associated with the printer you want to select. Press ^C to
+cancel the printer selection. The current selection is highlighted.
+========== h_oe_customp ==========
+Type the name of the Unix print command and press RETURN. Press ^C to
+cancel the printer selection.
+========== h_oe_searchview ==========
+Type the word or name you want to search for and press RETURN. If you press
+RETURN without entering anything the word in [] will be searched for.
+========== h_oe_keylock ==========
+The keyboard is in use and locked by another user. Only that user can
+unlock this keyboard by typing the password.
+========== h_wt_expire ==========
+At the beginning of each month Alpine offers to rename your current sent-mail
+folder to one named for the month so you have a sent-mail folder for each month
+========== h_wt_delete_old ==========
+It is the beginning of the month, and we need to conserve disk
+space. Please delete any sent-mail that you do not need.
+========== h_select_sort ==========
+Select the order for sorting the index by typing the capitalized letter.
+Arrival is by arrival in your mailbox; Date is by time/day message was sent.
+========== h_no_F_arg ============
+Enter name of file to be opened.
+
+========== h_sticky_personal_name ==========
+Type in your name as you want it to appear on outgoing email. This entry
+will be saved into your Alpine configuration file.
+========== h_sticky_inbox ============
+INBOX syntax is usually {complete.machine.name}INBOX
+This entry will be saved in your Alpine configuration file.
+========== h_sticky_smtp ============
+The name of the computer on your campus that relays your outgoing email
+to the Internet. This entry will be saved in your Alpine configuration file.
+========== h_sticky_user_id ==========
+The username or login-id part of your email address. This entry will be
+saved in your Alpine configuration file.
+========== h_sticky_domain ==========
+The domain part of your email address, NOT the name of your PC. This
+entry will be saved in your Alpine configuration file.
+========== h_bounce =========
+Enter the address or nickname of the intended recipient. Alpine will resend
+the message, which will retain the original author's From: address.
+========== h_incoming_add_folder_nickname =========
+Enter an (optional) nickname that will be used in lieu of the actual
+host and folder names in the FOLDER LIST display.
+========== h_anon_forward ==========
+Enter the address of your intended recipient, or ^C to cancel.
+Example: jsmith@somewhere.edu
+========== h_news_subscribe ==========
+Enter the name of the newsgroup to which you wish to subscribe,
+or ^C to cancel. Example: comp.mail.pine
+========== h_pipe_msg ==========
+Enter the name of the Unix command to which you wish to send this
+message, or ^C to cancel.
+========== h_pipe_attach ==========
+Enter the name of the Unix command to which you wish to send this
+attachment, or ^C to cancel.
+========== h_select_by_num ==========
+Enter a list of message numbers (or number ranges), or ^C to cancel. The word
+"end" represents the last message. Example: 2-5,7-9,11,19,35-end
+========== h_select_by_thrdnum ==========
+Enter a list of thread numbers (or number ranges), or ^C to cancel. The word
+"end" represents the last thread. Example: 2-5,7-9,11,19,35-end
+========== h_select_txt_from ==========
+Messages with From: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_not_from ==========
+Messages without From: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_to ==========
+Messages with To: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_not_to ==========
+Messages without To: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_cc ==========
+Messages with Cc: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_not_cc ==========
+Messages without Cc: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_subj ==========
+Messages with Subject: headers containing the entered string will be selected.
+^C to cancel. ^X enters Subject: line of current message.
+========== h_select_txt_not_subj ==========
+Messages without Subject headers containing the entered string will be selected.
+^C to cancel. ^X enters Subject: line of current message.
+========== h_select_txt_all ==========
+All messages containing the entered string will be selected. Headers and body,
+but not encoded attachments, will be compared. Enter ^C to cancel.
+========== h_select_txt_not_all ==========
+All messages that don't contain the entered string will be selected. Headers
+and body, but not encoded attachments, will be compared. Enter ^C to cancel.
+========== h_select_txt_body ==========
+All messages containing the entered string will be selected. Body text, but
+not headers or encoded attachments, will be compared. ^C to cancel.
+========== h_select_txt_not_body ==========
+All messages that don't contain the entered string will be selected. Body
+text, but not headers or encoded attachments, will be compared. ^C to cancel.
+========== h_select_txt_recip ==========
+Messages with Cc: or To: headers containing the entered string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_not_recip ==========
+Messages without Cc: or To: headers containing the string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_partic ==========
+Messages with Cc, To, or From headers containing the string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_txt_not_partic ==========
+Messages without Cc, To, or From headers containing the string will be selected.
+^C to cancel. ^G again to see original options.
+========== h_select_date ==========
+If typed, date may be in DD-MMM-YYYY format (04-Jul-2006) or in ISO format
+(2006-07-04). ^P/^N also changes default date. ^X enters date of current msg.
+========== h_attach_index_whereis ==========
+Enter some text that appears in the Attachment Index entry for the desired
+attachment. The first attachment containing that text will be highlighted.
+========== h_kb_lock ==========
+Keystrokes entered here (up to a RETURN) comprise a password that must
+be entered again later in order to unlock the keyboard.
+========== h_compose_default ==========
+N, compose a new message. R, set a role.
+^C to cancel.
+========== h_untranslatable ==========
+Send using UTF-8 character set; Send but replace untranslatable characters
+with question marks; return to the composer; or cancel message altogether.
+========== h_compose_intrptd ==========
+N, compose a new msg. I, continue interrupted msg. R, set a role.
+^C to cancel.
+========== h_compose_postponed ==========
+N, compose a new message. P, continue postponed msg. R, set a role.
+^C to cancel.
+========== h_compose_intrptd_postponed ==========
+N, compose a new msg. I, continue interrupted msg. P, continue postponed msg.
+R, set a role. ^C to cancel.
+========== h_compose_form ==========
+N, compose a new message. F, use form letter. R, set a role.
+^C to cancel.
+========== h_compose_intrptd_form ==========
+N, compose a new msg. I, continue interrupted msg. F, use form letter.
+R, set a role. ^C to cancel.
+========== h_compose_postponed_form ==========
+N, compose a new message. P, continue postponed msg. F, use form letter.
+R, set a role. ^C to cancel.
+========== h_compose_intrptd_postponed_form ==========
+N, compose a new msg. I, continue interrupted msg. P, continue postponed msg.
+F, use form letter. R, set a role. ^C to cancel.
+========== h_config_context_del_except ==========
+If you delete the last exceptional collection you can only add it back by
+manually editing the exceptions config file.
+========== h_config_whereis ==========
+To move quickly to a particular line, enter a search string or
+^C to cancel.
+========== h_config_edit_scorei ==========
+Enter interval in the form (min,max). -INF and INF may be used to represent
+-infinity and infinity. ^C to cancel change. RETURN to accept change.
+========== h_config_add ==========
+Enter desired value; use normal editing keys to modify (e.g. ^K, ^D). Just
+pressing RETURN sets the Empty Value (this turns off any global default).
+========== h_config_add_custom_color ==========
+Enter a header fieldname. For example, "subject" or "from".
+
+========== h_config_add_pat_hdr ==========
+Enter a header fieldname. For example, "reply-to" or "organization" or
+any fieldname you want that isn't included already.
+========== h_config_print_opt_choice ==========
+You may edit either the initialization string (characters printed before
+printing starts) or the trailer string. Choose one or ^C to cancel.
+========== h_config_print_init ==========
+Enter a C-style string for this. You may use common backslash escapes like
+\\n for newline, \\ooo for octal character, and \\xhh for hex character.
+========== h_config_change ==========
+Edit the existing value using arrow keys, ^K to delete entire entry, ^D to
+delete current (highlighted) character, etc. Enter ^C to cancel change.
+========== h_config_replace_add ==========
+Replace ignores the current default, Add places the current default in your
+editing buffer as if you had typed it in.
+========== h_config_insert_after ==========
+Enter a nickname for this print command. (InsertBefore puts the new item
+before the current line, InsertAfter puts it after the current line.)
+========== h_config_print_cmd ==========
+Enter command to be executed for the printer. Use normal editing keys
+to modify, ^C to cancel, carriage return to accept current value.
+========== h_config_role_del ==========
+Answering Yes will remove this rule completely from your rules list.
+========== h_config_role_addfile ==========
+Type the name of a file to add to your configuration. You don't need to
+use a file, you may add rules directly (with Add) without using a file.
+========== h_config_role_delfile ==========
+Answering Yes will remove this rule file completely from your rules list.
+The rules data file itself will not be removed.
+========== h_config_print_del ==========
+Answering Yes will remove this printer completely from your printer list.
+========== h_config_print_name_cmd ==========
+You may edit the Nickname of this printer, the Command to be executed when
+printing, or change the Options associated with this printer.
+========== h_send_check_fcc ==========
+Yes, send message without an Fcc.
+No, return to composer.
+========== h_send_check_subj ==========
+Yes, send message without a Subject.
+No, return to composer.
+========== h_send_check_to_cc ==========
+Yes, send message without a To address, or a Cc address, or a Newsgroup.
+No, return to composer.
+========== h_send_fcc_only ==========
+Yes, copy message to Fcc only and send to NO recipients.
+No, return to composer.
+========== h_send_prompt ==========
+Yes, send the message.
+No or ^C, return to composer.
+========== h_send_prompt_flowed ==========
+Yes, send the message. No or ^C, return to composer.
+What's Flowed? See Do Not Send Flowed Text in config screen.
+========== h_send_prompt_dsn ==========
+Yes, send the message. No or ^C, return to composer.
+What's DSNOpts? See Enable Delivery Status Notification in config screen.
+========== h_send_prompt_dsn_flowed ==========
+Yes, send the message. No or ^C, return to composer. What's DSNOpts? See
+Enable Delivery Status Notification. What's Flowed? See Do Not Send Flowed Text.
+========== h_role_confirm ==========
+Yes, use displayed role. No, compose without a role.
+^C, cancel the message. ^T, select a role from your other eligible roles.
+========== h_norole_confirm ==========
+Return, compose without a role.
+^C, cancel the message. ^T, select a role from your eligible roles.
+========== h_custom_print ==========
+Enter a Unix command that accepts its data on standard input.
+Alpine will display any information the command sends to standard output.
+========== h_convert_abooks_and_sigs ==========
+You will be given the opportunity to convert address books and signature files
+to remote configurations.
+========== h_convert_abooks ==========
+You will be given the opportunity to convert address books to remote
+configurations.
+========== h_flag_keyword ==========
+Enter the name of the keyword you want to add for this folder.
+No spaces, parentheses, braces, percents or asterisks are allowed.
+========== h_select_keyword ==========
+Enter the keyword you want to match, or use ^T to select a keyword from a list
+of possible keywords for this folder. Use ! to look for non-matches instead.
+========== h_type_keyword ==========
+Enter the keyword you want to add. You may add a nickname in the next step.
+No spaces, parentheses, braces, percents or asterisks are allowed.
+========== h_type_keyword_nickname ==========
+Enter an optional nickname for the keyword you want to add.
+Type Carriage return to use the keyword name instead of a nickname.
+========== h_convert_sigs ==========
+You will be given the opportunity to convert signature files to remote
+configurations.
+========== h_convert_abook ==========
+Yes is fairly safe. You will be ADDing a remote address book that is a copy
+of the current address book. The current abook won't be removed automatically.
+========== h_convert_sig ==========
+Answering Yes copies the contents of the signature file into your Alpine
+configuration file. After that, the contents of the file will not be used.
+========== h_save_addman ==========
+Enter the simple name of the folder you want to add. Carriage return to
+accept what you have typed so far. ^C to get back to SELECT FOLDER screen.
+========== h_reopen_folder ==========
+Yes reopens the folder, as if you were starting over. This uncovers new mail.
+No leaves the folder index as it was without discovering new mail.
+========== h_convert_pinerc_server ==========
+This is the name of the host (computer) where the remote Alpine configuration
+will be stored. This should be an IMAP server that you have permission to use.
+========== h_convert_pinerc_folder ==========
+Enter the correct remote folder name. This folder is special and should
+contain only configuration data. It shouldn't contain other mail messages.
+========== h_role_compose ==========
+Compose a New message, Reply to current message, Forward current message, or
+Bounce message. Then you will be asked to choose one of your Roles to be used.
+========== h_save_size_changed ==========
+The reported size of a message is not the same as the actual size. Answer Yes
+to continue and hope for the best or No to Cancel the entire Save.
+========== h_select_by_larger_size ==========
+Enter a number or ^C to cancel. All messages greater than this many characters
+in size will be selected. Examples: 2176, 1.53K (1530), or 3M (3000000).
+========== h_select_by_smaller_size ==========
+Enter a number or ^C to cancel. All messages less than this many characters
+in size will be selected. Examples: 2176, 1.53K (1530), or 3M (3000000).
diff --git a/pith/pineelt.h b/pith/pineelt.h
new file mode 100644
index 00000000..e44ae37a
--- /dev/null
+++ b/pith/pineelt.h
@@ -0,0 +1,60 @@
+/*
+ * $Id: pineelt.h 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_PINEELT_INCLUDED
+#define PITH_PINEELT_INCLUDED
+
+
+#include "../pith/thread.h"
+#include "../pith/indxtype.h"
+
+
+/*
+ * The two structs below hold all knowledge regarding
+ * messages requiring special handling
+ */
+typedef struct part_exception_s {
+ char *partno;
+ int handling;
+ struct part_exception_s *next;
+} PARTEX_S;
+
+
+/*
+ * Pine's private per-message data stored on the c-client's elt
+ * spare pointer.
+ */
+typedef struct pine_elt {
+ PINETHRD_S *pthrd;
+ PARTEX_S *exceptions;
+ ICE_S *ice;
+ /* per-message pine state bits */
+ unsigned int hidden:1;
+ unsigned int excluded:1;
+ unsigned int selected:1;
+ unsigned int searched:1;
+ unsigned int unsorted:1;
+ unsigned int collapsed:1;
+ unsigned int colhid:1;
+ unsigned int colhid2:1;
+ unsigned int tmp:1;
+} PINELT_S;
+
+
+/* exported protoypes */
+void msgno_free_exceptions(PARTEX_S **);
+
+
+#endif /* PITH_PINEELT_INCLUDED */
diff --git a/pith/pipe.c b/pith/pipe.c
new file mode 100644
index 00000000..35549eef
--- /dev/null
+++ b/pith/pipe.c
@@ -0,0 +1,102 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pipe.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/pipe.h"
+#include "../pith/status.h"
+
+
+/* Internal prototypes */
+
+
+
+/*
+ * pipe_* functions introduced so out.f, in.f don't need to be used
+ * by whatever's setting up the pipe.
+ * Should be similar on unix and windows, but since we're faking piping
+ * in windows, closing the write pipe would signify that the child
+ * process can start running. This makes it possible for PIPE_WRITE
+ * and PIPE_READ flags to be simultaneously set across all platforms.
+ * Unix piping should eventually have NON_BLOCKING_IO stuff rolled up
+ * into it.
+ */
+
+int
+pipe_putc(int c, PIPE_S *syspipe)
+{
+ if(!syspipe || !syspipe->out.f)
+ return -1;
+
+ return(fputc(c, syspipe->out.f));
+}
+
+int
+pipe_puts(char *str, PIPE_S *syspipe)
+{
+ if(!syspipe || !syspipe->out.f)
+ return -1;
+
+ return(fputs(str, syspipe->out.f));
+}
+
+char *
+pipe_gets(char *str, int size, PIPE_S *syspipe)
+{
+ if(!syspipe || !syspipe->in.f)
+ return NULL;
+
+ return(fgets(str, size, syspipe->in.f));
+}
+
+int
+pipe_readc(unsigned char *c, PIPE_S *syspipe)
+{
+ int rv = 0;
+
+ if(!syspipe || !syspipe->in.f)
+ return -1;
+
+ do {
+ errno = 0;
+ clearerr(syspipe->in.f);
+ rv = fread(c, sizeof(unsigned char), (size_t)1, syspipe->in.f);
+ } while(!rv && ferror(syspipe->in.f) && errno == EINTR);
+
+ return(rv);
+}
+
+int
+pipe_writec(int c, PIPE_S *syspipe)
+{
+ unsigned char ch = (unsigned char)c;
+ int rv = 0;
+
+ if(!syspipe || !syspipe->out.f)
+ return -1;
+
+ do
+ rv = fwrite(&ch, sizeof(unsigned char), (size_t)1, syspipe->out.f);
+ while(!rv && ferror(syspipe->out.f) && errno == EINTR);
+
+ return(rv);
+}
+
+
+void
+pipe_report_error(char *errormsg)
+{
+ q_status_message(SM_ORDER, 3, 3, errormsg);
+}
diff --git a/pith/pipe.h b/pith/pipe.h
new file mode 100644
index 00000000..f4a7f64f
--- /dev/null
+++ b/pith/pipe.h
@@ -0,0 +1,33 @@
+/*
+ * $Id: pipe.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_PIPE_INCLUDED
+#define PITH_PIPE_INCLUDED
+
+
+#include "../pith/filter.h"
+#include "../pith/osdep/pipe.h"
+
+
+/* exported protoypes */
+int pipe_putc(int, PIPE_S *);
+int pipe_puts(char *, PIPE_S *);
+char *pipe_gets(char *, int, PIPE_S *);
+int pipe_readc(unsigned char *, PIPE_S *);
+int pipe_writec(int, PIPE_S *);
+void pipe_report_error(char *);
+
+
+#endif /* PITH_PIPE_INCLUDED */
diff --git a/pith/readfile.c b/pith/readfile.c
new file mode 100644
index 00000000..4fc9fa3e
--- /dev/null
+++ b/pith/readfile.c
@@ -0,0 +1,71 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: readfile.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+
+#include "../pith/store.h"
+
+#include "readfile.h"
+
+
+/*----------------------------------------------------------------------
+ Read whole file into memory
+
+ Args: filename -- path name of file to read
+
+ Result: Returns pointer to malloced memory with the contents of the file
+ or NULL
+
+This won't work very well if the file has NULLs in it.
+ ----*/
+char *
+read_file(char *filename, int so_get_flags)
+{
+ STORE_S *in_file = NULL, *out_store = NULL;
+ unsigned char c;
+ char *return_text = NULL;
+
+ if((in_file = so_get(FileStar, filename, so_get_flags | READ_ACCESS))){
+
+
+ if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ so_give(&in_file);
+ return NULL;
+ }
+
+ /*
+ * We're just using the READ_FROM_LOCALE flag to translate
+ * to UTF-8.
+ */
+ while(so_readc(&c, in_file))
+ so_writec(c, out_store);
+
+ if(in_file)
+ so_give(&in_file);
+
+ if(out_store){
+ return_text = (char *) so_text(out_store);
+ /* avoid freeing this */
+ if(out_store->txt)
+ out_store->txt = NULL;
+
+ so_give(&out_store);
+ }
+ }
+
+ return(return_text);
+}
diff --git a/pith/readfile.h b/pith/readfile.h
new file mode 100644
index 00000000..485144be
--- /dev/null
+++ b/pith/readfile.h
@@ -0,0 +1,35 @@
+/*
+ * $Id: readfile.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_READFILE_INCLUDED
+#define PITH_READFILE_INCLUDED
+
+
+/*
+ * Does the string s begin with the UTF-8 Byte Order Mark?
+ */
+#define BOM_UTF8(s) ((s) && (s)[0] && (s)[1] && (s)[2] \
+ && (unsigned char)(s)[0] == 0xef \
+ && (unsigned char)(s)[1] == 0xbb \
+ && (unsigned char)(s)[2] == 0xbf)
+
+
+/*
+ * Exported Prototypes
+ */
+char *read_file(char *, int);
+
+
+#endif /* PITH_READFILE_INCLUDED */
diff --git a/pith/remote.c b/pith/remote.c
new file mode 100644
index 00000000..40a7f055
--- /dev/null
+++ b/pith/remote.c
@@ -0,0 +1,2820 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: remote.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ remote.c
+ Implements remote IMAP config files (remote config, remote abook).
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/remote.h"
+#include "../pith/conf.h"
+#include "../pith/imap.h"
+#include "../pith/msgno.h"
+#include "../pith/mailview.h"
+#include "../pith/status.h"
+#include "../pith/flag.h"
+#include "../pith/tempfile.h"
+#include "../pith/adrbklib.h"
+#include "../pith/detach.h"
+#include "../pith/filter.h"
+#include "../pith/stream.h"
+#include "../pith/options.h"
+#include "../pith/busy.h"
+#include "../pith/readfile.h"
+
+
+/*
+ * Internal prototypes
+ */
+REMDATA_META_S *rd_find_our_metadata(char *, unsigned long *);
+int rd_meta_is_broken(FILE *);
+int rd_add_hdr_msg(REMDATA_S *, char *);
+int rd_store_fake_hdrs(REMDATA_S *, char *, char *, char *);
+int rd_upgrade_cookies(REMDATA_S *, long, int);
+int rd_check_for_suspect_data(REMDATA_S *);
+
+
+char meta_prefix[] = ".ab";
+
+
+char *(*pith_opt_rd_metadata_name)(void);
+
+
+char *
+read_remote_pinerc(PINERC_S *prc, ParsePinerc which_vars)
+{
+ int try_cache, no_perm_create_pass = 0;
+ char *file = NULL;
+ unsigned flags;
+
+
+ dprint((7, "read_remote_pinerc \"%s\"\n",
+ prc->name ? prc->name : "?"));
+
+ /*
+ * We don't cache the pinerc, we always copy it.
+ *
+ * Don't store the config in a temporary file, just leave it
+ * in memory while using it.
+ * It is currently required that NO_PERM_CACHE be set if NO_FILE is set.
+ */
+ flags = (NO_PERM_CACHE | NO_FILE);
+
+create_the_remote_folder:
+
+ if(no_perm_create_pass){
+ if(prc->rd){
+ prc->rd->flags &= ~DO_REMTRIM;
+ rd_close_remdata(&prc->rd);
+ }
+
+ /* this will cause the remote folder to be created */
+ flags = 0;
+ }
+
+ /*
+ * We could parse the name here to find what type it is. So far we
+ * only have type RemImap.
+ */
+ prc->rd = rd_create_remote(RemImap, prc->name,
+ REMOTE_PINERC_SUBTYPE,
+ &flags, _("Error: "),
+ _(" Can't fetch remote configuration."));
+ if(!prc->rd)
+ goto bail_out;
+
+ /*
+ * On first use we just use a temp file instead of memory (NO_FILE).
+ * In other words, for our convenience, we don't turn NO_FILE back on
+ * here. Why is that convenient? Because of the stuff that happened in
+ * rd_create_remote when flags was set to zero.
+ */
+ if(no_perm_create_pass)
+ prc->rd->flags |= NO_PERM_CACHE;
+
+ try_cache = rd_read_metadata(prc->rd);
+
+ if(prc->rd->access == MaybeRorW){
+ if(prc->rd->read_status == 'R' ||
+ !(which_vars == ParsePers || which_vars == ParsePersPost)){
+ prc->rd->access = ReadOnly;
+ prc->rd->read_status = 'R';
+ }
+ else
+ prc->rd->access = ReadWrite;
+ }
+
+ if(prc->rd->access != NoExists){
+
+ rd_check_remvalid(prc->rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if((which_vars == ParsePers || which_vars == ParsePersPost) &&
+ prc->rd->read_status == 'R'){
+ /*
+ * We go to this trouble since readonly pinercs
+ * are likely a mistake. They are usually supposed to be
+ * readwrite so we open it and check if it's been fixed.
+ */
+ rd_check_readonly_access(prc->rd);
+ if(prc->rd->read_status == 'W'){
+ prc->rd->access = ReadWrite;
+ prc->rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ prc->rd->access = ReadOnly;
+ }
+
+ if(prc->rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(prc->rd) != 0){
+ if(!no_perm_create_pass && prc->rd->flags & NO_PERM_CACHE
+ && !(prc->rd->flags & USER_SAID_NO)){
+ /*
+ * We don't check for the existence of the remote
+ * folder when this flag is turned on, so we could
+ * fail here because the remote folder doesn't exist.
+ * We try to create it.
+ */
+ no_perm_create_pass++;
+ goto create_the_remote_folder;
+ }
+
+ dprint((1,
+ "read_pinerc_remote: rd_update_local failed\n"));
+ /*
+ * Don't give up altogether. We still may be
+ * able to use a cached copy.
+ */
+ }
+ else{
+ dprint((7,
+ "%s: copied remote to local (%ld)\n",
+ prc->rd->rn ? prc->rd->rn : "?",
+ (long)prc->rd->last_use));
+ }
+ }
+
+ if(prc->rd->access == ReadWrite)
+ prc->rd->flags |= DO_REMTRIM;
+ }
+
+ /* If we couldn't get to remote folder, try using the cached copy */
+ if(prc->rd->access == NoExists || prc->rd->flags & REM_OUTOFDATE){
+ if(try_cache){
+ prc->rd->access = ReadOnly;
+ prc->rd->flags |= USE_OLD_CACHE;
+ q_status_message(SM_ORDER, 3, 4,
+ "Can't contact remote config server, using cached copy");
+ dprint((2,
+ "Can't open remote pinerc %s, using local cached copy %s readonly\n",
+ prc->rd->rn ? prc->rd->rn : "?",
+ prc->rd->lf ? prc->rd->lf : "?"));
+ }
+ else{
+ prc->rd->flags &= ~DO_REMTRIM;
+ goto bail_out;
+ }
+ }
+
+ if(prc->rd->flags & NO_FILE)
+ /* copy text, leave sonofile for later use */
+ file = cpystr((char *)so_text(prc->rd->sonofile));
+ else
+ file = read_file(prc->rd->lf, 0);
+
+bail_out:
+ if((which_vars == ParsePers || which_vars == ParsePersPost) &&
+ (!file || !prc->rd || prc->rd->access != ReadWrite)){
+ prc->readonly = 1;
+ if(prc == ps_global->prc)
+ ps_global->readonly_pinerc = 1;
+ }
+
+ return(file);
+}
+
+
+/*
+ * Check if the remote data folder exists and create an empty folder
+ * if it doesn't exist.
+ *
+ * Args - type -- The type of remote storage.
+ * remote_name --
+ * type_spec -- Type-specific data.
+ * flags --
+ * err_prefix -- Should usually end with a SPACE
+ * err_suffix -- Should usually begin with a SPACE
+ *
+ * Returns a pointer to a REMDATA_S with access set to either
+ * NoExists or MaybeRorW. On success, "so" will point to a storage object.
+ */
+REMDATA_S *
+rd_create_remote(RemType type, char *remote_name, char *type_spec,
+ unsigned int *flags, char *err_prefix, char *err_suffix)
+{
+ REMDATA_S *rd = NULL;
+ CONTEXT_S *dummy_cntxt = NULL;
+
+ dprint((7, "rd_create_remote \"%s\"\n",
+ remote_name ? remote_name : "?"));
+
+ rd = rd_new_remdata(type, remote_name, type_spec);
+ if(flags)
+ rd->flags = *flags;
+
+ switch(rd->type){
+ case RemImap:
+ if(rd->flags & NO_PERM_CACHE){
+ if(rd->rn && (rd->so = so_get(CharStar, NULL, WRITE_ACCESS))){
+ if(rd->flags & NO_FILE){
+ rd->sonofile = so_get(CharStar, NULL, WRITE_ACCESS);
+ if(!rd->sonofile)
+ rd->flags &= ~NO_FILE;
+ }
+
+ /*
+ * We're not going to check if it is there in this case,
+ * in order to save ourselves some round trips and
+ * connections. We'll just try to select it and then
+ * recover at that point if it isn't already there.
+ */
+ rd->flags |= REM_OUTOFDATE;
+ rd->access = MaybeRorW;
+ }
+
+ }
+ else{
+ /*
+ * Open_fcc creates the folder if it didn't already exist.
+ */
+ if(rd->rn && (rd->so = open_fcc(rd->rn, &dummy_cntxt, 1,
+ err_prefix, err_suffix)) != NULL){
+ /*
+ * We know the folder is there but don't know what access
+ * rights we have until we try to select it, which we don't
+ * want to do unless we have to. So delay evaluating.
+ */
+ rd->access = MaybeRorW;
+ }
+ }
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3,5, "rd_create_remote: type not supported");
+ break;
+ }
+
+ return(rd);
+}
+
+
+REMDATA_S *
+rd_new_remdata(RemType type, char *remote_name, char *type_spec)
+{
+ REMDATA_S *rd = NULL;
+
+ rd = (REMDATA_S *)fs_get(sizeof(*rd));
+ memset((void *)rd, 0, sizeof(*rd));
+
+ rd->type = type;
+ rd->access = NoExists;
+
+ if(remote_name)
+ rd->rn = cpystr(remote_name);
+
+ switch(rd->type){
+ case RemImap:
+ if(type_spec)
+ rd->t.i.special_hdr = cpystr(type_spec);
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3,5, "rd_new_remdata: type not supported");
+ break;
+ }
+
+ return(rd);
+}
+
+
+/*
+ * Closes the remote stream and frees.
+ */
+void
+rd_free_remdata(REMDATA_S **rd)
+{
+ if(rd && *rd){
+ rd_close_remote(*rd);
+
+ if((*rd)->rn)
+ fs_give((void **)&(*rd)->rn);
+
+ if((*rd)->lf)
+ fs_give((void **)&(*rd)->lf);
+
+ if((*rd)->so){
+ so_give(&(*rd)->so);
+ (*rd)->so = NULL;
+ }
+
+ if((*rd)->sonofile){
+ so_give(&(*rd)->sonofile);
+ (*rd)->sonofile = NULL;
+ }
+
+ switch((*rd)->type){
+ case RemImap:
+ if((*rd)->t.i.special_hdr)
+ fs_give((void **)&(*rd)->t.i.special_hdr);
+
+ if((*rd)->t.i.chk_date)
+ fs_give((void **)&(*rd)->t.i.chk_date);
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_free_remdata: type not supported");
+ break;
+ }
+
+ fs_give((void **)rd);
+ }
+}
+
+
+/*
+ * Call this when finished with the remdata. This does the REMTRIM if the
+ * flag is set, the DEL_FILE if the flag is set. It also closes the stream
+ * and frees the rd.
+ */
+void
+rd_trim_remdata(REMDATA_S **rd)
+{
+ if(!(rd && *rd))
+ return;
+
+ switch((*rd)->type){
+ case RemImap:
+ /*
+ * Trim the number of saved copies of remote data history.
+ * The first message is a fake message that always
+ * stays there, then come ps_global->remote_abook_history messages
+ * which are each a revision of the data, then comes the active
+ * data.
+ */
+ if((*rd)->flags & DO_REMTRIM &&
+ !((*rd)->flags & REM_OUTOFDATE) &&
+ (*rd)->t.i.chk_nmsgs > ps_global->remote_abook_history + 2){
+
+ /* make sure stream is open */
+ rd_ping_stream(*rd);
+ rd_open_remote(*rd);
+
+ if(!rd_remote_is_readonly(*rd)){
+ if((*rd)->t.i.stream &&
+ (*rd)->t.i.stream->nmsgs >
+ ps_global->remote_abook_history + 2){
+ char sequence[20];
+ int user_deleted = 0;
+
+ /*
+ * If user manually deleted some, we'd better not delete
+ * any more.
+ */
+ if(count_flagged((*rd)->t.i.stream, F_DEL) == 0L){
+
+ dprint((4, " rd_trim: trimming remote: mark msgs 2-%ld deleted (%s)\n", (*rd)->t.i.stream->nmsgs - 1 - ps_global->remote_abook_history, (*rd)->rn ? (*rd)->rn : "?"));
+ snprintf(sequence, sizeof(sequence), "2:%ld",
+ (*rd)->t.i.stream->nmsgs - 1 - ps_global->remote_abook_history);
+ mail_flag((*rd)->t.i.stream, sequence,
+ "\\DELETED", ST_SET);
+ }
+ else
+ user_deleted++;
+
+ mail_expunge((*rd)->t.i.stream);
+
+ if(!user_deleted)
+ rd_update_metadata(*rd, NULL);
+ /* else
+ * don't update metafile because user is messing with
+ * the remote folder manually. We'd better re-read it next
+ * time. */
+ }
+ }
+
+ ps_global->noshow_error = 0;
+ }
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3,5, "rd_trim_remdata: type not supported");
+ break;
+ }
+}
+
+
+/*
+ * All done with this remote data. Trim the folder, close the
+ * stream, and free.
+ */
+void
+rd_close_remdata(REMDATA_S **rd)
+{
+ if(!(rd && *rd))
+ return;
+
+ rd_trim_remdata(rd);
+
+ if((*rd)->lf && (*rd)->flags & DEL_FILE)
+ our_unlink((*rd)->lf);
+
+ /* this closes the stream and frees memory */
+ rd_free_remdata(rd);
+}
+
+
+/*
+ * Looks in the metadata file for the cache line corresponding to rd and
+ * fills in data in rd.
+ *
+ * Return value -- 1 if it is likely that the filename we're returning
+ * is the permanent name of the local cache file and it may already have
+ * a cached copy of the data. This is to tell us if it makes sense to use
+ * the cached copy when we are unable to contact the remote server.
+ * 0 otherwise.
+ */
+int
+rd_read_metadata(REMDATA_S *rd)
+{
+ REMDATA_META_S *rab = NULL;
+ int try_cache = 0;
+
+ dprint((7, "rd_read_metadata \"%s\"\n",
+ (rd && rd->rn) ? rd->rn : "?"));
+
+ if(!rd)
+ return try_cache;
+
+ if(rd->flags & NO_PERM_CACHE)
+ rab = NULL;
+ else
+ rab = rd_find_our_metadata(rd->rn, &rd->flags);
+
+ if(!rab){
+ if(!rd->lf){
+ rd->flags |= (NO_META_UPDATE | REM_OUTOFDATE);
+ if(!(rd->flags & NO_FILE)){
+ rd->lf = temp_nam(NULL, "a6");
+ rd->flags |= DEL_FILE;
+ }
+
+ /* display error */
+ if(!(rd->flags & NO_PERM_CACHE))
+ display_message('x');
+
+ dprint((2, "using temp cache file %s\n",
+ rd->lf ? rd->lf : "<none>"));
+ }
+ }
+ else if(rab->local_cache_file){ /* A-OK, it was in the file already */
+ if(!is_absolute_path(rab->local_cache_file)){
+ char dir[MAXPATH+1], path[MAXPATH+1];
+ char *lc;
+
+ /*
+ * This should be the normal case. The file is stored as a
+ * filename in the pinerc dir, so that it can be
+ * accessed from the PC or from unix where the pathnames to
+ * get there will be different.
+ */
+
+ if((lc = last_cmpnt(ps_global->pinerc)) != NULL){
+ int to_copy;
+
+ to_copy = (lc - ps_global->pinerc > 1)
+ ? (lc - ps_global->pinerc - 1) : 1;
+ strncpy(dir, ps_global->pinerc, MIN(to_copy, sizeof(dir)-1));
+ dir[MIN(to_copy, sizeof(dir)-1)] = '\0';
+ }
+ else{
+ dir[0] = '.';
+ dir[1] = '\0';
+ }
+
+ build_path(path, dir, rab->local_cache_file, sizeof(path));
+ rd->lf = cpystr(path);
+ }
+ else{
+ rd->lf = rab->local_cache_file;
+ /* don't free this below, we're using it */
+ rab->local_cache_file = NULL;
+ }
+
+ rd->read_status = rab->read_status;
+
+ switch(rd->type){
+ case RemImap:
+ rd->t.i.chk_date = rab->date;
+ rab->date = NULL; /* don't free this below, we're using it */
+
+ dprint((7,
+ "in read_metadata, setting chk_date from metadata to ->%s<-\n",
+ rd->t.i.chk_date ? rd->t.i.chk_date : "?"));
+ rd->t.i.chk_nmsgs = rab->nmsgs;
+ rd->t.i.uidvalidity = rab->uidvalidity;
+ rd->t.i.uidnext = rab->uidnext;
+ rd->t.i.uid = rab->uid;
+ rd->t.i.chk_nmsgs = rab->nmsgs;
+ dprint((7,
+ "setting uid=%lu uidnext=%lu uidval=%lu read_stat=%c nmsgs=%lu\n",
+ rd->t.i.uid, rd->t.i.uidnext, rd->t.i.uidvalidity,
+ rd->read_status ? rd->read_status : '0',
+ rd->t.i.chk_nmsgs));
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_read_metadata: type not supported");
+ break;
+ }
+
+ if(rd->t.i.chk_nmsgs > 0)
+ try_cache++; /* cache should be valid if we can't contact server */
+ }
+ /*
+ * The line for this data wasn't in the metadata file yet.
+ * Figure out what should go there and put it in.
+ */
+ else{
+ /*
+ * The local_cache_file is where we will store the cached local
+ * copy of the remote data.
+ */
+ rab->local_cache_file = tempfile_in_same_dir(ps_global->pinerc,
+ meta_prefix, NULL);
+ if(rab->local_cache_file){
+ rd->lf = rab->local_cache_file;
+ rd_write_metadata(rd, 0);
+ rab->local_cache_file = NULL;
+ }
+ else
+ rd->lf = temp_nam(NULL, "a7");
+ }
+
+ if(rab){
+ if(rab->local_cache_file)
+ fs_give((void **)&rab->local_cache_file);
+ if(rab->date)
+ fs_give((void **)&rab->date);
+ fs_give((void **)&rab);
+ }
+
+ return(try_cache);
+}
+
+
+/*
+ * Write out the contents of the metadata file.
+ *
+ * Each line should be: folder_name cache_file uidvalidity uidnext uid nmsgs
+ * read_status date
+ *
+ * If delete_it is set, then remove the line instead of updating it.
+ *
+ * We have to be careful with the metadata, because it exists in user's
+ * existing metadata files now, and it is oriented towards only RemImap.
+ * We would have to change the version number and the format of the lines
+ * in the file if we add another type.
+ */
+void
+rd_write_metadata(REMDATA_S *rd, int delete_it)
+{
+ char *tempfile;
+ FILE *fp_old = NULL, *fp_new = NULL;
+ char *p = NULL, *pinerc_dir = NULL, *metafile = NULL;
+ char *rel_filename, *key;
+ char line[MAILTMPLEN];
+ int fd;
+
+ dprint((7, "rd_write_metadata \"%s\"\n",
+ (rd && rd->rn) ? rd->rn : "?"));
+
+ if(!rd || rd->flags & NO_META_UPDATE)
+ return;
+
+ if(rd->type != RemImap){
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_write_metadata: type not supported");
+ return;
+ }
+
+ dprint((9, " - rd_write_metadata: rn=%s lf=%s\n",
+ rd->rn ? rd->rn : "?", rd->lf ? rd->lf : "?"));
+
+ key = rd->rn;
+
+ if(!(pith_opt_rd_metadata_name && (metafile = (*pith_opt_rd_metadata_name)())))
+ goto io_err;
+
+ if(!(tempfile = tempfile_in_same_dir(metafile, "a9", &pinerc_dir)))
+ goto io_err;
+
+ if((fd = our_open(tempfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) >= 0)
+ fp_new = fdopen(fd, "w");
+
+ if(pinerc_dir && rd->lf && strlen(rd->lf) > strlen(pinerc_dir))
+ rel_filename = rd->lf + strlen(pinerc_dir) + 1;
+ else
+ rel_filename = rd->lf;
+
+ if(pinerc_dir)
+ fs_give((void **)&pinerc_dir);
+
+ fp_old = our_fopen(metafile, "rb");
+
+ if(fp_new && fp_old){
+ /*
+ * Write the header line.
+ */
+ if(fprintf(fp_new, "%s %s Pine Metadata\n",
+ PMAGIC, METAFILE_VERSION_NUM) == EOF)
+ goto io_err;
+
+ while((p = fgets(line, sizeof(line), fp_old)) != NULL){
+ /*
+ * Skip the header line and any lines that don't begin
+ * with a "{".
+ */
+ if(line[0] != '{')
+ continue;
+
+ /* skip the old cache line for this key */
+ if(strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == TAB)
+ continue;
+
+ /* add this line to new version of file */
+ if(fputs(p, fp_new) == EOF)
+ goto io_err;
+ }
+ }
+
+ /* add the cache line for this key */
+ /* Warning: this is type RemImap specific right now! */
+ if(!delete_it){
+ if(!tempfile ||
+ !fp_old ||
+ !fp_new ||
+ p ||
+ fprintf(fp_new, "%s\t%s\t%lu\t%lu\t%lu\t%lu\t%c\t%s\n",
+ key ? key : "",
+ rel_filename ? rel_filename : "",
+ rd->t.i.uidvalidity, rd->t.i.uidnext, rd->t.i.uid,
+ rd->t.i.chk_nmsgs,
+ rd->read_status ? rd->read_status : '?',
+ rd->t.i.chk_date ? rd->t.i.chk_date : "no-match")
+ == EOF)
+ goto io_err;
+ }
+
+ if(fclose(fp_new) == EOF){
+ fp_new = NULL;
+ goto io_err;
+ }
+
+ if(fclose(fp_old) == EOF){
+ fp_old = NULL;
+ goto io_err;
+ }
+
+ if(rename_file(tempfile, metafile) < 0)
+ goto io_err;
+
+ if(tempfile)
+ fs_give((void **)&tempfile);
+
+ if(metafile)
+ fs_give((void **)&metafile);
+
+ return;
+
+io_err:
+ dprint((2, "io_err in rd_write_metadata(%s), tempfile=%s: %s\n",
+ metafile ? metafile : "<NULL>", tempfile ? tempfile : "<NULL>",
+ error_description(errno)));
+ q_status_message2(SM_ORDER, 3, 5,
+ "Trouble updating metafile %s, continuing (%s)",
+ metafile ? metafile : "<NULL>", error_description(errno));
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+ if(metafile)
+ fs_give((void **)&metafile);
+ if(fp_old)
+ (void)fclose(fp_old);
+ if(fp_new)
+ (void)fclose(fp_new);
+}
+
+
+void
+rd_update_metadata(REMDATA_S *rd, char *date)
+{
+ if(!rd)
+ return;
+
+ dprint((9, " - rd_update_metadata: rn=%s lf=%s\n",
+ rd->rn ? rd->rn : "?", rd->lf ? rd->lf : "<none>"));
+
+ switch(rd->type){
+ case RemImap:
+ if(rd->t.i.stream){
+ ps_global->noshow_error = 1;
+ rd_ping_stream(rd);
+ if(rd->t.i.stream){
+ /*
+ * If nmsgs < 2 then something is wrong. Maybe it is just
+ * that we haven't been told about the messages we've
+ * appended ourselves. Try closing and re-opening the stream
+ * to see those.
+ */
+ if(rd->t.i.stream->nmsgs < 2 ||
+ (rd->t.i.shouldbe_nmsgs &&
+ (rd->t.i.shouldbe_nmsgs != rd->t.i.stream->nmsgs))){
+ pine_mail_check(rd->t.i.stream);
+ if(rd->t.i.stream->nmsgs < 2 ||
+ (rd->t.i.shouldbe_nmsgs &&
+ (rd->t.i.shouldbe_nmsgs != rd->t.i.stream->nmsgs))){
+ rd_close_remote(rd);
+ rd_open_remote(rd);
+ }
+ }
+
+ rd->t.i.chk_nmsgs = rd->t.i.stream ? rd->t.i.stream->nmsgs : 0L;
+
+ /*
+ * If nmsgs < 2 something is wrong.
+ */
+ if(rd->t.i.chk_nmsgs < 2){
+ rd->t.i.uidvalidity = 0L;
+ rd->t.i.uid = 0L;
+ rd->t.i.uidnext = 0L;
+ }
+ else{
+ rd->t.i.uidvalidity = rd->t.i.stream->uid_validity;
+ rd->t.i.uid = mail_uid(rd->t.i.stream,
+ rd->t.i.stream->nmsgs);
+ /*
+ * Uid_last is not always valid. If the last known uid is
+ * greater than uid_last, go with it instead (uid+1).
+ * If our guess is wrong (too low), the penalty is not
+ * harsh. When the uidnexts don't match we open the
+ * mailbox to check the uid of the actual last message.
+ * If it was a false hit then we adjust uidnext so it
+ * will be correct the next time through.
+ */
+ rd->t.i.uidnext = MAX(rd->t.i.stream->uid_last,rd->t.i.uid)
+ + 1;
+ }
+ }
+
+ ps_global->noshow_error = 0;
+
+ /*
+ * Save the date so that we can check if it changed next time
+ * we go to write.
+ */
+ if(date){
+ if(rd->t.i.chk_date)
+ fs_give((void **)&rd->t.i.chk_date);
+
+ rd->t.i.chk_date = cpystr(date);
+ }
+
+ rd_write_metadata(rd, 0);
+ }
+
+ rd->t.i.shouldbe_nmsgs = 0;
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_update_metadata: type not supported");
+ break;
+ }
+}
+
+
+
+
+REMDATA_META_S *
+rd_find_our_metadata(char *key, long unsigned int *flags)
+{
+ char *p, *q, *metafile = NULL;
+ char line[MAILTMPLEN];
+ REMDATA_META_S *rab = NULL;
+ FILE *fp;
+
+ dprint((9, "rd_find_our_metadata \"%s\"\n", key ? key : "?"));
+
+ if(!(key && *key))
+ return rab;
+
+ if(!(pith_opt_rd_metadata_name && (metafile = (*pith_opt_rd_metadata_name)()) != NULL))
+ return rab;
+
+ /*
+ * Open the metadata file and get some information out.
+ */
+ fp = our_fopen(metafile, "rb");
+ if(fp == NULL){
+ q_status_message2(SM_ORDER, 3, 5,
+ _("can't open metadata file %s, continuing (%s)"),
+ metafile, error_description(errno));
+ dprint((2,
+ "can't open existing metadata file %s: %s\n",
+ metafile ? metafile : "?", error_description(errno)));
+ if(flags)
+ (*flags) |= NO_META_UPDATE;
+
+ fs_give((void **)&metafile);
+
+ return rab;
+ }
+
+ /*
+ * If we make it to this point where we have opened the metadata file
+ * we return a structure (possibly empty) instead of just a NULL pointer.
+ */
+ rab = (REMDATA_META_S *)fs_get(sizeof(*rab));
+ memset(rab, 0, sizeof(*rab));
+
+ /*
+ * Check for header line. If it isn't there or is incorrect,
+ * return with the empty rab. This call also positions the file pointer
+ * past the header line.
+ */
+ if(rd_meta_is_broken(fp)){
+
+ dprint((2,
+ "metadata file is broken, creating new one: %s\n",
+ metafile ? metafile : "?"));
+
+ /* Make it size zero so we won't copy any bad stuff */
+ if(fp_file_size(fp) != 0){
+ int fd;
+
+ (void)fclose(fp);
+ our_unlink(metafile);
+
+ if((fd = our_open(metafile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0){
+ q_status_message2(SM_ORDER, 3, 5,
+ _("can't create metadata file %s, continuing (%s)"),
+ metafile, error_description(errno));
+ dprint((2,
+ "can't create metadata file %s: %s\n",
+ metafile ? metafile : "?",
+ error_description(errno)));
+ fs_give((void **)&rab);
+ fs_give((void **)&metafile);
+ return NULL;
+ }
+
+ (void)close(fd);
+ }
+ else
+ (void)fclose(fp);
+
+ fs_give((void **)&metafile);
+ return(rab);
+ }
+
+ fs_give((void **)&metafile);
+
+ /* Look for our line */
+ while((p = fgets(line, sizeof(line), fp)) != NULL)
+ if(strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == TAB)
+ break;
+
+#define SKIP_TO_TAB(p) do{while(*p && *p != TAB)p++;}while(0)
+
+ /*
+ * The line should be TAB-separated with fields:
+ * folder_name cache_file uidvalidity uidnext uid nmsgs read_status date
+ * This part is highly RemImap-specific right now.
+ */
+ if(p){ /* Found the line, parse it. */
+ SKIP_TO_TAB(p); /* skip to TAB following folder_name */
+ if(*p == TAB){
+ q = ++p; /* q points to cache_file */
+ SKIP_TO_TAB(p); /* skip to TAB following cache_file */
+ if(*p == TAB){
+ *p = '\0';
+ rab->local_cache_file = cpystr(q);
+ q = ++p; /* q points to uidvalidity */
+ SKIP_TO_TAB(p); /* skip to TAB following uidvalidity */
+ if(*p == TAB){
+ *p = '\0';
+ rab->uidvalidity = strtoul(q,(char **)NULL,10);
+ q = ++p; /* q points to uidnext */
+ SKIP_TO_TAB(p); /* skip to TAB following uidnext */
+ if(*p == TAB){
+ *p = '\0';
+ rab->uidnext = strtoul(q,(char **)NULL,10);
+ q = ++p; /* q points to uid */
+ SKIP_TO_TAB(p); /* skip to TAB following uid */
+ if(*p == TAB){
+ *p = '\0';
+ rab->uid = strtoul(q,(char **)NULL,10);
+ q = ++p; /* q points to nmsgs */
+ SKIP_TO_TAB(p); /* skip to TAB following nmsgs */
+ if(*p == TAB){
+ *p = '\0';
+ rab->nmsgs = strtoul(q,(char **)NULL,10);
+ q = ++p; /* q points to read_status */
+ SKIP_TO_TAB(p); /* skip to TAB following read_status */
+ if(*p == TAB){
+ *p = '\0';
+ rab->read_status = *q; /* just a char, not whole string */
+ q = ++p; /* q points to date */
+ while(*p && *p != '\n' && *p != '\r') /* skip to newline */
+ p++;
+
+ *p = '\0';
+ rab->date = cpystr(q);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ (void)fclose(fp);
+ return(rab);
+}
+
+
+/*
+ * Returns: -1 if this doesn't look like a metafile or some error,
+ * or if it looks like a non-current metafile.
+ * 0 if it looks like a current metafile.
+ *
+ * A side effect is that the file pointer will be pointing to the second
+ * line if 0 is returned.
+ */
+int
+rd_meta_is_broken(FILE *fp)
+{
+ char buf[MAILTMPLEN];
+
+ if(fp == (FILE *)NULL)
+ return -1;
+
+ if(fp_file_size(fp) <
+ (long)(SIZEOF_PMAGIC + SIZEOF_SPACE + SIZEOF_VERSION_NUM))
+ return -1;
+
+ if(fseek(fp, (long)TO_FIND_HDR_PMAGIC, 0))
+ return -1;
+
+ /* check for magic */
+ if(fread(buf, sizeof(char), (unsigned)SIZEOF_PMAGIC, fp) != SIZEOF_PMAGIC)
+ return -1;
+
+ buf[SIZEOF_PMAGIC] = '\0';
+ if(strcmp(buf, PMAGIC) != 0)
+ return -1;
+
+ /*
+ * If we change to a new version, we may want to add code here to convert
+ * or to cleanup after the old version. For example, it might just
+ * remove the old cache files here.
+ */
+
+ /* check for matching version number */
+ if(fseek(fp, (long)TO_FIND_VERSION_NUM, 0))
+ return -1;
+
+ if(fread(buf, sizeof(char), (unsigned)SIZEOF_VERSION_NUM, fp) !=
+ SIZEOF_VERSION_NUM)
+ return -1;
+
+ buf[SIZEOF_VERSION_NUM] = '\0';
+ if(strcmp(buf, METAFILE_VERSION_NUM) != 0)
+ return -1;
+
+ /* Position file pointer to second line */
+ if(fseek(fp, (long)TO_FIND_HDR_PMAGIC, 0))
+ return -1;
+
+ if(fgets(buf, sizeof(buf), fp) == NULL)
+ return -1;
+
+ return 0;
+}
+
+
+/*
+ * Open a data stream to the remote data.
+ */
+void
+rd_open_remote(REMDATA_S *rd)
+{
+ long openmode = SP_USEPOOL | SP_TEMPUSE;
+
+ dprint((7, "rd_open_remote \"%s\"\n",
+ (rd && rd->rn) ? rd->rn : "?"));
+
+ if(!rd)
+ return;
+
+ if(rd_stream_exists(rd)){
+ rd->last_use = get_adj_time();
+ return;
+ }
+
+ switch(rd->type){
+ case RemImap:
+
+ if(rd->access == ReadOnly)
+ openmode |= OP_READONLY;
+
+ ps_global->noshow_error = 1;
+ rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, NULL);
+ ps_global->noshow_error = 0;
+
+ /* Don't try to reopen if there was a problem (auth failure, etc.) */
+ if(!rd->t.i.stream)
+ rd->flags |= USER_SAID_NO; /* Caution: overloading USER_SAID_NO */
+
+ if(rd->t.i.stream && rd->t.i.stream->halfopen){
+ /* this is a failure */
+ rd_close_remote(rd);
+ }
+
+ if(rd->t.i.stream)
+ rd->last_use = get_adj_time();
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5, "rd_open_remote: type not supported");
+ break;
+ }
+}
+
+
+/*
+ * Close a data stream to the remote data.
+ */
+void
+rd_close_remote(REMDATA_S *rd)
+{
+ if(!rd || !rd_stream_exists(rd))
+ return;
+
+ switch(rd->type){
+ case RemImap:
+ ps_global->noshow_error = 1;
+ pine_mail_close(rd->t.i.stream);
+ rd->t.i.stream = NULL;
+ ps_global->noshow_error = 0;
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5, "rd_close_remote: type not supported");
+ break;
+ }
+}
+
+
+int
+rd_stream_exists(REMDATA_S *rd)
+{
+ if(!rd)
+ return(0);
+
+ switch(rd->type){
+ case RemImap:
+ return(rd->t.i.stream ? 1 : 0);
+
+ default:
+ q_status_message(SM_ORDER, 3,5, "rd_stream_exists: type not supported");
+ break;
+ }
+
+ return(0);
+}
+
+
+/*
+ * Ping the stream and return 1 if it is still alive.
+ */
+int
+rd_ping_stream(REMDATA_S *rd)
+{
+ int ret = 0;
+
+ dprint((7, "rd_ping_stream \"%s\"\n",
+ (rd && rd->rn) ? rd->rn : "?"));
+
+ if(!rd)
+ return(ret);
+
+ if(!rd_stream_exists(rd)){
+ switch(rd->type){
+ case RemImap:
+ /*
+ * If this stream is already actually open, officially open
+ * it and use it.
+ */
+ if(sp_stream_get(rd->rn, SP_MATCH)){
+ long openflags = SP_USEPOOL | SP_TEMPUSE;
+
+ if(rd->access == ReadOnly)
+ openflags |= OP_READONLY;
+
+ rd->t.i.stream = pine_mail_open(NULL, rd->rn, openflags, NULL);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(!rd_stream_exists(rd))
+ return(ret);
+
+ switch(rd->type){
+ case RemImap:
+ ps_global->noshow_error = 1;
+ if(pine_mail_ping(rd->t.i.stream))
+ ret = 1;
+ else
+ rd->t.i.stream = NULL;
+
+ ps_global->noshow_error = 0;
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5, "rd_ping_stream: type not supported");
+ break;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Change readonly access to readwrite if appropriate. Call this if
+ * the remote data ought to be readwrite but it is marked readonly in
+ * the metadata.
+ */
+void
+rd_check_readonly_access(REMDATA_S *rd)
+{
+ if(rd && rd->read_status == 'R'){
+ switch(rd->type){
+ case RemImap:
+ rd_open_remote(rd);
+ if(rd->t.i.stream && !rd->t.i.stream->rdonly){
+ rd->read_status = 'W';
+ rd->access = ReadWrite;
+ }
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_check_readonly_access: type not supported");
+ break;
+ }
+ }
+}
+
+
+/*
+ * Initialize remote data. The remote data is initialized
+ * with no data. On success, the local file will be empty and the remote
+ * folder (if type RemImap) will contain a header message and an empty
+ * data message.
+ * The remote folder already exists before we get here, though it may be empty.
+ *
+ * Returns -1 on failure
+ * 0 on success
+ */
+int
+rd_init_remote(REMDATA_S *rd, int add_only_first_msg)
+{
+ int err = 0;
+ char date[200];
+
+ dprint((7, "rd_init_remote \"%s\"\n",
+ (rd && rd->rn) ? rd->rn : "?"));
+
+ if(rd && rd->type != RemImap){
+ dprint((1, "rd_init_remote: type not supported\n"));
+ return -1;
+ }
+
+ /*
+ * The rest is currently type RemImap-specific.
+ */
+
+ if(!(rd->flags & NO_FILE || rd->lf) ||
+ !rd->rn || !rd->so || !rd->t.i.stream ||
+ !rd->t.i.special_hdr){
+ dprint((1,
+ "rd_init_remote: Unexpected error: %s is NULL\n",
+ !(rd->flags & NO_FILE || rd->lf) ? "localfile" :
+ !rd->rn ? "remotename" :
+ !rd->so ? "so" :
+ !rd->t.i.stream ? "stream" :
+ !rd->t.i.special_hdr ? "special_hdr" : "?"));
+ return -1;
+ }
+
+ /* already init'd */
+ if(rd->t.i.stream->nmsgs >= 2 ||
+ (rd->t.i.stream->nmsgs >= 1 && add_only_first_msg))
+ return err;
+
+ dprint((7, " - rd_init_remote(%s): %s\n",
+ rd->t.i.special_hdr ? rd->t.i.special_hdr : "?",
+ rd->rn ? rd->rn : "?"));
+
+ /*
+ * We want to protect the user who specifies an actual address book
+ * file on the remote system as a remote address book. For example,
+ * they might say address-book={host}.addressbook where .addressbook
+ * is just a file on host. Remote address books are folders, not
+ * files. We also want to protect the user who specifies an existing
+ * mail folder as a remote address book. We do that by requiring the
+ * first message in the folder to be our header message.
+ */
+
+ if(rd->t.i.stream->rdonly){
+ q_status_message1(SM_ORDER | SM_DING, 7, 10,
+ _("Can't initialize folder \"%s\" (write permission)"), rd->rn);
+ if(rd->t.i.stream->nmsgs > 0)
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Choose a new, unused folder for the remote data"));
+
+ dprint((1,
+ "Can't initialize remote folder \"%s\"\n",
+ rd->rn ? rd->rn : "?"));
+ dprint((1,
+ " No write permission for that remote folder.\n"));
+ dprint((1,
+ " Choose a new unused folder for the remote data.\n"));
+ err = -1;
+ }
+
+ if(!err){
+ if(rd->t.i.stream->nmsgs == 0){
+ int we_cancel;
+
+ we_cancel = busy_cue(_("Initializing remote data"), NULL, 1);
+ rfc822_date(date);
+ /*
+ * The first message in a remote data folder is a special
+ * header message. It is there as a way to explain what the
+ * folder is to users who open it trying to read it. It is also
+ * used as a consistency check so that we don't use a folder
+ * that was being used for something else as a remote
+ * data folder.
+ */
+ err = rd_add_hdr_msg(rd, date);
+ if(rd->t.i.stream->nmsgs == 0)
+ rd_ping_stream(rd);
+ if(rd->t.i.stream && rd->t.i.stream->nmsgs == 0)
+ pine_mail_check(rd->t.i.stream);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+ else{
+ char *eptr = NULL;
+
+ err = rd_chk_for_hdr_msg(&(rd->t.i.stream), rd, &eptr);
+ if(err){
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("\"%s\" has invalid format, can't initialize"), rd->rn);
+
+ dprint((1,
+ "Can't initialize remote data \"%s\"\n",
+ rd->rn ? rd->rn : "?"));
+
+ if(eptr){
+ q_status_message1(SM_ORDER, 3, 5, "%s", eptr);
+ dprint((1, "%s\n", eptr ? eptr : "?"));
+ fs_give((void **)&eptr);
+ }
+ }
+ }
+ }
+
+ /*
+ * Add the second (empty) message.
+ */
+ if(!err && !add_only_first_msg){
+ char *tempfile = NULL;
+ int fd = -1;
+
+ if(rd->flags & NO_FILE){
+ if(so_truncate(rd->sonofile, 0L) == 0)
+ err = -1;
+ }
+ else{
+ if(!(tempfile = tempfile_in_same_dir(rd->lf, "a8", NULL))){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary file: %s"),
+ error_description(errno));
+ dprint((2, "init_remote: Error opening file: %s\n",
+ error_description(errno)));
+ err = -1;
+ }
+
+ if(!err &&
+ (fd = our_open(tempfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "Error opening temporary file %.200s: %.200s",
+ tempfile, error_description(errno));
+ dprint((2,
+ "init_remote: Error opening temporary file: %s: %s\n",
+ tempfile ? tempfile : "?", error_description(errno)));
+ our_unlink(tempfile);
+ err = -1;
+ }
+ else
+ (void)close(fd);
+
+ if(!err && rename_file(tempfile, rd->lf) < 0){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "Error creating cache file %.200s: %.200s",
+ rd->lf, error_description(errno));
+ our_unlink(tempfile);
+ err = -1;
+ }
+
+ if(tempfile)
+ fs_give((void **)&tempfile);
+ }
+
+ if(!err){
+ err = rd_update_remote(rd, date);
+ if(err){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to remote folder: %s"),
+ error_description(errno));
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ "Creation of remote data folder failed");
+ }
+ }
+ }
+
+ if(!err){
+ rd_update_metadata(rd, date);
+ /* turn off out of date flag */
+ rd->flags &= ~REM_OUTOFDATE;
+ }
+
+ return(err ? -1 : 0);
+}
+
+
+/*
+ * IMAP stream should already be open to a remote folder.
+ * Check the first message in the folder to be sure it is the right
+ * kind of message, not some message from some other folder.
+ *
+ * Returns 0 if ok, < 0 if invalid format.
+ *
+ */
+int
+rd_chk_for_hdr_msg(MAILSTREAM **streamp, REMDATA_S *rd, char **errmsg)
+{
+ char *fields[3], *values[3];
+ char *h;
+ int tried_again = 0;
+ int ret;
+ MAILSTREAM *st = NULL;
+
+ fields[0] = rd->t.i.special_hdr;
+ fields[1] = "received";
+ fields[2] = NULL;
+
+try_again:
+ ret = -1;
+
+ if(!streamp || !*streamp){
+ dprint((1, "rd_chk_for_hdr_msg: stream is null\n"));
+ }
+ else if((*streamp)->nmsgs == 0){
+ ret = -2;
+ dprint((1,
+ "rd_chk_for_hdr_msg: stream has nmsgs=0, try a ping\n"));
+ if(!pine_mail_ping(*streamp))
+ *streamp = NULL;
+
+ if(*streamp && (*streamp)->nmsgs == 0){
+ dprint((1,
+ "rd_chk_for_hdr_msg: still looks like nmsgs=0, try a check\n"));
+ pine_mail_check(*streamp);
+ }
+
+ if(*streamp && (*streamp)->nmsgs == 0){
+ dprint((1,
+ "rd_chk_for_hdr_msg: still nmsgs=0, try re-opening stream\n"));
+
+ if(rd_stream_exists(rd))
+ rd_close_remote(rd);
+
+ rd_open_remote(rd);
+ if(rd_stream_exists(rd))
+ st = rd->t.i.stream;
+ }
+
+ if(!st)
+ st = *streamp;
+
+ if(st && st->nmsgs == 0){
+ dprint((1,
+ "rd_chk_for_hdr_msg: can't see header message\n"));
+ }
+ }
+ else
+ st = *streamp;
+
+ if(st && st->nmsgs != 0
+ && (h=pine_fetchheader_lines(st, 1L, NULL, fields))){
+ simple_header_parse(h, fields, values);
+ ret = -3;
+ if(values[1])
+ ret = -4;
+ else if(values[0]){
+ rd->cookie = strtoul(values[0], (char **)NULL, 10);
+ if(rd->cookie == 0)
+ ret = -5;
+ else if(rd->cookie == 1){
+ if(rd->flags & COOKIE_ONE_OK || tried_again)
+ ret = 0;
+ else
+ ret = -6;
+ }
+ else if(rd->cookie > 1)
+ ret = 0;
+ }
+
+ if(values[0])
+ fs_give((void **)&values[0]);
+
+ if(values[1])
+ fs_give((void **)&values[1]);
+
+ fs_give((void **)&h);
+ }
+
+
+ if(ret && ret != -6 && errmsg){
+ *errmsg = (char *)fs_get(500 * sizeof(char));
+ (*errmsg)[0] = '\0';
+ }
+
+ if(ret == -1){
+ /* null stream */
+ if(errmsg)
+ snprintf(*errmsg, 500, _("Can't open remote address book \"%s\""), rd->rn);
+ }
+ else if(ret == -2){
+ /* no messages in folder */
+ if(errmsg)
+ snprintf(*errmsg, 500,
+ _("Error: no messages in remote address book \"%s\"!"),
+ rd->rn);
+ }
+ else if(ret == -3){
+ /* no cookie */
+ if(errmsg)
+ snprintf(*errmsg, 500,
+ "First msg in \"%s\" should have \"%s\" header",
+ rd->rn, rd->t.i.special_hdr);
+ }
+ else if(ret == -4){
+ /* Received header */
+ if(errmsg)
+ snprintf(*errmsg, 500,
+ _("Suspicious Received headers in first msg in \"%s\""),
+ rd->rn);
+ }
+ else if(ret == -5){
+
+ /* cookie is 0 */
+
+ /*
+ * This is a failure and should not happen, but we're not going to
+ * fail on this condition.
+ */
+ dprint((1, "Unexpected value in \"%s\" header of \"%s\"\n",
+ rd->t.i.special_hdr ? rd->t.i.special_hdr : "?",
+ rd->rn ? rd->rn : "?"));
+ ret = 0;
+ }
+ else if(ret == -6){
+ dprint((1,
+ "rd_chk_for_hdr_msg: cookie is 1, try to upgrade it\n"));
+
+ if(rd_remote_is_readonly(rd)){
+ dprint((1,
+ "rd_chk_for_hdr_msg: can't upgrade, readonly\n"));
+ ret = 0; /* stick with 1 */
+ }
+ else{
+ /* cookie is 1, upgrade it */
+ if(rd_upgrade_cookies(rd, st->nmsgs, 0) == 0){
+ /* now check again */
+ if(!tried_again){
+ tried_again++;
+ goto try_again;
+ }
+ }
+
+ /*
+ * This is actually a failure but we've decided that this
+ * failure is ok.
+ */
+ ret = 0;
+ }
+ }
+
+ if(errmsg && *errmsg)
+ dprint((1, "rd_chk_for_hdr_msg: %s\n", *errmsg));
+
+ return ret;
+}
+
+
+/*
+ * For remote data, this adds the explanatory header
+ * message to the remote IMAP folder.
+ *
+ * Args: rd -- Remote data handle
+ * date -- The date string to use
+ *
+ * Result: 0 success
+ * -1 failure
+ */
+int
+rd_add_hdr_msg(REMDATA_S *rd, char *date)
+{
+ int err = 0;
+
+ if(!rd|| rd->type != RemImap || !rd->rn || !rd->so || !rd->t.i.special_hdr){
+ dprint((1,
+ "rd_add_hdr_msg: Unexpected error: %s is NULL\n",
+ !rd ? "rd" :
+ !rd->rn ? "remotename" :
+ !rd->so ? "so" :
+ !rd->t.i.special_hdr ? "special_hdr" : "?"));
+ return -1;
+ }
+
+ err = rd_store_fake_hdrs(rd, "Header Message for Remote Data",
+ "plain", date);
+
+ /* Write the dummy message */
+ if(!strucmp(rd->t.i.special_hdr, REMOTE_ABOOK_SUBTYPE)){
+ if(!err && so_puts(rd->so,
+ "This folder contains a single Alpine addressbook.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "This message is just an explanatory message.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The last message in the folder is the live addressbook data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The rest of the messages contain previous revisions of the addressbook data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "To restore a previous revision just delete and expunge all of the messages\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "which come after it.\015\012") == 0)
+ err = -1;
+ }
+ else if(!strucmp(rd->t.i.special_hdr, REMOTE_PINERC_SUBTYPE)){
+ if(!err && so_puts(rd->so,
+ "This folder contains a Alpine config file.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "This message is just an explanatory message.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The last message in the folder is the live config data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The rest of the messages contain previous revisions of the data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "To restore a previous revision just delete and expunge all of the messages\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "which come after it.\015\012") == 0)
+ err = -1;
+ }
+ else{
+ if(!err && so_puts(rd->so,
+ "This folder contains remote Alpine data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "This message is just an explanatory message.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The last message in the folder is the live data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "The rest of the messages contain previous revisions of the data.\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "To restore a previous revision just delete and expunge all of the messages\015\012") == 0)
+ err = -1;
+ if(!err && so_puts(rd->so,
+ "which come after it.\015\012") == 0)
+ err = -1;
+ }
+
+ /* Take the message from "so" to the remote folder */
+ if(!err){
+ MAILSTREAM *st;
+
+ if((st = rd->t.i.stream) != NULL)
+ rd->t.i.shouldbe_nmsgs = rd->t.i.stream->nmsgs + 1;
+ else
+ st = adrbk_handy_stream(rd->rn);
+
+ err = write_fcc(rd->rn, NULL, rd->so, st, "remote data", NULL) ? 0 : -1;
+ }
+
+ return err;
+}
+
+
+/*
+ * Write some fake header lines into storage object rd->so.
+ *
+ * Args: rd
+ * subject -- subject to put in header
+ * subtype -- subtype to put in header
+ * date -- date to put in header
+ */
+int
+rd_store_fake_hdrs(REMDATA_S *rd, char *subject, char *subtype, char *date)
+{
+ ENVELOPE *fake_env;
+ BODY *fake_body;
+ ADDRESS *fake_from;
+ int err = 0;
+ char vers[50], *p;
+ unsigned long r = 0L;
+ RFC822BUFFER rbuf;
+
+ if(!rd|| rd->type != RemImap || !rd->so || !rd->t.i.special_hdr)
+ return -1;
+
+ fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
+ memset(fake_env, 0, sizeof(ENVELOPE));
+ fake_body = (BODY *)fs_get(sizeof(BODY));
+ memset(fake_body, 0, sizeof(BODY));
+ fake_from = (ADDRESS *)fs_get(sizeof(ADDRESS));
+ memset(fake_from, 0, sizeof(ADDRESS));
+
+ fake_env->subject = cpystr(subject);
+ fake_env->date = (unsigned char *) cpystr(date);
+ fake_from->personal = cpystr("Pine Remote Data");
+ fake_from->mailbox = cpystr("nobody");
+ fake_from->host = cpystr("nowhere");
+ fake_env->from = fake_from;
+ fake_body->type = REMOTE_DATA_TYPE;
+ fake_body->subtype = cpystr(subtype);
+ set_parameter(&fake_body->parameter, "charset", "UTF-8");
+
+ if(rd->cookie > 0)
+ r = rd->cookie;
+
+ if(!r){
+ int i;
+
+ for(i = 100; i > 0 && r < 1000000; i--)
+ r = random();
+
+ if(r < 1000000)
+ r = 1712836L + getpid();
+
+ rd->cookie = r;
+ }
+
+ snprintf(vers, sizeof(vers), "%ld", r);
+
+ p = tmp_20k_buf;
+ *p = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = p;
+ rbuf.cur = p;
+ rbuf.end = p+SIZEOF_20KBUF-1;
+ rfc822_output_header_line(&rbuf, rd->t.i.special_hdr, 0L, vers);
+ rfc822_output_header(&rbuf, fake_env, fake_body, NULL, 0L);
+ *rbuf.cur = '\0';
+
+ mail_free_envelope(&fake_env);
+ mail_free_body(&fake_body);
+
+ /* Write the fake headers */
+ if(so_puts(rd->so, tmp_20k_buf) == 0)
+ err = -1;
+
+ return err;
+}
+
+
+/*
+ * We have discovered that the data in the remote folder is suspicious.
+ * In some cases it is just because it is from an old version of pine.
+ * We have decided to update the data so that it won't look suspicious
+ * next time.
+ *
+ * Args -- only_update_last If set, that means to just add a new last message
+ * by calling rd_update_remote. Don't create a new
+ * header message and delete the old header message.
+ * nmsgs Not used if only_update_last is set
+ */
+int
+rd_upgrade_cookies(REMDATA_S *rd, long int nmsgs, int only_update_last)
+{
+ char date[200];
+ int err = 0;
+
+ /*
+ * We need to copy the data from the last message, add a new header
+ * message with a random cookie, add the data back in with the
+ * new cookie, and delete the old messages.
+ */
+
+ /* get data */
+ rd->flags |= COOKIE_ONE_OK;
+
+ /*
+ * The local copy may be newer than the remote copy. We don't want to
+ * blast the local copy in that case. The BELIEVE_CACHE flag tells us
+ * to not do the blasting.
+ */
+ if(rd->flags & BELIEVE_CACHE)
+ rd->flags &= ~BELIEVE_CACHE;
+ else{
+ dprint((1, "rd_upgrade_cookies: copy abook data\n"));
+ err = rd_update_local(rd);
+ }
+
+ if(!err && !only_update_last){
+ rd->cookie = 0; /* causes new cookie to be generated */
+ rfc822_date(date);
+ dprint((1, "rd_upgrade_cookies: add new hdr msg to end\n"));
+ err = rd_add_hdr_msg(rd, date);
+ }
+
+ if(!err){
+ dprint((1, "rd_upgrade_cookies: copy back data\n"));
+ err = rd_update_remote(rd, NULL);
+ }
+
+ rd->flags &= ~COOKIE_ONE_OK;
+
+ if(!err && !only_update_last){
+ char sequence[20];
+
+ /*
+ * We've created a new header message and added a new copy of the
+ * data after it. Only problem is that the new copy will have used
+ * the original header message to get its cookie (== 1) from. We
+ * could have deleted the original messages before the last step
+ * to get it right but that would delete all copies of the data
+ * temporarily. Delete now and then re-update.
+ */
+ rd_ping_stream(rd);
+ rd_open_remote(rd);
+ if(rd->t.i.stream && rd->t.i.stream->nmsgs >= nmsgs+2){
+ snprintf(sequence, sizeof(sequence), "1:%ld", nmsgs);
+ mail_flag(rd->t.i.stream, sequence, "\\DELETED", ST_SET);
+ mail_expunge(rd->t.i.stream);
+ err = rd_update_remote(rd, NULL);
+ }
+ }
+
+ return(err);
+}
+
+
+/*
+ * Copy remote data to local file. If needed, the remote data is initialized
+ * with no data. On success, the local file contains the remote data (which
+ * means it will be empty if we initialized).
+ *
+ * Returns != 0 on failure
+ * 0 on success
+ */
+int
+rd_update_local(REMDATA_S *rd)
+{
+ char *error;
+ STORE_S *store;
+ gf_io_t pc;
+ int i, we_cancel = 0;
+ BODY *body = NULL;
+ ENVELOPE *env;
+ char *tempfile = NULL;
+
+
+ if(!rd || !(rd->flags & NO_FILE || rd->lf) || !rd->rn){
+ dprint((1,
+ "rd_update_local: Unexpected error: %s is NULL\n",
+ !rd ? "rd" :
+ !(rd->flags & NO_FILE || rd->lf) ? "localfile" :
+ !rd->rn ? "remotename" : "?"));
+
+ return -1;
+ }
+
+ dprint((3, " - rd_update_local(%s): %s => %s\n",
+ rd->type == RemImap ? "Imap" : "?", rd->rn ? rd->rn : "?",
+ (rd->flags & NO_FILE) ? "<mem>" : (rd->lf ? rd->lf : "?")));
+
+ switch(rd->type){
+ case RemImap:
+ if(!rd->so || !rd->t.i.special_hdr){
+ dprint((1,
+ "rd_update_local: Unexpected error: %s is NULL\n",
+ !rd->so ? "so" :
+ !rd->t.i.special_hdr ? "special_hdr" : "?"));
+ return -1;
+ }
+
+ rd_open_remote(rd);
+ if(!rd_stream_exists(rd)){
+ if(rd->flags & NO_PERM_CACHE){
+ dprint((1,
+ "rd_update_local: backtrack, remote folder does not exist yet\n"));
+ }
+ else{
+ dprint((1,
+ "rd_update_local: Unexpected error: stream is NULL\n"));
+ }
+
+ return -1;
+ }
+
+ if(rd->t.i.stream){
+ char ebuf[500];
+ char *eptr = NULL;
+ int chk;
+
+ /* force ReadOnly */
+ if(rd->t.i.stream->rdonly){
+ rd->read_status = 'R';
+ rd->access = ReadOnly;
+ }
+ else
+ rd->read_status = 'W';
+
+ if(rd->t.i.stream->nmsgs < 2)
+ return(rd_init_remote(rd, 0));
+ else if(rd_chk_for_hdr_msg(&(rd->t.i.stream), rd, &eptr)){
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("Can't initialize \"%s\" (invalid format)"), rd->rn);
+
+ if(eptr){
+ q_status_message1(SM_ORDER, 3, 5, "%s", eptr);
+ dprint((1, "%s\n", eptr));
+ fs_give((void **)&eptr);
+ }
+
+ dprint((1,
+ "Can't initialize remote data \"%s\"\n",
+ rd->rn ? rd->rn : "?"));
+ return -1;
+ }
+
+ we_cancel = busy_cue(_("Copying remote data"), NULL, 1);
+
+ if(rd->flags & NO_FILE){
+ store = rd->sonofile;
+ so_truncate(store, 0L);
+ }
+ else{
+ if(!(tempfile = tempfile_in_same_dir(rd->lf, "a8", NULL))){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary file: %s"),
+ error_description(errno));
+ dprint((2,
+ "rd_update_local: Error opening temporary file: %s\n",
+ error_description(errno)));
+ return -1;
+ }
+
+ /* Copy the data into tempfile */
+ if((store = so_get(FileStar, tempfile, WRITE_ACCESS|OWNER_ONLY))
+ == NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary file %s: %s"),
+ tempfile, error_description(errno));
+ dprint((2,
+ "rd_update_local: Error opening temporary file: %s: %s\n",
+ tempfile ? tempfile : "?",
+ error_description(errno)));
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ return -1;
+ }
+ }
+
+ /*
+ * Copy from the last message in the folder.
+ */
+ if(!pine_mail_fetchstructure(rd->t.i.stream, rd->t.i.stream->nmsgs,
+ &body)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't access remote IMAP data"));
+ dprint((2, "Can't access remote IMAP data\n"));
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+
+ if(!(rd->flags & NO_FILE))
+ so_give(&store);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return -1;
+ }
+
+ if(!body ||
+ body->type != REMOTE_DATA_TYPE ||
+ !body->subtype ||
+ strucmp(body->subtype, rd->t.i.special_hdr)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Remote IMAP folder has wrong contents"));
+ dprint((2,
+ "Remote IMAP folder has wrong contents\n"));
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+
+ if(!(rd->flags & NO_FILE))
+ so_give(&store);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return -1;
+ }
+
+ if(!(env = pine_mail_fetchenvelope(rd->t.i.stream,
+ rd->t.i.stream->nmsgs))){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't access check date in remote data"));
+ dprint((2,
+ "Can't access check date in remote data\n"));
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+
+ if(!(rd->flags & NO_FILE))
+ so_give(&store);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return -1;
+ }
+
+ if(rd && rd->flags & USER_SAID_YES)
+ chk = 0;
+ else
+ chk = rd_check_for_suspect_data(rd);
+
+ switch(chk){
+ case -1: /* suspicious data, user says abort */
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+
+ if(!(rd->flags & NO_FILE))
+ so_give(&store);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return -1;
+
+ case 1: /* suspicious data, user says go ahead */
+ if(rd_remote_is_readonly(rd)){
+ dprint((1,
+ "rd_update_local: can't upgrade, readonly\n"));
+ }
+ else
+ /* attempt to upgrade cookie in last message */
+ (void)rd_upgrade_cookies(rd, 0, 1);
+
+ break;
+
+ case 0: /* all is ok */
+ default:
+ break;
+ }
+
+
+ /* store may have been written to at this point, so we'll clear it out */
+ so_truncate(store, 0L);
+
+ gf_set_so_writec(&pc, store);
+
+ error = detach(rd->t.i.stream, rd->t.i.stream->nmsgs, "1", 0L,
+ NULL, pc, NULL, DT_NODFILTER);
+
+ gf_clear_so_writec(store);
+
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(!(rd->flags & NO_FILE)){
+ if(so_give(&store)){
+ snprintf(ebuf, sizeof(ebuf), _("Error writing temp file: %s"),
+ error_description(errno));
+ error = ebuf;
+ }
+ }
+
+ if(error){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("%s: Error copying remote IMAP data"), error);
+ dprint((2, "rd_update_local: Error copying: %s\n",
+ error ? error : "?"));
+ if(tempfile){
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ }
+
+ return -1;
+ }
+
+ if(tempfile && (i = rename_file(tempfile, rd->lf)) < 0){
+#ifdef _WINDOWS
+ if(i == -5){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error updating local file: %s: %s"),
+ rd->lf, error_description(errno));
+ q_status_message(SM_ORDER, 3, 4,
+ _("Perhaps another process has the file open?"));
+ dprint((2,
+ "Rename_file(%s,%s): %s: returned -5, another process has file open?\n",
+ tempfile ? tempfile : "?",
+ rd->lf ? rd->lf : "?",
+ error_description(errno)));
+ }
+ else
+#endif /* _WINDOWS */
+ {
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error updating cache file %s: %s"),
+ rd->lf, error_description(errno));
+ dprint((2,
+ "Error updating cache file %s: rename(%s,%s): %s\n",
+ tempfile ? tempfile : "?",
+ tempfile ? tempfile : "?",
+ rd->lf ? rd->lf : "?",
+ error_description(errno)));
+ }
+
+ our_unlink(tempfile);
+ fs_give((void **)&tempfile);
+ return -1;
+ }
+
+ dprint((5,
+ "in rd_update_local, setting chk_date to ->%s<-\n",
+ env->date ? (char *)env->date : "?"));
+ rd_update_metadata(rd, (char *) env->date);
+
+ /* turn off out of date flag */
+ rd->flags &= ~REM_OUTOFDATE;
+
+ if(tempfile)
+ fs_give((void **)&tempfile);
+
+ return 0;
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("Can't open remote IMAP folder \"%s\""), rd->rn);
+ dprint((1,
+ "Can't open remote IMAP folder \"%s\"\n",
+ rd->rn ? rd->rn : "?"));
+ rd->access = ReadOnly;
+ return -1;
+ }
+
+ break;
+
+ default:
+ dprint((1, "rd_update_local: Unsupported type\n"));
+ return -1;
+ }
+}
+
+
+/*
+ * Copy local data to remote folder.
+ *
+ * Args lf -- Local file name
+ * rn -- Remote name
+ * header_to_check -- Name of indicator header in remote folder
+ * imapstuff -- Structure holding info about connection
+ * returndate -- Pointer to the date that was stored in the remote folder
+ *
+ * Returns !=0 on failure
+ * 0 on success
+ */
+int
+rd_update_remote(REMDATA_S *rd, char *returndate)
+{
+ STORE_S *store;
+ int err = 0;
+ long openmode = SP_USEPOOL | SP_TEMPUSE;
+ MAILSTREAM *st;
+ char *eptr = NULL;
+
+ if(rd && rd->type != RemImap){
+ dprint((1, "rd_update_remote: type not supported\n"));
+ return -1;
+ }
+
+ if(!rd || !(rd->flags & NO_FILE || rd->lf) || !rd->rn ||
+ !rd->so || !rd->t.i.special_hdr){
+ dprint((1,
+ "rd_update_remote: Unexpected error: %s is NULL\n",
+ !rd ? "rd" :
+ !(rd->flags & NO_FILE || rd->lf) ? "localfile" :
+ !rd->rn ? "remotename" :
+ !rd->so ? "so" :
+ !rd->t.i.special_hdr ? "special_hdr" : "?"));
+ return -1;
+ }
+
+ dprint((7, " - rd_update_remote(%s): %s => %s\n",
+ rd->t.i.special_hdr ? rd->t.i.special_hdr : "?",
+ rd->lf ? rd->lf : "<mem>",
+ rd->rn ? rd->rn : "?"));
+
+ if(!(st = rd->t.i.stream))
+ st = adrbk_handy_stream(rd->rn);
+
+ if(!st)
+ st = rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, NULL);
+
+ if(!st){
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("Can't open \"%s\" for copying"), rd->rn);
+ dprint((1,
+ "rd_update_remote: Can't open remote folder \"%s\" for copying\n",
+ rd->rn ? rd->rn : "?"));
+ return 1;
+ }
+
+ rd->last_use = get_adj_time();
+ err = rd_chk_for_hdr_msg(&st, rd, &eptr);
+ if(err){
+ q_status_message1(SM_ORDER | SM_DING, 5, 5,
+ _("\"%s\" has invalid format"), rd->rn);
+
+ if(eptr){
+ q_status_message1(SM_ORDER, 3, 5, "%s", eptr);
+ dprint((1, "%s\n", eptr));
+ fs_give((void **)&eptr);
+ }
+
+ dprint((1,
+ "rd_update_remote: \"%s\" has invalid format\n",
+ rd->rn ? rd->rn : "?"));
+ return 1;
+ }
+
+ errno = 0;
+
+ /*
+ * The data that will be going to the remote folder rn is
+ * written into the following storage object and then copied to
+ * the remote folder from there.
+ */
+
+ if(rd->flags & NO_FILE){
+ store = rd->sonofile;
+ if(store)
+ so_seek(store, 0L, 0); /* rewind */
+ }
+ else
+ store = so_get(FileStar, rd->lf, READ_ACCESS);
+
+ if(store != NULL){
+ char date[200];
+ unsigned char c;
+ unsigned char last_c = 0;
+
+ /* Reset the storage object, since we may have already used it. */
+ if(so_truncate(rd->so, 0L) == 0)
+ err = 1;
+
+ rfc822_date(date);
+ dprint((7,
+ "in rd_update_remote, storing date ->%s<-\n",
+ date ? date : "?"));
+ if(!err && rd_store_fake_hdrs(rd, "Pine Remote Data Container",
+ rd->t.i.special_hdr, date))
+ err = 1;
+
+ /* save the date for later comparisons */
+ if(!err && returndate)
+ strncpy(returndate, date, 100);
+
+ /* Write the data */
+ while(!err && so_readc(&c, store)){
+ /*
+ * C-client expects CRLF-terminated lines. Convert them
+ * as we copy into c-client. Read ahead isn't available.
+ * Leave CRLF as is, convert LF to CRLF, leave CR as is.
+ */
+ if(last_c != '\r' && c == '\n'){
+ /* Convert \n to CRFL */
+ if(so_writec('\r', rd->so) == 0 || so_writec('\n', rd->so) == 0)
+ err = 1;
+
+ last_c = 0;
+ }
+ else{
+ last_c = c;
+ if(so_writec((int) c, rd->so) == 0)
+ err = 1;
+ }
+ }
+
+ /*
+ * Take that message from so to the remote folder.
+ * We append to that folder and always
+ * use the final message as the active data.
+ */
+ if(!err){
+ MAILSTREAM *st;
+
+ if((st = rd->t.i.stream) != NULL)
+ rd->t.i.shouldbe_nmsgs = rd->t.i.stream->nmsgs + 1;
+ else
+ st = adrbk_handy_stream(rd->rn);
+
+ err = write_fcc(rd->rn, NULL, rd->so, st,
+ "remote data", NULL) ? 0 : 1;
+ }
+
+
+ if(!(rd->flags & NO_FILE))
+ so_give(&store);
+ }
+ else
+ err = -1;
+
+ if(err)
+ dprint((2, "error in rd_update_remote for %s => %s\n",
+ rd->lf ? rd->lf : "<mem>", rd->rn ? rd->rn : "?"));
+
+ return(err);
+}
+
+
+/*
+ * Check to see if the remote data has changed since we cached it.
+ *
+ * Args rd -- REMDATA handle
+ * do_it_now -- If > 0, check now regardless
+ * If = 0, check if time since last chk more than default
+ * If < 0, check if time since last chk more than -do_it_now
+ */
+void
+rd_check_remvalid(REMDATA_S *rd, long int do_it_now)
+{
+ time_t chk_interval;
+ long openmode = SP_USEPOOL | SP_TEMPUSE;
+ MAILSTREAM *stat_stream = NULL;
+ int we_cancel = 0, got_cmsgs = 0;
+ unsigned long current_nmsgs;
+
+ dprint((7, "- rd_check_remvalid(%s) -\n",
+ (rd && rd->rn) ? rd->rn : ""));
+
+ if(rd && rd->type != RemImap){
+ dprint((1, "rd_check_remvalid: type not supported\n"));
+ return;
+ }
+
+ if(!rd || rd->flags & REM_OUTOFDATE || rd->flags & USE_OLD_CACHE)
+ return;
+
+ if(!rd->t.i.chk_date){
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (chk_date)\n",
+ rd->rn));
+ rd->flags |= REM_OUTOFDATE;
+ return;
+ }
+
+ if(rd->t.i.chk_nmsgs <= 1){
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (chk_nmsgs <= 1)\n",
+ rd->rn ? rd->rn : "?"));
+ rd->flags |= REM_OUTOFDATE;
+ return;
+ }
+
+ if(do_it_now < 0L){
+ chk_interval = -1L * do_it_now;
+ do_it_now = 0L;
+ }
+ else
+ chk_interval = ps_global->remote_abook_validity * 60L;
+
+ /* too soon to check again */
+ if(!do_it_now &&
+ (chk_interval == 0L ||
+ get_adj_time() <= rd->last_valid_chk + chk_interval))
+ return;
+
+ if(rd->access == ReadOnly)
+ openmode |= OP_READONLY;
+
+ rd->last_valid_chk = get_adj_time();
+ mm_status_result.flags = 0L;
+
+ /* make sure the cache file is still there */
+ if(rd->lf && can_access(rd->lf, READ_ACCESS) != 0){
+ dprint((2,
+ "rd_check_remvalid: %s: cache file %s disappeared\n",
+ rd->rn ? rd->rn : "?",
+ rd->lf ? rd->lf : "?"));
+ rd->flags |= REM_OUTOFDATE;
+ return;
+ }
+
+ /*
+ * If the stream is open we should check there instead of using
+ * a STATUS command. Check to see if it is really still alive with the
+ * ping. It would be convenient if we could use a status command
+ * on the open stream but apparently that won't work everywhere.
+ */
+ rd_ping_stream(rd);
+
+try_looking_in_stream:
+
+ /*
+ * Get the current number of messages in the folder to
+ * compare with our saved number of messages.
+ */
+ current_nmsgs = 0;
+ if(rd->t.i.stream){
+ dprint((7, "using open remote data stream\n"));
+ rd->last_use = get_adj_time();
+ current_nmsgs = rd->t.i.stream->nmsgs;
+ got_cmsgs++;
+ }
+ else{
+
+ /*
+ * Try to use the imap status command
+ * to get the information as cheaply as possible.
+ * If NO_STATUSCMD is set we just bypass all this stuff.
+ */
+
+ if(!(rd->flags & NO_STATUSCMD))
+ stat_stream = adrbk_handy_stream(rd->rn);
+
+ /*
+ * If we don't have a stream to use for the status command we
+ * skip it and open the folder instead. Then, next time we want to
+ * check we'll probably be able to use that open stream instead of
+ * opening another one each time for the status command.
+ */
+ if(stat_stream){
+ if(!LEVELSTATUS(stat_stream)){
+ rd->flags |= NO_STATUSCMD;
+ dprint((2,
+ "rd_check_remvalid: remote data %s: server doesn't support status\n",
+ rd->rn ? rd->rn : "?"));
+ }
+ else{
+ /*
+ * This sure seems like a crock. We have to check to
+ * see if the stream is actually open to the folder
+ * we want to do the status on because c-client can't
+ * do a status on an open folder. In this case, we fake
+ * the status command results ourselves.
+ * If we're so unlucky as to get back a stream that will
+ * work for the status command while we also have another
+ * stream that is rd->rn and we don't pick up on that,
+ * too bad.
+ */
+ if(same_stream_and_mailbox(rd->rn, stat_stream)){
+ dprint((7,
+ "rd_check_remvalid: faking status\n"));
+ mm_status_result.flags = SA_MESSAGES | SA_UIDVALIDITY
+ | SA_UIDNEXT;
+ mm_status_result.messages = stat_stream->nmsgs;
+ mm_status_result.uidvalidity = stat_stream->uid_validity;
+ mm_status_result.uidnext = stat_stream->uid_last+1;
+ }
+ else{
+
+ dprint((7,
+ "rd_check_remvalid: trying status\n"));
+ ps_global->noshow_error = 1;
+ if(!pine_mail_status(stat_stream, rd->rn,
+ SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES)){
+ /* failed, mark it so we won't try again */
+ rd->flags |= NO_STATUSCMD;
+ dprint((2,
+ "rd_check_remvalid: addrbook %s: status command failed\n",
+ rd->rn ? rd->rn : "?"));
+ mm_status_result.flags = 0L;
+ }
+ }
+
+ ps_global->noshow_error = 0;
+ }
+ }
+
+ /* if we got back a result from the status command, use it */
+ if(mm_status_result.flags){
+ dprint((7,
+ "rd_check_remvalid: got status_result 0x%x\n",
+ mm_status_result.flags));
+ if(mm_status_result.flags & SA_MESSAGES){
+ current_nmsgs = mm_status_result.messages;
+ got_cmsgs++;
+ }
+ }
+ }
+
+ /*
+ * Check current_nmsgs versus what we think it should be.
+ * If they're different we know things have changed and we can
+ * return now. If they're the same we don't know.
+ */
+ if(got_cmsgs && current_nmsgs != rd->t.i.chk_nmsgs){
+ rd->flags |= REM_OUTOFDATE;
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (current msgs (%ld) != chk_nmsgs (%ld))\n",
+ rd->rn ? rd->rn : "?", current_nmsgs, rd->t.i.chk_nmsgs));
+ return;
+ }
+
+ /*
+ * Get the current uidvalidity and uidnext values from the
+ * folder to compare with our saved values.
+ */
+ if(rd->t.i.stream){
+ if(rd->t.i.stream->uid_validity == rd->t.i.uidvalidity){
+ /*
+ * Uid is valid so we just have to check whether or not the
+ * uid of the last message has changed or not and return.
+ */
+ if(mail_uid(rd->t.i.stream, rd->t.i.stream->nmsgs) != rd->t.i.uid){
+ /* uid has changed so we're out of date */
+ rd->flags |= REM_OUTOFDATE;
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (uid)\n",
+ rd->rn ? rd->rn : "?"));
+ }
+ else{
+ dprint((7,"rd_check_remvalid: uids match\n"));
+ }
+
+ return;
+ }
+ else{
+ /*
+ * If the uidvalidity changed that probably means it can't
+ * be relied on to be meaningful, so don't use it in the future.
+ */
+ rd->flags |= NO_STATUSCMD;
+ dprint((7,
+ "rd_check_remvalid: remote data %s uidvalidity changed, don't use uid\n",
+ rd->rn ? rd->rn : "?"));
+ }
+ }
+ else{ /* stream not open, use status results */
+ if(mm_status_result.flags & SA_UIDVALIDITY &&
+ mm_status_result.flags & SA_UIDNEXT &&
+ mm_status_result.uidvalidity == rd->t.i.uidvalidity){
+
+ /*
+ * Uids are valid.
+ */
+
+ if(mm_status_result.uidnext == rd->t.i.uidnext){
+ /*
+ * Uidnext valid and unchanged, so the folder is unchanged.
+ */
+ dprint((7, "rd_check_remvalid: uidnexts match\n"));
+ return;
+ }
+ else{ /* uidnext changed, folder _may_ have changed */
+
+ dprint((7,
+ "rd_check_remvalid: uidnexts don't match, ours=%lu status=%lu\n",
+ rd->t.i.uidnext, mm_status_result.uidnext));
+
+ /*
+ * Since c-client can't handle a status cmd on the selected
+ * mailbox, we may have had to guess at the value of uidnext,
+ * and we don't know for sure that this is a real change.
+ * We need to open the mailbox and find out for sure.
+ */
+ we_cancel = busy_cue(NULL, NULL, 1);
+ ps_global->noshow_error = 1;
+ if((rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode,
+ NULL)) != NULL){
+ imapuid_t last_msg_uid;
+
+ if(rd->t.i.stream->rdonly)
+ rd->read_status = 'R';
+
+ last_msg_uid = mail_uid(rd->t.i.stream,
+ rd->t.i.stream->nmsgs);
+ ps_global->noshow_error = 0;
+ rd->last_use = get_adj_time();
+ dprint((7,
+ "%s: opened to check uid (%ld)\n",
+ rd->rn ? rd->rn : "?", (long)rd->last_use));
+ if(last_msg_uid != rd->t.i.uid){ /* really did change */
+ rd->flags |= REM_OUTOFDATE;
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed, our uid=%lu real uid=%lu\n",
+ rd->rn ? rd->rn : "?",
+ rd->t.i.uid, last_msg_uid));
+ }
+ else{ /* false hit */
+ /*
+ * The uid of the last message is the same as we
+ * remembered, so the problem is that our guess
+ * for the nextuid was wrong. It didn't actually
+ * change. Since we know the correct uidnext now we
+ * can reset that guess to the correct value for
+ * next time, avoiding this extra mail_open.
+ */
+ dprint((2,
+ "rd_check_remvalid: remote data %s false change: adjusting uidnext from %lu to %lu\n",
+ rd->rn ? rd->rn : "?",
+ rd->t.i.uidnext,
+ mm_status_result.uidnext));
+ rd->t.i.uidnext = mm_status_result.uidnext;
+ rd_write_metadata(rd, 0);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return;
+ }
+ else{
+ ps_global->noshow_error = 0;
+ dprint((2,
+ "rd_check_remvalid: couldn't open %s\n",
+ rd->rn ? rd->rn : "?"));
+ }
+ }
+ }
+ else{
+ /*
+ * If the status command doesn't return these or
+ * if the uidvalidity is changing that probably means it can't
+ * be relied on to be meaningful, so don't use it.
+ *
+ * We also come here if we don't have a stat_stream handy to
+ * look up the status. This happens, for example, if our
+ * address book is on a different server from the open mail
+ * folders. In that case, instead of opening a stream,
+ * doing a status command, and closing the stream, we open
+ * the stream and use it to check next time, too.
+ */
+ if(stat_stream){
+ rd->flags |= NO_STATUSCMD;
+ dprint((7,
+ "rd_check_remvalid: remote data %s don't use status\n",
+ rd->rn ? rd->rn : "?"));
+ }
+
+ dprint((7,
+ "opening remote data stream for validity check\n"));
+ we_cancel = busy_cue(NULL, NULL, 1);
+ ps_global->noshow_error = 1;
+ rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode,
+ NULL);
+ ps_global->noshow_error = 0;
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ we_cancel = 0;
+ if(rd->t.i.stream)
+ goto try_looking_in_stream;
+ else{
+ dprint((7,
+ "rd_check_remvalid: cannot open remote mailbox\n"));
+ return;
+ }
+ }
+ }
+
+ /*
+ * If we got here that means that we still don't know if the folder
+ * changed or not. If the stream is open then it must be the case that
+ * uidvalidity changed so we can't rely on using uids. If the stream
+ * isn't open, then either the status command didn't work or the
+ * uidvalidity changed. In any case, we need to fall back to looking
+ * inside the folder at the last message and checking whether or not the
+ * Date of the last message is the one we remembered.
+ */
+
+ dprint((7,
+ "rd_check_remvalid: falling back to Date check\n"));
+
+ /*
+ * Fall back to looking in the folder at the Date header.
+ */
+
+ if(!rd->t.i.stream)
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ ps_global->noshow_error = 1;
+ if(rd->t.i.stream ||
+ (rd->t.i.stream = context_open(NULL,NULL,rd->rn,openmode,NULL))){
+ ENVELOPE *env = NULL;
+
+ if(rd->t.i.stream->rdonly)
+ rd->read_status = 'R';
+
+ if(rd->t.i.stream->nmsgs != rd->t.i.chk_nmsgs){
+ rd->flags |= REM_OUTOFDATE;
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (expected nmsgs %ld, got %ld)\n",
+ rd->rn ? rd->rn : "?",
+ rd->t.i.chk_nmsgs, rd->t.i.stream->nmsgs));
+ }
+ else if(rd->t.i.stream->nmsgs > 1){
+ env = pine_mail_fetchenvelope(rd->t.i.stream,rd->t.i.stream->nmsgs);
+
+ if(!env || (env->date && strucmp((char *) env->date, rd->t.i.chk_date))){
+ rd->flags |= REM_OUTOFDATE;
+ dprint((2,
+ "rd_check_remvalid: remote data %s changed (%s)\n",
+ rd->rn ? rd->rn : "?", env ? "date" : "not enough msgs"));
+ }
+ }
+
+ rd->last_use = get_adj_time();
+ if(env)
+ dprint((7,
+ "%s: got envelope to check date (%ld)\n",
+ rd->rn ? rd->rn : "?", (long)rd->last_use));
+ }
+ /* else, we give up and go with the cache copy */
+
+ ps_global->noshow_error = 0;
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ dprint((7, "rd_check_remvalid: falling off end\n"));
+}
+
+
+/*
+ * Returns nonzero if remote data is currently readonly.
+ *
+ * Returns 0 -- apparently writeable
+ * 1 -- stream not open
+ * 2 -- stream open but not writeable
+ */
+int
+rd_remote_is_readonly(REMDATA_S *rd)
+{
+ if(!rd || rd->access == ReadOnly || !rd_stream_exists(rd))
+ return(1);
+
+ switch(rd->type){
+ case RemImap:
+ if(rd->t.i.stream->rdonly)
+ return(2);
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 5,
+ "rd_remote_is_readonly: type not supported");
+ break;
+ }
+
+ return(0);
+}
+
+
+/*
+ * Returns 0 if ok
+ * -1 if not ok and user says No
+ * 1 if not ok but user says Yes, ok to use it
+ */
+int
+rd_check_for_suspect_data(REMDATA_S *rd)
+{
+ int ans = -1;
+ char *fields[3], *values[3], *h;
+ unsigned long cookie;
+
+ if(!rd || rd->type != RemImap || !rd->so || !rd->rn || !rd->t.i.special_hdr)
+ return -1;
+
+ fields[0] = rd->t.i.special_hdr;
+ fields[1] = "received";
+ fields[2] = NULL;
+ cookie = 0L;
+ if((h=pine_fetchheader_lines(rd->t.i.stream, rd->t.i.stream->nmsgs,
+ NULL, fields)) != NULL){
+ simple_header_parse(h, fields, values);
+ if(values[1]) /* Received lines present! */
+ ans = rd_prompt_about_forged_remote_data(-1, rd, NULL);
+ else if(values[0]){
+ cookie = strtoul(values[0], (char **)NULL, 10);
+ if(cookie == rd->cookie) /* all's well */
+ ans = 0;
+ else
+ ans = rd_prompt_about_forged_remote_data(cookie > 1L
+ ? 100 : cookie,
+ rd, values[0]);
+ }
+ else
+ ans = rd_prompt_about_forged_remote_data(-2, rd, NULL);
+
+ if(values[0])
+ fs_give((void **)&values[0]);
+
+ if(values[1])
+ fs_give((void **)&values[1]);
+
+ fs_give((void **)&h);
+ }
+ else /* missing magic header */
+ ans = rd_prompt_about_forged_remote_data(-2, rd, NULL);
+
+ return ans;
+}
diff --git a/pith/remote.h b/pith/remote.h
new file mode 100644
index 00000000..621b6ed8
--- /dev/null
+++ b/pith/remote.h
@@ -0,0 +1,95 @@
+/*
+ * $Id: remote.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_REMOTE_INCLUDED
+#define PITH_REMOTE_INCLUDED
+
+
+#include "../pith/remtype.h"
+#include "../pith/conftype.h"
+
+
+#define REMOTE_DATA_TYPE TYPETEXT
+#define REMOTE_DATA_VERS_NUM 1
+#define REMOTE_ABOOK_SUBTYPE "x-pine-addrbook"
+#define REMOTE_PINERC_SUBTYPE "x-pine-pinerc"
+#define REMOTE_SIG_SUBTYPE "x-pine-sig"
+#define REMOTE_SMIME_SUBTYPE "x-pine-smime"
+
+#define PMAGIC "P#*E@"
+#define METAFILE_VERSION_NUM "01"
+#define SIZEOF_PMAGIC (5)
+#define SIZEOF_SPACE (1)
+#define SIZEOF_VERSION_NUM (2)
+#define TO_FIND_HDR_PMAGIC (0)
+#define TO_FIND_VERSION_NUM (TO_FIND_HDR_PMAGIC + SIZEOF_PMAGIC + SIZEOF_SPACE)
+
+
+/*
+ * For flags below. Also for address book flags in struct adrbk. Some of
+ * these flags are shared so they need to be in the same namespace.
+ */
+#define DEL_FILE 0x00001 /* remove addrbook file in adrbk_close */
+#define DO_REMTRIM 0x00004 /* trim remote data if needed and possible */
+#define USE_OLD_CACHE 0x00008 /* using cache copy, couldn't check if old */
+#define REM_OUTOFDATE 0x00010 /* remote data changed since cached */
+#define NO_STATUSCMD 0x00020 /* imap server doesn't have status command */
+#define NO_META_UPDATE 0x00040 /* don't try to update metafile */
+#define NO_PERM_CACHE 0x00080 /* remove temp cache files when done */
+#define NO_FILE 0x00100 /* use memory (sonofile) instead of a file */
+#define FILE_OUTOFDATE 0x00400 /* addrbook file discovered out of date */
+#define COOKIE_ONE_OK 0x02000 /* cookie with old value of one is ok */
+#define USER_SAID_NO 0x04000 /* user said no when asked about cookie */
+#define USER_SAID_YES 0x08000 /* user said yes when asked about cookie */
+#define BELIEVE_CACHE 0x10000 /* user said yes when asked about cookie */
+
+
+extern char meta_prefix[];
+
+
+/* exported protoypes */
+char *read_remote_pinerc(PINERC_S *, ParsePinerc);
+REMDATA_S *rd_create_remote(RemType, char *, char *, unsigned *, char *, char *);
+REMDATA_S *rd_new_remdata(RemType, char *, char *);
+void rd_free_remdata(REMDATA_S **);
+void rd_trim_remdata(REMDATA_S **);
+void rd_close_remdata(REMDATA_S **);
+int rd_read_metadata(REMDATA_S *);
+void rd_write_metadata(REMDATA_S *, int);
+void rd_update_metadata(REMDATA_S *, char *);
+void rd_open_remote(REMDATA_S *);
+void rd_close_remote(REMDATA_S *);
+int rd_stream_exists(REMDATA_S *);
+int rd_ping_stream(REMDATA_S *);
+void rd_check_readonly_access(REMDATA_S *);
+int rd_init_remote(REMDATA_S *, int);
+int rd_update_local(REMDATA_S *);
+int rd_update_remote(REMDATA_S *, char *);
+void rd_check_remvalid(REMDATA_S *, long);
+int rd_remote_is_readonly(REMDATA_S *);
+int rd_chk_for_hdr_msg(MAILSTREAM **, REMDATA_S *, char **);
+
+/* currently mandatory to implement stubs */
+
+/*
+ * This is used when we try to copy remote config data and find that
+ * the remote folder looks fishy. We ask the user what they want to
+ * do.
+ */
+int rd_prompt_about_forged_remote_data(int,REMDATA_S *, char *);
+
+
+#endif /* PITH_REMOTE_INCLUDED */
diff --git a/pith/remtype.h b/pith/remtype.h
new file mode 100644
index 00000000..a4e983bc
--- /dev/null
+++ b/pith/remtype.h
@@ -0,0 +1,60 @@
+/*
+ * $Id: remtype.h 764 2007-10-23 23:44:49Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_REMTYPE_INCLUDED
+#define PITH_REMTYPE_INCLUDED
+
+#include "../pith/store.h"
+
+
+typedef enum {Loc, RemImap} RemType;
+
+
+typedef enum {ReadOnly, ReadWrite, MaybeRorW, NoAccess, NoExists} AccessType;
+
+
+/* Remote data folder bookkeeping */
+typedef struct remote_data {
+ RemType type;
+ char *rn; /* remote name (name of folder) */
+ char *lf; /* name of local file */
+ STORE_S *sonofile; /* storage object which takes place of lf */
+ AccessType access; /* of remote folder */
+ time_t last_use; /* when remote was last accessed */
+ time_t last_valid_chk;/* when remote valid check was done */
+ time_t last_local_valid_chk;
+ STORE_S *so; /* storage object to use */
+ char read_status; /* R for readonly */
+ unsigned long flags;
+ unsigned long cookie;
+ union type_specific_data {
+ struct imap_remote_data {
+ char *special_hdr; /* header name for this type folder */
+ MAILSTREAM *stream; /* stream to use for remote folder */
+ char *chk_date; /* Date of last message */
+ unsigned long chk_nmsgs; /* Number of messages in folder */
+ unsigned long shouldbe_nmsgs; /* Number which should be in folder */
+ imapuid_t uidvalidity; /* UIDVALIDITY of folder */
+ imapuid_t uidnext; /* UIDNEXT of folder */
+ imapuid_t uid; /* UID of last message in folder */
+ }i;
+ }t;
+} REMDATA_S;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_REMTYPE_INCLUDED */
diff --git a/pith/repltype.h b/pith/repltype.h
new file mode 100644
index 00000000..b4ccbf46
--- /dev/null
+++ b/pith/repltype.h
@@ -0,0 +1,80 @@
+/*
+ * $Id: repltype.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_REPLTYPE_INCLUDED
+#define PITH_REPLTYPE_INCLUDED
+
+
+/*
+ * Cursor position when resuming postponed message.
+ */
+typedef struct redraft_pos_s {
+ char *hdrname; /* header field name, : if in body */
+ long offset; /* offset into header or body */
+} REDRAFT_POS_S;
+
+
+/*
+ * Message Reply control structure
+ */
+typedef struct reply_s {
+ unsigned int pseudo:1;
+ unsigned int forw:1;
+ unsigned int msgno:1;
+ unsigned int uid:1;
+ unsigned int forwarded:1;
+ char *mailbox; /* mailbox handles are valid in */
+ char *origmbox; /* above is canonical name, this is orig */
+ char *prefix; /* string to prepend reply-to text */
+ char *orig_charset;
+ union {
+ long pico_flags; /* Flags to manage pico initialization */
+ struct { /* UID information */
+ imapuid_t validity; /* validity token */
+ imapuid_t *msgs; /* array of reply'd to msgs */
+ } uid;
+ } data;
+} REPLY_S;
+
+
+
+/*
+ * Flag definitions to help control reply header building
+ */
+#define RSF_NONE 0x00
+#define RSF_FORCE_REPLY_TO 0x01
+#define RSF_QUERY_REPLY_ALL 0x02
+#define RSF_FORCE_REPLY_ALL 0x04
+
+
+/*
+ * Flag definitions to help build forwarded bodies
+ */
+#define FWD_NONE 0
+#define FWD_ANON 1
+#define FWD_NESTED 2
+
+
+/*
+ * Flag definitions to control composition of forwarded subject
+ */
+#define FS_NONE 0
+#define FS_CONVERT_QUOTES 1
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_REPLTYPE_INCLUDED */
diff --git a/pith/reply.c b/pith/reply.c
new file mode 100644
index 00000000..03468af3
--- /dev/null
+++ b/pith/reply.c
@@ -0,0 +1,3575 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/reply.h"
+#include "../pith/send.h"
+#include "../pith/init.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/remote.h"
+#include "../pith/status.h"
+#include "../pith/mailview.h"
+#include "../pith/filter.h"
+#include "../pith/newmail.h"
+#include "../pith/bldaddr.h"
+#include "../pith/mailindx.h"
+#include "../pith/mimedesc.h"
+#include "../pith/detach.h"
+#include "../pith/help.h"
+#include "../pith/pipe.h"
+#include "../pith/addrstring.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/pattern.h"
+#include "../pith/detoken.h"
+#include "../pith/stream.h"
+#include "../pith/busy.h"
+#include "../pith/readfile.h"
+#include "../pith/text.h"
+#include "../pith/list.h"
+#include "../pith/ablookup.h"
+#include "../pith/mailcmd.h"
+#include "../pith/margin.h"
+
+
+/*
+ * Internal prototypes
+ */
+void bounce_mask_header(char **, char *);
+
+
+int (*pith_opt_replyto_prompt)(void);
+int (*pith_opt_reply_to_all_prompt)(int *);
+
+
+/*
+ * standard type of storage object used for body parts...
+ */
+#define PART_SO_TYPE CharStar
+
+
+char *(*pith_opt_user_agent_prefix)(void);
+
+
+/*
+ * reply_harvest -
+ *
+ * Returns: 1 if addresses successfully copied
+ * 0 on user cancel or error
+ *
+ * Input flags:
+ * RSF_FORCE_REPLY_TO
+ * RSF_QUERY_REPLY_ALL
+ * RSF_FORCE_REPLY_ALL
+ *
+ * Output flags:
+ * RSF_FORCE_REPLY_ALL
+ *
+ */
+int
+reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env,
+ struct mail_address **saved_from, struct mail_address **saved_to,
+ struct mail_address **saved_cc, struct mail_address **saved_resent,
+ int *flags)
+{
+ ADDRESS *ap, *ap2, *rep_address;
+ int ret = 0, sniff_resent = 0;
+ char *rep_field;
+
+ /*
+ * If Reply-To is same as From just treat it like it was From.
+ * Otherwise, always use the reply-to if we're replying to more
+ * than one msg or say ok to using it, even if it's us.
+ * If there's no reply-to or it's the same as the from, assume
+ * that the user doesn't want to reply to himself, unless there's
+ * nobody else.
+ */
+ if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
+ && (F_ON(F_AUTO_REPLY_TO, ps_global)
+ || ((*flags) & RSF_FORCE_REPLY_TO)
+ || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){
+ rep_field = "reply-to";
+ rep_address = env->reply_to;
+ }
+ else{
+ rep_field = "From";
+ rep_address = env->from;
+ }
+
+ ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from,
+ (ADDRESS *) NULL, rep_address, RCA_NOT_US);
+
+ if(ret == 'x') {
+ cmd_cancelled("Reply");
+ return(0);
+ }
+
+ reply_append_addr(saved_from, ap);
+
+ /*--------- check for other recipients ---------*/
+ if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){
+
+ if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
+ *saved_from, env->to, RCA_NOT_US)) != NULL)
+ reply_append_addr(saved_to, ap);
+
+ if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
+ *saved_from, env->cc, RCA_NOT_US)) != NULL)
+ reply_append_addr(saved_cc, ap);
+
+ /*
+ * In these cases, we either need to look at the resent headers
+ * to include in the reply-to-all, or to decide whether or not
+ * we need to ask the reply-to-all question.
+ */
+ if(((*flags) & RSF_FORCE_REPLY_ALL)
+ || (((*flags) & RSF_QUERY_REPLY_ALL)
+ && ((!(*saved_from) && !(*saved_cc))
+ || (*saved_from && !(*saved_to) && !(*saved_cc))))){
+
+ sniff_resent++;
+ if((ap2 = reply_resent(ps, msgno, section)) != NULL){
+ /*
+ * look for bogus addr entries and replace
+ */
+ if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
+ *saved_from, ap2, RCA_NOT_US)) != NULL)
+
+ reply_append_addr(saved_resent, ap);
+
+ mail_free_address(&ap2);
+ }
+ }
+
+ /*
+ * It makes sense to ask reply-to-all now.
+ */
+ if(((*flags) & RSF_QUERY_REPLY_ALL)
+ && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
+ || (*saved_cc || *saved_resent))){
+ *flags &= ~RSF_QUERY_REPLY_ALL;
+ if(pith_opt_reply_to_all_prompt
+ && (*pith_opt_reply_to_all_prompt)(flags) < 0){
+ cmd_cancelled("Reply");
+ return(0);
+ }
+ }
+
+ /*
+ * If we just answered yes to the reply-to-all question and
+ * we still haven't collected the resent headers, do so now.
+ */
+ if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
+ && (ap2 = reply_resent(ps, msgno, section))){
+ /*
+ * look for bogus addr entries and replace
+ */
+ if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
+ *saved_from, ap2, RCA_NOT_US)) != NULL)
+ reply_append_addr(saved_resent, ap);
+
+ mail_free_address(&ap2);
+ }
+ }
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Return a pointer to a copy of the given address list
+ filtering out those already in the "mask" lists and ourself.
+
+Args: mask1 -- Don't copy if in this list
+ mask2 -- or if in this list
+ source -- List to be copied
+ us_too -- Don't filter out ourself.
+ flags -- RCA_NOT_US copy all addrs except for our own
+ RCA_ALL copy all addrs, including our own
+ RCA_ONLY_US copy only addrs that are our own
+
+ ---*/
+ADDRESS *
+reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field,
+ struct mail_address *mask1, struct mail_address *mask2,
+ struct mail_address *source, int flags)
+{
+ ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;
+
+ /* can only choose one of these flags values */
+ assert(!((flags & RCA_ALL && flags & RCA_ONLY_US)
+ || (flags & RCA_ALL && flags & RCA_NOT_US)
+ || (flags & RCA_ONLY_US && flags & RCA_NOT_US)));
+
+ for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
+ if(tmp1->host && tmp1->host[0] == '.'){
+ char *h, *fields[2];
+
+ fields[0] = field;
+ fields[1] = NULL;
+ if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
+ msgno, section, fields)) != NULL){
+ char *p, fname[32];
+ int q;
+
+ q = strlen(h);
+
+ strncpy(fname, field, sizeof(fname)-2);
+ fname[sizeof(fname)-2] = '\0';
+ strncat(fname, ":", sizeof(fname)-strlen(fname)-1);
+ fname[sizeof(fname)-1] = '\0';
+
+ for(p = h; (p = strstr(p, fname)) != NULL; )
+ rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
+
+ sqznewlines(h); /* blat out CR's & LF's */
+ for(p = h; (p = strchr(p, TAB)) != NULL; )
+ *p++ = ' '; /* turn TABs to whitespace */
+
+ if(*h){
+ long l;
+ size_t ll;
+
+ ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
+ memset(ret, 0, sizeof(ADDRESS));
+
+ /* get rid of leading white space */
+ for(p = h; *p == SPACE; p++)
+ ;
+
+ if(p != h){
+ memmove(h, p, l = strlen(p));
+ h[l] = '\0';
+ }
+
+ /* base64 armor plate the gunk to protect against
+ * c-client quoting in output.
+ */
+ p = (char *) rfc822_binary(h, strlen(h),
+ (unsigned long *) &l);
+ sqznewlines(p);
+ fs_give((void **) &h);
+ /*
+ * Seems like the 4 ought to be a 2, but I'll leave it
+ * to be safe, in case something else adds 2 chars later.
+ */
+ ll = strlen(p) + 4;
+ ret->mailbox = (char *) fs_get(ll * sizeof(char));
+ snprintf(ret->mailbox, ll, "&%s", p);
+ ret->mailbox[ll-1] = '\0';
+ fs_give((void **) &p);
+ ret->host = cpystr(RAWFIELD);
+ }
+ }
+
+ return(ret);
+ }
+
+ ret_tail = &ret;
+ for(source = first_addr(source); source; source = source->next){
+ for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
+ if(address_is_same(source, tmp1)) /* it is in mask1, skip it */
+ break;
+
+ for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next)
+ if(address_is_same(source, tmp2)) /* it is in mask2, skip it */
+ break;
+
+ /*
+ * If there's no match in masks and this address satisfies the
+ * flags requirement, copy it.
+ */
+ if(!tmp1 && !tmp2 /* no mask match */
+ && ((flags & RCA_ALL) /* including everybody */
+ || (flags & RCA_ONLY_US && address_is_us(source, ps))
+ || (flags & RCA_NOT_US && !address_is_us(source, ps)))){
+ tmp1 = source->next;
+ source->next = NULL; /* only copy one addr! */
+ *ret_tail = rfc822_cpy_adr(source);
+ ret_tail = &(*ret_tail)->next;
+ source->next = tmp1; /* restore rest of list */
+ }
+ }
+
+ return(ret);
+}
+
+
+ACTION_S *
+set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section)
+{
+ ACTION_S *role = NULL;
+ PAT_S *pat = NULL;
+ SEARCHSET *ss = NULL;
+ PAT_STATE pstate;
+
+ if(!nonempty_patterns(rflags, &pstate))
+ return(role);
+
+ if(msgno > 0L){
+ ss = mail_newsearchset();
+ ss->first = ss->last = (unsigned long)msgno;
+ }
+
+ /* Go through the possible roles one at a time until we get a match. */
+ pat = first_pattern(&pstate);
+
+ /* calculate this message's score if needed */
+ if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
+ get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
+ (void)calculate_some_scores(ps->mail_stream, ss, 0);
+
+ while(!role && pat){
+ if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
+ get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
+ if(!pat->action || pat->action->bogus)
+ break;
+
+ role = pat->action;
+ }
+ else
+ pat = next_pattern(&pstate);
+ }
+
+ if(ss)
+ mail_free_searchset(&ss);
+
+ return(role);
+}
+
+
+/*
+ * reply_seed - fill in reply header
+ *
+ */
+void
+reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env,
+ struct mail_address *saved_from, struct mail_address *saved_to,
+ struct mail_address *saved_cc, struct mail_address *saved_resent,
+ char **fcc, int replytoall, char **errmsg)
+{
+ ADDRESS **to_tail, **cc_tail;
+
+ to_tail = &outgoing->to;
+ cc_tail = &outgoing->cc;
+
+ if(saved_from){
+ /* Put Reply-To or From in To. */
+ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
+ (ADDRESS *) NULL, saved_from, RCA_ALL);
+ /* and the rest in cc */
+ if(replytoall){
+ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
+ outgoing->to, saved_to, RCA_ALL);
+ while(*cc_tail) /* stay on last address */
+ cc_tail = &(*cc_tail)->next;
+
+ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
+ outgoing->to, saved_cc, RCA_ALL);
+ while(*cc_tail)
+ cc_tail = &(*cc_tail)->next;
+
+ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
+ outgoing->to, saved_resent, RCA_ALL);
+ }
+ }
+ else if(saved_to){
+ /* No From (maybe from us), put To in To. */
+ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
+ (ADDRESS *)NULL, saved_to, RCA_ALL);
+ /* and the rest in cc */
+ if(replytoall){
+ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
+ outgoing->to, saved_cc, RCA_ALL);
+ while(*cc_tail)
+ cc_tail = &(*cc_tail)->next;
+
+ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
+ outgoing->to, saved_resent, RCA_ALL);
+ }
+ }
+ else{
+ /* No From or To, put everything else in To if replytoall, */
+ if(replytoall){
+ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
+ (ADDRESS *) NULL, saved_cc, RCA_ALL);
+ while(*to_tail)
+ to_tail = &(*to_tail)->next;
+
+ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
+ (ADDRESS *) NULL, saved_resent, RCA_ALL);
+ }
+ /* else, reply to original From which must be us */
+ else{
+ /*
+ * Put self in To if in original From.
+ */
+ if(!outgoing->newsgroups)
+ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
+ (ADDRESS *) NULL, env->from, RCA_ALL);
+ }
+ }
+
+ /* add any missing personal data */
+ reply_fish_personal(outgoing, env);
+
+ /* get fcc */
+ if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
+ *fcc = get_fcc_based_on_to(outgoing->to);
+ }
+ else if(fcc && outgoing->newsgroups){
+ char *newsgroups_returned = NULL;
+ int rv;
+
+ rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL);
+ if(rv != -1 &&
+ strcmp(outgoing->newsgroups, newsgroups_returned)){
+ fs_give((void **)&outgoing->newsgroups);
+ outgoing->newsgroups = newsgroups_returned;
+ }
+ else
+ fs_give((void **) &newsgroups_returned);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Test the given address lists for equivalence
+
+Args: x -- First address list for comparison
+ y -- Second address for comparison
+
+ ---*/
+int
+addr_lists_same(struct mail_address *x, struct mail_address *y)
+{
+ for(x = first_addr(x), y = first_addr(y);
+ x && y;
+ x = first_addr(x->next), y = first_addr(y->next)){
+ if(!address_is_same(x, y))
+ return(0);
+ }
+
+ return(!x && !y); /* true if ran off both lists */
+}
+
+
+/*----------------------------------------------------------------------
+ Test the given address against those in the given envelope's to, cc
+
+Args: addr -- address for comparison
+ env -- envelope to compare against
+
+ ---*/
+int
+addr_in_env(struct mail_address *addr, ENVELOPE *env)
+{
+ ADDRESS *ap;
+
+ for(ap = env ? env->to : NULL; ap; ap = ap->next)
+ if(address_is_same(addr, ap))
+ return(1);
+
+ for(ap = env ? env->cc : NULL; ap; ap = ap->next)
+ if(address_is_same(addr, ap))
+ return(1);
+
+ return(0); /* not found! */
+}
+
+
+/*----------------------------------------------------------------------
+ Add missing personal info dest from src envelope
+
+Args: dest -- envelope to add personal info to
+ src -- envelope to get personal info from
+
+NOTE: This is just kind of a courtesy function. It's really not adding
+ anything needed to get the mail thru, but it is nice for the user
+ under some odd circumstances.
+ ---*/
+void
+reply_fish_personal(ENVELOPE *dest, ENVELOPE *src)
+{
+ ADDRESS *da, *sa;
+
+ for(da = dest ? dest->to : NULL; da; da = da->next){
+ if(da->personal && !da->personal[0])
+ fs_give((void **)&da->personal);
+
+ for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
+ if(address_is_same(da, sa) && sa->personal && sa->personal[0])
+ da->personal = cpystr(sa->personal);
+
+ for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
+ if(address_is_same(da, sa) && sa->personal && sa->personal[0])
+ da->personal = cpystr(sa->personal);
+ }
+
+ for(da = dest ? dest->cc : NULL; da; da = da->next){
+ if(da->personal && !da->personal[0])
+ fs_give((void **)&da->personal);
+
+ for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
+ if(address_is_same(da, sa) && sa->personal && sa->personal[0])
+ da->personal = cpystr(sa->personal);
+
+ for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
+ if(address_is_same(da, sa) && sa->personal && sa->personal[0])
+ da->personal = cpystr(sa->personal);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Given a header field and envelope, build "References: " header data
+
+Args:
+
+Returns:
+ ---*/
+char *
+reply_build_refs(ENVELOPE *env)
+{
+ int len, id_len, first_ref_len = 0, foldslop;
+ char *p, *refs = NULL, *h = env->references;
+ char *first_ref = NULL, *tail_refs = NULL;
+
+
+ if(!(env->message_id && (id_len = strlen(env->message_id))))
+ return(NULL);
+
+ if(h){
+ /*
+ * The length we have to work with doesn't seem to appear in any
+ * standards. Steve Jones says that in comp.news discussions he
+ * has seen 1024 as the longest length of a header value.
+ * In the inn news source we find MAXHEADERSIZE = 1024. It appears
+ * that is the maximum length of the header value, including
+ * newlines for folded lines (that is, the newlines are counted).
+ * We'll be conservative and figure every reference will take up a
+ * line of its own when we fold. We'll also count 2 for CRLF instead
+ * of just one for LF just to be safe. hubert 2001-jan
+ * J.B. Moreno <planb@newsreaders.com> says "The server limit is
+ * more commonly encountered at 999/1000 bytes [...]". So we'll
+ * back off to 999 instead of 1024.
+ */
+#define MAXHEADERSIZE (999)
+
+ /* count the total number of potential folds, max of 2 bytes each */
+ for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); )
+ foldslop += 2;
+
+ if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE
+ && (p = strstr(h, "> <"))){
+ /*
+ * If the references line is so long that we are going to have
+ * to delete some of the references, delete the 2nd, 3rd, ...
+ * We don't want to delete the first message in the thread.
+ */
+ p[1] = '\0';
+ first_ref = cpystr(h);
+ first_ref_len = strlen(first_ref)+1; /* len includes space */
+ p[1] = ' ';
+ tail_refs = p+2;
+ /* get rid of 2nd, 3rd, ... until it fits */
+ while((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
+ foldslop >= MAXHEADERSIZE
+ && (p = strstr(tail_refs, "> <"))){
+ tail_refs = p + 2;
+ foldslop -= 2;
+ }
+
+ /*
+ * If the individual references are seriously long, somebody
+ * is messing with us and we don't care if it works right.
+ */
+ if((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
+ foldslop >= MAXHEADERSIZE){
+ first_ref_len = len = 0;
+ tail_refs = NULL;
+ if(first_ref)
+ fs_give((void **)&first_ref);
+ }
+ }
+ else
+ tail_refs = h;
+
+ refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) *
+ sizeof(char));
+ snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s",
+ first_ref ? first_ref : "",
+ first_ref ? " " : "",
+ tail_refs ? tail_refs : "",
+ tail_refs ? " " : "",
+ env->message_id);
+ refs[first_ref_len + 1+id_len + len] = '\0';
+ }
+
+ if(!refs && id_len)
+ refs = cpystr(env->message_id);
+
+ if(first_ref)
+ fs_give((void **)&first_ref);
+
+ return(refs);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Snoop for any Resent-* headers, and return an ADDRESS list
+
+Args: stream --
+ msgno --
+
+Returns: either NULL if no Resent-* or parsed ADDRESS struct list
+ ---*/
+ADDRESS *
+reply_resent(struct pine *pine_state, long int msgno, char *section)
+{
+#define RESENTFROM 0
+#define RESENTTO 1
+#define RESENTCC 2
+ ADDRESS *rlist = NULL, **a, **b;
+ char *hdrs, *values[RESENTCC+1];
+ int i;
+ static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
+ static char *fakedomain = "@";
+
+ if((hdrs = pine_fetchheader_lines(pine_state->mail_stream,
+ msgno, section, fields)) != NULL){
+ memset(values, 0, (RESENTCC+1) * sizeof(char *));
+ simple_header_parse(hdrs, fields, values);
+ for(i = RESENTFROM; i <= RESENTCC; i++)
+ rfc822_parse_adrlist(&rlist, values[i],
+ (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
+ ? fakedomain : pine_state->maildomain);
+
+ /* pare dup's ... */
+ for(a = &rlist; *a; a = &(*a)->next) /* compare every address */
+ for(b = &(*a)->next; *b; ) /* to the others */
+ if(address_is_same(*a, *b)){
+ ADDRESS *t = *b;
+
+ if(!(*a)->personal){ /* preserve personal name */
+ (*a)->personal = (*b)->personal;
+ (*b)->personal = NULL;
+ }
+
+ *b = t->next;
+ t->next = NULL;
+ mail_free_address(&t);
+ }
+ else
+ b = &(*b)->next;
+ }
+
+ if(hdrs)
+ fs_give((void **) &hdrs);
+
+ return(rlist);
+}
+
+
+/*----------------------------------------------------------------------
+ Format and return subject suitable for the reply command
+
+Args: subject -- subject to build reply subject for
+ buf -- buffer to use for writing. If non supplied, alloc one.
+ buflen -- length of buf if supplied, else ignored
+
+Returns: with either "Re:" prepended or not, if already there.
+ returned string is allocated.
+ ---*/
+char *
+reply_subject(char *subject, char *buf, size_t buflen)
+{
+ size_t l = (subject && *subject) ? 4*strlen(subject) : 10;
+ char *tmp = fs_get(l + 1), *decoded, *p;
+
+ if(!buf){
+ buflen = l + 5;
+ buf = fs_get(buflen);
+ }
+
+ /* decode any 8bit into tmp buffer */
+ decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject);
+
+ buf[0] = '\0';
+ if(decoded /* already "re:" ? */
+ && (decoded[0] == 'R' || decoded[0] == 'r')
+ && (decoded[1] == 'E' || decoded[1] == 'e')){
+
+ if(decoded[2] == ':')
+ snprintf(buf, buflen, "%.*s", buflen-1, subject);
+ else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){
+ p++;
+ while(*p && isspace((unsigned char)*p)) p++;
+ if(p[0] == ':')
+ snprintf(buf, buflen, "%.*s", buflen-1, subject);
+ }
+ }
+
+ if(!buf[0])
+ snprintf(buf, buflen, "Re: %.*s", buflen-1,
+ (subject && *subject) ? subject : "your mail");
+
+ buf[buflen-1] = '\0';
+
+ fs_give((void **) &tmp);
+ return(buf);
+}
+
+
+/*----------------------------------------------------------------------
+ return initials for the given personal name
+
+Args: name -- Personal name to extract initials from
+
+Returns: pointer to name overwritten with initials
+ ---*/
+char *
+reply_quote_initials(char *name)
+{
+ char *s = name,
+ *w = name;
+
+ /* while there are still characters to look at */
+ while(s && *s){
+ /* skip to next initial */
+ while(*s && isspace((unsigned char) *s))
+ s++;
+
+ /* skip over cruft like single quotes */
+ while(*s && !isalnum((unsigned char) *s))
+ s++;
+
+ /* copy initial */
+ if(*s)
+ *w++ = *s++;
+
+ /* skip to end of this piece of name */
+ while(*s && !isspace((unsigned char) *s))
+ s++;
+ }
+
+ if(w)
+ *w = '\0';
+
+ return(name);
+}
+
+/*
+ * There is an assumption that MAX_SUBSTITUTION is <= the size of the
+ * tokens being substituted for (only in the size of buf below).
+ */
+#define MAX_SUBSTITUTION 6
+#define MAX_PREFIX 63
+static char *from_token = "_FROM_";
+static char *nick_token = "_NICK_";
+static char *init_token = "_INIT_";
+
+/*----------------------------------------------------------------------
+ return a quoting string, "> " by default, for replied text
+
+Args: env -- envelope of message being replied to
+
+Returns: malloc'd array containing quoting string, freed by caller
+ ---*/
+char *
+reply_quote_str(ENVELOPE *env)
+{
+ char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1];
+
+ strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+
+ /* set up the prefix to quote included text */
+ if((p = strstr(buf, from_token)) != NULL){
+ repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
+ : "";
+ strncpy(pbf, repl, sizeof(pbf)-1);
+ pbf[sizeof(pbf)-1] = '\0';;
+ rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf);
+ }
+
+ if((p = strstr(buf, nick_token)) != NULL){
+ repl = (env &&
+ env->from &&
+ env->from &&
+ get_nickname_from_addr(env->from, tmp_20k_buf, 1000))
+ ? tmp_20k_buf : "";
+ strncpy(pbf, repl, sizeof(pbf)-1);
+ pbf[sizeof(pbf)-1] = '\0';;
+ rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf);
+ }
+
+ if((p = strstr(buf, init_token)) != NULL){
+ char *q = NULL;
+ char buftmp[MAILTMPLEN];
+
+ snprintf(buftmp, sizeof(buftmp), "%.200s",
+ (env && env->from && env->from->personal) ? env->from->personal : "");
+ buftmp[sizeof(buftmp)-1] = '\0';
+
+ repl = (env && env->from && env->from->personal)
+ ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8(
+ (unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp)))
+ : "";
+
+ istrncpy(pbf, repl, sizeof(pbf)-1);
+ pbf[sizeof(pbf)-1] = '\0';;
+ rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf);
+ if(q)
+ fs_give((void **)&q);
+ }
+
+ prefix = removing_quotes(cpystr(buf));
+
+ return(prefix);
+}
+
+int
+reply_quote_str_contains_tokens(void)
+{
+ return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
+ (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
+ strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
+ strstr(ps_global->VAR_REPLY_STRING, init_token)));
+}
+
+
+/*----------------------------------------------------------------------
+ Build the body for the message number/part being replied to
+
+ Args:
+
+ Result: BODY structure suitable for sending
+
+ ----------------------------------------------------------------------*/
+BODY *
+reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
+ long int msgno, char *sect_prefix, void *msgtext, char *prefix,
+ int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos)
+{
+ char *p, *sig = NULL, *section, sect_buf[256];
+ BODY *body = NULL, *tmp_body = NULL;
+ PART *part;
+ gf_io_t pc;
+ int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0;
+
+ if(sect_prefix)
+ snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", sizeof(sect_buf)-1, sect_prefix);
+ else
+ section = "1";
+
+ sect_buf[sizeof(sect_buf)-1] = '\0';
+
+ if(ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
+ reply_raw_body = 1;
+
+ gf_set_so_writec(&pc, (STORE_S *) msgtext);
+
+ if(toplevel){
+ char *filtered;
+
+ impl = 0;
+ filtered = detoken(role, env, 0,
+ F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
+ 0, redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)msgtext, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ else if(impl == 2)
+ leave_cursor_at_top++;
+ }
+
+ fs_give((void **)&filtered);
+ }
+ else
+ impl = 1;
+ }
+ else
+ impl = 1;
+
+ if(toplevel &&
+ (sig = reply_signature(role, env, redraft_pos, &impl)) &&
+ F_OFF(F_SIG_AT_BOTTOM, ps_global)){
+
+ /*
+ * If CURSORPOS was set explicitly in sig_file, and there was a
+ * template file before that, we need to adjust the offset by the
+ * length of the template file. However, if the template had
+ * a set CURSORPOS in it then impl was 2 before getting to the
+ * signature, so offset wouldn't have been reset by the signature
+ * CURSORPOS and offset would already be correct. That case will
+ * be ok here because template_len will be 0 and adding it does
+ * nothing. If template
+ * didn't have CURSORPOS in it, then impl was 1 and got set to 2
+ * by the CURSORPOS in the sig. In that case we have to adjust the
+ * offset. That's what the next line does. It adjusts it if
+ * template_len is nonzero and if CURSORPOS was set in sig_file.
+ */
+ if(impl == 2)
+ (*redraft_pos)->offset += template_len;
+
+ if(*sig)
+ so_puts((STORE_S *)msgtext, sig);
+
+ /*
+ * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
+ * is set, we won't have used it yet and want it to be non-NULL.
+ */
+ fs_give((void **)&sig);
+ }
+
+ /*
+ * Only put cursor in sig if there is a cursorpos there but not
+ * one in the template, and sig-at-bottom.
+ */
+ if(!(sig && impl == 2 && !leave_cursor_at_top))
+ leave_cursor_at_top++;
+
+ if(plustext){
+ if(!orig_body
+ || orig_body->type == TYPETEXT
+ || reply_raw_body
+ || F_OFF(F_ATTACHMENTS_IN_REPLY, ps_global)){
+ char *charset = NULL;
+
+ /*------ Simple text-only message ----*/
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, ps_global))
+ reply_forward_header(stream, msgno, sect_prefix,
+ env, pc, prefix);
+
+ if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){
+ BODY *bodyp = NULL;
+
+ bodyp = reply_raw_body ? NULL : tmp_body;
+
+ /*
+ * We set the charset in the outgoing message to the same
+ * as the one in the message we're replying to unless it
+ * is the unknown charset. We do that in order to attempt
+ * to preserve the same charset in the reply if possible.
+ * It may be safer to just set it to whatever we want instead
+ * but then the receiver may not be able to read it.
+ */
+ if(bodyp
+ && (charset = parameter_val(bodyp->parameter, "charset"))
+ && strucmp(charset, UNKNOWN_CHARSET))
+ set_parameter(&body->parameter, "charset", charset);
+
+ if(charset)
+ fs_give((void **) &charset);
+
+ get_body_part_text(stream, bodyp, msgno,
+ bodyp ? (p = body_partno(stream, msgno, bodyp))
+ : sect_prefix,
+ 0L, pc, prefix, NULL, GBPT_NONE);
+ if(bodyp && p)
+ fs_give((void **) &p);
+ }
+ else{
+ gf_puts(NEWLINE, pc);
+ gf_puts(" [NON-Text Body part not included]", pc);
+ gf_puts(NEWLINE, pc);
+ }
+ }
+ else if(orig_body->type == TYPEMULTIPART){
+ /*------ Message is Multipart ------*/
+ if(orig_body->subtype
+ && !strucmp(orig_body->subtype, "signed")
+ && orig_body->nested.part){
+ /* operate only on the signed part */
+ body = reply_body(stream, env,
+ &orig_body->nested.part->body,
+ msgno, section, msgtext, prefix,
+ plustext, role, 0, redraft_pos);
+ }
+ else if(orig_body->subtype
+ && !strucmp(orig_body->subtype, "alternative")){
+ /* Set up the simple text reply */
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+
+ if(reply_body_text(orig_body, &tmp_body)){
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, ps_global))
+ reply_forward_header(stream, msgno, sect_prefix,
+ env, pc, prefix);
+
+ get_body_part_text(stream, tmp_body, msgno,
+ p = body_partno(stream,msgno,tmp_body),
+ 0L, pc, prefix, NULL, GBPT_NONE);
+ if(p)
+ fs_give((void **) &p);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "No suitable multipart text found for inclusion!");
+ }
+ else{
+ body = copy_body(NULL, orig_body);
+
+ /*
+ * whatever subtype it is, demote it
+ * to plain old MIXED.
+ */
+ if(body->subtype)
+ fs_give((void **) &body->subtype);
+
+ body->subtype = cpystr("Mixed");
+
+ if(body->nested.part &&
+ body->nested.part->body.type == TYPETEXT) {
+ char *new_charset = NULL;
+
+ /*---- First part of the message is text -----*/
+ body->nested.part->body.contents.text.data = msgtext;
+ if(body->nested.part->body.subtype &&
+ strucmp(body->nested.part->body.subtype, "Plain")){
+ fs_give((void **)&body->nested.part->body.subtype);
+ body->nested.part->body.subtype = cpystr("Plain");
+ }
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, ps_global))
+ reply_forward_header(stream, msgno, sect_prefix,
+ env, pc, prefix);
+
+ if(!(get_body_part_text(stream,
+ &orig_body->nested.part->body,
+ msgno, section, 0L, pc, prefix,
+ &new_charset, GBPT_NONE)
+ && fetch_contents(stream, msgno, sect_prefix, body)))
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error including all message parts"));
+ else if(new_charset)
+ set_parameter(&body->nested.part->body.parameter, "charset", new_charset);
+ }
+ else if(orig_body->nested.part->body.type == TYPEMULTIPART
+ && orig_body->nested.part->body.subtype
+ && !strucmp(orig_body->nested.part->body.subtype,
+ "alternative")
+ && reply_body_text(&orig_body->nested.part->body,
+ &tmp_body)){
+ int partnum;
+
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, ps_global))
+ reply_forward_header(stream, msgno, sect_prefix,
+ env, pc, prefix);
+
+ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
+ sizeof(sect_buf)/2-2,
+ sect_prefix ? sect_prefix : "",
+ sect_prefix ? "." : "",
+ sizeof(sect_buf)/2-2,
+ p = partno(orig_body, tmp_body));
+ sect_buf[sizeof(sect_buf)-1] = '\0';
+ fs_give((void **) &p);
+ get_body_part_text(stream, tmp_body, msgno,
+ sect_buf, 0L, pc, prefix,
+ NULL, GBPT_NONE);
+
+ part = body->nested.part->next;
+ body->nested.part->next = NULL;
+ mail_free_body_part(&body->nested.part);
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ body->nested.part->body.subtype = cpystr("Plain");
+ body->nested.part->body.contents.text.data = msgtext;
+ body->nested.part->next = part;
+
+ for(partnum = 2; part != NULL; part = part->next){
+ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
+ sizeof(sect_buf)/2,
+ sect_prefix ? sect_prefix : "",
+ sect_prefix ? "." : "", partnum++);
+ sect_buf[sizeof(sect_buf)-1] = '\0';
+
+ if(!fetch_contents(stream, msgno,
+ sect_buf, &part->body)){
+ break;
+ }
+ }
+ }
+ else {
+ /*--- Fetch the original pieces ---*/
+ if(!fetch_contents(stream, msgno, sect_prefix, body))
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error including all message parts"));
+
+ /*--- No text part, create a blank one ---*/
+ part = mail_newbody_part();
+ part->next = body->nested.part;
+ body->nested.part = part;
+ part->body.contents.text.data = msgtext;
+ }
+ }
+ }
+ else{
+ /*---- Single non-text message of some sort ----*/
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+ part = mail_newbody_part();
+ body->nested.part = part;
+
+ /*--- The first part, a blank text part to be edited ---*/
+ part->body.type = TYPETEXT;
+ part->body.contents.text.data = msgtext;
+
+ /*--- The second part, what ever it is ---*/
+ part->next = mail_newbody_part();
+ part = part->next;
+ part->body.id = generate_message_id();
+ copy_body(&(part->body), orig_body);
+
+ /*
+ * the idea here is to fetch part into storage object
+ */
+ if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
+ NULL,EDIT_ACCESS)) != NULL){
+ if((p = pine_mail_fetch_body(stream, msgno, section,
+ &part->body.size.bytes, NIL)) != NULL){
+ so_nputs((STORE_S *)part->body.contents.text.data,
+ p, part->body.size.bytes);
+ }
+ else
+ mail_free_body(&body);
+ }
+ else
+ mail_free_body(&body);
+ }
+ }
+ else{
+ /*--------- No text included --------*/
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+ }
+
+ if(!leave_cursor_at_top){
+ long cnt = 0L;
+ unsigned char c;
+
+ /* rewind and count chars to start of sig file */
+ so_seek((STORE_S *)msgtext, 0L, 0);
+ while(so_readc(&c, (STORE_S *)msgtext))
+ cnt++;
+
+ if(!*redraft_pos){
+ *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
+ memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
+ (*redraft_pos)->hdrname = cpystr(":");
+ }
+
+ /*
+ * If explicit cursor positioning in sig file,
+ * add offset to start of sig file plus offset into sig file.
+ * Else, just offset to start of sig file.
+ */
+ (*redraft_pos)->offset += cnt;
+ }
+
+ if(sig){
+ if(*sig)
+ so_puts((STORE_S *)msgtext, sig);
+
+ fs_give((void **)&sig);
+ }
+
+ gf_clear_so_writec((STORE_S *) msgtext);
+
+ return(body);
+}
+
+
+/*
+ * reply_part - first replyable multipart of a multipart.
+ */
+int
+reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body)
+{
+ if(body){
+ switch(body->type){
+ case TYPETEXT :
+ *new_body = body;
+ return(1);
+
+ case TYPEMULTIPART :
+ if(body->subtype && !strucmp(body->subtype, "alternative")){
+ PART *part;
+ int got_one = 0;
+
+ if(ps_global->force_prefer_plain
+ || (!ps_global->force_no_prefer_plain
+ && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
+ for(part = body->nested.part; part; part = part->next)
+ if((!part->body.type || part->body.type == TYPETEXT)
+ && (!part->body.subtype
+ || !strucmp(part->body.subtype, "plain"))){
+ *new_body = &part->body;
+ return(1);
+ }
+ }
+
+ /*
+ * Else choose last alternative among plain or html parts.
+ * Perhaps we should really be using mime_show() to make this
+ * potentially more general than just plain or html.
+ */
+ for(part = body->nested.part; part; part = part->next){
+ if((!part->body.type || part->body.type == TYPETEXT)
+ && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
+ ||
+ (part->body.subtype && !strucmp(part->body.subtype, "html")))){
+ got_one++;
+ *new_body = &part->body;
+ }
+ }
+
+ if(got_one)
+ return(1);
+ }
+ else if(body->nested.part)
+ /* NOTE: we're only interested in "first" part of mixed */
+ return(reply_body_text(&body->nested.part->body, new_body));
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return(0);
+}
+
+
+char *
+reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
+{
+ char *sig;
+ size_t l;
+
+ sig = detoken(role, env,
+ 2, F_ON(F_SIG_AT_BOTTOM, ps_global) ? 0 : 1, 1,
+ redraft_pos, impl);
+
+ if(F_OFF(F_SIG_AT_BOTTOM, ps_global) && (!sig || !*sig)){
+ if(sig)
+ fs_give((void **)&sig);
+
+ l = 2 * strlen(NEWLINE);
+ sig = (char *)fs_get((l+1) * sizeof(char));
+ strncpy(sig, NEWLINE, l);
+ sig[l] = '\0';
+ strncat(sig, NEWLINE, l+1-1-strlen(sig));
+ sig[l] = '\0';
+ return(sig);
+ }
+
+ return(sig);
+}
+
+
+/*
+ * Buf is at least size maxlen+1
+ */
+void
+get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
+{
+ ADDRESS *addr = NULL;
+ ADDRESS *last_to = NULL;
+ ADDRESS *first_addr = NULL, *second_addr = NULL;
+ ADDRESS *third_addr = NULL, *fourth_addr = NULL;
+ int cntaddr, l;
+ size_t orig_maxlen;
+ char *p;
+
+ buf[0] = '\0';
+
+ switch(type){
+ case iFrom:
+ addr = env ? env->from : NULL;
+ break;
+
+ case iTo:
+ addr = env ? env->to : NULL;
+ break;
+
+ case iCc:
+ addr = env ? env->cc : NULL;
+ break;
+
+ case iSender:
+ addr = env ? env->sender : NULL;
+ break;
+
+ /*
+ * Recips is To and Cc togeter. We hook the two adrlists together
+ * temporarily.
+ */
+ case iRecips:
+ addr = env ? env->to : NULL;
+ /* Find end of To list */
+ for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
+ ;
+
+ /* Make the end of To list point to cc list */
+ if(last_to)
+ last_to->next = (env ? env->cc : NULL);
+
+ break;
+
+ /*
+ * Initials.
+ */
+ case iInit:
+ if(env && env->from && env->from->personal){
+ char *name, *initials = NULL;
+
+ name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, env->from->personal);
+ if(name == env->from->personal){
+ strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
+ tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
+ name = tmp_20k_buf;
+ }
+
+ if(name && *name){
+ initials = reply_quote_initials(name);
+ iutf8ncpy(buf, initials, maxlen);
+ buf[maxlen] = '\0';
+ }
+ }
+
+ return;
+
+ default:
+ break;
+ }
+
+ orig_maxlen = maxlen;
+
+ first_addr = addr;
+ /* skip over rest of c-client group addr */
+ if(first_addr && first_addr->mailbox && !first_addr->host){
+ for(second_addr = first_addr->next;
+ second_addr && second_addr->host;
+ second_addr = second_addr->next)
+ ;
+
+ if(second_addr && !second_addr->host)
+ second_addr = second_addr->next;
+ }
+ else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
+ second_addr = first_addr ? first_addr->next : NULL;
+
+ if(second_addr && second_addr->mailbox && !second_addr->host){
+ for(third_addr = second_addr->next;
+ third_addr && third_addr->host;
+ third_addr = third_addr->next)
+ ;
+
+ if(third_addr && !third_addr->host)
+ third_addr = third_addr->next;
+ }
+ else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
+ third_addr = second_addr ? second_addr->next : NULL;
+
+ if(third_addr && third_addr->mailbox && !third_addr->host){
+ for(fourth_addr = third_addr->next;
+ fourth_addr && fourth_addr->host;
+ fourth_addr = fourth_addr->next)
+ ;
+
+ if(fourth_addr && !fourth_addr->host)
+ fourth_addr = fourth_addr->next;
+ }
+ else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
+ fourth_addr = third_addr ? third_addr->next : NULL;
+
+ /* Just attempting to make a nice display */
+ if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
+ (first_addr->mailbox && first_addr->mailbox[0]))){
+ if(second_addr){
+ if((second_addr->personal && second_addr->personal[0]) ||
+ (second_addr->mailbox && second_addr->mailbox[0])){
+ if(third_addr){
+ if((third_addr->personal && third_addr->personal[0]) ||
+ (third_addr->mailbox && third_addr->mailbox[0])){
+ if(fourth_addr)
+ cntaddr = 4;
+ else
+ cntaddr = 3;
+ }
+ else
+ cntaddr = -1;
+ }
+ else
+ cntaddr = 2;
+ }
+ else
+ cntaddr = -1;
+ }
+ else
+ cntaddr = 1;
+ }
+ else
+ cntaddr = -1;
+
+ p = buf;
+ if(cntaddr == 1)
+ a_little_addr_string(first_addr, p, maxlen);
+ else if(cntaddr == 2){
+ a_little_addr_string(first_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen > 7){
+ strncpy(p, " and ", maxlen);
+ maxlen -= 5;
+ p += 5;
+ a_little_addr_string(second_addr, p, maxlen);
+ }
+ }
+ else if(cntaddr == 3){
+ a_little_addr_string(first_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen > 7){
+ strncpy(p, ", ", maxlen);
+ maxlen -= 2;
+ p += 2;
+ a_little_addr_string(second_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen > 7){
+ strncpy(p, ", and ", maxlen);
+ maxlen -= 6;
+ p += 6;
+ a_little_addr_string(third_addr, p, maxlen);
+ }
+ }
+ }
+ else if(cntaddr > 3){
+ a_little_addr_string(first_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen > 7){
+ strncpy(p, ", ", maxlen);
+ maxlen -= 2;
+ p += 2;
+ a_little_addr_string(second_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen > 7){
+ strncpy(p, ", ", maxlen);
+ maxlen -= 2;
+ p += 2;
+ a_little_addr_string(third_addr, p, maxlen);
+ maxlen -= (l=strlen(p));
+ p += l;
+ if(maxlen >= 12)
+ strncpy(p, ", and others", maxlen);
+ else if(maxlen >= 3)
+ strncpy(p, "...", maxlen);
+ }
+ }
+ }
+ else if(addr){
+ char *a_string;
+
+ a_string = addr_list_string(addr, NULL, 0);
+ iutf8ncpy(buf, a_string, maxlen);
+
+ fs_give((void **)&a_string);
+ }
+
+ if(last_to)
+ last_to->next = NULL;
+
+ buf[orig_maxlen] = '\0';
+}
+
+
+/*
+ * Buf is at least size maxlen+1
+ */
+void
+get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
+{
+ int cntnews = 0, orig_maxlen;
+ char *news = NULL, *p, *q;
+
+ switch(type){
+ case iNews:
+ case iNewsAndTo:
+ case iToAndNews:
+ case iNewsAndRecips:
+ case iRecipsAndNews:
+ news = env ? env->newsgroups : NULL;
+ break;
+
+ case iCurNews:
+ if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
+ news = ps_global->cur_folder;
+
+ break;
+
+ default:
+ break;
+ }
+
+ orig_maxlen = maxlen;
+
+ if(news){
+ q = news;
+ while(isspace((unsigned char)*q))
+ q++;
+
+ if(*q)
+ cntnews++;
+
+ while((q = strindex(q, ',')) != NULL){
+ q++;
+ while(isspace((unsigned char)*q))
+ q++;
+
+ if(*q)
+ cntnews++;
+ else
+ break;
+ }
+ }
+
+ if(cntnews == 1){
+ istrncpy(buf, news, maxlen);
+ buf[maxlen] = '\0';
+ removing_leading_and_trailing_white_space(buf);
+ }
+ else if(cntnews == 2){
+ p = buf;
+ q = news;
+ while(isspace((unsigned char)*q))
+ q++;
+
+ while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
+ *p++ = *q++;
+ maxlen--;
+ }
+
+ if(maxlen > 7){
+ strncpy(p, " and ", maxlen);
+ p += 5;
+ maxlen -= 5;
+ }
+
+ while(isspace((unsigned char)*q) || *q == ',')
+ q++;
+
+ while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
+ *p++ = *q++;
+ maxlen--;
+ }
+
+ *p = '\0';
+
+ istrncpy(tmp_20k_buf, buf, 10000);
+ strncpy(buf, tmp_20k_buf, orig_maxlen);
+ }
+ else if(cntnews > 2){
+ char b[100];
+
+ p = buf;
+ q = news;
+ while(isspace((unsigned char)*q))
+ q++;
+
+ while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
+ *p++ = *q++;
+ maxlen--;
+ }
+
+ *p = '\0';
+ snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
+ b[sizeof(b)-1] = '\0';
+ if(maxlen >= strlen(b))
+ strncpy(p, b, maxlen);
+ else if(maxlen >= 3)
+ strncpy(p, "...", maxlen);
+
+ buf[orig_maxlen] = '\0';
+
+ istrncpy(tmp_20k_buf, buf, 10000);
+ tmp_20k_buf[10000-1] = '\0';
+ strncpy(buf, tmp_20k_buf, orig_maxlen);
+ }
+
+ buf[orig_maxlen] = '\0';
+}
+
+
+/*
+ * Buf is at least size maxlen+1
+ */
+char *
+get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
+{
+ char *space = NULL;
+ IndexColType addrtype;
+
+ buf[0] = '\0';
+
+ switch(type){
+ case iRDate: case iSDate: case iSTime:
+ case iS1Date: case iS2Date: case iS3Date: case iS4Date:
+ case iSDateIso: case iSDateIsoS:
+ case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
+ case iSDateTime:
+ case iSDateTimeIso: case iSDateTimeIsoS:
+ case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
+ case iSDateTime24:
+ case iSDateTimeIso24: case iSDateTimeIsoS24:
+ case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
+ case iDateIso: case iDateIsoS: case iTime24: case iTime12:
+ case iDay: case iDayOrdinal: case iDay2Digit:
+ case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
+ case iYear: case iYear2Digit:
+ case iDate: case iLDate:
+ case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
+ case iPrefDate: case iPrefTime: case iPrefDateTime:
+ if(env && env->date && env->date[0] && maxlen >= 20)
+ date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
+
+ break;
+
+ case iCurDate:
+ case iCurDateIso:
+ case iCurDateIsoS:
+ case iCurTime24:
+ case iCurTime12:
+ case iCurDay:
+ case iCurDay2Digit:
+ case iCurDayOfWeek:
+ case iCurDayOfWeekAbb:
+ case iCurMon:
+ case iCurMon2Digit:
+ case iCurMonLong:
+ case iCurMonAbb:
+ case iCurYear:
+ case iCurYear2Digit:
+ case iCurPrefDate:
+ case iCurPrefDateTime:
+ case iCurPrefTime:
+ case iLstMon:
+ case iLstMon2Digit:
+ case iLstMonLong:
+ case iLstMonAbb:
+ case iLstMonYear:
+ case iLstMonYear2Digit:
+ case iLstYear:
+ case iLstYear2Digit:
+ if(maxlen >= 20)
+ date_str(NULL, type, 1, buf, maxlen+1, 0);
+
+ break;
+
+ case iFrom:
+ case iTo:
+ case iCc:
+ case iSender:
+ case iRecips:
+ case iInit:
+ get_addr_data(env, type, buf, maxlen);
+ break;
+
+ case iRoleNick:
+ if(role && role->nick){
+ strncpy(buf, role->nick, maxlen);
+ buf[maxlen] = '\0';
+ }
+ break;
+
+ case iNewLine:
+ if(maxlen >= strlen(NEWLINE)){
+ strncpy(buf, NEWLINE, maxlen);
+ buf[maxlen] = '\0';
+ }
+ break;
+
+ case iAddress:
+ case iMailbox:
+ if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
+ strlen(env->from->mailbox) <= maxlen){
+ strncpy(buf, env->from->mailbox, maxlen);
+ buf[maxlen] = '\0';
+ if(type == iAddress &&
+ env->from->host &&
+ env->from->host[0] &&
+ env->from->host[0] != '.' &&
+ strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
+ strncat(buf, "@", maxlen+1-1-strlen(buf));
+ buf[maxlen] = '\0';
+ strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
+ buf[maxlen] = '\0';
+ }
+ }
+
+ break;
+
+ case iNews:
+ case iCurNews:
+ get_news_data(env, type, buf, maxlen);
+ break;
+
+ case iToAndNews:
+ case iNewsAndTo:
+ case iRecipsAndNews:
+ case iNewsAndRecips:
+ if(type == iToAndNews || type == iNewsAndTo)
+ addrtype = iTo;
+ else
+ addrtype = iRecips;
+
+ if(env && env->newsgroups){
+ space = (char *)fs_get((maxlen+1) * sizeof(char));
+ get_news_data(env, type, space, maxlen);
+ }
+
+ get_addr_data(env, addrtype, buf, maxlen);
+
+ if(space && *space && *buf){
+ if(strlen(space) + strlen(buf) + 5 > maxlen){
+ if(strlen(space) > maxlen/2)
+ get_news_data(env, type, space, maxlen - strlen(buf) - 5);
+ else
+ get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
+ }
+
+ if(type == iToAndNews || type == iRecipsAndNews)
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
+ else
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ strncpy(buf, tmp_20k_buf, maxlen);
+ buf[maxlen] = '\0';
+ }
+ else if(space && *space){
+ strncpy(buf, space, maxlen);
+ buf[maxlen] = '\0';
+ }
+
+ if(space)
+ fs_give((void **)&space);
+
+ break;
+
+ case iSubject:
+ if(env && env->subject){
+ size_t n, len;
+ unsigned char *p, *tmp = NULL;
+
+ if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = tmp = (unsigned char *)fs_get(len * sizeof(char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *)tmp_20k_buf;
+ }
+
+ istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
+
+ buf[maxlen] = '\0';
+
+ if(tmp)
+ fs_give((void **)&tmp);
+ }
+
+ break;
+
+ case iMsgID:
+ if(env && env->message_id){
+ strncpy(buf, env->message_id, maxlen);
+ buf[maxlen] = '\0';
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ buf[maxlen] = '\0';
+ return(buf);
+}
+
+
+/*
+ * reply_delimiter - output formatted reply delimiter for given envelope
+ * with supplied character writing function.
+ */
+void
+reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
+{
+#define MAX_DELIM 2000
+ char buf[MAX_DELIM+1];
+ char *p;
+ char *filtered = NULL;
+ int contains_newline_token = 0;
+
+
+ if(!env)
+ return;
+
+ strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
+ buf[MAX_DELIM] = '\0';
+ /* preserve exact default behavior from before */
+ if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
+ struct date d;
+ int include_date;
+
+ parse_date((char *) env->date, &d);
+ include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
+ if(include_date){
+ gf_puts("On ", pc); /* All delims have... */
+ if(d.wkday != -1){ /* "On day, date month year" */
+ gf_puts(day_abbrev(d.wkday), pc); /* in common */
+ gf_puts(", ", pc);
+ }
+
+ gf_puts(int2string(d.day), pc);
+ (*pc)(' ');
+ gf_puts(month_abbrev(d.month), pc);
+ (*pc)(' ');
+ gf_puts(int2string(d.year), pc);
+ }
+
+ if(env->from
+ && ((env->from->personal && env->from->personal[0])
+ || (env->from->mailbox && env->from->mailbox[0]))){
+ char buftmp[MAILTMPLEN];
+
+ a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
+ if(include_date)
+ gf_puts(", ", pc);
+
+ gf_puts(buftmp, pc);
+ gf_puts(" wrote:", pc);
+ }
+ else{
+ if(include_date)
+ gf_puts(", it was written", pc);
+ else
+ gf_puts("It was written", pc);
+ }
+
+ }
+ else{
+ /*
+ * This is here for backwards compatibility. There didn't used
+ * to be a _NEWLINE_ token. The user would enter text that should
+ * all fit on one line and then that was followed by two newlines.
+ * Also, truncation occurs if it is long.
+ * Now, if _NEWLINE_ is not in the text, same thing still works
+ * the same. However, if _NEWLINE_ is in there, then all bets are
+ * off and the user is on his or her own. No automatic newlines
+ * are added, only those that come from the tokens. No truncation
+ * is done, the user is trusted to get it right. Newlines may be
+ * embedded so that the leadin is multi-line.
+ */
+ contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
+ filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
+ NULL, NULL);
+
+ /* try to truncate if too long */
+ if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
+ int ended_with_colon = 0;
+ int ended_with_quote = 0;
+ int ended_with_quote_colon = 0;
+ int l;
+
+ l = strlen(filtered);
+
+ if(filtered[l-1] == ':'){
+ ended_with_colon = ':';
+ if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
+ ended_with_quote_colon = filtered[l-2];
+ }
+ else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
+ ended_with_quote = filtered[l-1];
+
+ /* try to find space to break at */
+ for(p = &filtered[75]; p > &filtered[60] &&
+ !isspace((unsigned char)*p); p--)
+ ;
+
+ if(!isspace((unsigned char)*p))
+ p = &filtered[75];
+
+ *p++ = '.';
+ *p++ = '.';
+ *p++ = '.';
+ if(ended_with_quote_colon){
+ *p++ = ended_with_quote_colon;
+ *p++ = ':';
+ }
+ else if(ended_with_colon)
+ *p++ = ended_with_colon;
+ else if(ended_with_quote)
+ *p++ = ended_with_quote;
+
+ *p = '\0';
+ }
+
+ if(filtered && *filtered)
+ gf_puts(filtered, pc);
+ }
+
+ /* and end with two newlines unless no leadin at all */
+ if(!contains_newline_token){
+ if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
+ gf_puts(NEWLINE, pc);
+ gf_puts(NEWLINE, pc);
+ }
+ }
+
+ if(filtered)
+ fs_give((void **)&filtered);
+}
+
+
+void
+free_redraft_pos(REDRAFT_POS_S **redraft_pos)
+{
+ if(redraft_pos && *redraft_pos){
+ if((*redraft_pos)->hdrname)
+ fs_give((void **)&(*redraft_pos)->hdrname);
+
+ fs_give((void **)redraft_pos);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Build the body for the message number/part being forwarded as ATTACHMENT
+
+ Args:
+
+ Result: PARTS suitably attached to body
+
+ ----------------------------------------------------------------------*/
+int
+forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
+{
+ char *tmp_text;
+ unsigned long len;
+ BODY *b;
+
+ *partp = mail_newbody_part();
+ b = &(*partp)->body;
+ b->type = TYPEMESSAGE;
+ b->id = generate_message_id();
+ b->description = cpystr("Forwarded Message");
+ b->nested.msg = mail_newmsg();
+ b->disposition.type = cpystr("inline");
+
+ /*---- Package each message in a storage object ----*/
+ if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
+ && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
+ && *tmp_text){
+ so_puts((STORE_S *) b->contents.text.data, tmp_text);
+
+ b->size.bytes = strlen(tmp_text);
+ so_puts((STORE_S *) b->contents.text.data, "\015\012");
+ if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
+ so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
+ b->size.bytes += len;
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+
+/*
+ * forward_delimiter - return delimiter for forwarded text
+ */
+void
+forward_delimiter(gf_io_t pc)
+{
+ gf_puts(NEWLINE, pc);
+ /* TRANSLATORS: When a message is forwarded by the user this is the
+ text that shows where the forwarded part of the message begins. */
+ gf_puts(_("---------- Forwarded message ----------"), pc);
+ gf_puts(NEWLINE, pc);
+}
+
+
+/*----------------------------------------------------------------------
+ Wrapper for header formatting tool
+
+ Args: stream --
+ msgno --
+ env --
+ pc --
+ prefix --
+
+ Result: header suitable for reply/forward text written using "pc"
+
+ ----------------------------------------------------------------------*/
+void
+reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
+ gf_io_t pc, char *prefix)
+{
+ int rv;
+ HEADER_S h;
+ char **list, **new_list = NULL;
+
+ list = ps_global->VAR_VIEW_HEADERS;
+
+ /*
+ * If VIEW_HEADERS is set, we should remove BCC from the list so that
+ * the user doesn't inadvertently forward the BCC header.
+ */
+ if(list && list[0]){
+ int i, cnt = 0;
+ char **p;
+
+ while(list[cnt++])
+ ;
+
+ p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
+
+ for(i=0; list[i]; i++)
+ if(strucmp(list[i], "bcc"))
+ *p++ = cpystr(list[i]);
+
+ *p = NULL;
+
+ if(new_list && new_list[0])
+ list = new_list;
+
+ }
+
+ HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
+ if((rv = format_header(stream, msgno, part, env, &h,
+ prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
+ if(rv == 1)
+ gf_puts(" [Error fetching message header data]", pc);
+ }
+ else
+ gf_puts(NEWLINE, pc); /* write header delimiter */
+
+ if(new_list)
+ free_list_array(&new_list);
+}
+
+
+/*----------------------------------------------------------------------
+ Build the subject for the message number being forwarded
+
+ Args: pine_state -- The usual pine structure
+ msgno -- The message number to build subject for
+
+ Result: malloc'd string containing new subject or NULL on error
+
+ ----------------------------------------------------------------------*/
+char *
+forward_subject(ENVELOPE *env, int flags)
+{
+ size_t l;
+ char *p, buftmp[MAILTMPLEN];
+
+ if(!env)
+ return(NULL);
+
+ dprint((9, "checking subject: \"%s\"\n",
+ env->subject ? env->subject : "NULL"));
+
+ if(env->subject && env->subject[0]){ /* add (fwd)? */
+ snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
+ if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
+ strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ removing_trailing_white_space(tmp_20k_buf);
+ if((l = strlen(tmp_20k_buf)) < 1000 &&
+ (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
+ snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf);
+ tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0';
+ strncpy(tmp_20k_buf, tmp_20k_buf+2000, SIZEOF_20KBUF);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ /*
+ * HACK: composer can't handle embedded double quotes in attachment
+ * comments so we substitute two single quotes.
+ */
+ if(flags & FS_CONVERT_QUOTES)
+ while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
+ (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
+
+ return(cpystr(tmp_20k_buf));
+
+ }
+
+ return(cpystr("Forwarded mail...."));
+}
+
+
+/*----------------------------------------------------------------------
+ Build the body for the message number/part being forwarded
+
+ Args:
+
+ Result: BODY structure suitable for sending
+
+ ----------------------------------------------------------------------*/
+BODY *
+forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
+ long int msgno, char *sect_prefix, void *msgtext, int flags)
+{
+ BODY *body = NULL, *text_body, *tmp_body;
+ PART *part;
+ gf_io_t pc;
+ char *tmp_text, *section, sect_buf[256];
+ int forward_raw_body = 0;
+
+ /*
+ * Check to see if messages got expunged out from underneath us. This
+ * could have happened during the prompt to the user asking whether to
+ * include the message as an attachment. Either the message is gone or
+ * it might be at a different sequence number. We'd better bail.
+ */
+ if(ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
+ forward_raw_body = 1;
+ if(sp_expunge_count(stream))
+ return(NULL);
+
+ if(sect_prefix && forward_raw_body == 0)
+ snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
+ else if(sect_prefix && forward_raw_body)
+ section = sect_prefix;
+ else if(!sect_prefix && forward_raw_body)
+ section = NULL;
+ else
+ section = "1";
+
+ sect_buf[sizeof(sect_buf)-1] = '\0';
+
+ gf_set_so_writec(&pc, (STORE_S *) msgtext);
+ if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
+ char *charset = NULL;
+
+ /*---- Message has a single text part -----*/
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+ if(orig_body
+ && (charset = parameter_val(orig_body->parameter, "charset")))
+ set_parameter(&body->parameter, "charset", charset);
+
+ if(charset)
+ fs_give((void **) &charset);
+
+ if(!(flags & FWD_ANON)){
+ forward_delimiter(pc);
+ reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
+ }
+
+ if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
+ msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
+ mail_free_body(&body);
+ return(NULL);
+ }
+ }
+ else if(orig_body->type == TYPEMULTIPART) {
+ if(orig_body->subtype && !strucmp(orig_body->subtype, "signed")
+ && orig_body->nested.part){
+ /* only operate on the signed data (not the signature) */
+ body = forward_body(stream, env, &orig_body->nested.part->body,
+ msgno, sect_prefix, msgtext, flags);
+ }
+ /*---- Message is multipart ----*/
+ else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
+ "alternative")
+ && (body = forward_multi_alt(stream, env, orig_body, msgno,
+ sect_prefix, msgtext,
+ pc, flags)))){
+ /*--- Copy the body and entire structure ---*/
+ body = copy_body(NULL, orig_body);
+
+ /*
+ * whatever subtype it is, demote it
+ * to plain old MIXED.
+ */
+ if(body->subtype)
+ fs_give((void **) &body->subtype);
+
+ body->subtype = cpystr("Mixed");
+
+ /*--- The text part of the message ---*/
+ if(!body->nested.part){
+ q_status_message(SM_ORDER | SM_DING, 3, 6,
+ "Error referencing body part 1");
+ mail_free_body(&body);
+ }
+ else if(body->nested.part->body.type == TYPETEXT) {
+ char *new_charset = NULL;
+
+ /*--- The first part is text ----*/
+ text_body = &body->nested.part->body;
+ text_body->contents.text.data = msgtext;
+ if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
+ /* this text is going to the composer, it should be Plain */
+ fs_give((void **)&text_body->subtype);
+ text_body->subtype = cpystr("PLAIN");
+ }
+ if(!(flags & FWD_ANON)){
+ forward_delimiter(pc);
+ reply_forward_header(stream, msgno,
+ sect_prefix, env, pc, "");
+ }
+
+ if(!(get_body_part_text(stream, &orig_body->nested.part->body,
+ msgno, section, 0L, pc,
+ NULL, &new_charset, GBPT_NONE)
+ && fetch_contents(stream, msgno, sect_prefix, body)))
+ mail_free_body(&body);
+ else if(new_charset)
+ set_parameter(&text_body->parameter, "charset", new_charset);
+
+/* BUG: ? matter that we're not setting body.size.bytes */
+ }
+ else if(body->nested.part->body.type == TYPEMULTIPART
+ && body->nested.part->body.subtype
+ && !strucmp(body->nested.part->body.subtype, "alternative")
+ && (tmp_body = forward_multi_alt(stream, env,
+ &body->nested.part->body,
+ msgno, sect_prefix,
+ msgtext, pc,
+ flags | FWD_NESTED))){
+ /* for the forward_multi_alt call above, we want to pass
+ * sect_prefix instead of section so we can obtain the header.
+ * Set the FWD_NESTED flag so we fetch the right body_part.
+ */
+ int partnum;
+
+ part = body->nested.part->next;
+ body->nested.part->next = NULL;
+ mail_free_body_part(&body->nested.part);
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body = *tmp_body;
+ body->nested.part->next = part;
+
+ for(partnum = 2; part != NULL; part = part->next){
+ snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
+ sect_prefix ? sect_prefix : "",
+ sect_prefix ? "." : "", partnum++);
+ sect_buf[sizeof(sect_buf)-1] = '\0';
+
+ if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
+ mail_free_body(&body);
+ break;
+ }
+ }
+ }
+ else {
+ if(fetch_contents(stream, msgno, sect_prefix, body)){
+ /*--- Create a new blank text part ---*/
+ part = mail_newbody_part();
+ part->next = body->nested.part;
+ body->nested.part = part;
+ part->body.contents.text.data = msgtext;
+ }
+ else
+ mail_free_body(&body);
+ }
+ }
+ }
+ else {
+ /*---- A single part message, not of type text ----*/
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+ part = mail_newbody_part();
+ body->nested.part = part;
+
+ /*--- The first part, a blank text part to be edited ---*/
+ part->body.type = TYPETEXT;
+ part->body.contents.text.data = msgtext;
+
+ /*--- The second part, what ever it is ---*/
+ part->next = mail_newbody_part();
+ part = part->next;
+ part->body.id = generate_message_id();
+ copy_body(&(part->body), orig_body);
+
+ /*
+ * the idea here is to fetch part into storage object
+ */
+ if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
+ EDIT_ACCESS)) != NULL){
+ if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
+ &part->body.size.bytes, NIL)) != NULL)
+ so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
+ part->body.size.bytes);
+ else
+ mail_free_body(&body);
+ }
+ else
+ mail_free_body(&body);
+ }
+
+ gf_clear_so_writec((STORE_S *) msgtext);
+
+ return(body);
+}
+
+
+
+/*
+ * bounce_msg_body - build body from specified message suitable
+ * for sending as bounced message
+ */
+char *
+bounce_msg_body(MAILSTREAM *stream,
+ long int rawno,
+ char *part,
+ char **to,
+ char *subject,
+ ENVELOPE **outgoingp,
+ BODY **bodyp,
+ int *seenp)
+{
+ char *h, *p, *errstr = NULL;
+ int i;
+ STORE_S *msgtext;
+ gf_io_t pc;
+
+ *outgoingp = mail_newenvelope();
+ (*outgoingp)->message_id = generate_message_id();
+ (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
+
+ /*
+ * Fill in destination if we were given one. If so, note that we
+ * call p_s_s() below such that it won't prompt...
+ */
+ if(to && *to){
+ static char *fakedomain = "@";
+ char *tmp_a_string;
+
+ /* rfc822_parse_adrlist feels free to destroy input so copy */
+ tmp_a_string = cpystr(*to);
+ rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
+ (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
+ ? fakedomain : ps_global->maildomain);
+ fs_give((void **) &tmp_a_string);
+ }
+
+ /* build remail'd header */
+ if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
+ for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
+ i++;
+
+ /* allocate it */
+ (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
+
+ /*
+ * copy it, "X-"ing out transport headers bothersome to
+ * software but potentially useful to the human recipient...
+ */
+ p = (*outgoingp)->remail;
+ bounce_mask_header(&p, h);
+ do
+ if(*h == '\015' && *(h+1) == '\012'){
+ *p++ = *h++; /* copy CR LF */
+ *p++ = *h++;
+ bounce_mask_header(&p, h);
+ }
+ while((*p++ = *h++) != '\0');
+ }
+ /* BUG: else complain? */
+
+ /* NOT bound for the composer, so no need for PicoText */
+ if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
+ mail_free_envelope(outgoingp);
+ return(_("Error allocating message text"));
+ }
+
+ /* mark object for special handling */
+ so_attr(msgtext, "rawbody", "1");
+
+ /*
+ * Build a fake body description. It's ignored by pine_rfc822_header,
+ * but we need to set it to something that makes set_mime_types
+ * not sniff it and pine_rfc822_output_body not re-encode it.
+ * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
+ * problems unless something tries to access body_encodings[] using
+ * it without proper precautions. We don't want to use ENCOTHER
+ * cause that tells set_mime_types to sniff it, and we don't want to
+ * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
+ * it. When there's time, it'd be nice to clean this interaction
+ * up...
+ */
+ *bodyp = mail_newbody();
+ (*bodyp)->type = TYPETEXT;
+ (*bodyp)->encoding = ENCMAX + 1;
+ (*bodyp)->subtype = cpystr("Plain");
+ (*bodyp)->contents.text.data = (void *) msgtext;
+ gf_set_so_writec(&pc, msgtext);
+
+ if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
+ MESSAGECACHE *mc;
+
+ if((mc = mail_elt(stream, rawno)) != NULL)
+ *seenp = mc->seen;
+ }
+
+ /* pass NULL body to force mail_fetchtext */
+ if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
+ errstr = _("Error fetching message contents. Can't Bounce message");
+
+ gf_clear_so_writec(msgtext);
+
+ return(errstr);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Mask off any header entries we don't want xport software to see
+
+Args: d -- destination string pointer pointer
+ s -- source string pointer pointer
+
+ Postfix uses Delivered-To to detect loops.
+ Received line counting is also used to detect loops in places.
+
+ ----*/
+void
+bounce_mask_header(char **d, char *s)
+{
+ if(((*s == 'R' || *s == 'r')
+ && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
+ || !struncmp(s+1, "eturn-Path", 10)))
+ || !struncmp(s, "Delivered-To:", 13)){
+ *(*d)++ = 'X'; /* write mask */
+ *(*d)++ = '-';
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Fetch and format text for forwarding
+
+Args: stream -- Mail stream to fetch text from
+ body -- Body structure of message being forwarded
+ msg_no -- Message number of text for forward
+ part_no -- Part number of text to forward
+ partial -- If this is > 0 a partial fetch will be done and it will
+ be done using FT_PEEK so the message will remain unseen.
+ pc -- Function to write to
+ prefix -- Prefix for each line
+ ret_charset -- If we translate to another charset return that
+ new charset here
+
+Returns: true if OK, false if problem occured while filtering
+
+If the text is richtext, it will be converted to plain text, since there's
+no rich text editing capabilities in Pine (yet).
+
+It's up to calling routines to plug in signature appropriately
+
+As with all internal text, NVT end-of-line conventions are observed.
+DOESN'T sanity check the prefix given!!!
+ ----*/
+int
+get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
+ long int msg_no, char *part_no, long partial, gf_io_t pc,
+ char *prefix, char **ret_charset, unsigned flags)
+{
+ int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
+ FILTLIST_S filters[12];
+ long len;
+ char *err, *charset, *prefix_p = NULL;
+ int filtcnt = 0;
+ char *free_this = NULL;
+ DELQ_S dq;
+
+ memset(filters, 0, sizeof(filters));
+ if(ret_charset)
+ *ret_charset = NULL;
+
+ if(!pc_is_picotext(pc))
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ /* if null body, we must be talking to a non-IMAP2bis server.
+ * No MIME parsing provided, so we just grab the message text...
+ */
+ if(body == NULL){
+ char *text, *decode_error;
+ gf_io_t gc;
+ SourceType src = CharStar;
+ int rv = 0;
+
+ (void) pine_mail_fetchstructure(stream, msg_no, NULL);
+
+ if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
+ gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
+
+ gf_filter_init(); /* no filters needed */
+ if(prefix)
+ gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
+ if((decode_error = gf_pipe(gc, pc)) != NULL){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
+ NEWLINE, NEWLINE,
+ decode_error, NEWLINE);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ gf_puts(tmp_20k_buf, pc);
+ rv++;
+ }
+ }
+ else{
+ gf_puts(NEWLINE, pc);
+ gf_puts(_(" [ERROR fetching text of message]"), pc);
+ gf_puts(NEWLINE, pc);
+ gf_puts(NEWLINE, pc);
+ rv++;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return(rv == 0);
+ }
+
+ charset = parameter_val(body->parameter, "charset");
+
+ if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
+ if(ret_charset)
+ *ret_charset = "UTF-8";
+ }
+
+ /*
+ * just use detach, but add an auxiliary filter to insert prefix,
+ * and, perhaps, digest richtext
+ */
+ if(ps_global->full_header != 2
+ && !ps_global->postpone_no_flow
+ && (!body->subtype || !strucmp(body->subtype, "plain"))){
+ char *parmval;
+
+ flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
+ && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
+ && (!prefix || (strucmp(prefix,"> ") == 0)
+ || strucmp(prefix, ">") == 0));
+ if((parmval = parameter_val(body->parameter,
+ "format")) != NULL){
+ if(!strucmp(parmval, "flowed")){
+ wrapflags |= GFW_FLOWED;
+
+ fs_give((void **) &parmval);
+ if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
+ if(!strucmp(parmval, "yes")){
+ filters[filtcnt++].filter = gf_preflow;
+ wrapflags |= GFW_DELSP;
+ }
+
+ fs_give((void **) &parmval);
+ }
+
+ /*
+ * if there's no prefix we're forwarding text
+ * otherwise it's reply text. unless the user's
+ * tied our hands, alter the prefix to continue flowed
+ * formatting...
+ */
+ if(flow_res)
+ wrapflags |= GFW_FLOW_RESULT;
+
+ filters[filtcnt].filter = gf_wrap;
+ /*
+ * The 80 will cause longer lines than what is likely
+ * set by composer_fillcol, so we'll have longer
+ * quoted and forwarded lines than the lines we type.
+ * We're fine with that since the alternative is the
+ * possible wrapping of lines we shouldn't have, which
+ * is mitigated by this higher 80 limit.
+ *
+ * If we were to go back to using composer_fillcol,
+ * the correct value is composer_fillcol + 1, pine
+ * is off-by-one from pico.
+ */
+ filters[filtcnt++].data = gf_wrap_filter_opt(
+ MAX(MIN(ps_global->ttyo->screen_cols
+ - (prefix ? strlen(prefix) : 0),
+ 80 - (prefix ? strlen(prefix) : 0)),
+ 30), /* doesn't have to be 30 */
+ 990, /* 998 is the SMTP limit */
+ NULL, 0, wrapflags);
+ }
+ }
+
+ /*
+ * if not flowed, remove trailing whitespace to reduce
+ * confusion, since we're sending out as flowed if we
+ * can. At some future point, we might try
+ * plugging in a user-option-controlled heuristic
+ * flowing filter
+ *
+ * We also want to fold "> " quotes so we get the
+ * attributions correct.
+ */
+ if(flow_res && prefix && !strucmp(prefix, "> "))
+ *(prefix_p = prefix + 1) = '\0';
+
+ if(!(wrapflags & GFW_FLOWED)
+ && flow_res){
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
+
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
+ }
+ }
+ else if(body->subtype){
+ int plain_opt = 1;
+
+ if(strucmp(body->subtype,"richtext") == 0){
+ filters[filtcnt].filter = gf_rich2plain;
+ filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
+ }
+ else if(strucmp(body->subtype,"enriched") == 0){
+ filters[filtcnt].filter = gf_enriched2plain;
+ filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
+ }
+ else if(strucmp(body->subtype,"html") == 0){
+ if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
+ filters[filtcnt].filter = gf_html2plain;
+ filters[filtcnt++].data = gf_html2plain_opt(NULL,
+ ps_global->ttyo->screen_cols,
+ non_messageview_margin(),
+ NULL, NULL, GFHP_STRIPPED);
+ }
+ }
+ }
+
+ if(prefix){
+ if(ps_global->full_header != 2
+ && (F_ON(F_ENABLE_SIGDASHES, ps_global)
+ || F_ON(F_ENABLE_STRIP_SIGDASHES, ps_global))){
+ dashdata = 0;
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
+ }
+
+ filters[filtcnt].filter = gf_prefix;
+ filters[filtcnt++].data = gf_prefix_opt(prefix);
+
+ if(wrapflags & GFW_FLOWED || flow_res){
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
+ }
+ }
+
+ if(flags & GBPT_DELQUOTES){
+ memset(&dq, 0, sizeof(dq));
+ dq.lines = Q_DEL_ALL;
+ dq.is_flowed = 0;
+ dq.indent_length = 0;
+ dq.saved_line = &free_this;
+ dq.handlesp = NULL;
+ dq.do_color = 0;
+ dq.delete_all = 1;
+
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
+ }
+
+ err = detach(stream, msg_no, part_no, partial, &len, pc,
+ filters[0].filter ? filters : NULL,
+ ((flags & GBPT_PEEK) ? FT_PEEK : 0)
+ | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
+
+ if(free_this)
+ fs_give((void **) &free_this);
+
+ if(prefix_p)
+ *prefix_p = ' ';
+
+ if (err != (char *) NULL)
+ /* TRANSLATORS: The first arg is error text, the %ld is the message number */
+ q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
+ err, (void *) msg_no);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return((int) len);
+}
+
+
+int
+quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char *p;
+
+ if(*line == '>'){
+ for(p = line; *p; p++){
+ if(isspace((unsigned char) *p)){
+ if(*(p+1) == '>')
+ ins = gf_line_test_new_ins(ins, p, "", -1);
+ }
+ else if(*p != '>')
+ break;
+ }
+ }
+
+ return(0);
+}
+
+
+int
+twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char *p, *ws = NULL;
+
+ for(p = line; *p; p++){
+ /* don't strip trailing space on signature line */
+ if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
+ break;
+
+ if(isspace((unsigned char) *p)){
+ if(!ws)
+ ws = p;
+ }
+ else
+ ws = NULL;
+ }
+
+ if(ws)
+ ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
+
+ return(0);
+}
+
+int
+post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char *p;
+
+ for(p = line; *p; p++)
+ if(*p != '>'){
+ if(p != line && *p != ' ')
+ ins = gf_line_test_new_ins(ins, p, " ", 1);
+
+ break;
+ }
+
+ return(0);
+}
+
+
+int
+sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ if(*((int *)local)
+ || (*line == '-' && *(line+1) == '-'
+ && *(line+2) == ' ' && !*(line+3))){
+ *((int *) local) = 1;
+ return(2); /* skip this line! */
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ return the c-client reference name for the given end_body part
+ ----*/
+char *
+body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
+{
+ BODY *body;
+
+ (void) pine_mail_fetchstructure(stream, msgno, &body);
+ return(partno(body, end_body));
+}
+
+
+/*----------------------------------------------------------------------
+ return the c-client reference name for the given end_body part
+ ----*/
+char *
+partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
+{
+ PART *part;
+ int num = 0;
+ char tmp[64], *p = NULL;
+
+ if(body && body->type == TYPEMULTIPART) {
+ part = body->nested.part; /* first body part */
+
+ do { /* for each part */
+ num++;
+ if(&part->body == end_body || (p = partno(&part->body, end_body))){
+ snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
+ sizeof(tmp)-10, (p) ? p : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(p)
+ fs_give((void **)&p);
+
+ return(cpystr(tmp));
+ }
+ } while ((part = part->next) != NULL); /* until done */
+
+ return(NULL);
+ }
+ else if(body && body->type == TYPEMESSAGE && body->subtype
+ && !strucmp(body->subtype, "rfc822")){
+ return(partno(body->nested.msg->body, end_body));
+ }
+
+ return((body == end_body) ? cpystr("1") : NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Fill in the contents of each body part
+
+Args: stream -- Stream the message is on
+ msgno -- Message number the body structure is for
+ section -- body section associated with body pointer
+ body -- Body pointer to fill in
+
+Result: 1 if all went OK, 0 if there was a problem
+
+This function copies the contents from an original message/body to
+a new message/body. It recurses down all multipart levels.
+
+If one or more part (but not all) can't be fetched, a status message
+will be queued.
+ ----*/
+int
+fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
+{
+ char *tp;
+ int got_one = 0;
+
+ if(!body->id)
+ body->id = generate_message_id();
+
+ if(body->type == TYPEMULTIPART){
+ char subsection[256], *subp;
+ int n, last_one = 10; /* remember worst case */
+ PART *part = body->nested.part;
+
+ if(!(part = body->nested.part))
+ return(0);
+
+ subp = subsection;
+ if(section && *section){
+ for(n = 0;
+ n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
+ ;
+
+ *subp++ = '.';
+ }
+
+ n = 1;
+ do {
+ snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
+ subsection[sizeof(subsection)-1] = '\0';
+ got_one = fetch_contents(stream, msgno, subsection, &part->body);
+ last_one = MIN(last_one, got_one);
+ }
+ while((part = part->next) != NULL);
+
+ return(last_one);
+ }
+
+ if(body->contents.text.data)
+ return(1); /* already taken care of... */
+
+ if(body->type == TYPEMESSAGE){
+ if(body->subtype && strucmp(body->subtype,"external-body")){
+ /*
+ * the idea here is to fetch everything into storage objects
+ */
+ body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
+ EDIT_ACCESS);
+ if(body->contents.text.data
+ && (tp = pine_mail_fetch_body(stream, msgno, section,
+ &body->size.bytes, NIL))){
+ so_truncate((STORE_S *)body->contents.text.data,
+ body->size.bytes + 2048);
+ so_nputs((STORE_S *)body->contents.text.data, tp,
+ body->size.bytes);
+ got_one = 1;
+ }
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error fetching part %s"), section);
+ } else {
+ got_one = 1;
+ }
+ } else {
+ /*
+ * the idea here is to fetch everything into storage objects
+ * so, grab one, then fetch the body part
+ */
+ body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
+ if(body->contents.text.data
+ && (tp=pine_mail_fetch_body(stream, msgno, section,
+ &body->size.bytes, NIL))){
+ so_truncate((STORE_S *)body->contents.text.data,
+ body->size.bytes + 2048);
+ so_nputs((STORE_S *)body->contents.text.data, tp,
+ body->size.bytes);
+ got_one = 1;
+ }
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error fetching part %s"), section);
+ }
+
+ return(got_one);
+}
+
+
+/*----------------------------------------------------------------------
+ Copy the body structure
+
+Args: new_body -- Pointer to already allocated body, or NULL, if none
+ old_body -- The Body to copy
+
+
+ This is traverses the body structure recursively copying all elements.
+The new_body parameter can be NULL in which case a new body is
+allocated. Alternatively it can point to an already allocated body
+structure. This is used when copying body parts since a PART includes a
+BODY. The contents fields are *not* filled in.
+ ----*/
+
+BODY *
+copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
+{
+ if(old_body == NULL)
+ return(NULL);
+
+ if(new_body == NULL)
+ new_body = mail_newbody();
+
+ new_body->type = old_body->type;
+ new_body->encoding = old_body->encoding;
+
+ if(old_body->subtype)
+ new_body->subtype = cpystr(old_body->subtype);
+
+ new_body->parameter = copy_parameters(old_body->parameter);
+
+ if(old_body->id)
+ new_body->id = cpystr(old_body->id);
+
+ if(old_body->description)
+ new_body->description = cpystr(old_body->description);
+
+ if(old_body->disposition.type)
+ new_body->disposition.type = cpystr(old_body->disposition.type);
+
+ new_body->disposition.parameter
+ = copy_parameters(old_body->disposition.parameter);
+
+ new_body->size = old_body->size;
+
+ if(new_body->type == TYPEMESSAGE
+ && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
+ new_body->nested.msg = mail_newmsg();
+ new_body->nested.msg->body
+ = copy_body(NULL, old_body->nested.msg->body);
+ }
+ else if(new_body->type == TYPEMULTIPART) {
+ PART **new_partp, *old_part;
+
+ new_partp = &new_body->nested.part;
+ for(old_part = old_body->nested.part;
+ old_part != NULL;
+ old_part = old_part->next){
+ *new_partp = mail_newbody_part();
+ copy_body(&(*new_partp)->body, &old_part->body);
+ new_partp = &(*new_partp)->next;
+ }
+ }
+
+ return(new_body);
+}
+
+
+/*----------------------------------------------------------------------
+ Copy the MIME parameter list
+
+ Allocates storage for new part, and returns pointer to new paramter
+list. If old_p is NULL, NULL is returned.
+ ----*/
+PARAMETER *
+copy_parameters(PARAMETER *old_p)
+{
+ PARAMETER *new_p, *p1, *p2;
+
+ if(old_p == NULL)
+ return((PARAMETER *)NULL);
+
+ new_p = p2 = NULL;
+ for(p1 = old_p; p1 != NULL; p1 = p1->next){
+ set_parameter(&p2, p1->attribute, p1->value);
+ if(new_p == NULL)
+ new_p = p2;
+ }
+
+ return(new_p);
+}
+
+
+/*----------------------------------------------------------------------
+ Make a complete copy of an envelope and all it's fields
+
+Args: e -- the envelope to copy
+
+Result: returns the new envelope, or NULL, if the given envelope was NULL
+
+ ----*/
+
+ENVELOPE *
+copy_envelope(register ENVELOPE *e)
+{
+ register ENVELOPE *e2;
+
+ if(!e)
+ return(NULL);
+
+ e2 = mail_newenvelope();
+ e2->remail = e->remail ? cpystr(e->remail) : NULL;
+ e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
+ e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
+ : NULL;
+ e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
+ e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
+ e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
+ e2->subject = e->subject ? cpystr(e->subject) : NULL;
+ e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
+ e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
+ e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
+ e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
+ e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
+ e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
+ e2->references = e->references ? cpystr(e->references) : NULL;
+ e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
+ return(e2);
+}
+
+
+/*----------------------------------------------------------------------
+ Generate the "In-reply-to" text from message header
+
+ Args: message -- Envelope of original message
+
+ Result: returns an alloc'd string or NULL if there is a problem
+ ----*/
+char *
+reply_in_reply_to(ENVELOPE *env)
+{
+ return((env && env->message_id) ? cpystr(env->message_id) : NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Generate a unique message id string.
+
+ Args: ps -- The usual pine structure
+
+ Result: Alloc'd unique string is returned
+
+Uniqueness is gaurenteed by using the host name, process id, date to the
+second and a single unique character
+*----------------------------------------------------------------------*/
+char *
+generate_message_id(void)
+{
+ static short osec = 0, cnt = 0;
+ char idbuf[128];
+ char *id;
+ time_t now;
+ struct tm *now_x;
+ char *hostpart = NULL;
+
+ now = time((time_t *)0);
+ now_x = localtime(&now);
+
+ if(now_x->tm_sec == osec)
+ cnt++;
+ else{
+ cnt = 0;
+ osec = now_x->tm_sec;
+ }
+
+ hostpart = F_ON(F_ROT13_MESSAGE_ID, ps_global)
+ ? rot13(ps_global->hostname)
+ : cpystr(ps_global->hostname);
+
+ if(!hostpart)
+ hostpart = cpystr("huh");
+
+ snprintf(idbuf, sizeof(idbuf), "<alpine.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
+ SYSTYPE, ALPINE_VERSION, (now_x->tm_year) % 100, now_x->tm_mon + 1,
+ now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec,
+ cnt, getpid(), hostpart);
+ idbuf[sizeof(idbuf)-1] = '\0';
+
+ id = cpystr(idbuf);
+
+ if(hostpart)
+ fs_give((void **) &hostpart);
+
+ return(id);
+}
+
+
+char *
+generate_user_agent(void)
+{
+ char buf[128];
+ char rev[128];
+
+ if(F_ON(F_QUELL_USERAGENT, ps_global))
+ return(NULL);
+
+ snprintf(buf, sizeof(buf),
+ "%sAlpine %s (%s %s)",
+ (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
+ ALPINE_VERSION, SYSTYPE,
+ get_alpine_revision_string(rev, sizeof(rev)));
+
+ return(cpystr(buf));
+}
+
+
+char *
+rot13(char *src)
+{
+ char byte, cap, *p, *ret = NULL;
+
+ if(src && *src){
+ ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
+ p = ret;
+ while((byte = *src++) != '\0'){
+ cap = byte & 32;
+ byte &= ~cap;
+ *p++ = ((byte >= 'A') && (byte <= 'Z')
+ ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
+ }
+
+ *p = '\0';
+ }
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the first true address pointer (modulo group syntax allowance)
+
+ Args: addr -- Address list
+
+ Result: First real address pointer, or NULL
+ ----------------------------------------------------------------------*/
+ADDRESS *
+first_addr(struct mail_address *addr)
+{
+ while(addr && !addr->host)
+ addr = addr->next;
+
+ return(addr);
+}
+
+
+/*----------------------------------------------------------------------
+ lit -- this is the source
+ prenewlines -- prefix the file contents with this many newlines
+ postnewlines -- postfix the file contents with this many newlines
+ is_sig -- this is a signature (not a template)
+ decode_constants -- change C-style constants into their values
+ ----*/
+char *
+get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
+{
+ char *sig = NULL;
+
+ /*
+ * Should make this smart enough not to do the copying and double
+ * allocation of space.
+ */
+ if(lit){
+ char *tmplit = NULL, *p, *q, *d, save;
+ size_t len;
+
+ if(decode_constants){
+ tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
+ tmplit[0] = '\0';
+ cstring_to_string(lit, tmplit);
+ }
+ else
+ tmplit = cpystr(lit);
+
+ len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
+ sig = (char *) fs_get((len+1) * sizeof(char));
+ memset(sig, 0, len+1);
+ d = sig;
+ while(prenewlines--)
+ sstrncpy(&d, NEWLINE, len-(d-sig));
+
+ if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
+ !sigdashes_are_present(tmplit)){
+ sstrncpy(&d, SIGDASHES, len-(d-sig));
+ sstrncpy(&d, NEWLINE, len-(d-sig));
+ }
+
+ sig[len] = '\0';
+
+ p = tmplit;
+ while(*p){
+ /* get a line */
+ q = strpbrk(p, "\n\r");
+ if(q){
+ save = *q;
+ *q = '\0';
+ }
+
+ /*
+ * Strip trailing space if we are doing a signature and
+ * this line is not sigdashes.
+ */
+ if(is_sig && strcmp(p, SIGDASHES))
+ removing_trailing_white_space(p);
+
+ while((d-sig) <= len && (*d = *p++) != '\0')
+ d++;
+
+ if(q){
+ if((d-sig) <= len)
+ *d++ = save;
+
+ p = q+1;
+ }
+ else
+ break;
+ }
+
+ while(postnewlines--)
+ sstrncpy(&d, NEWLINE, len-(d-sig));
+
+ sig[len] = '\0';
+
+ if((d-sig) <= len)
+ *d = '\0';
+
+ if(tmplit)
+ fs_give((void **) &tmplit);
+ }
+
+ return(sig);
+}
+
+
+int
+sigdashes_are_present(char *sig)
+{
+ char *p;
+
+ p = srchstr(sig, SIGDASHES);
+ while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
+ (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
+ p = srchstr(p+1, SIGDASHES);
+
+ return(p ? 1 : 0);
+}
+
+
+/*----------------------------------------------------------------------
+ Acquire the pinerc defined signature file pathname
+
+ ----*/
+char *
+signature_path(char *sname, char *sbuf, size_t len)
+{
+ *sbuf = '\0';
+ if(sname && *sname){
+ size_t spl = strlen(sname);
+ if(IS_REMOTE(sname)){
+ if(spl < len - 1)
+ strncpy(sbuf, sname, len-1);
+ }
+ else if(is_absolute_path(sname)){
+ strncpy(sbuf, sname, len-1);
+ sbuf[len-1] = '\0';
+ fnexpand(sbuf, len);
+ }
+ else if(ps_global->VAR_OPER_DIR){
+ if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
+ build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
+ }
+ else{
+ char *lc = last_cmpnt(ps_global->pinerc);
+
+ sbuf[0] = '\0';
+ if(lc != NULL){
+ strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
+ sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
+ }
+
+ strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
+ sbuf[len-1] = '\0';
+ }
+ }
+
+ return(*sbuf ? sbuf : NULL);
+}
+
+
+char *
+simple_read_remote_file(char *name, char *subtype)
+{
+ int try_cache;
+ REMDATA_S *rd;
+ char *file = NULL;
+
+
+ dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
+
+ /*
+ * We could parse the name here to find what type it is. So far we
+ * only have type RemImap.
+ */
+ rd = rd_create_remote(RemImap, name, subtype,
+ NULL, _("Error: "), _("Can't fetch remote configuration."));
+ if(!rd)
+ goto bail_out;
+
+ try_cache = rd_read_metadata(rd);
+
+ if(rd->access == MaybeRorW){
+ if(rd->read_status == 'R')
+ rd->access = ReadOnly;
+ else
+ rd->access = ReadWrite;
+ }
+
+ if(rd->access != NoExists){
+
+ rd_check_remvalid(rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(rd->read_status == 'R'){
+ /*
+ * We go to this trouble since readonly sigfiles
+ * are likely a mistake. They are usually supposed to be
+ * readwrite so we open it and check if it's been fixed.
+ */
+ rd_check_readonly_access(rd);
+ if(rd->read_status == 'W'){
+ rd->access = ReadWrite;
+ rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ rd->access = ReadOnly;
+ }
+
+ if(rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(rd) != 0){
+
+ dprint((1,
+ "simple_read_remote_file: rd_update_local failed\n"));
+ /*
+ * Don't give up altogether. We still may be
+ * able to use a cached copy.
+ */
+ }
+ else{
+ dprint((7,
+ "%s: copied remote to local (%ld)\n",
+ rd->rn ? rd->rn : "?", (long)rd->last_use));
+ }
+ }
+
+ if(rd->access == ReadWrite)
+ rd->flags |= DO_REMTRIM;
+ }
+
+ /* If we couldn't get to remote folder, try using the cached copy */
+ if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
+ if(try_cache){
+ rd->access = ReadOnly;
+ rd->flags |= USE_OLD_CACHE;
+ q_status_message(SM_ORDER, 3, 4,
+ "Can't contact remote server, using cached copy");
+ dprint((2,
+ "Can't open remote file %s, using local cached copy %s readonly\n",
+ rd->rn ? rd->rn : "?",
+ rd->lf ? rd->lf : "?"));
+ }
+ else{
+ rd->flags &= ~DO_REMTRIM;
+ goto bail_out;
+ }
+ }
+
+ file = read_file(rd->lf, READ_FROM_LOCALE);
+
+bail_out:
+ if(rd)
+ rd_close_remdata(&rd);
+
+ return(file);
+}
+
+
+/*----------------------------------------------------------------------
+ Build the body for the multipart/alternative part
+
+ Args:
+
+ Result:
+
+ ----------------------------------------------------------------------*/
+BODY *
+forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
+ long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
+{
+ BODY *body = NULL;
+ PART *part = NULL, *bestpart = NULL;
+ char tmp_buf[256];
+ char *new_charset = NULL;
+ int partnum, bestpartnum;
+
+ if(ps_global->force_prefer_plain
+ || (!ps_global->force_no_prefer_plain
+ && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
+ for(part = orig_body->nested.part, partnum = 1;
+ part;
+ part = part->next, partnum++)
+ if((!part->body.type || part->body.type == TYPETEXT)
+ && (!part->body.subtype
+ || !strucmp(part->body.subtype, "plain")))
+ break;
+ }
+
+ /*
+ * Else choose last alternative among plain or html parts.
+ * Perhaps we should really be using mime_show() to make this
+ * potentially more general than just plain or html.
+ */
+ if(!part){
+ for(part = orig_body->nested.part, partnum = 1;
+ part;
+ part = part->next, partnum++){
+ if((!part->body.type || part->body.type == TYPETEXT)
+ && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
+ ||
+ (part->body.subtype && !strucmp(part->body.subtype, "html")))){
+ bestpart = part;
+ bestpartnum = partnum;
+ }
+ }
+
+ part = bestpart;
+ partnum = bestpartnum;
+ }
+
+ /*
+ * IF something's interesting insert it
+ * AND forget the rest of the multipart
+ */
+ if(part){
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+
+ /* record character set, flowing, etc */
+ body->parameter = copy_parameters(part->body.parameter);
+ body->size.bytes = part->body.size.bytes;
+
+ if(!(flags & FWD_ANON)){
+ forward_delimiter(pc);
+ reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
+ }
+
+ snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
+ sizeof(tmp_buf)/2, sect_prefix ? sect_prefix : "",
+ sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
+ partnum);
+ tmp_buf[sizeof(tmp_buf)-1] = '\0';
+ get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
+ NULL, &new_charset, GBPT_NONE);
+
+ /*
+ * get_body_part_text translated the data to a new charset.
+ * We need to record that fact in body.
+ */
+ if(new_charset)
+ set_parameter(&body->parameter, "charset", new_charset);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "No suitable part found. Forwarding as attachment");
+
+ return(body);
+}
+
+
+void
+reply_append_addr(struct mail_address **dest, struct mail_address *src)
+{
+ for( ; *dest; dest = &(*dest)->next)
+ ;
+
+ *dest = src;
+}
diff --git a/pith/reply.h b/pith/reply.h
new file mode 100644
index 00000000..a80257c8
--- /dev/null
+++ b/pith/reply.h
@@ -0,0 +1,106 @@
+/*
+ * $Id: reply.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_REPLY_INCLUDED
+#define PITH_REPLY_INCLUDED
+
+
+#define ALLOWED_SUBTYPE(st) (!strucmp((st), "plain") \
+ || !strucmp((st), "html") \
+ || !strucmp((st), "enriched") \
+ || !strucmp((st), "richtext"))
+
+/* flags for get_body_part_text */
+#define GBPT_NONE 0x00
+#define GBPT_PEEK 0x01
+#define GBPT_NOINTR 0x02
+#define GBPT_DELQUOTES 0x04
+#define GBPT_HTML_OK 0x08
+
+
+/* flags for reply_cp_addr */
+#define RCA_NONE 0x00
+#define RCA_NOT_US 0x01 /* copy addrs that aren't us */
+#define RCA_ONLY_US 0x02 /* copy only addrs that are us */
+#define RCA_ALL 0x04 /* copy all addrs, including us */
+
+
+#include "../pith/repltype.h"
+#include "../pith/filttype.h"
+#include "../pith/indxtype.h"
+#include "../pith/pattern.h"
+#include "../pith/state.h"
+#include "../pith/addrstring.h"
+
+
+/* exported protoypes */
+int reply_harvest(struct pine *, long, char *, ENVELOPE *, ADDRESS **,
+ ADDRESS **, ADDRESS **, ADDRESS **,int *);
+ADDRESS *reply_cp_addr(struct pine *, long, char *, char *,
+ ADDRESS *, ADDRESS *, ADDRESS *, int);
+void reply_append_addr(ADDRESS **, ADDRESS *);
+ACTION_S *set_role_from_msg(struct pine *, long, long, char *);
+void reply_seed(struct pine *, ENVELOPE *, ENVELOPE *, ADDRESS *, ADDRESS *,
+ ADDRESS *, ADDRESS *, char **, int, char **);
+int addr_lists_same(ADDRESS *, ADDRESS *);
+int addr_in_env(ADDRESS *, ENVELOPE *);
+void reply_fish_personal(ENVELOPE *, ENVELOPE *);
+char *reply_build_refs(ENVELOPE *);
+ADDRESS *reply_resent(struct pine *, long, char *);
+char *reply_subject(char *, char *, size_t);
+char *reply_quote_initials(char *);
+char *reply_quote_str(ENVELOPE *);
+int reply_quote_str_contains_tokens(void);
+BODY *reply_body(MAILSTREAM *, ENVELOPE *, BODY *, long, char *, void *,
+ char *, int, ACTION_S *, int, REDRAFT_POS_S **);
+int reply_body_text(BODY *, BODY **);
+char *reply_signature(ACTION_S *, ENVELOPE *, REDRAFT_POS_S **, int *);
+void get_addr_data(ENVELOPE *, IndexColType, char *, size_t);
+void get_news_data(ENVELOPE *, IndexColType, char *, size_t);
+char *get_reply_data(ENVELOPE *, ACTION_S *, IndexColType, char *, size_t);
+void reply_delimiter(ENVELOPE *, ACTION_S *, gf_io_t);
+void free_redraft_pos(REDRAFT_POS_S **);
+int forward_mime_msg(MAILSTREAM *, long, char *, ENVELOPE *, PART **, void *);
+void forward_delimiter(gf_io_t);
+void reply_forward_header(MAILSTREAM *, long, char *, ENVELOPE *, gf_io_t, char *);
+char *forward_subject(ENVELOPE *, int);
+BODY *forward_body(MAILSTREAM *, ENVELOPE *, BODY *, long, char *, void *, int);
+char *bounce_msg_body(MAILSTREAM *, long int, char *, char **, char *, ENVELOPE **, BODY **, int *);
+int get_body_part_text(MAILSTREAM *, BODY *, long, char *, long,
+ gf_io_t, char *, char **, unsigned);
+int quote_fold(long, char *, LT_INS_S **, void *);
+int twsp_strip(long, char *, LT_INS_S **, void *);
+int post_quote_space(long, char *, LT_INS_S **, void *);
+int sigdash_strip(long, char *, LT_INS_S **, void *);
+char *body_partno(MAILSTREAM *, long, BODY *);
+char *partno(BODY *, BODY *);
+int fetch_contents(MAILSTREAM *, long, char *, BODY *);
+BODY *copy_body(BODY *, BODY *);
+PARAMETER *copy_parameters(PARAMETER *);
+ENVELOPE *copy_envelope(ENVELOPE *);
+char *reply_in_reply_to(ENVELOPE *);
+char *generate_message_id(void);
+char *generate_user_agent(void);
+char *rot13(char *);
+ADDRESS *first_addr(ADDRESS *);
+char *get_signature_lit(char *, int, int, int, int);
+int sigdashes_are_present(char *);
+char *signature_path(char *, char *, size_t);
+char *simple_read_remote_file(char *, char *);
+BODY *forward_multi_alt(MAILSTREAM *, ENVELOPE *, BODY *, long, char *, void *, gf_io_t, int);
+
+
+#endif /* PITH_REPLY_INCLUDED */
diff --git a/pith/rfc2231.c b/pith/rfc2231.c
new file mode 100644
index 00000000..1aa0752f
--- /dev/null
+++ b/pith/rfc2231.c
@@ -0,0 +1,313 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: rfc2231.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/rfc2231.h"
+#include "../pith/mimedesc.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/status.h"
+#include "../pith/send.h"
+#include "../pith/string.h"
+
+
+/*
+ * * * * * * * * * RFC 2231 support routines * * * * * * * *
+ */
+
+
+/* Useful def's */
+#define RFC2231_MAX 64
+
+
+char *
+rfc2231_get_param(PARAMETER *parms, char *name,
+ char **charset, char **lang)
+{
+ char *buf, *p;
+ int decode = 0, name_len, i;
+ unsigned n;
+
+ name_len = strlen(name);
+ for(; parms ; parms = parms->next)
+ if(!struncmp(name, parms->attribute, name_len)){
+ if(parms->attribute[name_len] == '*'){
+ for(p = &parms->attribute[name_len + 1], n = 0; *(p+n); n++)
+ ;
+
+ decode = *(p + n - 1) == '*';
+
+ if(isdigit((unsigned char) *p)){
+ char *pieces[RFC2231_MAX];
+ int count = 0, len;
+
+ memset(pieces, 0, RFC2231_MAX * sizeof(char *));
+
+ while(parms){
+ n = 0;
+ do
+ n = (n * 10) + (*p - '0');
+ while(isdigit(*++p) && n < RFC2231_MAX);
+
+ if(n < RFC2231_MAX){
+ pieces[n] = parms->value;
+ if(n > count)
+ count = n;
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ "Invalid attachment parameter segment number: %.25s",
+ name);
+ return(NULL); /* Too many segments! */
+ }
+
+ while((parms = parms->next) != NULL)
+ if(!struncmp(name, parms->attribute, name_len)){
+ if(*(p = &parms->attribute[name_len]) == '*'
+ && isdigit((unsigned char) *++p))
+ break;
+ else
+ return(NULL); /* missing segment no.! */
+ }
+ }
+
+ for(i = len = 0; i <= count; i++)
+ if(pieces[i])
+ len += strlen(pieces[i]);
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ "Missing attachment parameter sequence: %.25s",
+ name);
+
+ return(NULL); /* hole! */
+ }
+
+ buf = (char *) fs_get((len + 1) * sizeof(char));
+
+ for(i = len = 0; i <= count; i++){
+ if((n = *(p = pieces[i]) == '\"') != 0) /* quoted? */
+ p++;
+
+ while(*p && !(n && *p == '\"' && !*(p+1)))
+ buf[len++] = *p++;
+ }
+
+ buf[len] = '\0';
+ }
+ else
+ buf = cpystr(parms->value);
+
+ /* Do any RFC 2231 decoding? */
+ if(decode){
+ char *converted = NULL, cs[1000];
+
+ cs[0] = '\0';
+ n = 0;
+
+ if((p = strchr(buf, '\'')) != NULL){
+ n = (p - buf) + 1;
+ *p = '\0';
+ strncpy(cs, buf, sizeof(cs));
+ cs[sizeof(cs)-1] = '\0';
+ *p = '\'';
+ if(charset)
+ *charset = cpystr(cs);
+
+ if((p = strchr(&buf[n], '\'')) != NULL){
+ n = (p - buf) + 1;
+ if(lang){
+ *p = '\0';
+ *lang = cpystr(p);
+ *p = '\'';
+ }
+ }
+ }
+
+ if(n){
+ /* Suck out the charset & lang while decoding hex */
+ p = &buf[n];
+ for(i = 0; (buf[i] = *p) != '\0'; i++)
+ if(*p++ == '%' && isxpair(p)){
+ buf[i] = X2C(p);
+ p += 2;
+ }
+ }
+ else
+ fs_give((void **) &buf); /* problems!?! */
+
+ /*
+ * Callers will expect the returned value to be UTF-8
+ * text, so we may need to translate here.
+ */
+ if(buf)
+ converted = convert_to_utf8(buf, cs, 0);
+
+ if(converted && converted != buf){
+ fs_give((void **) &buf);
+ buf = converted;
+ }
+ }
+
+ return(buf);
+ }
+ else
+ return(cpystr(parms->value ? parms->value : ""));
+ }
+
+ return(NULL);
+}
+
+
+int
+rfc2231_output(STORE_S *so, char *attrib, char *value, char *specials, char *charset)
+{
+ int i, line = 0, encode = 0, quote = 0;
+
+ /*
+ * scan for hibit first since encoding clue has to
+ * come on first line if any parms are broken up...
+ */
+ for(i = 0; value && value[i]; i++)
+ if(value[i] & 0x80){
+ encode++;
+ break;
+ }
+
+ for(i = 0; ; i++){
+ if(!(value && value[i]) || i > 80){ /* flush! */
+ if((line++ && !so_puts(so, ";\015\012 "))
+ || !so_puts(so, attrib))
+ return(0);
+
+ if(value){
+ if(((value[i] || line > 1) /* more lines or already lines */
+ && !(so_writec('*', so)
+ && so_puts(so, int2string(line - 1))))
+ || (encode && !so_writec('*', so))
+ || !so_writec('=', so)
+ || (quote && !so_writec('\"', so))
+ || ((line == 1 && encode)
+ && !(so_puts(so, charset ? charset : UNKNOWN_CHARSET)
+ && so_puts(so, "''"))))
+ return(0);
+
+ while(i--){
+ if(*value & 0x80){
+ char tmp[3], *p;
+
+ p = tmp;
+ C2XPAIR(*value, p);
+ *p = '\0';
+ if(!(so_writec('%', so) && so_puts(so, tmp)))
+ return(0);
+ }
+ else if(((*value == '\\' || *value == '\"')
+ && !so_writec('\\', so))
+ || !so_writec(*value, so))
+ return(0);
+
+ value++;
+ }
+
+ if(quote && !so_writec('\"', so))
+ return(0);
+
+ if(*value) /* more? */
+ i = quote = 0; /* reset! */
+ else
+ return(1); /* done! */
+ }
+ else
+ return(1);
+ }
+
+ if(!quote && strchr(specials, value[i]))
+ quote++;
+ }
+}
+
+
+PARMLIST_S *
+rfc2231_newparmlist(PARAMETER *params)
+{
+ PARMLIST_S *p = NULL;
+
+ if(params){
+ p = (PARMLIST_S *) fs_get(sizeof(PARMLIST_S));
+ memset(p, 0, sizeof(PARMLIST_S));
+ p->list = params;
+ }
+
+ return(p);
+}
+
+
+void
+rfc2231_free_parmlist(PARMLIST_S **p)
+{
+ if(*p){
+ if((*p)->value)
+ fs_give((void **) &(*p)->value);
+
+ mail_free_body_parameter(&(*p)->seen);
+ fs_give((void **) p);
+ }
+}
+
+
+int
+rfc2231_list_params(PARMLIST_S *plist)
+{
+ PARAMETER *pp, **ppp;
+ int i;
+
+ if(plist->value)
+ fs_give((void **) &plist->value);
+
+ for(pp = plist->list; pp; pp = pp->next){
+ /* get a name */
+ for(i = 0; i < 32; i++)
+ if(!(plist->attrib[i] = pp->attribute[i]) || pp->attribute[i] == '*'){
+ plist->attrib[i] = '\0';
+
+ for(ppp = &plist->seen;
+ *ppp && strucmp((*ppp)->attribute, plist->attrib);
+ ppp = &(*ppp)->next)
+ ;
+
+ if(!*ppp){
+ plist->list = pp->next;
+ *ppp = mail_newbody_parameter(); /* add to seen list */
+ (*ppp)->attribute = cpystr(plist->attrib);
+ plist->value = parameter_val(pp,plist->attrib);
+ return(TRUE);
+ }
+
+ break;
+ }
+
+ if(i >= 32)
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ "Overly long attachment parameter ignored: %.25s...",
+ pp->attribute);
+ }
+
+
+ return(FALSE);
+}
diff --git a/pith/rfc2231.h b/pith/rfc2231.h
new file mode 100644
index 00000000..12915f39
--- /dev/null
+++ b/pith/rfc2231.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: rfc2231.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_RFC2231_INCLUDED
+#define PITH_RFC2231_INCLUDED
+
+
+#include "../pith/atttype.h"
+#include "../pith/store.h"
+
+
+/* exported protoypes */
+char *rfc2231_get_param(PARAMETER *, char *, char **, char **);
+int rfc2231_output(STORE_S *, char *, char *, char *, char *);
+PARMLIST_S *rfc2231_newparmlist(PARAMETER *);
+void rfc2231_free_parmlist(PARMLIST_S **);
+int rfc2231_list_params(PARMLIST_S *);
+
+
+#endif /* PITH_RFC2231_INCLUDED */
diff --git a/pith/save.c b/pith/save.c
new file mode 100644
index 00000000..957e163b
--- /dev/null
+++ b/pith/save.c
@@ -0,0 +1,1777 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: save.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/save.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/mimedesc.h"
+#include "../pith/filter.h"
+#include "../pith/context.h"
+#include "../pith/folder.h"
+#include "../pith/copyaddr.h"
+#include "../pith/mailview.h"
+#include "../pith/mailcmd.h"
+#include "../pith/bldaddr.h"
+#include "../pith/flag.h"
+#include "../pith/status.h"
+#include "../pith/ablookup.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/reply.h"
+#include "../pith/sequence.h"
+#include "../pith/stream.h"
+#include "../pith/options.h"
+
+
+/*
+ * Internal prototypes
+ */
+int save_ex_replace_body(char *, unsigned long *,BODY *,gf_io_t);
+int save_ex_output_body(MAILSTREAM *, long, char *, BODY *, unsigned long *, gf_io_t);
+int save_ex_mask_types(char *, unsigned long *, gf_io_t);
+int save_ex_explain_body(BODY *, unsigned long *, gf_io_t);
+int save_ex_explain_parts(BODY *, int, unsigned long *, gf_io_t);
+int save_ex_output_line(char *, unsigned long *, gf_io_t);
+
+
+/*
+ * pith hook
+ */
+int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
+int (*pith_opt_save_size_changed_prompt)(long, int);
+
+
+
+/*----------------------------------------------------------------------
+ save_get_default - return default folder name for saving
+ ----*/
+char *
+save_get_default(struct pine *state, ENVELOPE *e, long int rawno,
+ char *section, CONTEXT_S **cntxt)
+{
+ int context_was_set;
+
+ if(!cntxt)
+ return("");
+
+ context_was_set = ((*cntxt) != NULL);
+
+ /* start with the default save context */
+ if(!(*cntxt)
+ && ((*cntxt) = default_save_context(state->context_list)) == NULL)
+ (*cntxt) = state->context_list;
+
+ if(!e || ps_global->save_msg_rule == SAV_RULE_LAST
+ || ps_global->save_msg_rule == SAV_RULE_DEFLT){
+ if(ps_global->save_msg_rule == SAV_RULE_LAST && ps_global->last_save_context){
+ if(!context_was_set)
+ (*cntxt) = ps_global->last_save_context;
+ }
+ else{
+ strncpy(ps_global->last_save_folder,
+ ps_global->VAR_DEFAULT_SAVE_FOLDER,
+ sizeof(ps_global->last_save_folder)-1);
+ ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
+
+ /*
+ * If the user entered "inbox" as their default save folder it is very
+ * likely they meant the real inbox, not the inbox in the primary collection.
+ */
+ if(!context_was_set
+ && !strucmp(ps_global->inbox_name, ps_global->last_save_folder))
+ (*cntxt) = state->context_list;
+ }
+ }
+ else{
+ save_get_fldr_from_env(ps_global->last_save_folder,
+ sizeof(ps_global->last_save_folder),
+ e, state, rawno, section);
+ /* somebody expunged current message */
+ if(sp_expunge_count(ps_global->mail_stream))
+ return(NULL);
+ }
+
+ return(ps_global->last_save_folder);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Grope through envelope to find default folder name to save to
+
+ Args: fbuf -- Buffer to return result in
+ nfbuf -- Size of fbuf
+ e -- The envelope to look in
+ state -- Usual pine state
+ rawmsgno -- Raw c-client sequence number of message
+ section -- Mime section of header data (for message/rfc822)
+
+ Result: The appropriate default folder name is copied into fbuf.
+ ----*/
+void
+save_get_fldr_from_env(char *fbuf, int nfbuf, ENVELOPE *e, struct pine *state,
+ long int rawmsgno, char *section)
+{
+ char fakedomain[2];
+ ADDRESS *tmp_adr = NULL;
+ char buf[MAX(MAXFOLDER,MAX_NICKNAME) + 1];
+ char *bufp;
+ char *folder_name = NULL;
+ static char botch[] = "programmer botch, unknown message save rule";
+ unsigned save_msg_rule;
+
+ if(!e)
+ return;
+
+ /* copy this because we might change it below */
+ save_msg_rule = state->save_msg_rule;
+
+ /* first get the relevant address to base the folder name on */
+ switch(save_msg_rule){
+ case SAV_RULE_FROM:
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_RN_FROM:
+ case SAV_RULE_RN_FROM_DEF:
+ tmp_adr = e->from ? copyaddr(e->from)
+ : e->sender ? copyaddr(e->sender) : NULL;
+ break;
+
+ case SAV_RULE_SENDER:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_RN_SENDER:
+ case SAV_RULE_RN_SENDER_DEF:
+ tmp_adr = e->sender ? copyaddr(e->sender)
+ : e->from ? copyaddr(e->from) : NULL;
+ break;
+
+ case SAV_RULE_REPLYTO:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_RN_REPLYTO:
+ case SAV_RULE_RN_REPLYTO_DEF:
+ tmp_adr = e->reply_to ? copyaddr(e->reply_to)
+ : e->from ? copyaddr(e->from)
+ : e->sender ? copyaddr(e->sender) : NULL;
+ break;
+
+ case SAV_RULE_RECIP:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_NICK_RECIP_DEF:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_FCC_RECIP_DEF:
+ case SAV_RULE_RN_RECIP:
+ case SAV_RULE_RN_RECIP_DEF:
+ /* news */
+ if(state->mail_stream && IS_NEWS(state->mail_stream)){
+ char *tmp_a_string, *ng_name;
+
+ fakedomain[0] = '@';
+ fakedomain[1] = '\0';
+
+ /* find the news group name */
+ if((ng_name = strstr(state->mail_stream->mailbox,"#news")) != NULL)
+ ng_name += 6;
+ else
+ ng_name = state->mail_stream->mailbox; /* shouldn't happen */
+
+ /* copy this string so rfc822_parse_adrlist can't blast it */
+ tmp_a_string = cpystr(ng_name);
+ /* make an adr */
+ rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
+ tmp_adr->host[0] = '\0';
+ }
+ /* not news */
+ else{
+ static char *fields[] = {"Resent-To", NULL};
+ char *extras, *values[sizeof(fields)/sizeof(fields[0])];
+
+ extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
+ section, fields);
+ if(extras){
+ long i;
+
+ memset(values, 0, sizeof(fields));
+ simple_header_parse(extras, fields, values);
+ fs_give((void **)&extras);
+
+ for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
+ if(values[i]){
+ if(tmp_adr) /* take last matching value */
+ mail_free_address(&tmp_adr);
+
+ /* build a temporary address list */
+ fakedomain[0] = '@';
+ fakedomain[1] = '\0';
+ rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
+ fs_give((void **)&values[i]);
+ }
+ }
+
+ if(!tmp_adr)
+ tmp_adr = e->to ? copyaddr(e->to) : NULL;
+ }
+
+ break;
+
+ default:
+ panic(botch);
+ break;
+ }
+
+ /* For that address, lookup the fcc or nickname from address book */
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_NICK_RECIP_DEF:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_FCC_RECIP_DEF:
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_NICK_RECIP_DEF:
+ bufp = get_nickname_from_addr(tmp_adr, buf, sizeof(buf));
+ break;
+
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_FCC_RECIP_DEF:
+ bufp = get_fcc_from_addr(tmp_adr, buf, sizeof(buf));
+ break;
+ }
+
+ if(bufp && *bufp){
+ istrncpy(fbuf, bufp, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+ else
+ /* fall back to non-nick/non-fcc version of rule */
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_FCC_FROM:
+ save_msg_rule = SAV_RULE_FROM;
+ break;
+
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_FCC_SENDER:
+ save_msg_rule = SAV_RULE_SENDER;
+ break;
+
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_FCC_REPLYTO:
+ save_msg_rule = SAV_RULE_REPLYTO;
+ break;
+
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_FCC_RECIP:
+ save_msg_rule = SAV_RULE_RECIP;
+ break;
+
+ default:
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ break;
+ }
+
+ break;
+ }
+
+ /* Realname */
+ switch(save_msg_rule){
+ case SAV_RULE_RN_FROM_DEF:
+ case SAV_RULE_RN_FROM:
+ case SAV_RULE_RN_SENDER_DEF:
+ case SAV_RULE_RN_SENDER:
+ case SAV_RULE_RN_RECIP_DEF:
+ case SAV_RULE_RN_RECIP:
+ case SAV_RULE_RN_REPLYTO_DEF:
+ case SAV_RULE_RN_REPLYTO:
+ /* Fish out the realname */
+ if(tmp_adr && tmp_adr->personal && tmp_adr->personal[0])
+ folder_name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, tmp_adr->personal);
+
+ if(folder_name && folder_name[0]){
+ istrncpy(fbuf, folder_name, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+ else{ /* fall back to other behaviors */
+ switch(save_msg_rule){
+ case SAV_RULE_RN_FROM:
+ save_msg_rule = SAV_RULE_FROM;
+ break;
+
+ case SAV_RULE_RN_SENDER:
+ save_msg_rule = SAV_RULE_SENDER;
+ break;
+
+ case SAV_RULE_RN_RECIP:
+ save_msg_rule = SAV_RULE_RECIP;
+ break;
+
+ case SAV_RULE_RN_REPLYTO:
+ save_msg_rule = SAV_RULE_REPLYTO;
+ break;
+
+ default:
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ break;
+ }
+ }
+
+ break;
+ }
+
+ /* get the username out of the mailbox for this address */
+ switch(save_msg_rule){
+ case SAV_RULE_FROM:
+ case SAV_RULE_SENDER:
+ case SAV_RULE_REPLYTO:
+ case SAV_RULE_RECIP:
+ /*
+ * Fish out the user's name from the mailbox portion of
+ * the address and put it in folder.
+ */
+ folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
+ ? tmp_adr->mailbox : NULL;
+ if(!get_uname(folder_name, fbuf, nfbuf)){
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+
+ break;
+ }
+
+ if(tmp_adr)
+ mail_free_address(&tmp_adr);
+}
+
+
+/*----------------------------------------------------------------------
+ Do the work of actually saving messages to a folder
+
+ Args: state -- pine state struct (for stream pointers)
+ stream -- source stream, which msgmap refers to
+ context -- context to interpret name in if not fully qualified
+ folder -- The folder to save the message in
+ msgmap -- message map of currently selected messages
+ flgs -- Possible bits are
+ SV_DELETE - delete after saving
+ SV_FOR_FILT - called from filtering function, not save
+ SV_FIX_DELS - remove Del mark before saving
+ SV_INBOXWOCNTXT - "inbox" is interpreted without context making
+ it the one-true inbox instead
+
+ Result: Returns number of messages saved
+
+ Note: There's a bit going on here; temporary clearing of deleted flags
+ since they are *not* preserved, picking or creating the stream for
+ copy or append, and dealing with errors...
+ We try to preserve user keywords by setting them in the destination.
+ ----*/
+long
+save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder,
+ MSGNO_S *msgmap, int flgs)
+{
+ int rv, rc, j, our_stream = 0, cancelled = 0;
+ int delete, filter, k, worry_about_keywords = 0;
+ char *save_folder, *seq, *flags = NULL, date[64], tmp[MAILTMPLEN];
+ long i, nmsgs, rawno;
+ size_t len;
+ STORE_S *so = NULL;
+ MAILSTREAM *save_stream = NULL;
+ MESSAGECACHE *mc;
+
+ delete = flgs & SV_DELETE;
+ filter = flgs & SV_FOR_FILT;
+
+ if(strucmp(folder, state->inbox_name) == 0 && flgs & SV_INBOXWOCNTXT){
+ save_folder = state->VAR_INBOX_PATH;
+ context = NULL;
+ }
+ else
+ save_folder = folder;
+
+ /*
+ * Because the COPY/APPEND command doesn't always create keywords when they
+ * aren't already defined in a mailbox, we need to ensure that the keywords
+ * exist in the destination (are defined and settable) before we do the copies.
+ * Here's what the code is doing
+ *
+ * If we have keywords set in the source messages
+ * Add a dummy message to destination mailbox
+ *
+ * for each keyword that is set in the set of messages we're saving
+ * set the keyword in that message (thus creating it)
+ *
+ * remember deleted messages
+ * undelete them
+ * delete dummy message
+ * expunge
+ * delete remembered messages
+ *
+ * After that the assumption is that the keywords will be saved by a
+ * COPY command. We need to set the flags string ourself for appends.
+ */
+
+ /* are any keywords set in the source messages? */
+ for(i = mn_first_cur(msgmap); !worry_about_keywords && i > 0L; i = mn_next_cur(msgmap)){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && mc->user_flags)
+ worry_about_keywords++;
+ }
+
+ if(worry_about_keywords){
+ MAILSTREAM *dstn_stream = NULL;
+ int already_open = 0;
+ int we_blocked_reuse = 0;
+
+ /*
+ * Possible problem created by our stream re-use
+ * strategy. If we are going to open a new stream
+ * here, we want to be sure not to re-use the
+ * stream we are saving _from_, so take it out of the
+ * re-use pool before we call open.
+ */
+ if(sp_flagged(stream, SP_USEPOOL)){
+ we_blocked_reuse++;
+ sp_unflag(stream, SP_USEPOOL);
+ }
+
+ /* see if there is a stream open already */
+ if(!dstn_stream){
+ dstn_stream = context_already_open_stream(context,
+ save_folder,
+ AOS_RW_ONLY);
+ already_open = dstn_stream ? 1 : 0;
+ }
+
+ if(!dstn_stream)
+ dstn_stream = context_open(context, NULL,
+ save_folder,
+ SP_USEPOOL | SP_TEMPUSE,
+ NULL);
+
+ if(dstn_stream && dstn_stream->kwd_create){
+ imapuid_t dummy_uid = 0L;
+ long dummy_msgno, delete_count;
+ int expbits, set;
+ char *flags = NULL;
+ char *user_flag_name, *duser_flag_name;
+
+ /* find keywords that need to be defined */
+ for(k = 0; stream_to_user_flag_name(stream, k); k++){
+ user_flag_name = stream_to_user_flag_name(stream, k);
+ if(user_flag_name && user_flag_name[0]){
+ /* is this flag set in any of the save set? */
+ for(set = 0, i = mn_first_cur(msgmap);
+ !set && i > 0L;
+ i = mn_next_cur(msgmap)){
+ rawno = mn_m2raw(msgmap, i);
+ if(user_flag_is_set(stream, rawno, user_flag_name))
+ set++;
+ }
+
+ if(set){
+ /*
+ * The flag may already be defined in this
+ * mailbox. Check for that first.
+ */
+ for(j = 0; stream_to_user_flag_name(dstn_stream, j); j++){
+ duser_flag_name = stream_to_user_flag_name(dstn_stream, j);
+ if(duser_flag_name && duser_flag_name[0]
+ && !strucmp(duser_flag_name, user_flag_name)){
+ set = 0;
+ break;
+ }
+ }
+ }
+
+ if(set){
+ if(flags == NULL){
+ len = strlen(user_flag_name) + 1;
+ flags = (char *) fs_get((len+1) * sizeof(char));
+ snprintf(flags, len+1, "%s ", user_flag_name);
+ }
+ else{
+ char *p;
+ size_t newlen;
+
+ newlen = strlen(user_flag_name) + 1;
+ len += newlen;
+ fs_resize((void **) &flags, (len+1) * sizeof(char));
+ p = flags + strlen(flags);
+ snprintf(p, newlen+1, "%s ", user_flag_name);
+ }
+ }
+ }
+ }
+
+ if(flags){
+ char *p;
+ size_t newlen;
+ STRING msg;
+ char dummymsg[1000];
+ char *id = NULL;
+ char *idused;
+ appenduid_t *au;
+
+ newlen = strlen("\\DELETED");
+ len += newlen;
+ fs_resize((void **) &flags, (len+1) * sizeof(char));
+ p = flags + strlen(flags);
+ snprintf(p, newlen+1, "%s", "\\DELETED");
+
+ id = generate_message_id();
+ idused = id ? id : "<xyz>";
+ snprintf(dummymsg, sizeof(dummymsg), "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\nMessage-ID: %s\r\n\r\ndummy\r\n", idused);
+
+ /*
+ * We need to get the uid of the message we are about to
+ * append so that we can delete it when we're done and
+ * so we don't affect other messages.
+ */
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream)){
+ au = mail_parameters(NIL, GET_APPENDUID, NIL);
+ mail_parameters(NIL, SET_APPENDUID, (void *) appenduid_cb);
+ }
+
+ INIT(&msg, mail_string, (void *) dummymsg, strlen(dummymsg));
+ if(pine_mail_append(dstn_stream, dstn_stream->mailbox, &msg)){
+
+ (void) pine_mail_ping(dstn_stream);
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
+ dummy_uid = get_last_append_uid();
+
+ if(dummy_uid == 0L){
+ dummy_msgno = get_msgno_by_msg_id(dstn_stream, idused,
+ sp_msgmap(dstn_stream));
+ if(dummy_msgno <= 0L || dummy_msgno > dstn_stream->nmsgs)
+ dummy_msgno = dstn_stream->nmsgs;
+
+ rawno = mn_m2raw(sp_msgmap(dstn_stream), dummy_msgno);
+ if(rawno > 0L && rawno <= dstn_stream->nmsgs)
+ dummy_uid = mail_uid(dstn_stream, rawno);
+
+ if(dummy_uid == 0L)
+ dummy_msgno = dstn_stream->nmsgs;
+ }
+
+ /*
+ * We need to remember which messages are deleted,
+ * undelete them, do the expunge, then delete them again.
+ */
+ delete_count = count_flagged(dstn_stream, F_DEL);
+ if(delete_count){
+ for(i = 1L; i <= dstn_stream->nmsgs; i++)
+ if(((mc = mail_elt(dstn_stream, i)) && mc->valid && mc->deleted)
+ || (mc && !mc->valid && mc->searched)){
+ mc->sequence = 1;
+ expbits = MSG_EX_DELETE;
+ msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
+ }
+ else if((mc = mail_elt(dstn_stream, i)) != NULL)
+ mc->sequence = 0;
+
+ if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
+ mail_flag(dstn_stream, seq, "\\DELETED", ST_SILENT);
+ fs_give((void **) &seq);
+ }
+ }
+
+ if(dummy_uid > 0L)
+ mail_flag(dstn_stream, ulong2string(dummy_uid),
+ flags, ST_SET | ST_UID | ST_SILENT);
+ else
+ mail_flag(dstn_stream, ulong2string(dummy_msgno),
+ flags, ST_SET | ST_SILENT);
+
+ ps_global->mm_log_error = 0;
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(dstn_stream);
+ ps_global->expunge_in_progress = 0;
+
+ if(delete_count){
+ for(i = 1L; i <= dstn_stream->nmsgs; i++)
+ if((mc = mail_elt(dstn_stream, i)) != NULL){
+ mc->sequence
+ = (msgno_exceptions(dstn_stream, i, "0", &expbits, FALSE)
+ && (expbits & MSG_EX_DELETE));
+
+ /*
+ * Remove the EX_DELETE bit in case we're still using
+ * this stream.
+ */
+ if(mc->sequence){
+ expbits &= ~MSG_EX_DELETE;
+ msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
+ }
+ }
+
+ if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
+ mail_flag(dstn_stream, seq, "\\DELETED", ST_SET | ST_SILENT);
+ fs_give((void **) &seq);
+ }
+ }
+ }
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
+ mail_parameters(NIL, SET_APPENDUID, (void *) au);
+
+ if(id)
+ fs_give((void **) &id);
+
+ fs_give((void **) &flags);
+ }
+ }
+
+ if(dstn_stream && !already_open)
+ pine_mail_close(dstn_stream);
+
+ if(we_blocked_reuse)
+ sp_set_flags(stream, sp_flags(stream) | SP_USEPOOL);
+ }
+
+ /*
+ * If any of the messages have exceptional attachment handling
+ * we have to fall thru below to do the APPEND by hand...
+ */
+ if(!msgno_any_deletedparts(stream, msgmap)){
+ int loc_to_loc = 0;
+
+ /*
+ * Compare the current stream (the save's source) and the stream
+ * the destination folder will need...
+ */
+ context_apply(tmp, context, save_folder, sizeof(tmp));
+
+ /*
+ * If we're going to be doing a cross-format copy we're better off
+ * using the else code below that knows how to do multi-append.
+ * The part in the if is leaving save_stream set to NULL in the
+ * case that the stream is local and the folder is local and they
+ * are different formats (like unix and tenex). That will cause us
+ * to fall thru to the APPEND case which is faster than using
+ * copy which will use our imap_proxycopy which doesn't know
+ * about multiappend.
+ */
+ loc_to_loc = stream && stream->dtb
+ && stream->dtb->flags & DR_LOCAL && !IS_REMOTE(tmp);
+ if(!loc_to_loc || (stream->dtb->valid && (*stream->dtb->valid)(tmp)))
+ save_stream = loc_to_loc ? stream
+ : context_same_stream(context, save_folder, stream);
+ }
+
+ /* if needed, this'll get set in mm_notify */
+ ps_global->try_to_create = 0;
+ rv = rc = 0;
+ nmsgs = 0L;
+
+ /*
+ * At this point, if we have a save_stream, then none of the messages
+ * being saved involve special handling that would require our use
+ * of mail_append, so go with mail_copy since in the IMAP case it
+ * means no data on the wire...
+ */
+ if(save_stream){
+ char *dseq = NULL, *oseq;
+
+ if((flgs & SV_FIX_DELS) &&
+ (dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,
+ 0, NULL, NULL)))
+ mail_flag(stream, dseq, "\\DELETED", 0L);
+
+ seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0, NULL, NULL);
+ if(!(flgs & SV_PRESERVE)
+ && (F_ON(F_AGG_SEQ_COPY, ps_global)
+ || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap)))){
+
+ /*
+ * currentf_sequence() above lit all the elt "sequence"
+ * bits of the interesting messages. Now, build a sequence
+ * that preserves sort order...
+ */
+ oseq = build_sequence(stream, msgmap, &nmsgs);
+ }
+ else{
+ oseq = NULL; /* no single sequence! */
+ nmsgs = 0L;
+ i = mn_first_cur(msgmap); /* set first to copy */
+ }
+
+ do{
+ while(!(rv = (int) context_copy(context, save_stream,
+ oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
+ save_folder))){
+ if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
+ break; /* c-client returned error? */
+
+ if((context && context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ cancelled = 1; /* user cancels */
+
+ break;
+ }
+ }
+
+ if(rv){ /* failure or finished? */
+ if(oseq) /* all done? */
+ break;
+ else
+ nmsgs++;
+ }
+ else{ /* failure! */
+ if(oseq)
+ nmsgs = 0L; /* nothing copy'd */
+
+ break;
+ }
+ }
+ while((i = mn_next_cur(msgmap)) > 0L);
+
+ if(rv && delete) /* delete those saved */
+ mail_flag(stream, seq, "\\DELETED", ST_SET);
+ else if(dseq) /* or restore previous state */
+ mail_flag(stream, dseq, "\\DELETED", ST_SET);
+
+ if(dseq) /* clean up */
+ fs_give((void **)&dseq);
+
+ if(oseq)
+ fs_give((void **)&oseq);
+
+ fs_give((void **)&seq);
+ }
+ else{
+ /*
+ * Special handling requires mail_append. See if we can
+ * re-use stream source messages are on...
+ */
+ save_stream = context_same_stream(context, save_folder, stream);
+
+ /*
+ * IF the destination's REMOTE, open a stream here so c-client
+ * doesn't have to open it for each aggregate save...
+ */
+ if(!save_stream){
+ if(context_apply(tmp, context, save_folder, sizeof(tmp))[0] == '{'
+ && (save_stream = context_open(context, NULL,
+ save_folder,
+ OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
+ NULL)))
+ our_stream = 1;
+ }
+
+ /*
+ * Allocate a storage object to temporarily store the message
+ * object in. Below it'll get mapped into a c-client STRING struct
+ * in preparation for handing off to context_append...
+ */
+ if(!(so = so_get(CharStar, NULL, WRITE_ACCESS))){
+ dprint((1, "Can't allocate store for save: %s\n",
+ error_description(errno)));
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem creating space for message text.");
+ }
+
+ /*
+ * get a sequence of invalid elt's so we can get their flags...
+ */
+ if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
+ mail_fetch_fast(stream, seq, 0L);
+ fs_give((void **) &seq);
+ }
+
+ /*
+ * If we're supposed set the deleted flag, clear the elt bit
+ * we'll use to build the sequence later...
+ */
+ if(delete)
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ nmsgs = 0L;
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ /*
+ * if there is more than one message, do multiappend.
+ * otherwise, we can use our already open stream.
+ */
+ if(!save_stream || !is_imap_stream(save_stream) ||
+ (LEVELMULTIAPPEND(save_stream) && mn_total_cur(msgmap) > 1)){
+ APPENDPACKAGE pkg;
+ STRING msg;
+
+ pkg.stream = stream;
+ /* tell save_fetch_append_cb whether or not to leave deleted flag */
+ pkg.flags = flgs & SV_FIX_DELS ? NULL : cpystr("\\DELETED");
+ pkg.date = date;
+ pkg.msg = &msg;
+ pkg.msgmap = msgmap;
+
+ if ((pkg.so = so) && ((pkg.msgno = mn_first_cur(msgmap)) > 0L)) {
+ so_truncate(so, 0L);
+
+ /*
+ * we've gotta make sure this is a stream that we've
+ * opened ourselves.
+ */
+ rc = 0;
+ while(!(rv = context_append_multiple(context,
+ our_stream ? save_stream
+ : NULL, save_folder,
+ save_fetch_append_cb,
+ &pkg,
+ stream))) {
+
+ if(rc++ || !ps_global->try_to_create)
+ break;
+ if((context && context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0;
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ cancelled = 1;
+ break;
+ }
+ }
+
+ if(pkg.flags)
+ fs_give((void **) &pkg.flags);
+
+ ps_global->noshow_error = 0;
+
+ if(rv){
+ /*
+ * Success! Count it, and if it's not already deleted and
+ * it's supposed to be, mark it to get deleted later...
+ */
+ for(i = mn_first_cur(msgmap); so && i > 0L;
+ i = mn_next_cur(msgmap)){
+ nmsgs++;
+ if(delete){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && !mc->deleted)
+ mc->sequence = 1; /* mark for later deletion */
+ }
+ }
+ }
+ }
+ else
+ cancelled = 1; /* No messages to append! */
+
+ if(sp_expunge_count(stream))
+ cancelled = 1; /* All bets are off! */
+ }
+ else
+ for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
+ int preserve_these_flags;
+
+ so_truncate(so, 0L);
+
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+
+ /* always preserve these flags */
+ preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
+ /* maybe preserve deleted flag */
+ preserve_these_flags |= flgs & SV_FIX_DELS ? 0 : F_DEL;
+ flags = flag_string(stream, rawno, preserve_these_flags);
+
+ if(mc && mc->day)
+ mail_date(date, mc);
+ else
+ *date = '\0';
+
+ rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
+ NULL, save_stream, save_folder, context,
+ mc ? mc->rfc822_size : 0L, flags, date, so);
+
+ if(flags)
+ fs_give((void **) &flags);
+
+ if(sp_expunge_count(stream))
+ rv = -1; /* All bets are off! */
+
+ if(rv == 1){
+ /*
+ * Success! Count it, and if it's not already deleted and
+ * it's supposed to be, mark it to get deleted later...
+ */
+ nmsgs++;
+ if(delete){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && !mc->deleted)
+ mc->sequence = 1; /* mark for later deletion */
+ }
+ }
+ else{
+ if(rv == -1)
+ cancelled = 1; /* else horrendous failure */
+
+ break;
+ }
+ }
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ if(our_stream)
+ pine_mail_close(save_stream);
+
+ if(so)
+ so_give(&so);
+
+ if(delete && (seq = build_sequence(stream, NULL, NULL))){
+ mail_flag(stream, seq, "\\DELETED", ST_SET);
+ fs_give((void **)&seq);
+ }
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if(!cancelled && nmsgs < mn_total_cur(msgmap)){
+ dprint((1, "FAILED save of msg %s (c-client sequence #)\n",
+ long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
+ if((mn_total_cur(msgmap) > 1L) && nmsgs != 0){
+ /* this shouldn't happen cause it should be all or nothing */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%ld of %ld messages saved before error occurred",
+ nmsgs, mn_total_cur(msgmap));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(mn_total_cur(msgmap) == 1){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s to folder \"%s\" FAILED",
+ filter ? "Filter" : "Save",
+ strsquish(tmp_20k_buf+500, 500, folder, 35));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s of %s messages to folder \"%s\" FAILED",
+ filter ? "Filter" : "Save", comatose(mn_total_cur(msgmap)),
+ strsquish(tmp_20k_buf+500, 500, folder, 35));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ }
+
+ return(nmsgs);
+}
+
+
+/* Append message callback
+ * Accepts: MAIL stream
+ * append data package
+ * pointer to return initial flags
+ * pointer to return message internal date
+ * pointer to return stringstruct of message or NIL to stop
+ * Returns: T if success (have message or stop), NIL if error
+ */
+
+long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags,
+ char **date, STRING **message)
+{
+ unsigned long size = 0;
+ APPENDPACKAGE *pkg = (APPENDPACKAGE *) data;
+ MESSAGECACHE *mc;
+ char *fetch;
+ int rc;
+ unsigned long raw, hlen, tlen, mlen;
+
+ if(pkg->so && (pkg->msgno > 0L)) {
+ raw = mn_m2raw(pkg->msgmap, pkg->msgno);
+ mc = (raw > 0L && pkg->stream && raw <= pkg->stream->nmsgs)
+ ? mail_elt(pkg->stream, raw) : NULL;
+ if(mc){
+ int preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
+
+ size = mc->rfc822_size;
+
+ /*
+ * If the caller wants us to preserve the state of the
+ * \DELETED flag then pkg->flags will be \DELETED, otherwise
+ * let it be undeleted. Fix from Eduardo Chappa.
+ */
+ if(pkg->flags){
+ if(strstr(pkg->flags,"\\DELETED"))
+ preserve_these_flags |= F_DEL;
+
+ /* not used anymore */
+ fs_give((void **) &pkg->flags);
+ }
+
+ pkg->flags = flag_string(pkg->stream, raw, preserve_these_flags);
+ }
+
+ if(mc && mc->day)
+ mail_date(pkg->date, mc);
+ else
+ *pkg->date = '\0';
+ if((fetch = mail_fetch_header(pkg->stream, raw, NULL, NULL, &hlen,
+ FT_PEEK)) != NULL){
+ if(!*pkg->date)
+ saved_date(pkg->date, fetch);
+ }
+ else
+ return(0); /* fetchtext writes error */
+
+ rc = MSG_EX_DELETE; /* "rc" overloaded */
+ if(msgno_exceptions(pkg->stream, raw, NULL, &rc, 0)){
+ char section[64];
+ int failure = 0;
+ BODY *body;
+ gf_io_t pc;
+
+ size = 0; /* all bets off, abort sanity test */
+ gf_set_so_writec(&pc, pkg->so);
+
+ section[0] = '\0';
+ if(!pine_mail_fetch_structure(pkg->stream, raw, &body, 0))
+ return(0);
+
+ if(msgno_part_deleted(pkg->stream, raw, "")){
+ tlen = 0;
+ failure = !save_ex_replace_body(fetch, &hlen, body, pc);
+ }
+ else
+ failure = !(so_nputs(pkg->so, fetch, (long) hlen)
+ && save_ex_output_body(pkg->stream, raw, section,
+ body, &tlen, pc));
+
+ gf_clear_so_writec(pkg->so);
+
+ if(failure)
+ return(0);
+
+ q_status_message(SM_ORDER, 3, 3,
+ /* TRANSLATORS: A warning to user that the message parts
+ they deleted will not be included in the copy they
+ are now saving to. */
+ _("NOTE: Deleted message parts NOT included in saved copy!"));
+
+ }
+ else{
+ if(!so_nputs(pkg->so, fetch, (long) hlen))
+ return(0);
+
+ fetch = pine_mail_fetch_text(pkg->stream, raw, NULL, &tlen, FT_PEEK);
+
+ if(!(fetch && so_nputs(pkg->so, fetch, tlen)))
+ return(0);
+ }
+
+ so_seek(pkg->so, 0L, 0); /* rewind just in case */
+ mlen = hlen + tlen;
+
+ if(size && mlen < size){
+ char buf[128];
+
+ if(sp_dead_stream(pkg->stream)){
+ snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
+ q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
+ dprint((1, "save_fetch_append_cb: %s\n", buf));
+ return(0);
+ }
+ else{
+ if(pith_opt_save_size_changed_prompt
+ && (*pith_opt_save_size_changed_prompt)(mn_raw2m(pkg->msgmap, raw), 0) == 'y'){
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append_cb: %s", buf));
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source msg # %ld may be saved incorrectly",
+ mn_raw2m(pkg->msgmap, raw));
+ q_status_message(SM_ORDER, 0, 3, buf);
+ }
+ else{
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: raw msg # %ld went from announced size %ld to actual size %ld",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append_cb: %s", buf));
+ return(0);
+ }
+ }
+ }
+
+ INIT(pkg->msg, mail_string, (void *)so_text(pkg->so), mlen);
+ *message = pkg->msg;
+ /* Next message */
+ pkg->msgno = mn_next_cur(pkg->msgmap);
+ }
+ else /* No more messages */
+ *message = NIL;
+
+ *flags = pkg->flags;
+ *date = (pkg->date && *pkg->date) ? pkg->date : NIL;
+ return LONGT; /* Return success */
+}
+
+
+/*----------------------------------------------------------------------
+ FETCH an rfc822 message header and body and APPEND to destination folder
+
+ Args:
+
+
+ Result:
+
+ ----*/
+int
+save_fetch_append(MAILSTREAM *stream, long int raw, char *sect,
+ MAILSTREAM *save_stream, char *save_folder, CONTEXT_S *context,
+ long unsigned int size, char *flags, char *date, STORE_S *so)
+{
+ int rc, rv, old_imap_server = 0;
+ long j;
+ char *fetch;
+ unsigned long hlen, tlen, mlen;
+ STRING msg;
+
+ if((fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)) != NULL){
+ /*
+ * If there's no date string, then caller found the
+ * MESSAGECACHE for this message element didn't already have it.
+ * So, parse the "internal date" by hand since fetchstructure
+ * hasn't been called yet for this particular message, and
+ * we don't want to call it now just to get the date since
+ * the full header has what we want. Likewise, don't even
+ * think about calling mail_fetchfast either since it also
+ * wants to load mc->rfc822_size (which we could actually
+ * use but...), which under some drivers is *very*
+ * expensive to acquire (can you say NNTP?)...
+ */
+ if(!*date)
+ saved_date(date, fetch);
+ }
+ else
+ return(0); /* fetchtext writes error */
+
+ rc = MSG_EX_DELETE; /* "rc" overloaded */
+ if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
+ char section[64];
+ int failure = 0;
+ BODY *body;
+ gf_io_t pc;
+
+ size = 0; /* all bets off, abort sanity test */
+ gf_set_so_writec(&pc, so);
+
+ if(sect && *sect){
+ snprintf(section, sizeof(section), "%s.1", sect);
+ if(!(body = mail_body(stream, raw, (unsigned char *) section)))
+ return(0);
+ }
+ else{
+ section[0] = '\0';
+ if(!pine_mail_fetch_structure(stream, raw, &body, 0))
+ return(0);
+ }
+
+ /*
+ * Walk the MIME structure looking for exceptional segments,
+ * writing them in the requested fashion.
+ *
+ * First, though, check for the easy case...
+ */
+ if(msgno_part_deleted(stream, raw, sect ? sect : "")){
+ tlen = 0;
+ failure = !save_ex_replace_body(fetch, &hlen, body, pc);
+ }
+ else{
+ /*
+ * Otherwise, roll up your sleeves and get to work...
+ * start by writing msg header and then the processed body
+ */
+ failure = !(so_nputs(so, fetch, (long) hlen)
+ && save_ex_output_body(stream, raw, section,
+ body, &tlen, pc));
+ }
+
+ gf_clear_so_writec(so);
+
+ if(failure)
+ return(0);
+
+ q_status_message(SM_ORDER, 3, 3,
+ _("NOTE: Deleted message parts NOT included in saved copy!"));
+
+ }
+ else{
+ /* First, write the header we just fetched... */
+ if(!so_nputs(so, fetch, (long) hlen))
+ return(0);
+
+ old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);
+
+ /* Second, go fetch the corresponding text... */
+ fetch = pine_mail_fetch_text(stream, raw, sect, &tlen,
+ !old_imap_server ? FT_PEEK : 0);
+
+ /*
+ * Special handling in case we're fetching a Message/rfc822
+ * segment and we're talking to an old server...
+ */
+ if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
+ so_seek(so, 0L, 0);
+ fetch = pine_mail_fetch_body(stream, raw, sect, &tlen, 0L);
+ }
+
+ /*
+ * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
+ * so if the message we are saving from was originally unseen,
+ * we have to change it back to unseen. Flags contains the
+ * string "SEEN" if the original message was seen.
+ */
+ if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
+ char seq[20];
+
+ strncpy(seq, long2string(raw), sizeof(seq));
+ seq[sizeof(seq)-1] = '\0';
+ mail_flag(stream, seq, "\\SEEN", 0);
+ }
+
+ /* If fetch succeeded, write the result */
+ if(!(fetch && so_nputs(so, fetch, tlen)))
+ return(0);
+ }
+
+ so_seek(so, 0L, 0); /* rewind just in case */
+
+ /*
+ * Set up a c-client string driver so we can hand the
+ * collected text down to mail_append.
+ *
+ * NOTE: We only test the size if and only if we already
+ * have it. See, in some drivers, especially under
+ * dos, its too expensive to get the size (full
+ * header and body text fetch plus MIME parse), so
+ * we only verify the size if we already know it.
+ */
+ mlen = hlen + tlen;
+
+ if(size && mlen < size){
+ char buf[128];
+
+ if(sp_dead_stream(stream)){
+ snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
+ q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
+ dprint((1, "save_fetch_append: %s", buf));
+ return(0);
+ }
+ else{
+ if(pith_opt_save_size_changed_prompt
+ && (*pith_opt_save_size_changed_prompt)(mn_raw2m(sp_msgmap(stream), raw), 0) == 'y'){
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append: %s", buf));
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source msg # %ld may be saved incorrectly",
+ mn_raw2m(sp_msgmap(stream), raw));
+ q_status_message(SM_ORDER, 0, 3, buf);
+ }
+ else{
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source raw msg # %ld went from announced size %ld to actual size %ld",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append: %s", buf));
+ return(0);
+ }
+ }
+ }
+
+ INIT(&msg, mail_string, (void *)so_text(so), mlen);
+
+ rc = 0;
+ while(!(rv = (int) context_append_full(context, save_stream,
+ save_folder, flags,
+ *date ? date : NULL,
+ &msg))){
+ if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
+ break; /* c-client returned error? */
+
+ if(context && (context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ rv = -1; /* user cancelled */
+
+ break;
+ }
+
+ SETPOS((&msg), 0L); /* reset string driver */
+ }
+
+ return(rv);
+}
+
+
+/*
+ * save_ex_replace_body -
+ *
+ * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
+ * *BUT* which is to contain the count of written bytes on exit
+ */
+int
+save_ex_replace_body(char *hdr, long unsigned int *hlen, struct mail_bodystruct *body, gf_io_t pc)
+{
+ unsigned long len;
+
+ /*
+ * "X-" out the given MIME headers unless they're
+ * the same as we're going to substitute...
+ */
+ if(body->type == TYPETEXT
+ && (!body->subtype || !strucmp(body->subtype, "plain"))
+ && body->encoding == ENC7BIT){
+ if(!gf_nputs(hdr, *hlen, pc)) /* write out header */
+ return(0);
+ }
+ else{
+ int bol = 1;
+
+ /*
+ * write header, "X-"ing out transport headers bothersome to
+ * software but potentially useful to the human recipient...
+ */
+ for(len = *hlen; len; len--){
+ if(bol){
+ unsigned long n;
+
+ bol = 0;
+ if(save_ex_mask_types(hdr, &n, pc))
+ *hlen += n; /* add what we inserted */
+ else
+ break;
+ }
+
+ if(*hdr == '\015' && *(hdr+1) == '\012'){
+ bol++; /* remember beginning of line */
+ len--; /* account for LF */
+ if(gf_nputs(hdr, 2, pc))
+ hdr += 2;
+ else
+ break;
+ }
+ else if(!(*pc)(*hdr++))
+ break;
+ }
+
+ if(len) /* bytes remain! */
+ return(0);
+ }
+
+ /* Now, blat out explanatory text as the body... */
+ if(save_ex_explain_body(body, &len, pc)){
+ *hlen += len;
+ return(1);
+ }
+ else
+ return(0);
+}
+
+
+int
+save_ex_output_body(MAILSTREAM *stream, long int raw, char *section,
+ struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
+{
+ char *txtp, newsect[128];
+ unsigned long ilen;
+
+ txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);
+
+ if(msgno_part_deleted(stream, raw, section))
+ return(save_ex_replace_body(txtp, len, body, pc));
+
+ if(body->type == TYPEMULTIPART){
+ char *subsect, boundary[128];
+ int n, blen;
+ PART *part = body->nested.part;
+ PARAMETER *param;
+
+ /* Locate supplied multipart boundary */
+ for (param = body->parameter; param; param = param->next)
+ if (!strucmp(param->attribute, "boundary")){
+ snprintf(boundary, sizeof(boundary), "--%.*s\015\012", sizeof(boundary)-10,
+ param->value);
+ blen = strlen(boundary);
+ break;
+ }
+
+ if(!param){
+ q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
+ return(0);
+ }
+
+ /*
+ * BUG: if multi/digest and a message deleted (which we'll
+ * change to text/plain), we need to coerce this composite
+ * type to multi/mixed !!
+ */
+ if(!gf_nputs(txtp, *len, pc)) /* write MIME header */
+ return(0);
+
+ /* Prepare to specify sub-sections */
+ strncpy(newsect, section, sizeof(newsect));
+ newsect[sizeof(newsect)-1] = '\0';
+ subsect = &newsect[n = strlen(newsect)];
+ if(n > 2 && !strcmp(&newsect[n-2], ".0"))
+ subsect--;
+ else if(n){
+ if((subsect-newsect) < sizeof(newsect))
+ *subsect++ = '.';
+ }
+
+ n = 1;
+ do { /* for each part */
+ strncpy(subsect, int2string(n++), sizeof(newsect)-(subsect-newsect));
+ newsect[sizeof(newsect)-1] = '\0';
+ if(gf_puts(boundary, pc)
+ && save_ex_output_body(stream, raw, newsect,
+ &part->body, &ilen, pc))
+ *len += blen + ilen;
+ else
+ return(0);
+ }
+ while ((part = part->next) != NULL); /* until done */
+
+ snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", sizeof(boundary)-10,param->value);
+ *len += blen + 2;
+ return(gf_puts(boundary, pc));
+ }
+
+ /* Start by writing the part's MIME header */
+ if(!gf_nputs(txtp, *len, pc))
+ return(0);
+
+ if(body->type == TYPEMESSAGE
+ && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
+ /* write RFC 822 message's header */
+ if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
+ && gf_nputs(txtp, ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ /* then go deal with its body parts */
+ snprintf(newsect, sizeof(newsect), "%.10s%s%s", section, section ? "." : "",
+ (body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
+ if(save_ex_output_body(stream, raw, newsect,
+ body->nested.msg->body, &ilen, pc)){
+ *len += ilen;
+ return(1);
+ }
+
+ return(0);
+ }
+
+ /* Write corresponding body part */
+ if((txtp = pine_mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
+ && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
+ *len += ilen + 2;
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Mask off any header entries we don't want to show up in exceptional saves
+
+Args: hdr -- pointer to start of a header line
+ pc -- function to write the prefix
+
+ ----*/
+int
+save_ex_mask_types(char *hdr, long unsigned int *len, gf_io_t pc)
+{
+ char *s = NULL;
+
+ if(!struncmp(hdr, "content-type:", 13))
+ s = "Content-Type: Text/Plain; charset=US-ASCII\015\012X-";
+ else if(!struncmp(hdr, "content-description:", 20))
+ s = "Content-Description: Deleted Attachment\015\012X-";
+ else if(!struncmp(hdr, "content-transfer-encoding:", 26)
+ || !struncmp(hdr, "content-disposition:", 20))
+ s = "X-";
+
+ return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
+}
+
+
+int
+save_ex_explain_body(struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
+{
+ unsigned long ilen;
+ char **blurbp;
+ static char *blurb[] = {
+ N_("The following attachment was DELETED when this message was saved:"),
+ NULL
+ };
+
+ *len = 0;
+ for(blurbp = blurb; *blurbp; blurbp++)
+ if(save_ex_output_line(_(*blurbp), &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ if(!save_ex_explain_parts(body, 0, &ilen, pc))
+ return(0);
+
+ *len += ilen;
+ return(1);
+}
+
+
+int
+save_ex_explain_parts(struct mail_bodystruct *body, int depth, long unsigned int *len, gf_io_t pc)
+{
+ char tmp[MAILTMPLEN], buftmp[MAILTMPLEN];
+ unsigned long ilen;
+ char *name = parameter_val(body->parameter, "name");
+
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ PART *part = body->nested.part; /* first body part */
+
+ *len = 0;
+ if(body->description && *body->description){
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment described",
+ depth, depth, " ", body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "");
+ if(!save_ex_output_line(tmp, len, pc))
+ return(0);
+
+ snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
+ snprintf(tmp, sizeof(tmp), "%*.*s as \"%.50s\" containing:", depth, depth, " ",
+ (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp));
+ }
+ else{
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment containing:",
+ depth, depth, " ",
+ body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "");
+ }
+
+ if(save_ex_output_line(tmp, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ depth++;
+ do /* for each part */
+ if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+ while ((part = part->next) != NULL); /* until done */
+ }
+ else{
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment of about %s bytes%s",
+ depth, depth, " ",
+ body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "",
+ comatose((body->encoding == ENCBASE64)
+ ? ((body->size.bytes * 3)/4)
+ : body->size.bytes),
+ body->description ? "," : ".");
+ if(!save_ex_output_line(tmp, len, pc))
+ return(0);
+
+ if(body->description && *body->description){
+ snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
+ snprintf(tmp, sizeof(tmp), "%*.*s described as \"%.*s\"", depth, depth, " ",
+ sizeof(tmp)-100,
+ (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp));
+ if(save_ex_output_line(tmp, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+ }
+ }
+
+ return(1);
+}
+
+
+int
+save_ex_output_line(char *line, long unsigned int *len, gf_io_t pc)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, " [ %-*.*s ]\015\012", 68, 68, line);
+ *len = strlen(tmp_20k_buf);
+ return(gf_puts(tmp_20k_buf, pc));
+}
+
+
+/*----------------------------------------------------------------------
+ Save() helper function to create canonical date string from given header
+
+ Args: date -- buf to recieve canonical date string
+ header -- rfc822 header to fish date string from
+
+ Result: date filled with canonicalized date in header, or null string
+ ----*/
+void
+saved_date(char *date, char *header)
+{
+ char *d, *p, c;
+ MESSAGECACHE elt;
+
+ *date = '\0';
+ if((toupper((unsigned char)(*(d = header)))
+ == 'D' && !strncmp(d, "date:", 5))
+ || (d = srchstr(header, "\015\012date:"))){
+ for(d += 7; *d == ' '; d++)
+ ; /* skip white space */
+
+ if((p = strstr(d, "\015\012")) != NULL){
+ for(; p > d && *p == ' '; p--)
+ ; /* skip white space */
+
+ c = *p;
+ *p = '\0'; /* tie off internal date */
+ }
+
+ if(mail_parse_date(&elt, (unsigned char *) d)) /* normalize it */
+ mail_date(date, &elt);
+
+ if(p) /* restore header */
+ *p = c;
+ }
+}
+
+
+MAILSTREAM *
+save_msg_stream(CONTEXT_S *context, char *folder, int *we_opened)
+{
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *save_stream = NULL;
+
+ if(IS_REMOTE(context_apply(tmp, context, folder, sizeof(tmp)))
+ && !(save_stream = sp_stream_get(tmp, SP_MATCH))
+ && !(save_stream = sp_stream_get(tmp, SP_SAME))){
+ if((save_stream = context_open(context, NULL, folder,
+ OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
+ NULL)) != NULL)
+ *we_opened = 1;
+ }
+
+ return(save_stream);
+}
+
+
+/*----------------------------------------------------------------------
+ Offer to create a non-existant folder to copy message[s] into
+
+ Args: context -- context to create folder in
+ folder -- name of folder to create
+
+ Result: 0 if create failed (c-client writes error)
+ 1 if create successful
+ -1 if user declines to create folder
+ ----*/
+int
+create_for_save(CONTEXT_S *context, char *folder)
+{
+ int ret;
+
+ if(pith_opt_save_create_prompt
+ && (ret = (*pith_opt_save_create_prompt)(context, folder, 1)) != 1)
+ return(ret);
+
+ return(context_create(context, NULL, folder));
+}
diff --git a/pith/save.h b/pith/save.h
new file mode 100644
index 00000000..6a6f1873
--- /dev/null
+++ b/pith/save.h
@@ -0,0 +1,54 @@
+/*
+ * $Id: save.h 942 2008-03-04 18:21:33Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SAVE_INCLUDED
+#define PITH_SAVE_INCLUDED
+
+
+#include "../pith/savetype.h"
+#include "../pith/context.h"
+#include "../pith/msgno.h"
+#include "../pith/state.h"
+#include "../pith/store.h"
+
+
+#define SV_DELETE 0x1 /* delete source msg after save */
+#define SV_FOR_FILT 0x2 /* save called from filtering routine, */
+ /* just different status messages */
+#define SV_FIX_DELS 0x4 /* remove Del mark before saving */
+#define SV_INBOXWOCNTXT 0x8 /* interpret "inbox" as one true inbox */
+#define SV_PRESERVE 0x10 /* preserve order */
+
+
+#define SSCP_NONE 0x0
+#define SSCP_INIT 0x1
+#define SSCP_END 0x2
+#define SSCP_ANSWER_IS_YES 0x4
+
+
+/* exported protoypes */
+char *save_get_default(struct pine *, ENVELOPE *, long, char *, CONTEXT_S **);
+void save_get_fldr_from_env(char *, int, ENVELOPE *, struct pine *, long, char *);
+long save(struct pine *, MAILSTREAM *, CONTEXT_S *, char *, MSGNO_S *, int);
+long save_fetch_append_cb(MAILSTREAM *, void *, char **, char **, STRING **);
+int save_fetch_append(MAILSTREAM *, long, char *, MAILSTREAM *, char *, CONTEXT_S *,
+ unsigned long, char *, char *, STORE_S *);
+void saved_date(char *, char *);
+MAILSTREAM *save_msg_stream(CONTEXT_S *, char *, int *);
+int create_for_save(CONTEXT_S *, char *);
+
+
+#endif /* PITH_SAVE_INCLUDED */
diff --git a/pith/savetype.h b/pith/savetype.h
new file mode 100644
index 00000000..5e7cb74c
--- /dev/null
+++ b/pith/savetype.h
@@ -0,0 +1,38 @@
+/*
+ * $Id: savetype.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SAVETYPE_INCLUDED
+#define PITH_SAVETYPE_INCLUDED
+
+
+#include "../pith/msgno.h"
+#include "../pith/store.h"
+
+
+typedef struct append_package {
+ MAILSTREAM *stream;
+ char *flags;
+ char *date;
+ STRING *msg;
+ MSGNO_S *msgmap;
+ long msgno;
+ STORE_S *so;
+} APPENDPACKAGE;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_SAVETYPE_INCLUDED */
diff --git a/pith/search.c b/pith/search.c
new file mode 100644
index 00000000..23e2f353
--- /dev/null
+++ b/pith/search.c
@@ -0,0 +1,70 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: search.c 854 2007-12-07 17:44:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/search.h"
+
+
+SEARCHSET *
+build_searchset(MAILSTREAM *stream)
+{
+ long i, run;
+ SEARCHSET *ret_s = NULL, **set;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(NULL);
+
+ for(i = 1L, set = &ret_s, run = 0L; i <= stream->nmsgs; i++){
+ if(!((mc = mail_elt(stream, i)) && mc->sequence)){ /* end of run */
+ if(run){ /* run in progress */
+ set = &(*set)->next;
+ run = 0L;
+ }
+ }
+ else if(run++){ /* next in run */
+ (*set)->last = i;
+ }
+ else{ /* start of new run */
+ *set = mail_newsearchset();
+ /*
+ * Leave off (*set)->last until we get more than one msg
+ * in the run, to avoid 607:607 in SEARCH.
+ */
+ (*set)->first = (*set)->last = i;
+ }
+ }
+
+ return(ret_s);
+}
+
+
+int
+in_searchset(SEARCHSET *srch, long unsigned int num)
+{
+ SEARCHSET *s;
+ unsigned long i;
+
+ if(srch)
+ for(s = srch; s; s = s->next)
+ for(i = s->first; i <= s->last; i++){
+ if(i == num)
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/pith/search.h b/pith/search.h
new file mode 100644
index 00000000..d1da3897
--- /dev/null
+++ b/pith/search.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: search.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SEARCH_INCLUDED
+#define PITH_SEARCH_INCLUDED
+
+
+/* exported protoypes */
+SEARCHSET *build_searchset(MAILSTREAM *);
+int in_searchset(SEARCHSET *, unsigned long);
+
+
+#endif /* PITH_SEARCH_INCLUDED */
diff --git a/pith/send.c b/pith/send.c
new file mode 100644
index 00000000..a0c60439
--- /dev/null
+++ b/pith/send.c
@@ -0,0 +1,5901 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/send.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/mimedesc.h"
+#include "../pith/context.h"
+#include "../pith/status.h"
+#include "../pith/folder.h"
+#include "../pith/bldaddr.h"
+#include "../pith/pipe.h"
+#include "../pith/mailview.h"
+#include "../pith/mailindx.h"
+#include "../pith/list.h"
+#include "../pith/filter.h"
+#include "../pith/reply.h"
+#include "../pith/addrstring.h"
+#include "../pith/rfc2231.h"
+#include "../pith/stream.h"
+#include "../pith/util.h"
+#include "../pith/adrbklib.h"
+#include "../pith/options.h"
+#include "../pith/busy.h"
+#include "../pith/text.h"
+#include "../pith/imap.h"
+#include "../pith/ablookup.h"
+#include "../pith/sort.h"
+#include "../pith/smime.h"
+
+#include "../c-client/smtp.h"
+#include "../c-client/nntp.h"
+
+
+/* this is used in pine_send and pine_simple_send */
+/* name::type::canedit::writehdr::localcopy::rcptto */
+PINEFIELD pf_template[] = {
+ {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */
+ {"From", Address, 0, 1, 1, 0},
+ {"Reply-To", Address, 0, 1, 1, 0},
+ {TONAME, Address, 1, 1, 1, 1},
+ {CCNAME, Address, 1, 1, 1, 1},
+ {"bcc", Address, 1, 0, 1, 1},
+ {"Newsgroups", FreeText, 1, 1, 1, 0},
+ {"Fcc", Fcc, 1, 0, 0, 0},
+ {"Lcc", Address, 1, 0, 1, 1},
+ {"Attchmnt", Attachment, 1, 1, 1, 0},
+ {SUBJNAME, Subject, 1, 1, 1, 0},
+ {"References", FreeText, 0, 1, 1, 0},
+ {"Date", FreeText, 0, 1, 1, 0},
+ {"In-Reply-To", FreeText, 0, 1, 1, 0},
+ {"Message-ID", FreeText, 0, 1, 1, 0},
+ {PRIORITYNAME, FreeText, 0, 1, 1, 0},
+ {"User-Agent", FreeText, 0, 1, 1, 0},
+ {"To", Address, 0, 0, 0, 0}, /* N_NOBODY */
+ {"X-Post-Error",FreeText, 0, 0, 0, 0}, /* N_POSTERR */
+ {"X-Reply-UID", FreeText, 0, 0, 0, 0}, /* N_RPLUID */
+ {"X-Reply-Mbox",FreeText, 0, 0, 0, 0}, /* N_RPLMBOX */
+ {"X-SMTP-Server",FreeText, 0, 0, 0, 0}, /* N_SMTP */
+ {"X-NNTP-Server",FreeText, 0, 0, 0, 0}, /* N_NNTP */
+ {"X-Cursor-Pos",FreeText, 0, 0, 0, 0}, /* N_CURPOS */
+ {"X-Our-ReplyTo",FreeText, 0, 0, 0, 0}, /* N_OURREPLYTO */
+ {OUR_HDRS_LIST, FreeText, 0, 0, 0, 0}, /* N_OURHDRS */
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ {"X-X-Sender", Address, 0, 1, 1, 0},
+#endif
+ {NULL, FreeText}
+};
+
+
+PRIORITY_S priorities[] = {
+ {1, "Highest"},
+ {2, "High"},
+ {3, "Normal"},
+ {4, "Low"},
+ {5, "Lowest"},
+ {0, NULL}
+};
+
+
+#define ctrl(c) ((c) & 0x1f)
+
+/* which message part to test for xliteration */
+typedef enum {MsgBody, HdrText} MsgPart;
+
+
+/*
+ * Internal prototypes
+ */
+long post_rfc822_output(char *, ENVELOPE *, BODY *, soutr_t, TCPSTREAM *, long);
+int l_flush_net(int);
+int l_putc(int);
+int pine_write_header_line(char *, char *, STORE_S *);
+int pine_write_params(PARAMETER *, STORE_S *);
+char *tidy_smtp_mess(char *, char *, char *, size_t);
+int lmc_body_header_line(char *, int);
+int lmc_body_header_finish(void);
+int pwbh_finish(int, STORE_S *);
+int sent_percent(void);
+unsigned short *setup_avoid_table(void);
+#ifndef _WINDOWS
+int mta_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+char *post_handoff(METAENV *, BODY *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+char *mta_parse_post(METAENV *, BODY *, char *, char *, size_t, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+long pine_pipe_soutr_nl(void *, char *);
+#endif
+char *smtp_command(char *, size_t);
+void *piped_smtp_open(char *, char *, unsigned long);
+void *piped_aopen(NETMBX *, char *, char *);
+long piped_soutr(void *, char *);
+long piped_sout(void *, char *, unsigned long);
+char *piped_getline(void *);
+void piped_close(void *);
+void piped_abort(void *);
+char *piped_host(void *);
+unsigned long piped_port(void *);
+char *posting_characterset(void *, char *, MsgPart);
+int body_is_translatable(void *, char *);
+int text_is_translatable(void *, char *);
+int dummy_putc(int);
+unsigned long *init_charsetchecker(char *);
+int representable_in_charset(unsigned long, char *);
+char *most_preferred_charset(unsigned long);
+
+/*
+ * Storage object where the FCC (or postponed msg) is to be written.
+ * This is amazingly bogus. Much work was done to put messages
+ * together and encode them as they went to the tmp file for sendmail
+ * or into the SMTP slot (especially for for DOS, to prevent a temporary
+ * file (and needlessly copying the message).
+ *
+ * HOWEVER, since there's no piping into c-client routines
+ * (particularly mail_append() which copies the fcc), the fcc will have
+ * to be copied to disk. This global tells pine's copy of the rfc822
+ * output functions where to also write the message bytes for the fcc.
+ * With piping in the c-client we could just have two pipes to shove
+ * down rather than messing with damn copies. FIX THIS!
+ *
+ * The function open_fcc, locates the actual folder and creates it if
+ * requested before any mailing or posting is done.
+ */
+struct local_message_copy lmc;
+
+
+/*
+ * Locally global pointer to stream used for sending/posting.
+ * It's also used to indicate when/if we write the Bcc: field in
+ * the header.
+ */
+static SENDSTREAM *sending_stream = NULL;
+
+
+static struct hooks {
+ void *rfc822_out; /* Message outputter */
+} sending_hooks;
+
+
+static FILE *verbose_send_output = NULL;
+static long send_bytes_sent, send_bytes_to_send;
+static METAENV *send_header = NULL;
+
+/*
+ * Hooks for prompts and confirmations
+ */
+int (*pith_opt_daemon_confirm)(void);
+
+
+static NETDRIVER piped_io = {
+ piped_smtp_open, /* open a connection */
+ piped_aopen, /* open an authenticated connection */
+ piped_getline, /* get a line */
+ NULL, /* get a buffer */
+ piped_soutr, /* output pushed data */
+ piped_sout, /* output string */
+ piped_close, /* close connection */
+ piped_host, /* return host name */
+ piped_host, /* remotehost */
+ piped_port, /* return port number */
+ piped_host /* return local host (NOTE: same as host!) */
+};
+
+
+/*
+ * Since c-client preallocates, it's necessary here to define a limit
+ * such that we don't blow up in c-client (see rfc822_address_line()).
+ */
+#define MAX_SINGLE_ADDR MAILTMPLEN
+
+#define AVOID_2022_JP_FOR_PUNC "AVOID_2022_JP_FOR_PUNC"
+
+
+/*
+ * Phone home hash controls
+ */
+#define PH_HASHBITS 24
+#define PH_MAXHASH (1<<(PH_HASHBITS))
+
+
+/*
+ * postponed_stream - return stream associated with postponed messages
+ * in argument.
+ */
+int
+postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs)
+{
+ MAILSTREAM *stream = NULL;
+ CONTEXT_S *p_cntxt = NULL;
+ char *p, *q, tmp[MAILTMPLEN], *fullname = NULL;
+ int exists;
+
+ if(!(streamp && mbox))
+ return(0);
+
+ *streamp = NULL;
+
+ /*
+ * find default context to look for folder...
+ *
+ * The "mbox" is assumed to be local if we're given what looks
+ * like an absolute path. This is different from Goto/Save
+ * where we do alot of work to interpret paths relative to the
+ * server. This reason is to support all the pre-4.00 pinerc'
+ * that specified a path and because there's yet to be a way
+ * in c-client to specify otherwise in the face of a remote
+ * context.
+ */
+ if(!is_absolute_path(mbox)
+ && !(p_cntxt = default_save_context(ps_global->context_list)))
+ p_cntxt = ps_global->context_list;
+
+ /* check to see if the folder exists, the user wants to continue
+ * and that we can actually read something in...
+ */
+ exists = folder_name_exists(p_cntxt, mbox, &fullname);
+ if(fullname)
+ mbox = fullname;
+
+ if(exists & FEX_ISFILE){
+ context_apply(tmp, p_cntxt, mbox, sizeof(tmp));
+ if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){
+ /*
+ * The mbox is relative to the home directory.
+ * Make it absolute so we can compare it to
+ * stream->mailbox.
+ */
+ build_path(tmp_20k_buf, ps_global->ui.homedir, tmp,
+ SIZEOF_20KBUF);
+ strncpy(tmp, tmp_20k_buf, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ if((stream = ps_global->mail_stream)
+ && !(stream->mailbox
+ && ((*tmp != '{' && !strcmp(tmp, stream->mailbox))
+ || (*tmp == '{'
+ && same_stream(tmp, stream)
+ && (p = strchr(tmp, '}'))
+ && (q = strchr(stream->mailbox,'}'))
+ && !strcmp(p + 1, q + 1)))))
+ stream = NULL;
+
+ if(!stream){
+ stream = context_open(p_cntxt, NULL, mbox,
+ SP_USEPOOL|SP_TEMPUSE, NULL);
+ if(stream && !stream->halfopen){
+ if(stream->nmsgs > 0)
+ refresh_sort(stream, sp_msgmap(stream), SRT_NON);
+
+ if(checknmsgs && stream->nmsgs < 1){
+ pine_mail_close(stream);
+ exists = 0;
+ stream = NULL;
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ _("Can't open %s mailbox: %s"), type, mbox);
+ if(stream)
+ pine_mail_close(stream);
+
+ exists = 0;
+ stream = NULL;
+ }
+ }
+ }
+ else{
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("%s message folder doesn't exist!"), type);
+ }
+ }
+
+ if(fullname)
+ fs_give((void **) &fullname);
+
+ *streamp = stream;
+
+ return(exists);
+}
+
+
+int
+redraft_work(MAILSTREAM **streamp, long int cont_msg, ENVELOPE **outgoing,
+ struct mail_bodystruct **body, char **fcc, char **lcc,
+ REPLY_S **reply, REDRAFT_POS_S **redraft_pos, PINEFIELD **custom,
+ ACTION_S **role, int flags, STORE_S *so)
+{
+ MAILSTREAM *stream;
+ ENVELOPE *e = NULL;
+ BODY *b;
+ PART *part;
+ PINEFIELD *pf;
+ gf_io_t pc;
+ char *extras, **fields, **values, *p;
+ char *hdrs[2], *h, *charset;
+ char **smtp_servers = NULL, **nntp_servers = NULL;
+ int i, pine_generated = 0, our_replyto = 0;
+ int added_to_role = 0;
+ unsigned gbpt_flags = GBPT_NONE;
+ MESSAGECACHE *mc;
+
+ if(!(streamp && *streamp))
+ return(redraft_cleanup(streamp, TRUE, flags));
+
+ stream = *streamp;
+
+ if(flags & REDRAFT_HTML)
+ gbpt_flags |= GBPT_HTML_OK;
+
+ /* grok any user-defined or non-c-client headers */
+ if((e = pine_mail_fetchstructure(stream, cont_msg, &b)) != NULL){
+
+ /*
+ * The custom headers to look for in the suspended message should
+ * have been stored in the X-Our-Headers header. So first we get
+ * that list. If we can't find it (version that stored the
+ * message < 4.30) then we use the global custom list.
+ */
+ hdrs[0] = OUR_HDRS_LIST;
+ hdrs[1] = NULL;
+ if((h = pine_fetchheader_lines(stream, cont_msg, NULL, hdrs)) != NULL){
+ int commas = 0;
+ char **list;
+ char *hdrval = NULL;
+
+ if((hdrval = strindex(h, ':')) != NULL){
+ for(hdrval++; *hdrval && isspace((unsigned char)*hdrval);
+ hdrval++)
+ ;
+ }
+
+ /* count elements in list */
+ for(p = hdrval; p && *p; p++)
+ if(*p == ',')
+ commas++;
+
+ if(hdrval && (list = parse_list(hdrval,commas+1,0,NULL)) != NULL){
+
+ *custom = parse_custom_hdrs(list, Replace);
+ add_defaults_from_list(*custom,
+ ps_global->VAR_CUSTOM_HDRS);
+ free_list_array(&list);
+ }
+
+ if(*custom && !(*custom)->name){
+ free_customs(*custom);
+ *custom = NULL;
+ }
+
+ fs_give((void **)&h);
+ }
+
+ if(!*custom)
+ *custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
+
+#define INDEX_FCC 0
+#define INDEX_POSTERR 1
+#define INDEX_REPLYUID 2
+#define INDEX_REPLYMBOX 3
+#define INDEX_SMTP 4
+#define INDEX_NNTP 5
+#define INDEX_CURSORPOS 6
+#define INDEX_OUR_REPLYTO 7
+#define INDEX_LCC 8 /* MUST REMAIN LAST FIELD DECLARED */
+#define FIELD_COUNT 9
+
+ i = count_custom_hdrs_pf(*custom,1) + FIELD_COUNT + 1;
+
+ /*
+ * Having these two fields separated isn't the slickest, but
+ * converting the pointer array for fetchheader_lines() to
+ * a list of structures or some such for simple_header_parse()
+ * is too goonie. We could do something like re-use c-client's
+ * PARAMETER struct which is a simple char * pairing, but that
+ * doesn't make sense to pass to fetchheader_lines()...
+ */
+ fields = (char **) fs_get((size_t) i * sizeof(char *));
+ values = (char **) fs_get((size_t) i * sizeof(char *));
+ memset(fields, 0, (size_t) i * sizeof(char *));
+ memset(values, 0, (size_t) i * sizeof(char *));
+
+ fields[i = INDEX_FCC] = "Fcc"; /* Fcc: special case */
+ fields[++i] = "X-Post-Error"; /* posting errors too */
+ fields[++i] = "X-Reply-UID"; /* Reply'd to msg's UID */
+ fields[++i] = "X-Reply-Mbox"; /* Reply'd to msg's Mailbox */
+ fields[++i] = "X-SMTP-Server";/* SMTP server to use */
+ fields[++i] = "X-NNTP-Server";/* NNTP server to use */
+ fields[++i] = "X-Cursor-Pos"; /* Cursor position */
+ fields[++i] = "X-Our-ReplyTo"; /* ReplyTo is real */
+ fields[++i] = "Lcc"; /* Lcc: too... */
+ if(++i != FIELD_COUNT)
+ panic("Fix FIELD_COUNT");
+
+ for(pf = *custom; pf && pf->name; pf = pf->next)
+ if(!pf->standard)
+ fields[i++] = pf->name; /* assign custom fields */
+
+ if((extras = pine_fetchheader_lines(stream, cont_msg, NULL,fields)) != NULL){
+ simple_header_parse(extras, fields, values);
+ fs_give((void **) &extras);
+
+ /*
+ * translate RFC 1522 strings,
+ * starting with "Lcc" field
+ */
+ for(i = INDEX_LCC; fields[i]; i++)
+ if(values[i]){
+ size_t len;
+ char *bufp, *biggerbuf = NULL;
+
+ if((len=4*strlen(values[i])) > SIZEOF_20KBUF-1){
+ len++;
+ biggerbuf = (char *)fs_get(len * sizeof(char));
+ bufp = biggerbuf;
+ }
+ else{
+ bufp = tmp_20k_buf;
+ len = SIZEOF_20KBUF;
+ }
+
+ p = (char *)rfc1522_decode_to_utf8((unsigned char*)bufp, len, values[i]);
+
+ if(p == tmp_20k_buf){
+ fs_give((void **)&values[i]);
+ values[i] = cpystr(p);
+ }
+
+ if(biggerbuf)
+ fs_give((void **)&biggerbuf);
+ }
+
+ for(pf = *custom, i = FIELD_COUNT;
+ pf && pf->name;
+ pf = pf->next){
+ if(pf->standard){
+ /*
+ * Because the value is already in the envelope.
+ */
+ pf->cstmtype = NoMatch;
+ continue;
+ }
+
+ if(values[i]){ /* use this instead of default */
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ pf->textbuf = values[i]; /* freed in pine_send! */
+ }
+ else if(pf->textbuf) /* was erased before postpone */
+ fs_give((void **)&pf->textbuf);
+
+ i++;
+ }
+
+ if(values[INDEX_FCC]) /* If "Fcc:" was there... */
+ pine_generated = 1; /* we put it there? */
+
+ /*
+ * Since c-client fills in the reply_to field in the envelope
+ * even if there isn't a Reply-To header in the message we
+ * have to work around that. When we postpone we add
+ * a second header that has value "Empty" if there really
+ * was a Reply-To and it was empty. It has the
+ * value "Full" if we put the Reply-To contents there
+ * intentionally (and it isn't empty).
+ */
+ if(values[INDEX_OUR_REPLYTO]){
+ if(values[INDEX_OUR_REPLYTO][0] == 'E')
+ our_replyto = 'E'; /* we put an empty one there */
+ else if(values[INDEX_OUR_REPLYTO][0] == 'F')
+ our_replyto = 'F'; /* we put it there */
+
+ fs_give((void **) &values[INDEX_OUR_REPLYTO]);
+ }
+
+ if(fcc) /* fcc: special case... */
+ *fcc = values[INDEX_FCC] ? values[INDEX_FCC] : cpystr("");
+ else if(values[INDEX_FCC])
+ fs_give((void **) &values[INDEX_FCC]);
+
+ if(values[INDEX_POSTERR]){ /* x-post-error?!?1 */
+ q_status_message(SM_ORDER|SM_DING, 4, 4,
+ values[INDEX_POSTERR]);
+ fs_give((void **) &values[INDEX_POSTERR]);
+ }
+
+ if(values[INDEX_REPLYUID]){
+ if(reply)
+ *reply = build_reply_uid(values[INDEX_REPLYUID]);
+
+ fs_give((void **) &values[INDEX_REPLYUID]);
+
+ if(values[INDEX_REPLYMBOX] && reply && *reply)
+ (*reply)->origmbox = cpystr(values[INDEX_REPLYMBOX]);
+
+ if(reply && *reply && !(*reply)->origmbox && (*reply)->mailbox)
+ (*reply)->origmbox = cpystr((*reply)->mailbox);
+ }
+
+ if(values[INDEX_REPLYMBOX])
+ fs_give((void **) &values[INDEX_REPLYMBOX]);
+
+ if(values[INDEX_SMTP]){
+ char *q;
+ size_t cnt = 0;
+
+ /*
+ * Turn the space delimited list of smtp servers into
+ * a char ** list.
+ */
+ p = values[INDEX_SMTP];
+ do{
+ if(!*p || isspace((unsigned char) *p))
+ cnt++;
+ } while(*p++);
+
+ smtp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
+ memset(smtp_servers, 0, (cnt+1) * sizeof(char *));
+
+ cnt = 0;
+ q = p = values[INDEX_SMTP];
+ do{
+ if(!*p || isspace((unsigned char) *p)){
+ if(*p){
+ *p = '\0';
+ smtp_servers[cnt++] = cpystr(q);
+ *p = ' ';
+ q = p+1;
+ }
+ else
+ smtp_servers[cnt++] = cpystr(q);
+ }
+ } while(*p++);
+
+ fs_give((void **) &values[INDEX_SMTP]);
+ }
+
+ if(values[INDEX_NNTP]){
+ char *q;
+ size_t cnt = 0;
+
+ /*
+ * Turn the space delimited list of smtp nntp into
+ * a char ** list.
+ */
+ p = values[INDEX_NNTP];
+ do{
+ if(!*p || isspace((unsigned char) *p))
+ cnt++;
+ } while(*p++);
+
+ nntp_servers = (char **) fs_get((cnt+1) * sizeof(char *));
+ memset(nntp_servers, 0, (cnt+1) * sizeof(char *));
+
+ cnt = 0;
+ q = p = values[INDEX_NNTP];
+ do{
+ if(!*p || isspace((unsigned char) *p)){
+ if(*p){
+ *p = '\0';
+ nntp_servers[cnt++] = cpystr(q);
+ *p = ' ';
+ q = p+1;
+ }
+ else
+ nntp_servers[cnt++] = cpystr(q);
+ }
+ } while(*p++);
+
+ fs_give((void **) &values[INDEX_NNTP]);
+ }
+
+ if(values[INDEX_CURSORPOS]){
+ /*
+ * The redraft cursor position is written as two fields
+ * separated by a space. First comes the name of the
+ * header field we're in, or just a ":" if we're in the
+ * body. Then comes the offset into that header or into
+ * the body.
+ */
+ if(redraft_pos){
+ char *q1, *q2;
+
+ *redraft_pos
+ = (REDRAFT_POS_S *)fs_get(sizeof(REDRAFT_POS_S));
+ (*redraft_pos)->offset = 0L;
+
+ q1 = skip_white_space(values[INDEX_CURSORPOS]);
+ if(*q1 && (q2 = strindex(q1, SPACE))){
+ *q2 = '\0';
+ (*redraft_pos)->hdrname = cpystr(q1);
+ q1 = skip_white_space(q2+1);
+ if(*q1)
+ (*redraft_pos)->offset = atol(q1);
+ }
+ else
+ (*redraft_pos)->hdrname = cpystr(":");
+ }
+
+ fs_give((void **) &values[INDEX_CURSORPOS]);
+ }
+
+ if(lcc)
+ *lcc = values[INDEX_LCC];
+ else
+ fs_give((void **) &values[INDEX_LCC]);
+ }
+
+ fs_give((void **)&fields);
+ fs_give((void **)&values);
+
+ *outgoing = copy_envelope(e);
+
+ /*
+ * If the postponed message has a From which is different from
+ * the default, it is either because allow-changing-from is on
+ * or because there was a role with a from that allowed it to happen.
+ * If allow-changing-from is not on, put this back in a role
+ * so that it will be allowed again in pine_send.
+ */
+ if(role && *role == NULL &&
+ !ps_global->never_allow_changing_from &&
+ *outgoing){
+ /*
+ * Now check to see if the from is different from default from.
+ */
+ ADDRESS *deffrom;
+
+ deffrom = generate_from();
+ if(!((*outgoing)->from &&
+ address_is_same(deffrom, (*outgoing)->from) &&
+ ((!(deffrom->personal && deffrom->personal[0]) &&
+ !((*outgoing)->from->personal &&
+ (*outgoing)->from->personal[0])) ||
+ (deffrom->personal && (*outgoing)->from->personal &&
+ !strcmp(deffrom->personal, (*outgoing)->from->personal))))){
+
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ if(!(*outgoing)->from)
+ (*outgoing)->from = mail_newaddr();
+
+ (*role)->from = (*outgoing)->from;
+ (*outgoing)->from = NULL;
+ added_to_role++;
+ }
+
+ mail_free_address(&deffrom);
+ }
+
+ /*
+ * Look at each empty address and see if the user has specified
+ * a default for that field or not. If they have, that means
+ * they have erased it before postponing, so they won't want
+ * the default to come back. If they haven't specified a default,
+ * then the default should be generated in pine_send. We prevent
+ * the default from being assigned by assigning an empty address
+ * to the variable here.
+ *
+ * BUG: We should do this for custom Address headers, too, but
+ * there isn't such a thing yet.
+ */
+ if(!(*outgoing)->to && hdr_is_in_list("to", *custom))
+ (*outgoing)->to = mail_newaddr();
+ if(!(*outgoing)->cc && hdr_is_in_list("cc", *custom))
+ (*outgoing)->cc = mail_newaddr();
+ if(!(*outgoing)->bcc && hdr_is_in_list("bcc", *custom))
+ (*outgoing)->bcc = mail_newaddr();
+
+ if(our_replyto == 'E'){
+ /* user erased reply-to before postponing */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+
+ /*
+ * If empty is not the normal default, make the outgoing
+ * reply_to be an emtpy address. If it is default, leave it
+ * as NULL and the default will be used.
+ */
+ if(hdr_is_in_list("reply-to", *custom)){
+ PINEFIELD pf;
+
+ pf.name = "reply-to";
+ set_default_hdrval(&pf, *custom);
+ if(pf.textbuf){
+ if(pf.textbuf[0]) /* empty is not default */
+ (*outgoing)->reply_to = mail_newaddr();
+
+ fs_give((void **)&pf.textbuf);
+ }
+ }
+ }
+ else if(our_replyto == 'F'){
+ int add_to_role = 0;
+
+ /*
+ * The reply-to is real. If it is different from the default
+ * reply-to, put it in the role so that it will show up when
+ * the user edits.
+ */
+ if(hdr_is_in_list("reply-to", *custom)){
+ PINEFIELD pf;
+ char *str;
+
+ pf.name = "reply-to";
+ set_default_hdrval(&pf, *custom);
+ if(pf.textbuf && pf.textbuf[0]){
+ if((str = addr_list_string((*outgoing)->reply_to,NULL,1)) != NULL){
+ if(!strcmp(str, pf.textbuf)){
+ /* standard value, leave it alone */
+ ;
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ fs_give((void **)&str);
+ }
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ if(pf.textbuf)
+ fs_give((void **)&pf.textbuf);
+ }
+ else /* not standard, put in role */
+ add_to_role++;
+
+ if(add_to_role && role && (*role == NULL || added_to_role)){
+ if(*role == NULL){
+ added_to_role++;
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ }
+
+ (*role)->replyto = (*outgoing)->reply_to;
+ (*outgoing)->reply_to = NULL;
+ }
+ }
+ else{
+ /* this is a bogus c-client generated replyto */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+ }
+
+ if((smtp_servers || nntp_servers)
+ && role && (*role == NULL || added_to_role)){
+ if(*role == NULL){
+ *role = (ACTION_S *)fs_get(sizeof(**role));
+ memset((void *)*role, 0, sizeof(**role));
+ }
+
+ if(smtp_servers)
+ (*role)->smtp = smtp_servers;
+ if(nntp_servers)
+ (*role)->nntp = nntp_servers;
+ }
+
+ if(!(*outgoing)->subject && hdr_is_in_list("subject", *custom))
+ (*outgoing)->subject = cpystr("");
+
+ if(!pine_generated){
+ /*
+ * Now, this is interesting. We should have found
+ * the "fcc:" field if pine wrote the message being
+ * redrafted. Hence, we probably can't trust the
+ * "originator" type fields, so we'll blast them and let
+ * them get set later in pine_send. This should allow
+ * folks with custom or edited From's and such to still
+ * use redraft reasonably, without inadvertently sending
+ * messages that appear to be "From" others...
+ */
+ if((*outgoing)->from)
+ mail_free_address(&(*outgoing)->from);
+
+ /*
+ * Ditto for Reply-To and Sender...
+ */
+ if((*outgoing)->reply_to)
+ mail_free_address(&(*outgoing)->reply_to);
+
+ if((*outgoing)->sender)
+ mail_free_address(&(*outgoing)->sender);
+ }
+
+ if(!pine_generated || !(flags & REDRAFT_DEL)){
+
+ /*
+ * Generate a fresh message id for pretty much the same
+ * reason From and such got wacked...
+ * Also, if we're coming from a form letter, we need to
+ * generate a different id each time.
+ */
+ if((*outgoing)->message_id)
+ fs_give((void **)&(*outgoing)->message_id);
+
+ (*outgoing)->message_id = generate_message_id();
+ }
+
+ if(b && b->type != TYPETEXT){
+ if(b->type == TYPEMULTIPART){
+ if(strucmp(b->subtype, "mixed")){
+ q_status_message1(SM_INFO, 3, 4,
+ "Converting Multipart/%s to Multipart/Mixed",
+ b->subtype);
+ fs_give((void **)&b->subtype);
+ b->subtype = cpystr("mixed");
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Unable to resume type %s/%s message",
+ body_types[b->type], b->subtype);
+ return(redraft_cleanup(streamp, TRUE, flags));
+ }
+ }
+
+ gf_set_so_writec(&pc, so);
+
+ if(b && b->type != TYPETEXT){ /* already TYPEMULTIPART */
+ *body = copy_body(NULL, b);
+ part = (*body)->nested.part;
+ part->body.contents.text.data = (void *)so;
+ set_mime_type_by_grope(&part->body);
+ if(part->body.type != TYPETEXT){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Unable to resume; first part is non-text: %s/%s",
+ body_types[part->body.type],
+ part->body.subtype);
+ return(redraft_cleanup(streamp, TRUE, flags));
+ }
+
+ if((charset = parameter_val(part->body.parameter,"charset")) != NULL){
+ /* let outgoing routines decide on charset */
+ if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
+ set_parameter(&part->body.parameter, "charset", NULL);
+
+ fs_give((void **) &charset);
+ }
+
+ ps_global->postpone_no_flow = 1;
+
+ get_body_part_text(stream, &b->nested.part->body,
+ cont_msg, "1", 0L, pc, NULL, NULL, gbpt_flags);
+ ps_global->postpone_no_flow = 0;
+
+ if(!fetch_contents(stream, cont_msg, NULL, *body))
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error including all message parts"));
+ }
+ else{
+ *body = mail_newbody();
+ (*body)->type = TYPETEXT;
+ if(b->subtype)
+ (*body)->subtype = cpystr(b->subtype);
+
+ if((charset = parameter_val(b->parameter,"charset")) != NULL){
+ /* let outgoing routines decide on charset */
+ if(!strucmp(charset, "US-ASCII") || !strucmp(charset, "UTF-8"))
+ fs_give((void **) &charset);
+ else{
+ (*body)->parameter = mail_newbody_parameter();
+ (*body)->parameter->attribute = cpystr("charset");
+ if(utf8_charset(charset)){
+ fs_give((void **) &charset);
+ (*body)->parameter->value = cpystr("UTF-8");
+ }
+ else
+ (*body)->parameter->value = charset;
+ }
+ }
+
+ (*body)->contents.text.data = (void *)so;
+ ps_global->postpone_no_flow = 1;
+ get_body_part_text(stream, b, cont_msg, "1", 0L, pc,
+ NULL, NULL, gbpt_flags);
+ ps_global->postpone_no_flow = 0;
+ }
+
+ gf_clear_so_writec(so);
+
+ /* We have what we want, blast this message... */
+ if((flags & REDRAFT_DEL)
+ && cont_msg > 0L && stream && cont_msg <= stream->nmsgs
+ && (mc = mail_elt(stream, cont_msg)) && !mc->deleted)
+ mail_flag(stream, long2string(cont_msg), "\\DELETED", ST_SET);
+ }
+ else
+ return(redraft_cleanup(streamp, TRUE, flags));
+
+ return(redraft_cleanup(streamp, FALSE, flags));
+}
+
+
+/*----------------------------------------------------------------------
+ Clear deleted messages from given stream and expunge if necessary
+
+Args: stream --
+ problem --
+
+ ----*/
+int
+redraft_cleanup(MAILSTREAM **streamp, int problem, int flags)
+{
+ MAILSTREAM *stream;
+
+ if(!(streamp && *streamp))
+ return(0);
+
+ if(!problem && streamp && (stream = *streamp)){
+ if(stream->nmsgs){
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(stream); /* clean out deleted */
+ ps_global->expunge_in_progress = 0;
+ }
+
+ if(!stream->nmsgs){ /* close and delete folder */
+ int do_the_broach = 0;
+ char *mbox = NULL;
+
+ if(stream){
+ if(stream->original_mailbox && stream->original_mailbox[0])
+ mbox = cpystr(stream->original_mailbox);
+ else if(stream->mailbox && stream->mailbox[0])
+ mbox = cpystr(stream->mailbox);
+ }
+
+ /* if it is current, we have to change folders */
+ if(stream == ps_global->mail_stream)
+ do_the_broach++;
+
+ /*
+ * This is the stream to the empty postponed-msgs folder.
+ * We are going to delete the folder in a second. It is
+ * probably preferable to unselect the mailbox and leave
+ * this stream open for re-use instead of actually closing it,
+ * so we do that if possible.
+ */
+ if(is_imap_stream(stream) && LEVELUNSELECT(stream)){
+ /*
+ * This does the UNSELECT on the stream. A NULL
+ * return should mean that something went wrong and
+ * a mail_close already happened, so that should have
+ * cleaned things up in the callback.
+ */
+ if((stream=mail_open(stream, stream->mailbox,
+ OP_HALFOPEN | (stream->debug ? OP_DEBUG : NIL))) != NULL){
+ /* now close it so it is put into the stream cache */
+ sp_set_flags(stream, sp_flags(stream) | SP_TEMPUSE);
+ pine_mail_close(stream);
+ }
+ }
+ else
+ pine_mail_actually_close(stream);
+
+ *streamp = NULL;
+
+ if(do_the_broach){
+ ps_global->mail_stream = NULL; /* already closed above */
+ }
+
+ if(mbox && !pine_mail_delete(NULL, mbox))
+ q_status_message1(SM_ORDER|SM_DING, 3, 3,
+ /* TRANSLATORS: Arg is a mailbox name */
+ _("Can't delete %s"), mbox);
+
+ if(mbox)
+ fs_give((void **) &mbox);
+ }
+ }
+
+ return(!problem);
+}
+
+
+/*----------------------------------------------------------------------
+ Parse the given header text for any given fields
+
+Args: text -- Text to parse for fcc and attachments refs
+ fields -- array of field names to look for
+ values -- array of pointer to save values to, returned NULL if
+ fields isn't in text.
+
+This function simply looks for the given fields in the given header
+text string.
+NOTE: newlines are expected CRLF, and we'll ignore continuations
+ ----*/
+void
+simple_header_parse(char *text, char **fields, char **values)
+{
+ int i, n;
+ char *p, *t;
+
+ for(i = 0; fields[i]; i++)
+ values[i] = NULL; /* clear values array */
+
+ /*---- Loop until the end of the header ----*/
+ for(p = text; *p; ){
+ for(i = 0; fields[i]; i++) /* find matching field? */
+ if(!struncmp(p, fields[i], (n=strlen(fields[i]))) && p[n] == ':'){
+ for(p += n + 1; *p; p++){ /* find start of value */
+ if(*p == '\015' && *(p+1) == '\012'
+ && !isspace((unsigned char) *(p+2)))
+ break;
+
+ if(!isspace((unsigned char) *p))
+ break; /* order here is key... */
+ }
+
+ if(!values[i]){ /* if we haven't already */
+ values[i] = fs_get(strlen(text) + 1);
+ values[i][0] = '\0'; /* alloc space for it */
+ }
+
+ if(*p && *p != '\015'){ /* non-blank value. */
+ t = values[i] + (values[i][0] ? strlen(values[i]) : 0);
+ while(*p){ /* check for cont'n lines */
+ if(*p == '\015' && *(p+1) == '\012'){
+ if(isspace((unsigned char) *(p+2))){
+ p += 2;
+ continue;
+ }
+ else
+ break;
+ }
+
+ *t++ = *p++;
+ }
+
+ *t = '\0';
+ }
+
+ break;
+ }
+
+ /* Skip to end of line, what ever it was */
+ for(; *p ; p++)
+ if(*p == '\015' && *(p+1) == '\012'){
+ p += 2;
+ break;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ build a fresh REPLY_S from the given string (see pine_send for format)
+
+Args: s -- "X-Reply-UID" header value
+
+Returns: filled in REPLY_S or NULL on parse error
+ ----*/
+REPLY_S *
+build_reply_uid(char *s)
+{
+ char *p, *prefix = NULL, *val, *seq, *mbox;
+ int i, nseq, forwarded = 0;
+ REPLY_S *reply = NULL;
+
+ /* FORMAT: (n prefix)(n validity uidlist)mailbox */
+ /* if 'n prefix' is empty, uid list represents forwarded msgs */
+ if(*s == '('){
+ if(*(p = s + 1) == ')'){
+ forwarded = 1;
+ }
+ else{
+ for(; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+
+ if((i = atoi(s+1)) && i < strlen(p)){
+ prefix = p;
+ *(p += i) = '\0';
+ }
+ }
+ else
+ return(NULL);
+ }
+
+ if(*++p == '(' && *++p){
+ for(seq = p; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+ for(val = p; isdigit(*p); p++)
+ ;
+
+ if(*p == ' '){
+ *p++ = '\0';
+
+ if((nseq = atoi(seq)) && isdigit(*(seq = p))
+ && (p = strchr(p, ')')) && *(mbox = ++p)){
+ imapuid_t *uidl;
+
+ uidl = (imapuid_t *) fs_get ((nseq+1)*sizeof(imapuid_t));
+ for(i = 0; i < nseq; i++)
+ if((p = strchr(seq,',')) != NULL){
+ *p = '\0';
+ if((uidl[i]= strtoul(seq,NULL,10)) != 0)
+ seq = ++p;
+ else
+ break;
+ }
+ else if((p = strchr(seq, ')')) != NULL){
+ if((uidl[i] = strtoul(seq,NULL,10)) != 0)
+ i++;
+
+ break;
+ }
+
+ if(i == nseq){
+ reply = (REPLY_S *)fs_get(sizeof(REPLY_S));
+ memset(reply, 0, sizeof(REPLY_S));
+ reply->uid = 1;
+ reply->data.uid.validity = strtoul(val, NULL, 10);
+ if(forwarded)
+ reply->forwarded = 1;
+ else
+ reply->prefix = cpystr(prefix);
+
+ reply->mailbox = cpystr(mbox);
+ uidl[nseq] = 0;
+ reply->data.uid.msgs = uidl;
+ }
+ else
+ fs_give((void **) &uidl);
+ }
+ }
+ }
+ }
+ }
+
+ return(reply);
+}
+
+
+/*
+ * pine_new_env - allocate a new METAENV and fill it in.
+ */
+METAENV *
+pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom)
+{
+ int cnt, i, stdcnt;
+ char *p;
+ PINEFIELD *pfields, *pf, **sending_order;
+ METAENV *header;
+
+ header = (METAENV *) fs_get(sizeof(METAENV));
+
+ /* how many fields are there? */
+ for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
+ ;
+
+ stdcnt = cnt;
+
+ for(pf = custom; pf; pf = pf->next)
+ cnt++;
+
+ /* temporary PINEFIELD array */
+ i = (cnt + 1) * sizeof(PINEFIELD);
+ pfields = (PINEFIELD *)fs_get((size_t) i);
+ memset(pfields, 0, (size_t) i);
+
+ i = (cnt + 1) * sizeof(PINEFIELD *);
+ sending_order = (PINEFIELD **)fs_get((size_t) i);
+ memset(sending_order, 0, (size_t) i);
+
+ header->env = outgoing;
+ header->local = pfields;
+ header->custom = custom;
+ header->sending_order = sending_order;
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+# define NN 4
+#else
+# define NN 3
+#endif
+
+ /* initialize pfield */
+ pf = pfields;
+ for(i=0; i < stdcnt; i++, pf++){
+
+ pf->name = cpystr(pf_template[i].name);
+ if(i == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
+ /* slide string over so it is Sender instead of X-X-Sender */
+ for(p=pf->name; *(p+1); p++)
+ *p = *(p+4);
+
+ pf->type = pf_template[i].type;
+ pf->canedit = pf_template[i].canedit;
+ pf->rcptto = pf_template[i].rcptto;
+ pf->writehdr = pf_template[i].writehdr;
+ pf->localcopy = pf_template[i].localcopy;
+ pf->extdata = NULL; /* unused */
+ pf->next = pf + 1;
+
+ switch(pf->type){
+ case FreeText:
+ switch(i){
+ case N_AUTHRCVD:
+ sending_order[0] = pf;
+ break;
+
+ case N_NEWS:
+ pf->text = &outgoing->newsgroups;
+ sending_order[1] = pf;
+ break;
+
+ case N_DATE:
+ pf->text = (char **) &outgoing->date;
+ sending_order[2] = pf;
+ break;
+
+ case N_INREPLY:
+ pf->text = &outgoing->in_reply_to;
+ sending_order[NN+9] = pf;
+ break;
+
+ case N_MSGID:
+ pf->text = &outgoing->message_id;
+ sending_order[NN+10] = pf;
+ break;
+
+ case N_REF: /* won't be used here */
+ sending_order[NN+11] = pf;
+ break;
+
+ case N_PRIORITY:
+ sending_order[NN+12] = pf;
+ break;
+
+ case N_USERAGENT:
+ pf->text = &pf->textbuf;
+ pf->textbuf = generate_user_agent();
+ sending_order[NN+13] = pf;
+ break;
+
+ case N_POSTERR: /* won't be used here */
+ sending_order[NN+14] = pf;
+ break;
+
+ case N_RPLUID: /* won't be used here */
+ sending_order[NN+15] = pf;
+ break;
+
+ case N_RPLMBOX: /* won't be used here */
+ sending_order[NN+16] = pf;
+ break;
+
+ case N_SMTP: /* won't be used here */
+ sending_order[NN+17] = pf;
+ break;
+
+ case N_NNTP: /* won't be used here */
+ sending_order[NN+18] = pf;
+ break;
+
+ case N_CURPOS: /* won't be used here */
+ sending_order[NN+19] = pf;
+ break;
+
+ case N_OURREPLYTO: /* won't be used here */
+ sending_order[NN+20] = pf;
+ break;
+
+ case N_OURHDRS: /* won't be used here */
+ sending_order[NN+21] = pf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Internal error: 1)FreeText header %s", comatose(i));
+ break;
+ }
+
+ break;
+
+ case Attachment:
+ break;
+
+ case Address:
+ switch(i){
+ case N_FROM:
+ sending_order[3] = pf;
+ pf->addr = &outgoing->from;
+ break;
+
+ case N_TO:
+ sending_order[NN+2] = pf;
+ pf->addr = &outgoing->to;
+ if(tobufpp)
+ (*tobufpp) = &pf->scratch;
+
+ break;
+
+ case N_CC:
+ sending_order[NN+3] = pf;
+ pf->addr = &outgoing->cc;
+ break;
+
+ case N_BCC:
+ sending_order[NN+4] = pf;
+ pf->addr = &outgoing->bcc;
+ break;
+
+ case N_REPLYTO:
+ sending_order[NN+1] = pf;
+ pf->addr = &outgoing->reply_to;
+ break;
+
+ case N_LCC: /* won't be used here */
+ sending_order[NN+7] = pf;
+ break;
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ case N_SENDER:
+ sending_order[4] = pf;
+ pf->addr = &outgoing->sender;
+ break;
+#endif
+
+ case N_NOBODY: /* won't be used here */
+ sending_order[NN+5] = pf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Internal error: Address header %s", comatose(i));
+ break;
+ }
+ break;
+
+ case Fcc:
+ sending_order[NN+8] = pf;
+ pf->text = fccp;
+ break;
+
+ case Subject:
+ sending_order[NN+6] = pf;
+ pf->text = &outgoing->subject;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,3,
+ "Unknown header type %d in pine_new_send", (void *)pf->type);
+ break;
+ }
+ }
+
+ if(((--pf)->next = custom) != NULL){
+ i--;
+
+ /*
+ * NOTE: "i" is assumed to now index first custom field in sending
+ * order.
+ */
+ for(pf = pf->next; pf && pf->name; pf = pf->next){
+ if(pf->standard)
+ continue;
+
+ pf->canedit = 1;
+ pf->rcptto = 0;
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+
+ switch(pf->type){
+ case Address:
+ if(pf->addr){ /* better be set */
+ char *addr = NULL;
+ BuildTo bldto;
+
+ bldto.type = Str;
+ bldto.arg.str = pf->textbuf;
+
+ sending_order[i++] = pf;
+ /* change default text into an ADDRESS */
+ /* strip quotes around whole default */
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address_internal(bldto, &addr, NULL, NULL, NULL, NULL, NULL, 0, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+ }
+
+ break;
+
+ case FreeText:
+ sending_order[i++] = pf;
+ pf->text = &pf->textbuf;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
+ (void *)pf->type);
+ break;
+ }
+ }
+ }
+
+
+ return(header);
+}
+
+
+void
+pine_free_env(METAENV **menv)
+{
+ int cnt;
+
+
+ if((*menv)->local){
+ for(cnt = 0; pf_template && pf_template[cnt].name; cnt++)
+ ;
+
+ for(; cnt >= 0; cnt--){
+ if((*menv)->local[cnt].textbuf)
+ fs_give((void **) &(*menv)->local[cnt].textbuf);
+
+ fs_give((void **) &(*menv)->local[cnt].name);
+ }
+
+ fs_give((void **) &(*menv)->local);
+ }
+
+ if((*menv)->sending_order)
+ fs_give((void **) &(*menv)->sending_order);
+
+ fs_give((void **) menv);
+}
+
+
+/*----------------------------------------------------------------------
+ Check for addresses the user is not permitted to send to, or probably
+ doesn't want to send to
+
+Returns: 0 if OK
+ 1 if there are only empty groups
+ -1 if the message shouldn't be sent
+
+Queues a message indicating what happened
+ ---*/
+int
+check_addresses(METAENV *header)
+{
+ PINEFIELD *pf;
+ ADDRESS *a;
+ int send_daemon = 0, rv = CA_EMPTY;
+
+ /*---- Is he/she trying to send mail to the mailer-daemon ----*/
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ for(a = *pf->addr; a != NULL; a = a->next){
+ if(a->host && (a->host[0] == '.'
+ || (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
+ && a->host[0] == '@'))){
+ q_status_message2(SM_ORDER, 4, 7,
+ /* TRANSLATORS: First arg is the address we can't
+ send to, second arg is "not in addressbook". */
+ _("Can't send to address %s: %s"),
+ a->mailbox,
+ (a->host[0] == '.')
+ ? a->host
+ : _("not in addressbook"));
+ return(CA_BAD);
+ }
+ else if(ps_global->restricted
+ && !address_is_us(*pf->addr, ps_global)){
+ q_status_message(SM_ORDER, 3, 3,
+ "Restricted demo version of Alpine. You may only send mail to yourself");
+ return(CA_BAD);
+ }
+ else if(a->mailbox && strucmp(a->mailbox, "mailer-daemon") == 0 && !send_daemon){
+ send_daemon = 1;
+ rv = (pith_opt_daemon_confirm && (*pith_opt_daemon_confirm)()) ? CA_OK : CA_BAD;
+ }
+ else if(a->mailbox && a->host){
+ rv = CA_OK;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * If this isn't general enough we can modify it. The value passed in
+ * is expected to be one of the desc settings from the priorities array,
+ * like "High". The header value is X-Priority: 2 (High)
+ * or something similar. If value doesn't match any of the values then
+ * the actual value is used instead.
+ */
+PINEFIELD *
+set_priority_header(METAENV *header, char *value)
+{
+ PINEFIELD *pf;
+
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == FreeText && !strcmp(pf->name, PRIORITYNAME))
+ break;
+
+ if(pf){
+ if(pf->textbuf)
+ fs_give((void **) &pf->textbuf);
+
+ if(value){
+ PRIORITY_S *p;
+
+ for(p = priorities; p && p->desc; p++)
+ if(!strcmp(p->desc, value))
+ break;
+
+ if(p && p->desc){
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), "%d (%s)", p->val, p->desc);
+ pf->textbuf = cpystr(buf);
+ }
+ else
+ pf->textbuf = cpystr(value);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Set answered flags for messages specified by reply structure
+
+Args: reply --
+
+Returns: with appropriate flags set and index cache entries suitably tweeked
+----*/
+void
+update_answered_flags(REPLY_S *reply)
+{
+ char *seq = NULL, *p;
+ long i, ourstream = 0, we_cancel = 0;
+ MAILSTREAM *stream = NULL;
+
+ /* nothing to flip in a pseudo reply */
+ if(reply && (reply->msgno || reply->uid)){
+ int j;
+ MAILSTREAM *m;
+
+ /*
+ * If an established stream will do, use it, else
+ * build one unless we have an array of msgno's...
+ *
+ * I was just mimicking what was already here. I don't really
+ * understand why we use strcmp instead of same_stream_and_mailbox().
+ * Or sp_stream_get(reply->mailbox, SP_MATCH).
+ * Hubert 2003-07-09
+ */
+ for(j = 0; !stream && j < ps_global->s_pool.nstream; j++){
+ m = ps_global->s_pool.streams[j];
+ if(m && reply->mailbox && m->mailbox
+ && !strcmp(reply->mailbox, m->mailbox))
+ stream = m;
+ }
+
+ if(!stream && reply->msgno)
+ return;
+
+ /*
+ * This is here only for people who ran pine4.42 and are
+ * processing postponed mail from 4.42 now. Pine4.42 saved the
+ * original mailbox name in the canonical name's position in
+ * the postponed-msgs folder so it won't match the canonical
+ * name from the stream.
+ */
+ if(!stream && (!reply->origmbox ||
+ (reply->mailbox &&
+ !strcmp(reply->origmbox, reply->mailbox))))
+ stream = sp_stream_get(reply->mailbox, SP_MATCH);
+
+ /* TRANSLATORS: program is busy updating the Answered flags so warns user */
+ we_cancel = busy_cue(_("Updating \"Answered\" Flags"), NULL, 0);
+ if(!stream){
+ if((stream = pine_mail_open(NULL,
+ reply->origmbox ? reply->origmbox
+ : reply->mailbox,
+ OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
+ NULL)) != NULL){
+ ourstream++;
+ }
+ else{
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return;
+ }
+ }
+
+ if(stream->uid_validity == reply->data.uid.validity){
+ for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
+ if(i){
+ sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ sstrncpy(&p, ulong2string(reply->data.uid.msgs[i]),
+ SIZEOF_20KBUF-(p-tmp_20k_buf));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ if(reply->forwarded){
+ /*
+ * $Forwarded is a regular keyword so we only try to
+ * set it if the stream allows keywords.
+ * We could mess up if the stream has keywords but just
+ * isn't allowing anymore and $Forwarded already exists,
+ * but what are the odds?
+ */
+ if(stream && stream->kwd_create)
+ mail_flag(stream, seq = cpystr(tmp_20k_buf),
+ FORWARDED_FLAG,
+ ST_SET | ((reply->uid) ? ST_UID : 0L));
+ }
+ else
+ mail_flag(stream, seq = cpystr(tmp_20k_buf),
+ "\\ANSWERED",
+ ST_SET | ((reply->uid) ? ST_UID : 0L));
+
+ if(seq)
+ fs_give((void **)&seq);
+ }
+
+ if(ourstream)
+ pine_mail_close(stream); /* clean up dangling stream */
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+ }
+}
+
+
+/*
+ * phone_home_from - make phone home request's from address IMpersonal.
+ * Doesn't include user's personal name.
+ */
+ADDRESS *
+phone_home_from(void)
+{
+ ADDRESS *addr = mail_newaddr();
+ char tmp[32];
+
+ /* garble up mailbox name */
+ snprintf(tmp, sizeof(tmp), "hash_%08u", phone_home_hash(ps_global->VAR_USER_ID));
+ tmp[sizeof(tmp)-1] = '\0';
+ addr->mailbox = cpystr(tmp);
+ addr->host = cpystr(ps_global->maildomain);
+ return(addr);
+}
+
+
+/*
+ * one-way-hash a username into an 8-digit decimal number
+ *
+ * Corey Satten, corey@cac.washington.edu, 7/15/98
+ */
+unsigned int
+phone_home_hash(char *s)
+{
+ unsigned int h;
+
+ for (h=0; *s; ++s) {
+ if (h & 1)
+ h = (h>>1) | (PH_MAXHASH/2);
+ else
+ h = (h>>1);
+
+ h = ((h+1) * ((unsigned char) *s)) & (PH_MAXHASH - 1);
+ }
+
+ return (h);
+}
+
+
+/*----------------------------------------------------------------------
+ Call the mailer, SMTP, sendmail or whatever
+
+Args: header -- full header (envelope and local parts) of message to send
+ body -- The full body of the message including text
+ alt_smtp_servers --
+ verbosefile -- non-null means caller wants verbose interaction and the resulting
+ output file name to be returned
+
+Returns: -1 if failed, 1 if succeeded
+----*/
+int
+call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_servers,
+ int flags, void (*bigresult_f)(char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char error_buf[200], *error_mess = NULL, *postcmd;
+ ADDRESS *a;
+ ENVELOPE *fake_env = NULL;
+ int addr_error_count, we_cancel = 0;
+ long smtp_opts = 0L;
+ char *verbose_file = NULL;
+ BODY *bp = NULL;
+ PINEFIELD *pf;
+ BODY *origBody = body;
+
+ dprint((4, "Sending mail...\n"));
+
+ /* Check for any recipients */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ break;
+
+ if(!pf){
+ q_status_message(SM_ORDER,3,3,
+ _("Can't send message. No recipients specified!"));
+ return(0);
+ }
+
+#ifdef SMIME
+ if(ps_global->smime && (ps_global->smime->do_encrypt || ps_global->smime->do_sign)){
+ int result;
+
+ STORE_S *so = lmc.so;
+ lmc.so = NULL;
+
+ result = 1;
+
+ if(ps_global->smime->do_encrypt)
+ result = encrypt_outgoing_message(header, &body);
+
+ /* need to free new body from encrypt if sign fails? */
+ if(result && ps_global->smime->do_sign)
+ result = sign_outgoing_message(header, &body, ps_global->smime->do_encrypt);
+
+ lmc.so = so;
+
+ if(!result)
+ return 0;
+ }
+#endif
+
+ /* set up counts and such to keep track sent percentage */
+ send_bytes_sent = 0;
+ gf_filter_init(); /* zero piped byte count, 'n */
+ send_bytes_to_send = send_body_size(body); /* count body bytes */
+ ps_global->c_client_error[0] = error_buf[0] = '\0';
+ we_cancel = busy_cue(_("Sending mail"),
+ send_bytes_to_send ? sent_percent : NULL, 0);
+
+#ifndef _WINDOWS
+
+ /* try posting via local "<mta> <-t>" if specified */
+ if(mta_handoff(header, body, error_buf, sizeof(error_buf), bigresult_f, pipecb_f)){
+ if(error_buf[0])
+ error_mess = error_buf;
+
+ goto done;
+ }
+
+#endif
+
+ /*
+ * If the user's asked for it, and we find that the first text
+ * part (attachments all get b64'd) is non-7bit, ask for 8BITMIME.
+ */
+ if(F_ON(F_ENABLE_8BIT, ps_global) && (bp = first_text_8bit(body)))
+ smtp_opts |= SOP_8BITMIME;
+
+#ifdef DEBUG
+#ifndef DEBUGJOURNAL
+ if(debug > 5 || (flags & CM_VERBOSE))
+#endif
+ smtp_opts |= SOP_DEBUG;
+#endif
+
+ if(flags & (CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL)){
+ smtp_opts |= SOP_DSN;
+ if(!(flags & CM_DSN_NEVER)){ /* if never, don't turn others on */
+ if(flags & CM_DSN_DELAY)
+ smtp_opts |= SOP_DSN_NOTIFY_DELAY;
+ if(flags & CM_DSN_SUCCESS)
+ smtp_opts |= SOP_DSN_NOTIFY_SUCCESS;
+
+ /*
+ * If it isn't Never, then we're always going to let them
+ * know about failures. This means we don't allow for the
+ * possibility of setting delay or success without failure.
+ */
+ smtp_opts |= SOP_DSN_NOTIFY_FAILURE;
+
+ if(flags & CM_DSN_FULL)
+ smtp_opts |= SOP_DSN_RETURN_FULL;
+ }
+ }
+
+
+ /*
+ * Set global header pointer so post_rfc822_output can get at it when
+ * it's called back from c-client's sending routine...
+ */
+ send_header = header;
+
+ /*
+ * Fabricate a fake ENVELOPE to hand c-client's SMTP engine.
+ * The purpose is to give smtp_mail the list for SMTP RCPT when
+ * there are recipients in pine's METAENV that are outside c-client's
+ * envelope.
+ *
+ * NOTE: If there aren't any, don't bother. Dealt with it below.
+ */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr
+ && !(*pf->addr == header->env->to || *pf->addr == header->env->cc
+ || *pf->addr == header->env->bcc))
+ break;
+
+ if(pf && pf->name){
+ ADDRESS **tail;
+
+ fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
+ memset(fake_env, 0, sizeof(ENVELOPE));
+ fake_env->return_path = rfc822_cpy_adr(header->env->return_path);
+ tail = &(fake_env->to);
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
+ *tail = rfc822_cpy_adr(*pf->addr);
+ while(*tail)
+ tail = &((*tail)->next);
+ }
+ }
+
+ /*
+ * Install our rfc822 output routine
+ */
+ sending_hooks.rfc822_out = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
+ (void)mail_parameters(NULL, SET_RFC822OUTPUT, (void *)post_rfc822_output);
+
+ /*
+ * Allow for verbose posting
+ */
+ (void) mail_parameters(NULL, SET_SMTPVERBOSE,
+ (void *) pine_smtp_verbose_out);
+
+ /*
+ * We do this because we want mm_log to put the error message into
+ * c_client_error instead of showing it itself.
+ */
+ ps_global->noshow_error = 1;
+
+ /*
+ * OK, who posts what? We tried an mta_handoff above, but there
+ * was either none specified or we decided not to use it. So,
+ * if there's an smtp-server defined anywhere,
+ */
+ if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){
+ /*---------- SMTP ----------*/
+ dprint((4, "call_mailer: via TCP (%s)\n",
+ alt_smtp_servers[0]));
+ TIME_STAMP("smtp-open start (tcp)", 1);
+ sending_stream = smtp_open(alt_smtp_servers, smtp_opts);
+ }
+ else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
+ && ps_global->VAR_SMTP_SERVER[0][0]){
+ /*---------- SMTP ----------*/
+ dprint((4, "call_mailer: via TCP\n"));
+ TIME_STAMP("smtp-open start (tcp)", 1);
+ sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts);
+ }
+ else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
+ char *cmdlist[2];
+
+ /*----- Send via LOCAL SMTP agent ------*/
+ dprint((4, "call_mailer: via \"%s\"\n", postcmd));
+
+ TIME_STAMP("smtp-open start (pipe)", 1);
+ fs_give((void **) &postcmd);
+ cmdlist[0] = "localhost";
+ cmdlist[1] = NULL;
+ sending_stream = smtp_open_full(&piped_io, cmdlist, "smtp",
+ SMTPTCPPORT, smtp_opts);
+/* BUG: should provide separate stderr output! */
+ }
+
+ ps_global->noshow_error = 0;
+
+ TIME_STAMP("smtp open", 1);
+ if(sending_stream){
+ unsigned short save_encoding, added_encoding;
+
+ dprint((1, "Opened SMTP server \"%s\"\n",
+ net_host(sending_stream->netstream)
+ ? net_host(sending_stream->netstream) : "?"));
+
+ if(flags & CM_VERBOSE){
+ TIME_STAMP("verbose start", 1);
+ if((verbose_file = temp_nam(NULL, "sd")) != NULL){
+ if((verbose_send_output = our_fopen(verbose_file, "w")) != NULL){
+ if(!smtp_verbose(sending_stream)){
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ "Mail not sent. VERBOSE mode error%s%.50s.",
+ (sending_stream && sending_stream->reply)
+ ? ": ": "",
+ (sending_stream && sending_stream->reply)
+ ? sending_stream->reply : "");
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+ }
+ else{
+ our_unlink(verbose_file);
+ strncpy(error_mess = error_buf,
+ "Can't open tmp file for VERBOSE mode.", sizeof(error_buf));
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+ }
+ else{
+ strncpy(error_mess = error_buf,
+ "Can't create tmp file name for VERBOSE mode.", sizeof(error_buf));
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+
+ TIME_STAMP("verbose end", 1);
+ }
+
+ /*
+ * Before we actually send data, see if we have to protect
+ * the first text body part from getting encoded. We protect
+ * it from getting encoded in "pine_rfc822_output_body" by
+ * temporarily inventing a synonym for ENC8BIT...
+ * This works like so:
+ * Suppose bp->encoding is set to ENC8BIT.
+ * We change that here to some unused value (added_encoding) and
+ * set body_encodings[added_encoding] to "8BIT".
+ * Then post_rfc822_output is called which calls
+ * pine_rfc822_output_body. Inside that routine
+ * pine_write_body_header writes out the encoding for the
+ * part. Normally it would see encoding == ENC8BIT and it would
+ * change that to QUOTED-PRINTABLE, but since encoding has been
+ * set to added_encoding it uses body_encodings[added_encoding]
+ * which is "8BIT" instead. Then the actual body is written by
+ * pine_write_body_header which does not do the gf_8bit_qp
+ * filtering because encoding != ENC8BIT (instead it's equal
+ * to added_encoding).
+ */
+ if(bp && sending_stream->protocol.esmtp.eightbit.ok
+ && sending_stream->protocol.esmtp.eightbit.want){
+ int i;
+
+ for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
+ ;
+
+ if(i > ENCMAX){ /* no empty encoding slots! */
+ bp = NULL;
+ }
+ else {
+ added_encoding = i;
+ body_encodings[added_encoding] = body_encodings[ENC8BIT];
+ save_encoding = bp->encoding;
+ bp->encoding = added_encoding;
+ }
+ }
+
+ if(sending_stream->protocol.esmtp.ok
+ && sending_stream->protocol.esmtp.dsn.want
+ && !sending_stream->protocol.esmtp.dsn.ok)
+ q_status_message(SM_ORDER,3,3,
+ _("Delivery Status Notification not available from this server."));
+
+ TIME_STAMP("smtp start", 1);
+ if(!error_mess && !smtp_mail(sending_stream, "MAIL",
+ fake_env ? fake_env : header->env, body)){
+
+ snprintf(error_buf, sizeof(error_buf),
+ _("Mail not sent. Sending error%s%s"),
+ (sending_stream && sending_stream->reply) ? ": ": ".",
+ (sending_stream && sending_stream->reply)
+ ? sending_stream->reply : "");
+ error_buf[sizeof(error_buf)-1] = '\0';
+ dprint((1, error_buf));
+ addr_error_count = 0;
+ if(fake_env){
+ for(a = fake_env->to; a != NULL; a = a->next)
+ if(a->error != NULL){
+ if(addr_error_count++ < MAX_ADDR_ERROR){
+
+ /*
+ * Too complicated to figure out which header line
+ * has the error in the fake_env case, so just
+ * leave cursor at default.
+ */
+
+
+ if(error_mess) /* previous error? */
+ q_status_message(SM_ORDER, 4, 7, error_mess);
+
+ error_mess = tidy_smtp_mess(a->error,
+ _("Mail not sent: %.80s"),
+ error_buf, sizeof(error_buf));
+ }
+
+ dprint((1, "Send Error: \"%s\"\n",
+ a->error));
+ }
+ }
+ else{
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ for(a = *pf->addr; a != NULL; a = a->next)
+ if(a->error != NULL){
+ if(addr_error_count++ < MAX_ADDR_ERROR){
+
+ if(error_mess) /* previous error? */
+ q_status_message(SM_ORDER, 4, 7, error_mess);
+
+ error_mess = tidy_smtp_mess(a->error,
+ _("Mail not sent: %.80s"),
+ error_buf, sizeof(error_buf));
+ }
+
+ dprint((1, "Send Error: \"%s\"\n",
+ a->error));
+ }
+ }
+
+ if(!error_mess)
+ error_mess = error_buf;
+ }
+
+ /* repair modified "body_encodings" array? */
+ if(bp && sending_stream->protocol.esmtp.eightbit.ok
+ && sending_stream->protocol.esmtp.eightbit.want){
+ body_encodings[added_encoding] = NULL;
+ bp->encoding = save_encoding;
+ }
+
+ TIME_STAMP("smtp closing", 1);
+ smtp_close(sending_stream);
+ sending_stream = NULL;
+ TIME_STAMP("smtp done", 1);
+ }
+ else if(!error_mess){
+ snprintf(error_mess = error_buf, sizeof(error_buf), _("Error sending%.2s%.80s"),
+ ps_global->c_client_error[0] ? ": " : "",
+ ps_global->c_client_error);
+ error_buf[sizeof(error_buf)-1] = '\0';
+ }
+
+ if(verbose_file){
+ if(verbose_send_output){
+ TIME_STAMP("verbose start", 1);
+ fclose(verbose_send_output);
+ verbose_send_output = NULL;
+ q_status_message(SM_ORDER, 0, 3, "Verbose SMTP output received");
+
+ if(bigresult_f)
+ (*bigresult_f)(verbose_file, CM_BR_VERBOSE);
+
+ TIME_STAMP("verbose end", 1);
+ }
+
+ fs_give((void **)&verbose_file);
+ }
+
+ /*
+ * Restore original 822 emitter...
+ */
+ (void) mail_parameters(NULL, SET_RFC822OUTPUT, sending_hooks.rfc822_out);
+
+ if(fake_env)
+ mail_free_envelope(&fake_env);
+
+ done:
+
+#ifdef SMIME
+ /* Free replacement encrypted body */
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && body != origBody){
+
+ if(body->type == TYPEMULTIPART){
+ /* Just get rid of first part, it's actually origBody */
+ void *x = body->nested.part;
+
+ body->nested.part = body->nested.part->next;
+
+ fs_give(&x);
+ }
+
+ pine_free_body(&body);
+ }
+#endif
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ TIME_STAMP("call_mailer done", 1);
+ /*-------- Did message make it ? ----------*/
+ if(error_mess){
+ /*---- Error sending mail -----*/
+ if(lmc.so && !lmc.all_written)
+ so_give(&lmc.so);
+
+ if(error_mess){
+ q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess);
+ dprint((1, "call_mailer ERROR: %s\n", error_mess));
+ }
+
+ return(-1);
+ }
+ else{
+ lmc.all_written = 1;
+ return(1);
+ }
+}
+
+
+/*
+ * write_postponed - exported method to write the given message
+ * to the postponed folder
+ */
+int
+write_postponed(METAENV *header, struct mail_bodystruct *body)
+{
+ char **pp, *folder;
+ int rv = 0, sz;
+ CONTEXT_S *fcc_cntxt = NULL;
+ PINEFIELD *pf;
+ static char *writem[] = {"To", "References", "Fcc", "X-Reply-UID", NULL};
+
+ if(!ps_global->VAR_POSTPONED_FOLDER
+ || !ps_global->VAR_POSTPONED_FOLDER[0]){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("No postponed file defined"));
+ return(-1);
+ }
+
+ folder = cpystr(ps_global->VAR_POSTPONED_FOLDER);
+
+ lmc.all_written = lmc.text_written = lmc.text_only = 0;
+
+ lmc.so = open_fcc(folder, &fcc_cntxt, 1, NULL, NULL);
+
+ if(lmc.so){
+ /* BUG: writem sufficient ? */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ for(pp = writem; *pp; pp++)
+ if(!strucmp(pf->name, *pp)){
+ pf->localcopy = 1;
+ pf->writehdr = 1;
+ break;
+ }
+
+ /*
+ * Work around c-client reply-to bug. C-client will
+ * return a reply_to in an envelope even if there is
+ * no reply-to header field. We want to note here whether
+ * the reply-to is real or not.
+ */
+ if(header->env->reply_to || hdr_is_in_list("reply-to", header->custom))
+ for(pf = header->local; pf; pf = pf->next)
+ if(!strcmp(pf->name, "Reply-To")){
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+ if(header->env->reply_to)
+ pf->textbuf = cpystr("Full");
+ else
+ pf->textbuf = cpystr("Empty");
+ }
+
+ /*
+ * Write the list of custom headers to the
+ * X-Our-Headers header so that we can recover the
+ * list in redraft.
+ */
+ sz = 0;
+ for(pf = header->custom; pf && pf->name; pf = pf->next)
+ sz += strlen(pf->name) + 1;
+
+ if(sz){
+ int i;
+ char *pstart, *pend;
+
+ for(i = 0, pf = header->local; i != N_OURHDRS; i++, pf = pf->next)
+ ;
+
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+ pf->textbuf = pstart = pend = (char *) fs_get(sz + 1);
+ pf->text = &pf->textbuf;
+ pf->textbuf[sz] = '\0'; /* tie off overflow */
+ /* note: "pf" overloaded */
+ for(pf = header->custom; pf && pf->name; pf = pf->next){
+ int r = sz - (pend - pstart); /* remaining buffer */
+
+ if(r > 0 && r != sz){
+ r--;
+ *pend++ = ',';
+ }
+
+ sstrncpy(&pend, pf->name, r);
+ }
+ }
+
+ if(pine_rfc822_output(header, body, NULL, NULL) < 0
+ || write_fcc(folder, fcc_cntxt, lmc.so, NULL, "postponed message", NULL) < 0)
+ rv = -1;
+
+ so_give(&lmc.so);
+ }
+ else {
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Can't allocate internal storage: %s ",
+ error_description(errno));
+ rv = -1;
+ }
+
+ fs_give((void **) &folder);
+ return(rv);
+}
+
+
+int
+commence_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int forced)
+{
+ if(fcc && *fcc){
+ lmc.all_written = lmc.text_written = 0;
+ lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
+ return((lmc.so = open_fcc(fcc, fcc_cntxt, 0, NULL, NULL)) != NULL);
+ }
+ else
+ lmc.so = NULL;
+
+ return(TRUE);
+}
+
+
+int
+wrapup_fcc(char *fcc, CONTEXT_S *fcc_cntxt, METAENV *header, struct mail_bodystruct *body)
+{
+ int rv = TRUE;
+
+ if(lmc.so){
+ if(!header || pine_rfc822_output(header, body, NULL, NULL)){
+ char label[50];
+
+ strncpy(label, "Fcc", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
+ snprintf(label + 3, sizeof(label)-3, " to %.40s", fcc);
+ label[sizeof(label)-1] = '\0';
+ }
+
+ rv = write_fcc(fcc,fcc_cntxt,lmc.so,NULL,NULL,NULL);
+ }
+ else{
+ rv = FALSE;
+ }
+
+ so_give(&lmc.so);
+ }
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Checks to make sure the fcc is available and can be opened
+
+Args: fcc -- the name of the fcc to create. It can't be NULL.
+ fcc_cntxt -- Returns the context the fcc is in.
+ force -- supress user option prompt
+
+Returns allocated storage object on success, NULL on failure
+ ----*/
+STORE_S *
+open_fcc(char *fcc, CONTEXT_S **fcc_cntxt, int force, char *err_prefix, char *err_suffix)
+{
+ int exists, ok = 0;
+
+ ps_global->mm_log_error = 0;
+
+ /*
+ * check for fcc's existance...
+ */
+ TIME_STAMP("open_fcc start", 1);
+ if(!is_absolute_path(fcc) && context_isambig(fcc)
+ && (strucmp(ps_global->inbox_name, fcc) != 0)){
+ int flip_dot = 0;
+
+ /*
+ * Don't want to preclude a user from Fcc'ing a .name'd folder
+ */
+ if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global)){
+ flip_dot = 1;
+ F_TURN_ON(F_ENABLE_DOT_FOLDERS, ps_global);
+ }
+
+ /*
+ * We only want to set the "context" if fcc is an ambiguous
+ * name. Otherwise, our "relativeness" rules for contexts
+ * (implemented in context.c) might cause the name to be
+ * interpreted in the wrong context...
+ */
+ if(!(*fcc_cntxt || (*fcc_cntxt = default_save_context(ps_global->context_list))))
+ *fcc_cntxt = ps_global->context_list;
+
+ build_folder_list(NULL, *fcc_cntxt, fcc, NULL, BFL_FLDRONLY);
+ if(folder_index(fcc, *fcc_cntxt, FI_FOLDER) < 0){
+ if(force
+ || (pith_opt_save_create_prompt
+ && (*pith_opt_save_create_prompt)(*fcc_cntxt, fcc, 0) > 0)){
+
+ ps_global->noshow_error = 1;
+
+ if(context_create(*fcc_cntxt, NULL, fcc))
+ ok++;
+
+ ps_global->noshow_error = 0;
+ }
+ else
+ ok--; /* declined! */
+ }
+ else
+ ok++; /* found! */
+
+ if(flip_dot)
+ F_TURN_OFF(F_ENABLE_DOT_FOLDERS, ps_global);
+
+ free_folder_list(*fcc_cntxt);
+ }
+ else if((exists = folder_exists(NULL, fcc)) != FEX_ERROR){
+ if(exists & (FEX_ISFILE | FEX_ISDIR)){
+ ok++;
+ }
+ else{
+ if(force
+ || (pith_opt_save_create_prompt
+ && (*pith_opt_save_create_prompt)(NULL, fcc, 0) > 0)){
+
+ ps_global->mm_log_error = 0;
+ ps_global->noshow_error = 1;
+
+ ok = pine_mail_create(NULL, fcc) != 0L;
+
+ ps_global->noshow_error = 0;
+ }
+ else
+ ok--; /* declined! */
+ }
+ }
+
+ TIME_STAMP("open_fcc done.", 1);
+ if(ok > 0){
+ return(so_get(FCC_SOURCE, NULL, WRITE_ACCESS));
+ }
+ else{
+ int l1, l2, l3, wid, w;
+ char *errstr, tmp[MAILTMPLEN];
+ char *s1, *s2;
+
+ if(ok == 0){
+ if(ps_global->mm_log_error){
+ s1 = err_prefix ? err_prefix : "Fcc Error: ";
+ s2 = err_suffix ? err_suffix : " Message NOT sent or copied.";
+
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ l3 = strlen(ps_global->c_client_error);
+ wid = (ps_global->ttyo && ps_global->ttyo->screen_cols > 0)
+ ? ps_global->ttyo->screen_cols : 80;
+ w = wid - l1 - l2 - 5;
+
+ snprintf(errstr = tmp, sizeof(tmp),
+ "%.99s\"%.*s%.99s\".%.99s",
+ s1,
+ (l3 > w) ? MAX(w-3,0) : MAX(w,0),
+ ps_global->c_client_error,
+ (l3 > w) ? "..." : "",
+ s2);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ }
+ else
+ errstr = _("Fcc creation error. Message NOT sent or copied.");
+ }
+ else
+ errstr = _("Fcc creation rejected. Message NOT sent or copied.");
+
+ q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
+ }
+
+ return(NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ mail_append() the fcc accumulated in temp_storage to proper destination
+
+Args: fcc -- name of folder
+ fcc_cntxt -- context for folder
+ temp_storage -- String of file where Fcc has been accumulated
+
+This copies the string of file to the actual folder, which might be IMAP
+or a disk folder. The temp_storage is freed after it is written.
+An error message is produced if this fails.
+ ----*/
+int
+write_fcc(char *fcc, CONTEXT_S *fcc_cntxt, STORE_S *tmp_storage,
+ MAILSTREAM *stream, char *label, char *flags)
+{
+ STRING msg;
+ CONTEXT_S *cntxt;
+ int we_cancel = 0;
+
+ if(!tmp_storage)
+ return(0);
+
+ TIME_STAMP("write_fcc start.", 1);
+ dprint((4, "Writing %s\n", (label && *label) ? label : ""));
+ if(label && *label){
+ char msg_buf[80];
+
+ strncpy(msg_buf, "Writing ", sizeof(msg_buf));
+ msg_buf[sizeof(msg_buf)-1] = '\0';
+ strncat(msg_buf, label, sizeof(msg_buf)-10);
+ we_cancel = busy_cue(msg_buf, NULL, 0);
+ }
+ else
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ so_seek(tmp_storage, 0L, 0);
+
+/*
+ * Before changing this note that these lines depend on the
+ * definition of FCC_SOURCE.
+ */
+ INIT(&msg, mail_string, (void *)so_text(tmp_storage),
+ strlen((char *)so_text(tmp_storage)));
+
+ cntxt = fcc_cntxt;
+
+ if(!context_append_full(cntxt, stream, fcc, flags, NULL, &msg)){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ "Write to \"%s\" FAILED!!!", fcc);
+ dprint((1, "ERROR appending %s in \"%s\"",
+ fcc ? fcc : "?",
+ (cntxt && cntxt->context) ? cntxt->context : "NULL"));
+ return(0);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(label ? 0 : -1);
+
+ dprint((4, "done.\n"));
+ TIME_STAMP("write_fcc done.", 1);
+ return(1);
+}
+
+
+/*
+ * first_text_8bit - return TRUE if somewhere in the body 8BIT data's
+ * contained.
+ */
+BODY *
+first_text_8bit(struct mail_bodystruct *body)
+{
+ if(body->type == TYPEMULTIPART) /* advance to first contained part */
+ body = &body->nested.part->body;
+
+ return((body->type == TYPETEXT && body->encoding != ENC7BIT)
+ ? body : NULL);
+}
+
+
+/*
+ * Build and return the "From:" address for outbound messages from
+ * global data...
+ */
+ADDRESS *
+generate_from(void)
+{
+ ADDRESS *addr = mail_newaddr();
+ if(ps_global->VAR_PERSONAL_NAME){
+ addr->personal = cpystr(ps_global->VAR_PERSONAL_NAME);
+ removing_leading_and_trailing_white_space(addr->personal);
+ if(addr->personal[0] == '\0')
+ fs_give((void **)&addr->personal);
+ }
+
+ addr->mailbox = cpystr(ps_global->VAR_USER_ID);
+ addr->host = cpystr(ps_global->maildomain);
+ removing_leading_and_trailing_white_space(addr->mailbox);
+ removing_leading_and_trailing_white_space(addr->host);
+ return(addr);
+}
+
+
+/*
+ * set_mime_type_by_grope - sniff the given storage object to determine its
+ * type, subtype, encoding, and charset
+ *
+ * "Type" and "encoding" must be set before calling this routine.
+ * If "type" is set to something other than TYPEOTHER on entry,
+ * then that is the "type" we wish to use. Same for "encoding"
+ * using ENCOTHER instead of TYPEOTHER. Otherwise, we
+ * figure them out here. If "type" is already set, we also
+ * leave subtype alone. If not, we figure out subtype here.
+ * There is a chance that we will upgrade encoding to a "higher"
+ * level. For example, if it comes in as 7BIT we may change
+ * that to 8BIT if we find a From_ we want to escape.
+ * We may also set the charset attribute if the type is TEXT.
+ *
+ * NOTE: this is rather inefficient if the store object is a CharStar
+ * but the win is all types are handled the same
+ */
+void
+set_mime_type_by_grope(struct mail_bodystruct *body)
+{
+#define RBUFSZ (8193)
+ unsigned char *buf, *p, *bol;
+ register size_t n;
+ long max_line = 0L,
+ eight_bit_chars = 0L,
+ line_so_far = 0L,
+ len = 0L;
+ STORE_S *so = (STORE_S *)body->contents.text.data;
+ unsigned short new_encoding = ENCOTHER;
+ int we_cancel = 0;
+#ifdef ENCODE_FROMS
+ short froms = 0, dots = 0,
+ bmap = 0x1, dmap = 0x1;
+#endif
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ buf = (unsigned char *)fs_get(RBUFSZ);
+ so_seek(so, 0L, 0);
+
+ for(n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
+ ;
+
+ buf[n] = '\0';
+
+ if(n){ /* check first few bytes to look for magic numbers */
+ if(body->type == TYPEOTHER){
+ if(buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F'){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("GIF");
+ }
+ else if((n > 9) && buf[0] == 0xFF && buf[1] == 0xD8
+ && buf[2] == 0xFF && buf[3] == 0xE0
+ && !strncmp((char *)&buf[6], "JFIF", 4)){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("JPEG");
+ }
+ else if((buf[0] == 'M' && buf[1] == 'M')
+ || (buf[0] == 'I' && buf[1] == 'I')){
+ body->type = TYPEIMAGE;
+ body->subtype = cpystr("TIFF");
+ }
+ else if((buf[0] == '%' && buf[1] == '!')
+ || (buf[0] == '\004' && buf[1] == '%' && buf[2] == '!')){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("PostScript");
+ }
+ else if(buf[0] == '%' && !strncmp((char *)buf+1, "PDF-", 4)){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("PDF");
+ }
+ else if(buf[0] == '.' && !strncmp((char *)buf+1, "snd", 3)){
+ body->type = TYPEAUDIO;
+ body->subtype = cpystr("Basic");
+ }
+ else if((n > 3) && buf[0] == 0x00 && buf[1] == 0x05
+ && buf[2] == 0x16 && buf[3] == 0x00){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("APPLEFILE");
+ }
+ else if((n > 3) && buf[0] == 0x50 && buf[1] == 0x4b
+ && buf[2] == 0x03 && buf[3] == 0x04){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("ZIP");
+ }
+
+ /*
+ * if type was set above, but no encoding specified, go
+ * ahead and make it BASE64...
+ */
+ if(body->type != TYPEOTHER && body->encoding == ENCOTHER)
+ body->encoding = ENCBINARY;
+ }
+ }
+ else{
+ /* PROBLEM !!! */
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ if(body->encoding == ENCOTHER)
+ body->encoding = ENCBINARY;
+ }
+ }
+
+ if (body->encoding == ENCOTHER || body->type == TYPEOTHER){
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ int lastchar = '\0';
+#define BREAKOUT 300 /* a value that a character can't be */
+#endif
+
+ p = bol = buf;
+ len = n;
+ while (n--){
+/* Some people don't like quoted-printable caused by leading Froms */
+#ifdef ENCODE_FROMS
+ Find_Froms(froms, dots, bmap, dmap, *p);
+#endif
+ if(*p == '\n'){
+ max_line = MAX(max_line, line_so_far + p - bol);
+ bol = NULL; /* clear beginning of line */
+ line_so_far = 0L; /* clear line count */
+#if defined(DOS) || defined(OS2)
+ /* LF with no CR!! */
+ if(lastchar != '\r') /* must be non-text data! */
+ lastchar = BREAKOUT;
+#endif
+ }
+ else if(*p & 0x80){
+ eight_bit_chars++;
+ }
+ else if(!*p){
+ /* NULL found. Unless we're told otherwise, must be binary */
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ /*
+ * The "TYPETEXT" here handles the case that the NULL
+ * comes from imported text generated by some external
+ * editor that permits or inserts NULLS. Otherwise,
+ * assume it's a binary segment...
+ */
+ new_encoding = (body->type==TYPETEXT) ? ENC8BIT : ENCBINARY;
+
+ /*
+ * Since we've already set encoding, count this as a
+ * hi bit char and continue. The reason is that if this
+ * is text, there may be a high percentage of encoded
+ * characters, so base64 may get set below...
+ */
+ if(body->type == TYPETEXT)
+ eight_bit_chars++;
+ else
+ break;
+ }
+
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ if(lastchar != BREAKOUT)
+ lastchar = *p;
+#endif
+
+ /* read another buffer in */
+ if(n == 0){
+ if(bol)
+ line_so_far += p - bol;
+
+ for (n = 0; n < RBUFSZ-1 && so_readc(&buf[n], so) != 0; n++)
+ ;
+
+ len += n;
+ p = buf;
+ }
+ else
+ p++;
+
+ /*
+ * If there's no beginning-of-line pointer, then we must
+ * have seen an end-of-line. Set bol to the start of the
+ * new line...
+ */
+ if(!bol)
+ bol = p;
+
+#if defined(DOS) || defined(OS2) /* for binary file detection */
+ /* either a lone \r or lone \n indicate binary file */
+ if(lastchar == '\r' || lastchar == BREAKOUT){
+ if(lastchar == BREAKOUT || n == 0 || *p != '\n'){
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ new_encoding = ENCBINARY;
+ break;
+ }
+ }
+#endif
+ }
+ }
+
+ /* stash away for later */
+ so_attr(so, "maxline", long2string(max_line));
+
+ if(body->encoding == ENCOTHER || body->type == TYPEOTHER){
+ /*
+ * Since the type or encoding aren't set yet, fall thru a
+ * series of tests to make sure an adequate type and
+ * encoding are set...
+ */
+
+ if(max_line >= 1000L){ /* 1000 comes from rfc821 */
+ if(body->type == TYPEOTHER){
+ /*
+ * Since the types not set, then we didn't find a NULL.
+ * If there's no NULL, then this is likely text. However,
+ * since we can't be *completely* sure, we set it to
+ * the generic type.
+ */
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ if(new_encoding != ENCBINARY)
+ /*
+ * As with NULL handling, if we're told it's text,
+ * qp-encode it, else it gets base 64...
+ */
+ new_encoding = (body->type == TYPETEXT) ? ENC8BIT : ENCBINARY;
+ }
+
+ if(eight_bit_chars == 0L){
+ if(body->type == TYPEOTHER)
+ body->type = TYPETEXT;
+
+ if(new_encoding == ENCOTHER)
+ new_encoding = ENC7BIT; /* short lines, no 8 bit */
+ }
+ else if(len <= 3000L || (eight_bit_chars * 100L)/len < 30L){
+ /*
+ * The 30% threshold is based on qp encoded readability
+ * on non-MIME UA's.
+ */
+ if(body->type == TYPEOTHER)
+ body->type = TYPETEXT;
+
+ if(new_encoding != ENCBINARY)
+ new_encoding = ENC8BIT; /* short lines, < 30% 8 bit chars */
+ }
+ else{
+ if(body->type == TYPEOTHER){
+ body->type = TYPEAPPLICATION;
+ body->subtype = cpystr("octet-stream");
+ }
+
+ /*
+ * Apply maximal encoding regardless of previous
+ * setting. This segment's either not text, or is
+ * unlikely to be readable with > 30% of the
+ * text encoded anyway, so we might as well save space...
+ */
+ new_encoding = ENCBINARY; /* > 30% 8 bit chars */
+ }
+ }
+
+#ifdef ENCODE_FROMS
+ /* If there were From_'s at the beginning of a line or standalone dots */
+ if((froms || dots) && new_encoding != ENCBINARY)
+ new_encoding = ENC8BIT;
+#endif
+
+ /* Set the subtype */
+ if(body->subtype == NULL)
+ body->subtype = cpystr(rfc822_default_subtype(body->type));
+
+ if(body->encoding == ENCOTHER)
+ body->encoding = new_encoding;
+
+ fs_give((void **)&buf);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * Call this to set the charset of an attachment we have
+ * created. If the attachment contains any non-ascii characters
+ * then we'll set the charset to the passed in charset, otherwise
+ * we'll make it us-ascii.
+ */
+void
+set_charset_possibly_to_ascii(struct mail_bodystruct *body, char *charset)
+{
+ unsigned char c;
+ int can_be_ascii = 1;
+ STORE_S *so = (STORE_S *)body->contents.text.data;
+ int we_cancel = 0;
+
+ if(!body || body->type != TYPETEXT)
+ return;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ so_seek(so, 0L, 0);
+
+ while(can_be_ascii && so_readc(&c, so))
+ if(!c || c & 0x80)
+ can_be_ascii--;
+
+ if(can_be_ascii)
+ set_parameter(&body->parameter, "charset", "US-ASCII");
+ else if(charset && *charset && strucmp(charset, "US-ASCII"))
+ set_parameter(&body->parameter, "charset", charset);
+ else{
+ /*
+ * Else we don't know. There are non ascii characters but we either
+ * don't have a charset to set it to or that charset is just us_ascii,
+ * which is impossible. So we label it unknown. An alternative would
+ * have been to strip the high bits instead and label it ascii.
+ */
+ set_parameter(&body->parameter, "charset", UNKNOWN_CHARSET);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * since encoding happens on the way out the door, this is basically
+ * just needed to handle TYPEMULTIPART
+ */
+void
+pine_encode_body (struct mail_bodystruct *body)
+{
+ PART *part;
+
+ dprint((4, "-- pine_encode_body: %d\n", body ? body->type : 0));
+ if (body) switch (body->type) {
+ char *freethis;
+
+ case TYPEMULTIPART: /* multi-part */
+ if(!(freethis=parameter_val(body->parameter, "BOUNDARY"))){
+ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
+
+ snprintf (tmp,sizeof(tmp),"%ld-%ld-%ld=:%ld",gethostid (),random (),(long) time (0),
+ (long) getpid ());
+ tmp[sizeof(tmp)-1] = '\0';
+ set_parameter(&body->parameter, "BOUNDARY", tmp);
+ }
+
+ if(freethis)
+ fs_give((void **) &freethis);
+
+ part = body->nested.part; /* encode body parts */
+ do pine_encode_body (&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ break;
+
+ case TYPETEXT :
+ /*
+ * If the part is text we edited, then it is UTF-8.
+ * The user may be asking us to send it as something else
+ * or we may want to downconvert to a more-specific characterset.
+ * Mark it for conversion here so the right MIME header's written.
+ * Do conversion pine_rfc822_output_body.
+ * Attachments are left as is.
+ */
+ if(body->contents.text.data
+ && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)){
+ char *charset, *posting_charset, *lp;
+
+ if(!((charset = parameter_val(body->parameter, "charset"))
+ && !strucmp(charset, UNKNOWN_CHARSET))
+ && (posting_charset = posting_characterset(body, charset, MsgBody))){
+
+ set_parameter(&body->parameter, "charset", posting_charset);
+
+ /*
+ * Fix iso-2022-jp encoding to ENC7BIT since it's escape based
+ * and doesn't use anything but ASCII characters.
+ * Why is it not ENC7BIT already? Because when we set the encoding
+ * in set_mime_type_by_grope we were groping through UTF-8 text
+ * not 2022 text. Not only that, but we didn't know at that point
+ * that it wouldn't stay UTF-8 when we sent it, which would require
+ * encoding.
+ */
+ if(!strucmp(posting_charset, "iso-2022-jp")
+ && (lp = so_attr((STORE_S *) body->contents.text.data, "maxline", NULL))
+ && strlen(lp) < 4)
+ body->encoding = ENC7BIT;
+ }
+
+ if(charset)
+ fs_give((void **)&charset);
+ }
+
+ break;
+
+/* case MESSAGE: */ /* here for documentation */
+ /* Encapsulated messages are always treated as text objects at this point.
+ This means that you must replace body->contents.msg with
+ body->contents.text, which probably involves copying
+ body->contents.msg.text to body->contents.text */
+ default: /* all else has some encoding */
+ /*
+ * but we'll delay encoding it until the message is on the way
+ * into the mail slot...
+ */
+ break;
+ }
+}
+
+
+/*
+ * pine_header_line - simple wrapper around c-client call to contain
+ * repeated code, and to write fcc if required.
+ */
+int
+pine_header_line(char *field, METAENV *header, char *text, soutr_t f, void *s,
+ int writehdr, int localcopy)
+{
+ int ret = 1;
+ int big = 10000;
+ char *value, *folded = NULL, *cs;
+ char *converted;
+
+ if(!text)
+ return 1;
+
+ converted = utf8_to_charset(text, cs = posting_characterset(text, NULL, HdrText), 0);
+
+ if(converted){
+ if(cs && !strucmp(cs, "us-ascii"))
+ value = converted;
+ else
+ value = encode_header_value(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs,
+ encode_whole_header(field, header));
+
+ if(value && value == converted){ /* no encoding was done, have to fold */
+ int fold_by, len;
+ char *actual_field;
+
+ len = ((header && header->env && header->env->remail)
+ ? strlen("ReSent-") : 0) +
+ (field ? strlen(field) : 0) + 2;
+
+ actual_field = (char *)fs_get((len+1) * sizeof(char));
+ snprintf(actual_field, len+1, "%s%s: ",
+ (header && header->env && header->env->remail) ? "ReSent-" : "",
+ field ? field : "");
+ actual_field[len] = '\0';
+
+ /*
+ * We were folding everything except message-id, but that wasn't
+ * sufficient. Since 822 only allows folding where linear-white-space
+ * is allowed we'd need a smarter folder than "fold" to do it. So,
+ * instead of inventing that smarter folder (which would have to
+ * know 822 syntax)
+ *
+ * We could just alloc space and copy the actual_field followed by
+ * the value into it, but since that's what fold does anyway we'll
+ * waste some cpu time and use fold with a big fold parameter.
+ *
+ * We upped the references folding from 75 to 256 because we were
+ * encountering longer-than-75 message ids, and to break one line
+ * in references is to break them all.
+ */
+ if(field && !strucmp("Subject", field))
+ fold_by = 75;
+ else if(field && !strucmp("References", field))
+ fold_by = 256;
+ else
+ fold_by = big;
+
+ folded = fold(value, fold_by, big, actual_field, " ", FLD_CRLF);
+
+ if(actual_field)
+ fs_give((void **)&actual_field);
+ }
+ else if(value){ /* encoding was done */
+ RFC822BUFFER rbuf;
+ size_t ll;
+
+ /*
+ * rfc1522_encode already inserted continuation lines and did
+ * the necessary folding so we don't have to do it. Let
+ * rfc822_header_line add the trailing crlf and the resent- if
+ * necessary. The 20 could actually be a 12.
+ */
+ ll = strlen(field) + strlen(value) + 20;
+ folded = (char *) fs_get(ll * sizeof(char));
+ *folded = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = folded;
+ rbuf.cur = folded;
+ rbuf.end = folded+ll-1;
+ rfc822_output_header_line(&rbuf, field,
+ (header && header->env && header->env->remail) ? LONGT : 0L, value);
+ *rbuf.cur = '\0';
+ }
+
+ if(value && folded){
+ if(writehdr && f)
+ ret = (*f)(s, folded);
+
+ if(ret && localcopy && lmc.so && !lmc.all_written)
+ ret = so_puts(lmc.so, folded);
+ }
+
+ if(folded)
+ fs_give((void **)&folded);
+
+ if(converted && converted != text)
+ fs_give((void **) &converted);
+ }
+ else
+ ret = 0;
+
+ return(ret);
+}
+
+
+/*
+ * Do appropriate encoding of text header lines.
+ * For some field types (those that consist of 822 *text) we just encode
+ * the whole thing. For structured fields we encode only within comments
+ * if possible.
+ *
+ * Args d -- Destination buffer if needed. (tmp_20k_buf)
+ * s -- Source string.
+ * charset -- Charset to encode with.
+ * encode_all -- If set, encode the whole string. If not, try to encode
+ * only within comments if possible.
+ *
+ * Returns S is returned if no encoding is done. D is returned if encoding
+ * was needed.
+ */
+char *
+encode_header_value(char *d, size_t dlen, unsigned char *s, char *charset, int encode_all)
+{
+ char *p, *q, *r, *start_of_comment = NULL, *value = NULL;
+ int in_comment = 0;
+
+ if(!s)
+ return((char *)s);
+
+ if(dlen < SIZEOF_20KBUF)
+ panic("bad call to encode_header_value");
+
+ if(!encode_all){
+ /*
+ * We don't have to worry about keeping track of quoted-strings because
+ * none of these fields which aren't addresses contain quoted-strings.
+ * We do keep track of escaped parens inside of comments and comment
+ * nesting.
+ */
+ p = d+7000;
+ for(q = (char *)s; *q; q++){
+ switch(*q){
+ case LPAREN:
+ if(in_comment++ == 0)
+ start_of_comment = q;
+
+ break;
+
+ case RPAREN:
+ if(--in_comment == 0){
+ /* encode the comment, excluding the outer parens */
+ if(p-d < dlen-1)
+ *p++ = LPAREN;
+
+ *q = '\0';
+ r = rfc1522_encode(d+14000, dlen-14000,
+ (unsigned char *)start_of_comment+1,
+ charset);
+ if(r != start_of_comment+1)
+ value = d+7000; /* some encoding was done */
+
+ start_of_comment = NULL;
+ if(r)
+ sstrncpy(&p, r, dlen-1-(p-d));
+
+ *q = RPAREN;
+ if(p-d < dlen-1)
+ *p++ = *q;
+ }
+ else if(in_comment < 0){
+ in_comment = 0;
+ if(p-d < dlen-1)
+ *p++ = *q;
+ }
+
+ break;
+
+ case BSLASH:
+ if(!in_comment && *(q+1)){
+ if(p-d < dlen-2){
+ *p++ = *q++;
+ *p++ = *q;
+ }
+ }
+
+ break;
+
+ default:
+ if(!in_comment && p-d < dlen-1)
+ *p++ = *q;
+
+ break;
+ }
+ }
+
+ if(value){
+ /* Unterminated comment (wasn't really a comment) */
+ if(start_of_comment)
+ sstrncpy(&p, start_of_comment, dlen-1-(p-d));
+
+ *p = '\0';
+ }
+ }
+
+ /*
+ * We have to check if there is anything that needs to be encoded that
+ * wasn't in a comment. If there is, we'd better just start over and
+ * encode the whole thing. So, if no encoding has been done within
+ * comments, or if encoding is needed both within and outside of
+ * comments, then we encode the whole thing. Otherwise, go with
+ * the version that has only comments encoded.
+ */
+ if(!value || rfc1522_encode(d, dlen,
+ (unsigned char *)value, charset) != value)
+ return(rfc1522_encode(d, dlen, s, charset));
+ else{
+ strncpy(d, value, dlen-1);
+ d[dlen-1] = '\0';
+ return(d);
+ }
+}
+
+
+/*
+ * pine_address_line - write a header field containing addresses,
+ * one by one (so there's no buffer limit), and
+ * wrapping where necessary.
+ * Note: we use c-client functions to properly build the text string,
+ * but have to screw around with pointers to fool c-client functions
+ * into not blatting all the text into a single buffer. Yeah, I know.
+ */
+int
+pine_address_line(char *field, METAENV *header, struct mail_address *alist,
+ soutr_t f, void *s, int writehdr, int localcopy)
+{
+ char tmp[MAX_SINGLE_ADDR], *tmpptr = NULL;
+ size_t alloced = 0, sz;
+ char *delim, *ptmp, *mtmp, buftmp[MAILTMPLEN];
+ char *converted, *cs;
+ ADDRESS *atmp;
+ int i, count;
+ int in_group = 0, was_start_of_group = 0, fix_lcc = 0, failed = 0;
+ RFC822BUFFER rbuf;
+ static char comma[] = ", ";
+ static char end_group[] = ";";
+#define no_comma (&comma[1])
+
+ if(!alist) /* nothing in field! */
+ return(1);
+
+ if(!alist->host && alist->mailbox){ /* c-client group convention */
+ in_group++;
+ was_start_of_group++;
+ /* encode mailbox of group */
+ mtmp = alist->mailbox;
+ if(mtmp){
+ snprintf(buftmp, sizeof(buftmp), "%s", mtmp);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+ }
+ }
+ else
+ mtmp = NULL;
+
+ ptmp = alist->personal; /* remember personal name */
+ /* make sure personal name is encoded */
+ if(ptmp){
+ snprintf(buftmp, sizeof(buftmp), "%s", ptmp);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+ }
+
+ atmp = alist->next;
+ alist->next = NULL; /* digest only first address! */
+
+ /* use automatic buffer unless it isn't big enough */
+ if((alloced = est_size(alist)) > sizeof(tmp)){
+ tmpptr = (char *)fs_get(alloced);
+ sz = alloced;
+ }
+ else{
+ tmpptr = tmp;
+ sz = sizeof(tmp);
+ }
+
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmpptr;
+ rbuf.cur = tmpptr;
+ rbuf.end = tmpptr+sz-1;
+ rfc822_output_address_line(&rbuf, field,
+ (header && header->env && header->env->remail) ? LONGT : 0L, alist, NULL);
+ *rbuf.cur = '\0';
+
+ alist->next = atmp; /* restore pointer to next addr */
+
+ if(alist->personal && alist->personal != ptmp)
+ fs_give((void **) &alist->personal);
+
+ alist->personal = ptmp; /* in case it changed, restore name */
+
+ if(mtmp){
+ if(alist->mailbox && alist->mailbox != mtmp)
+ fs_give((void **) &alist->mailbox);
+
+ alist->mailbox = mtmp;
+ }
+
+ if((count = strlen(tmpptr)) > 2){ /* back over CRLF */
+ count -= 2;
+ tmpptr[count] = '\0';
+ }
+
+ /*
+ * If there is no sending_stream and we are writing the Lcc header,
+ * then we are piping it to sendmail -t which expects it to be a bcc,
+ * not lcc.
+ *
+ * When we write it to the fcc or postponed (the lmc.so),
+ * we want it to be lcc, not bcc, so we put it back.
+ */
+ if(!sending_stream && writehdr && struncmp("lcc:", tmpptr, 4) == 0)
+ fix_lcc = 1;
+
+ if(writehdr && f && *tmpptr){
+ if(fix_lcc)
+ tmpptr[0] = 'b';
+
+ failed = !(*f)(s, tmpptr);
+ if(fix_lcc)
+ tmpptr[0] = 'L';
+
+ if(failed)
+ goto bail_out;
+ }
+
+ if(localcopy && lmc.so &&
+ !lmc.all_written && *tmpptr && !so_puts(lmc.so, tmpptr))
+ goto bail_out;
+
+ for(alist = atmp; alist; alist = alist->next){
+ delim = comma;
+ /* account for c-client's representation of group names */
+ if(in_group){
+ if(!alist->host){ /* end of group */
+ in_group = 0;
+ was_start_of_group = 0;
+ /*
+ * Rfc822_write_address no longer writes out the end of group
+ * unless the whole group address is passed to it, so we do
+ * it ourselves.
+ */
+ delim = end_group;
+ }
+ else if(!localcopy || !lmc.so || lmc.all_written)
+ continue;
+ }
+ /* start of new group, print phrase below */
+ else if(!alist->host && alist->mailbox){
+ in_group++;
+ was_start_of_group++;
+ }
+
+ /* no comma before first address in group syntax */
+ if(was_start_of_group && alist->host){
+ delim = no_comma;
+ was_start_of_group = 0;
+ }
+
+ /* write delimiter */
+ if(((!in_group||was_start_of_group) && writehdr && f && !(*f)(s, delim))
+ || (localcopy && lmc.so && !lmc.all_written
+ && !so_puts(lmc.so, delim)))
+ goto bail_out;
+
+ ptmp = alist->personal; /* remember personal name */
+ snprintf(buftmp, sizeof(buftmp), "%.200s", ptmp ? ptmp : "");
+ buftmp[sizeof(buftmp)-1] = '\0';
+ converted = utf8_to_charset(buftmp, cs = posting_characterset(buftmp, NULL, HdrText), 0);
+ if(converted){
+ alist->personal = cpystr(rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) converted, cs));
+ if(converted && converted != buftmp)
+ fs_give((void **) &converted);
+ }
+ else{
+ failed++;
+ goto bail_out;
+ }
+
+ atmp = alist->next;
+ alist->next = NULL; /* tie off linked list */
+ if((i = est_size(alist)) > MAX(sizeof(tmp), alloced)){
+ alloced = i;
+ sz = alloced;
+ fs_resize((void **)&tmpptr, alloced);
+ }
+
+ *tmpptr = '\0';
+ /* make sure we don't write out group end with rfc822_write_address */
+ if(alist->host || alist->mailbox){
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmpptr;
+ rbuf.cur = tmpptr;
+ rbuf.end = tmpptr+sz-1;
+ rfc822_output_address_list(&rbuf, alist, 0L, NULL);
+ *rbuf.cur = '\0';
+ }
+
+ alist->next = atmp; /* restore next pointer */
+
+ if(alist->personal && alist->personal != ptmp)
+ fs_give((void **) &alist->personal);
+
+ alist->personal = ptmp; /* in case it changed, restore name */
+
+ /*
+ * BUG
+ * With group syntax addresses we no longer have two identical
+ * streams of output. Instead, for the fcc/postpone copy we include
+ * all of the addresses inside the :; of the group, and for the
+ * mail we're sending we don't include them. That means we aren't
+ * correctly keeping track of the column to wrap in, below. That is,
+ * we are keeping track of the fcc copy but we aren't keeping track
+ * of the regular copy. It could result in too long or too short
+ * lines. Should almost never come up since group addresses are almost
+ * never followed by other addresses in the same header, and even
+ * when they are, you have to go out of your way to get the headers
+ * messed up.
+ */
+ if(count + 2 + (i = strlen(tmpptr)) > 78){ /* wrap long lines... */
+ count = i + 4;
+ if((!in_group && writehdr && f && !(*f)(s, "\015\012 "))
+ || (localcopy && lmc.so && !lmc.all_written &&
+ !so_puts(lmc.so, "\015\012 ")))
+ goto bail_out;
+ }
+ else
+ count += i + 2;
+
+ if(((!in_group || was_start_of_group)
+ && writehdr && *tmpptr && f && !(*f)(s, tmpptr))
+ || (localcopy && lmc.so && !lmc.all_written
+ && *tmpptr && !so_puts(lmc.so, tmpptr)))
+ goto bail_out;
+ }
+
+bail_out:
+ if(tmpptr && tmpptr != tmp)
+ fs_give((void **)&tmpptr);
+
+ if(failed)
+ return(0);
+
+ return((writehdr && f ? (*f)(s, "\015\012") : 1)
+ && ((localcopy && lmc.so
+ && !lmc.all_written) ? so_puts(lmc.so, "\015\012") : 1));
+}
+
+
+/*
+ * mutated pine version of c-client's rfc822_header() function.
+ * changed to call pine-wrapped header and address functions
+ * so we don't have to limit the header size to a fixed buffer.
+ * This function also calls pine's body_header write function
+ * because encoding is delayed until output_body() is called.
+ */
+long
+pine_rfc822_header(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ PINEFIELD *pf;
+ int j;
+
+ if(header->env->remail){ /* if remailing */
+ long i = strlen (header->env->remail);
+ if(i > 4 && header->env->remail[i-4] == '\015')
+ header->env->remail[i-2] = '\0'; /* flush extra blank line */
+
+ if((f && !(*f)(s, header->env->remail))
+ || (lmc.so && !lmc.all_written
+ && !so_puts(lmc.so, header->env->remail)))
+ return(0L); /* start with remail header */
+ }
+
+ j = 0;
+ for(pf = header->sending_order[j]; pf; pf = header->sending_order[++j]){
+ switch(pf->type){
+ /*
+ * Warning: This is confusing. The 2nd to last argument used to
+ * be just pf->writehdr. We want Bcc lines to be written out
+ * if we are handing off to a sendmail temp file but not if we
+ * are talking smtp, so bcc's writehdr is set to 0 and
+ * pine_address_line was sending if writehdr OR !sending_stream.
+ * That works as long as we want to write everything when
+ * !sending_stream (an mta handoff to sendmail). But then we
+ * added the undisclosed recipients line which should only get
+ * written if writehdr is set, and not when we pass to a
+ * sendmail temp file. So pine_address_line has been changed
+ * so it bases its decision solely on the writehdr passed to it,
+ * and the logic that worries about Bcc and sending_stream
+ * was moved up to the caller (here) to decide when to set it.
+ *
+ * So we have:
+ * undisclosed recipients:; This will just be written
+ * if writehdr was set and not
+ * otherwise, nothing magical.
+ *** We may want to change this, because sendmail -t doesn't handle
+ *** the empty group syntax well unless it has been configured to
+ *** do so. It isn't configured by default, or in any of the
+ *** sendmail v8 configs. So we may want to not write this line
+ *** if we're doing an mta_handoff (!sending_stream).
+ *
+ * !sending_stream (which means a handoff to a sendmail -t)
+ * bcc or lcc both set the arg so they'll get written
+ * (There is also Lcc hocus pocus in pine_address_line
+ * which converts the Lcc: to Bcc: for sendmail
+ * processing.)
+ * sending_stream (which means an smtp handoff)
+ * bcc and lcc will never have writehdr set, so
+ * will never be written (They both do have rcptto set,
+ * so they both do cause RCPT TO commands.)
+ *
+ * The localcopy is independent of sending_stream and is just
+ * written if it is set for all of these.
+ */
+ case Address:
+ if(!pine_address_line(pf->name,
+ header,
+ pf->addr ? *pf->addr : NULL,
+ f,
+ s,
+ (!strucmp("bcc",pf->name ? pf->name : "")
+ || !strucmp("Lcc",pf->name ? pf->name : ""))
+ ? !sending_stream
+ : pf->writehdr,
+ pf->localcopy))
+ return(0L);
+
+ break;
+
+ case Fcc:
+ case FreeText:
+ case Subject:
+ if(!pine_header_line(pf->name, header,
+ pf->text ? *pf->text : NULL,
+ f, s, pf->writehdr, pf->localcopy))
+ return(0L);
+
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,7,"Unknown header type: %.200s",
+ pf->name);
+ break;
+ }
+ }
+
+
+#if (defined(DOS) || defined(OS2)) && !defined(NOAUTH)
+ /*
+ * Add comforting "X-" header line indicating what sort of
+ * authenticity the receiver can expect...
+ */
+ if(F_OFF(F_DISABLE_SENDER, ps_global)){
+ NETMBX netmbox;
+ char sstring[MAILTMPLEN], *label; /* place to write */
+ MAILSTREAM *m;
+ int i, anonymous = 1;
+
+ for(i = 0; anonymous && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED)
+ && mail_valid_net_parse(m->mailbox, &netmbox)
+ && !netmbox.anoflag)
+ anonymous = 0;
+ }
+
+ if(!anonymous){
+ char last_char = netmbox.host[strlen(netmbox.host) - 1],
+ *user = (*netmbox.user)
+ ? netmbox.user
+ : cached_user_name(netmbox.mailbox);
+ snprintf(sstring, sizeof(sstring), "%.300s@%s%.300s%s", user ? user : "NULL",
+ isdigit((unsigned char)last_char) ? "[" : "",
+ netmbox.host,
+ isdigit((unsigned char) last_char) ? "]" : "");
+ sstring[sizeof(sstring)-1] = '\0';
+ label = "X-X-Sender"; /* Jeez. */
+ if(F_ON(F_USE_SENDER_NOT_X,ps_global))
+ label += 4;
+ }
+ else{
+ strncpy(sstring,"UNAuthenticated Sender", sizeof(sstring));
+ sstring[sizeof(sstring)-1] = '\0';
+ label = "X-Warning";
+ }
+
+ if(!pine_header_line(label, header, sstring, f, s, 1, 1))
+ return(0L);
+ }
+#endif
+
+ if(body && !header->env->remail){ /* not if remail or no body */
+ if((f && !(*f)(s, MIME_VER))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, MIME_VER))
+ || !pine_write_body_header(body, f, s))
+ return(0L);
+ }
+ else{ /* write terminating newline */
+ if((f && !(*f)(s, "\015\012"))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, "\015\012")))
+ return(0L);
+ }
+
+ return(1L);
+}
+
+
+/*
+ * pine_rfc822_output - pine's version of c-client call. Necessary here
+ * since we're not using its structures as intended!
+ */
+long
+pine_rfc822_output(METAENV *header, struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ int we_cancel = 0;
+ long retval;
+
+ dprint((4, "-- pine_rfc822_output\n"));
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+ pine_encode_body(body); /* encode body as necessary */
+ /* build and output RFC822 header, output body */
+ retval = pine_rfc822_header(header, body, f, s)
+ && (body ? pine_rfc822_output_body(body, f, s) : 1L);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return(retval);
+}
+
+
+/*
+ * post_rfc822_output - cloak for pine's 822 output routine. Since
+ * we can't pass opaque envelope thru c-client posting
+ * logic, we need to wrap the real output inside
+ * something that c-client knows how to call.
+ */
+long
+post_rfc822_output(char *tmp,
+ ENVELOPE *env,
+ struct mail_bodystruct *body,
+ soutr_t f,
+ void *s,
+ long int ok8bit)
+{
+ return(pine_rfc822_output(send_header, body, f, s));
+}
+
+
+/*
+ * posting_characterset- determine what transliteration is reasonable
+ * for posting the given non-ascii messsage data.
+ *
+ * preferred_charset is the charset the original data was labeled in.
+ * If we can keep that we do.
+ *
+ * Returns: always returns the preferred character set.
+ */
+char *
+posting_characterset(void *data, char *preferred_charset, MsgPart mp)
+{
+ unsigned long *charsetmap = NULL;
+ unsigned long validbitmap;
+ static char *ascii = "US-ASCII";
+ static char *utf8 = "UTF-8";
+ int notcjk = 0;
+
+ if(!ps_global->post_utf8){
+ validbitmap = 0;
+
+ if(mp == HdrText){
+ char *text = NULL;
+ UCS *ucs = NULL, *ucsp;
+
+ text = (char *) data;
+
+ /* convert text in header to UCS characters */
+ if(text)
+ ucsp = ucs = utf8_to_ucs4_cpystr(text);
+
+ if(!(ucs && *ucs))
+ return(ascii);
+
+ /*
+ * After the while loop is done the validbitmap has
+ * a 1 bit for all the character sets that can
+ * represent all of the characters of this header.
+ */
+ charsetmap = init_charsetchecker(preferred_charset);
+
+ if(!charsetmap)
+ return(utf8);
+
+ validbitmap = ~0;
+ while((validbitmap & ~0x1) && (*ucsp)){
+ if(*ucsp > 0xffff){
+ fs_give((void **) &ucs);
+ return(utf8);
+ }
+
+ validbitmap &= charsetmap[(unsigned long) (*ucsp++)];
+ }
+
+ fs_give((void **) &ucs);
+
+ notcjk = validbitmap & 0x1;
+ validbitmap &= ~0x1;
+
+ if(!validbitmap)
+ return(utf8);
+ }
+ else{
+ struct mail_bodystruct *body = NULL;
+ STORE_S *the_text = NULL;
+ int outchars;
+ unsigned char c;
+ UCS ucs;
+ CBUF_S cbuf;
+
+ cbuf.cbuf[0] = '\0';
+ cbuf.cbufp = cbuf.cbuf;
+ cbuf.cbufend = cbuf.cbuf;
+
+ body = (struct mail_bodystruct *) data;
+
+ if(body && body->type == TYPEMULTIPART)
+ body = &body->nested.part->body;
+
+ if(body && body->type == TYPETEXT)
+ the_text = (STORE_S *) body->contents.text.data;
+
+ if(!the_text)
+ return(ascii);
+
+ so_seek(the_text, 0L, 0); /* rewind */
+
+ charsetmap = init_charsetchecker(preferred_charset);
+
+ if(!charsetmap)
+ return(utf8);
+
+ validbitmap = ~0;
+
+ /*
+ * Read a stream of UTF-8 characters from the_text
+ * and convert them to UCS-4 characters for the translatable
+ * test.
+ */
+ while((validbitmap & ~0x1) && so_readc(&c, the_text)){
+ if((outchars = utf8_to_ucs4_oneatatime(c, &cbuf, &ucs, NULL)) > 0){
+ /* got a ucs character */
+ if(ucs > 0xffff)
+ return(utf8);
+
+ validbitmap &= charsetmap[(unsigned long) ucs];
+ }
+ }
+
+ notcjk = validbitmap & 0x1;
+ validbitmap &= ~0x1;
+
+ if(!validbitmap)
+ return(utf8);
+ }
+
+ /* user chooses something other than UTF-8 */
+ if(strucmp(ps_global->posting_charmap, utf8)){
+ /*
+ * If we're to post in other than UTF-8, and it can be
+ * transliterated without losing fidelity, do it.
+ * Else, use UTF-8.
+ */
+
+ /* if ascii works, always use that */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* does the user's posting character set work? */
+ if(representable_in_charset(validbitmap, ps_global->posting_charmap))
+ return(ps_global->posting_charmap);
+
+ /* this is the charset the message we are replying to was in */
+ if(preferred_charset
+ && strucmp(preferred_charset, ascii)
+ && representable_in_charset(validbitmap, preferred_charset))
+ return(preferred_charset);
+
+ /* else, use UTF-8 */
+
+ }
+ /* user chooses nothing, going with the default */
+ else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
+ char *most_preferred;
+
+ /*
+ * In this case the user didn't specify a posting character set
+ * and we will choose the most-specific one from our list.
+ */
+
+ /* ascii is best */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* Can we keep the original from the message we're replying to? */
+ if(preferred_charset
+ && strucmp(preferred_charset, ascii)
+ && representable_in_charset(validbitmap, preferred_charset))
+ return(preferred_charset);
+
+ /* choose the best of the rest */
+ most_preferred = most_preferred_charset(validbitmap);
+ if(!most_preferred)
+ return(utf8);
+
+ /*
+ * If the text we're labeling contains something like
+ * smart quotes but no CJK characters, then instead of
+ * labeling it as ISO-2022-JP we want to use UTF-8.
+ */
+ if(notcjk){
+ const CHARSET *cs;
+
+ cs = utf8_charset(most_preferred);
+ if(!cs
+ || cs->script == SC_CHINESE_SIMPLIFIED
+ || cs->script == SC_CHINESE_TRADITIONAL
+ || cs->script == SC_JAPANESE
+ || cs->script == SC_KOREAN)
+ return(utf8);
+ }
+
+ return(most_preferred);
+ }
+ /* user explicitly chooses UTF-8 */
+ else{
+ /* if ascii works, always use that */
+ if(representable_in_charset(validbitmap, ascii))
+ return(ascii);
+
+ /* else, use UTF-8 */
+
+ }
+ }
+
+ return(utf8);
+}
+
+
+static char **charsetlist = NULL;
+static int items_in_charsetlist = 0;
+static unsigned long *charsetmap = NULL;
+
+static char *downgrades[] = {
+ "US-ASCII",
+ "ISO-8859-15",
+ "ISO-8859-1",
+ "ISO-8859-2",
+ "VISCII",
+ "KOI8-R",
+ "KOI8-U",
+ "ISO-8859-7",
+ "ISO-8859-6",
+ "ISO-8859-8",
+ "TIS-620",
+ "ISO-2022-JP",
+ "GB2312",
+ "BIG5",
+ "EUC-KR"
+};
+
+
+unsigned long *
+init_charsetchecker(char *preferred_charset)
+{
+ int i, count = 0, reset = 0;
+ char *ascii = "US-ASCII";
+ char *utf8 = "UTF-8";
+
+ /*
+ * When user doesn't set a posting character set posting_charmap ends up
+ * set to UTF-8. That also happens if user sets UTF-8 explicitly.
+ * That's where the strange set of if-else's come from.
+ */
+
+ /* user chooses something other than UTF-8 */
+ if(strucmp(ps_global->posting_charmap, utf8)){
+ count++; /* US-ASCII */
+ if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
+ reset++;
+
+ /* if posting_charmap is valid, include it in list */
+ if(ps_global->posting_charmap && ps_global->posting_charmap[0]
+ && strucmp(ps_global->posting_charmap, ascii)
+ && strucmp(ps_global->posting_charmap, utf8)
+ && utf8_charset(ps_global->posting_charmap)){
+ count++;
+ if(!reset
+ && (items_in_charsetlist < count
+ || strucmp(charsetlist[count-1], ps_global->posting_charmap)))
+ reset++;
+ }
+
+ if(preferred_charset && preferred_charset[0]
+ && strucmp(preferred_charset, ascii)
+ && strucmp(preferred_charset, utf8)
+ && (count < 2 || strucmp(preferred_charset, ps_global->posting_charmap))){
+ count++;
+ if(!reset
+ && (items_in_charsetlist < count
+ || strucmp(charsetlist[count-1], preferred_charset)))
+ reset++;
+ }
+
+ if(items_in_charsetlist != count)
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+
+ i = 0;
+ charsetlist[i++] = cpystr(ascii);
+
+ if(ps_global->posting_charmap && ps_global->posting_charmap[0]
+ && strucmp(ps_global->posting_charmap, ascii)
+ && strucmp(ps_global->posting_charmap, utf8)
+ && utf8_charset(ps_global->posting_charmap))
+ charsetlist[i++] = cpystr(ps_global->posting_charmap);
+
+ if(preferred_charset && preferred_charset[0]
+ && strucmp(preferred_charset, ascii)
+ && strucmp(preferred_charset, utf8)
+ && (i < 2 || strucmp(preferred_charset, ps_global->posting_charmap)))
+ charsetlist[i++] = cpystr(preferred_charset);
+
+ charsetlist[i] = NULL;
+ }
+ }
+ /* user chooses nothing, going with the default */
+ else if(ps_global->vars[V_POST_CHAR_SET].main_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].post_user_val.p == NULL
+ && ps_global->vars[V_POST_CHAR_SET].fixed_val.p == NULL){
+ int add_preferred = 0;
+
+ /* does preferred_charset have to be added to the list? */
+ if(preferred_charset && preferred_charset[0] && strucmp(preferred_charset, utf8)){
+ add_preferred = 1;
+ for(i = 0; add_preferred && i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ if(!strucmp(downgrades[i], preferred_charset))
+ add_preferred = 0;
+ }
+
+ if(add_preferred){
+ /* existing list is right size already */
+ if(items_in_charsetlist == sizeof(downgrades)/sizeof(downgrades[0]) + 1){
+ /* just check to see if last list item is the preferred_charset */
+ if(strucmp(preferred_charset, charsetlist[items_in_charsetlist-1])){
+ /* no, fix it */
+ reset++;
+ fs_give((void **) &charsetlist[items_in_charsetlist-1]);
+ charsetlist[items_in_charsetlist-1] = cpystr(preferred_charset);
+ }
+ }
+ else{
+ reset++;
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ count = sizeof(downgrades)/sizeof(downgrades[0]) + 1;
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+ for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ charsetlist[i] = cpystr(downgrades[i]);
+
+ charsetlist[i++] = cpystr(preferred_charset);
+ charsetlist[i] = NULL;
+ }
+ }
+ else{
+ /* if list is same size as downgrades, consider it good */
+ if(items_in_charsetlist != sizeof(downgrades)/sizeof(downgrades[0]))
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ count = sizeof(downgrades)/sizeof(downgrades[0]);
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+ for(i = 0; i < sizeof(downgrades)/sizeof(downgrades[0]); i++)
+ charsetlist[i] = cpystr(downgrades[i]);
+
+ charsetlist[i] = NULL;
+ }
+ }
+ }
+ /* user explicitly chooses UTF-8 */
+ else{
+ /* include possibility of ascii even if they explicitly ask for UTF-8 */
+ count++; /* US-ASCII */
+ if(items_in_charsetlist < 1 || strucmp(charsetlist[0], ascii))
+ reset++;
+
+ if(items_in_charsetlist != count)
+ reset++;
+
+ if(reset){
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ /* the list is just ascii and nothing else */
+ items_in_charsetlist = count;
+ charsetlist = (char **) fs_get((count + 1) * sizeof(char *));
+
+ i = 0;
+ charsetlist[i++] = cpystr(ascii);
+ charsetlist[i] = NULL;
+ }
+ }
+
+
+ if(reset){
+ if(charsetmap)
+ fs_give((void **) &charsetmap);
+
+ if(charsetlist)
+ charsetmap = utf8_csvalidmap(charsetlist);
+ }
+
+ return(charsetmap);
+}
+
+
+/* total reset */
+void
+free_charsetchecker(void)
+{
+ if(charsetlist)
+ free_list_array(&charsetlist);
+
+ items_in_charsetlist = 0;
+
+ if(charsetmap)
+ fs_give((void **) &charsetmap);
+}
+
+
+int
+representable_in_charset(unsigned long validbitmap, char *charset)
+{
+ int i, done = 0, ret = 0;
+ unsigned long j;
+
+ if(!(charset && charset[0]))
+ return ret;
+
+ if(!strucmp(charset, "UTF-8"))
+ return 1;
+
+ for(i = 0; !done && i < items_in_charsetlist; i++){
+ if(!strucmp(charset, charsetlist[i])){
+ j = 1;
+ j <<= (i+1);
+ done++;
+ if(validbitmap & j)
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+
+char *
+most_preferred_charset(unsigned long validbitmap)
+{
+ unsigned long bm;
+ unsigned long rm;
+ int index;
+
+ if(!(validbitmap && items_in_charsetlist > 0))
+ return("UTF-8");
+
+ /* careful, find_rightmost_bit modifies the bitmap */
+ bm = validbitmap;
+ rm = find_rightmost_bit(&bm);
+ index = MIN(MAX(rm-1,0), items_in_charsetlist-1);
+
+ return(charsetlist[index]);
+}
+
+
+/*
+ * Set parameter to new value.
+ */
+void
+set_parameter(PARAMETER **param, char *paramname, char *new_value)
+{
+ PARAMETER *pm;
+
+ if(!param || !(paramname && *paramname))
+ return;
+
+ if(*param == NULL){
+ pm = (*param) = mail_newbody_parameter();
+ pm->attribute = cpystr(paramname);
+ }
+ else{
+ int nomatch;
+
+ for(pm = *param;
+ (nomatch=strucmp(pm->attribute, paramname)) && pm->next != NULL;
+ pm = pm->next)
+ ;/* searching for paramname parameter */
+
+ if(nomatch){ /* add charset parameter */
+ pm->next = mail_newbody_parameter();
+ pm = pm->next;
+ pm->attribute = cpystr(paramname);
+ }
+ /* else pm is existing paramname parameter */
+ }
+
+ if(pm){
+ if(!(pm->value && new_value && !strcmp(pm->value, new_value))){
+ if(pm->value)
+ fs_give((void **) &pm->value);
+
+ if(new_value)
+ pm->value = cpystr(new_value);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Remove the leading digits from SMTP error messages
+ -----*/
+char *
+tidy_smtp_mess(char *error, char *printstring, char *outbuf, size_t outbuflen)
+{
+ while(isdigit((unsigned char)*error) || isspace((unsigned char)*error) ||
+ (*error == '.' && isdigit((unsigned char)*(error+1))))
+ error++;
+
+ snprintf(outbuf, outbuflen, printstring, error);
+ outbuf[outbuflen-1] = '\0';
+ return(outbuf);
+}
+
+
+/*
+ * Local globals pine's body output routine needs
+ */
+static soutr_t l_f;
+static TCPSTREAM *l_stream;
+static unsigned c_in_buf = 0;
+
+/*
+ * def to make our pipe write's more friendly
+ */
+#ifdef PIPE_MAX
+#if PIPE_MAX > 20000
+#undef PIPE_MAX
+#endif
+#endif
+
+#ifndef PIPE_MAX
+#define PIPE_MAX 1024
+#endif
+
+
+/*
+ * l_flust_net - empties gf_io terminal function's buffer
+ */
+int
+l_flush_net(int force)
+{
+ if(c_in_buf && c_in_buf < SIZEOF_20KBUF){
+ char *p = &tmp_20k_buf[0], *lp = NULL, c = '\0';
+
+ tmp_20k_buf[c_in_buf] = '\0';
+ if(!force){
+ /*
+ * The start of each write is expected to be the start of a
+ * "record" (i.e., a CRLF terminated line). Make sure that is true
+ * else we might screw up SMTP dot quoting...
+ */
+ for(p = tmp_20k_buf, lp = NULL;
+ (p = strstr(p, "\015\012")) != NULL;
+ lp = (p += 2))
+ ;
+
+
+ if(!lp && c_in_buf > 2) /* no CRLF! */
+ for(p = &tmp_20k_buf[c_in_buf] - 2;
+ p > &tmp_20k_buf[0] && *p == '.';
+ p--) /* find last non-dot */
+ ;
+
+ if(lp && *lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF){
+ /* snippet remains */
+ c = *lp;
+ *lp = '\0';
+ }
+ }
+
+ if((l_f && !(*l_f)(l_stream, tmp_20k_buf))
+ || (lmc.so && !lmc.all_written
+ && !(lmc.text_only && lmc.text_written)
+ && !so_puts(lmc.so, tmp_20k_buf)))
+ return(0);
+
+ c_in_buf = 0;
+ if(lp && lp >= tmp_20k_buf && lp < tmp_20k_buf + SIZEOF_20KBUF && (*lp = c)) /* Shift text left? */
+ while(c_in_buf < SIZEOF_20KBUF && (tmp_20k_buf[c_in_buf] = *lp))
+ c_in_buf++, lp++;
+ }
+
+ return(1);
+}
+
+
+/*
+ * l_putc - gf_io terminal function that calls smtp's soutr_t function.
+ *
+ */
+int
+l_putc(int c)
+{
+ if(c_in_buf >= 0 && c_in_buf < SIZEOF_20KBUF)
+ tmp_20k_buf[c_in_buf++] = (char) c;
+
+ return((c_in_buf >= PIPE_MAX) ? l_flush_net(FALSE) : TRUE);
+}
+
+
+
+/*
+ * pine_rfc822_output_body - pine's version of c-client call. Again,
+ * necessary since c-client doesn't know about how
+ * we're treating attachments
+ */
+long
+pine_rfc822_output_body(struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ PART *part;
+ PARAMETER *param;
+ char *t, *cookie = NIL, *encode_error;
+ char tmp[MAILTMPLEN];
+ int add_trailing_crlf;
+ LOC_2022_JP ljp;
+ gf_io_t gc;
+
+ dprint((4, "-- pine_rfc822_output_body: %d\n",
+ body ? body->type : 0));
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ part = body->nested.part; /* first body part */
+ /* find cookie */
+ for (param = body->parameter; param && !cookie; param = param->next)
+ if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value;
+ if (!cookie) cookie = "-"; /* yucky default */
+
+ /*
+ * Output a bit of text before the first multipart delimiter
+ * to warn unsuspecting users of non-mime-aware ua's that
+ * they should expect weirdness...
+ */
+ if(f && !(*f)(s, " This message is in MIME format. The first part should be readable text,\015\012 while the remaining parts are likely unreadable without MIME-aware tools.\015\012\015\012"))
+ return(0);
+
+ do { /* for each part */
+ /* build cookie */
+ snprintf (tmp, sizeof(tmp), "--%s\015\012", cookie);
+ tmp[sizeof(tmp)-1] = '\0';
+ /* append cookie,mini-hdr,contents */
+ if((f && !(*f)(s, tmp))
+ || (lmc.so && !lmc.all_written && !so_puts(lmc.so, tmp))
+ || !pine_write_body_header(&part->body,f,s)
+ || !pine_rfc822_output_body (&part->body,f,s))
+ return(0);
+ } while ((part = part->next) != NULL); /* until done */
+ /* output trailing cookie */
+ snprintf (t = tmp, sizeof(tmp), "--%s--",cookie);
+ tmp[sizeof(tmp)-1] = '\0';
+ if(lmc.so && !lmc.all_written){
+ so_puts(lmc.so, t);
+ so_puts(lmc.so, "\015\012");
+ }
+
+ return(f ? ((*f) (s,t) && (*f) (s,"\015\012")) : 1);
+ }
+
+ l_f = f; /* set up for writing chars... */
+ l_stream = s; /* out other end of pipe... */
+ gf_filter_init();
+ dprint((4, "-- pine_rfc822_output_body: segment %ld bytes\n",
+ body->size.bytes));
+
+ if(body->contents.text.data)
+ gf_set_so_readc(&gc, (STORE_S *) body->contents.text.data);
+ else
+ return(1);
+
+ /*
+ * Don't add trailing line if it is ExternalText, which already guarantees
+ * a trailing newline.
+ */
+ add_trailing_crlf = !(((STORE_S *) body->contents.text.data)->src == ExternalText);
+
+ so_seek((STORE_S *) body->contents.text.data, 0L, 0);
+
+ if(body->type != TYPEMESSAGE){ /* NOT encapsulated message */
+ char *charset;
+
+ if(body->type == TYPETEXT
+ && so_attr((STORE_S *) body->contents.text.data, "edited", NULL)
+ && (charset = parameter_val(body->parameter, "charset"))){
+ if(strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
+ if(!strucmp(charset, "iso-2022-jp")){
+ ljp.report_err = 0;
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(translate_utf8_to_2022_jp,&ljp));
+ }
+ else{
+ void *table = utf8_rmap(charset);
+
+ if(table){
+ gf_link_filter(gf_convert_utf8_charset,
+ gf_convert_utf8_charset_opt(table,0));
+ }
+ else{
+ /* else, just send it? */
+ set_parameter(&body->parameter, "charset", "UTF-8");
+ }
+ }
+ }
+
+ fs_give((void **)&charset);
+ }
+
+ /*
+ * Convert text pieces to canonical form
+ * BEFORE applying any encoding (rfc1341: appendix G)...
+ * NOTE: almost all filters expect CRLF newlines
+ */
+ if(body->type == TYPETEXT
+ && body->encoding != ENCBASE64
+ && !so_attr((STORE_S *) body->contents.text.data, "rawbody", NULL)){
+ gf_link_filter(gf_local_nvtnl, NULL);
+ }
+
+ switch (body->encoding) { /* all else needs filtering */
+ case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
+ gf_link_filter(gf_8bit_qp, NULL);
+ break;
+
+ case ENCBINARY: /* encode binary into BASE64 */
+ gf_link_filter(gf_binary_b64, NULL);
+ break;
+
+ default: /* otherwise text */
+ break;
+ }
+ }
+
+ if((encode_error = gf_pipe(gc, l_putc)) != NULL){ /* shove body part down pipe */
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Encoding Error \"%s\""), encode_error);
+ display_message('x');
+ }
+
+ gf_clear_so_readc((STORE_S *) body->contents.text.data);
+
+ if(encode_error || !l_flush_net(TRUE))
+ return(0);
+
+ send_bytes_sent += gf_bytes_piped();
+ so_release((STORE_S *)body->contents.text.data);
+
+ if(lmc.so && !lmc.all_written && lmc.text_only){
+ if(lmc.text_written){ /* we have some splainin' to do */
+ char tmp[MAILTMPLEN];
+ char *name = NULL;
+
+ if(!(so_puts(lmc.so,_("The following attachment was sent,\015\012"))
+ && so_puts(lmc.so,_("but NOT saved in the Fcc copy:\015\012"))))
+ return(0);
+
+ /*
+ * BUG: If this name is not ascii it's going to cause trouble.
+ */
+ name = parameter_val(body->parameter, "name");
+ snprintf(tmp, sizeof(tmp),
+ " A %s/%s%s%s%s segment of about %s bytes.\015\012",
+ body_type_names(body->type),
+ body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "",
+ comatose(body->size.bytes));
+ tmp[sizeof(tmp)-1] = '\0';
+ if(name)
+ fs_give((void **)&name);
+
+ if(!so_puts(lmc.so, tmp))
+ return(0);
+ }
+ else /* suppress everything after first text part */
+ lmc.text_written = (body->type == TYPETEXT
+ && (!body->subtype
+ || !strucmp(body->subtype, "plain")));
+ }
+
+ if(add_trailing_crlf)
+ return((f ? (*f)(s, "\015\012") : 1) /* output final stuff */
+ && ((lmc.so && !lmc.all_written) ? so_puts(lmc.so,"\015\012") : 1));
+ else
+ return(1);
+}
+
+
+/*
+ * pine_write_body_header - another c-client clone. This time
+ * so the final encoding labels get set
+ * correctly since it hasn't happened yet,
+ * and to be paranoid about line lengths.
+ *
+ * Returns: TRUE/nonzero on success, zero on error
+ */
+int
+pine_write_body_header(struct mail_bodystruct *body, soutr_t f, void *s)
+{
+ char tmp[MAILTMPLEN];
+ RFC822BUFFER rbuf;
+ int i;
+ unsigned char c;
+ STRINGLIST *stl;
+ STORE_S *so;
+ extern const char *tspecials;
+
+ if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
+ if(!(so_puts(so, "Content-Type: ")
+ && so_puts(so, body_types[body->type])
+ && so_puts(so, "/")
+ && so_puts(so, body->subtype
+ ? body->subtype
+ : rfc822_default_subtype (body->type))))
+ return(pwbh_finish(0, so));
+
+ if(body->parameter){
+ if(!pine_write_params(body->parameter, so))
+ return(pwbh_finish(0, so));
+ }
+ else if(!so_puts(so, "; CHARSET=US-ASCII"))
+ return(pwbh_finish(0, so));
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+
+ if ((body->encoding /* note: encoding 7BIT never output! */
+ && !(so_puts(so, "Content-Transfer-Encoding: ")
+ && so_puts(so, body_encodings[(body->encoding==ENCBINARY)
+ ? ENCBASE64
+ : (body->encoding == ENC8BIT)
+ ? ENCQUOTEDPRINTABLE
+ : (body->encoding <= ENCMAX)
+ ? body->encoding
+ : ENCOTHER])
+ && so_puts(so, "\015\012")))
+ /*
+ * If requested, strip Content-ID headers that don't look like they
+ * are needed. Microsoft's Outlook XP has a bug that causes it to
+ * not show that there is an attachment when there is a Content-ID
+ * header present on that attachment.
+ *
+ * If user has quell-content-id turned on, don't output content-id
+ * unless it is of type message/external-body.
+ * Since this code doesn't look inside messages being forwarded
+ * type message content-ids will remain as is and type multipart
+ * alternative will remain as is. We don't create those on our
+ * own. If we did, we'd have to worry about getting this right.
+ */
+ || (body->id && (F_OFF(F_QUELL_CONTENT_ID, ps_global)
+ || (body->type == TYPEMESSAGE
+ && body->subtype
+ && !strucmp(body->subtype, "external-body")))
+ && !(so_puts(so, "Content-ID: ") && so_puts(so, body->id)
+ && so_puts(so, "\015\012")))
+ || (body->description
+ && strlen(body->description) < 5000 /* arbitrary! */
+ && !pine_write_header_line("Content-Description: ", body->description, so))
+ || (body->md5
+ && !(so_puts(so, "Content-MD5: ")
+ && so_puts(so, body->md5)
+ && so_puts(so, "\015\012"))))
+ return(pwbh_finish(0, so));
+
+ if ((stl = body->language) != NULL) {
+ if(!so_puts(so, "Content-Language: "))
+ return(pwbh_finish(0, so));
+
+ do {
+ if(strlen((char *)stl->text.data) > 500) /* arbitrary! */
+ return(pwbh_finish(0, so));
+
+ tmp[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = tmp;
+ rbuf.cur = tmp;
+ rbuf.end = tmp+sizeof(tmp)-1;
+ rfc822_output_cat(&rbuf, (char *)stl->text.data, tspecials);
+ *rbuf.cur = '\0';
+
+ if(!so_puts(so, tmp)
+ || ((stl = stl->next) && !so_puts(so, ", ")))
+ return(pwbh_finish(0, so));
+ }
+ while (stl);
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+ }
+
+ if (body->disposition.type) {
+ if(!(so_puts(so, "Content-Disposition: ")
+ && so_puts(so, body->disposition.type)))
+ return(pwbh_finish(0, so));
+
+ if(!pine_write_params(body->disposition.parameter, so))
+ return(pwbh_finish(0, so));
+
+ if(!so_puts(so, "\015\012"))
+ return(pwbh_finish(0, so));
+ }
+
+ /* copy out of so, a line at a time (or less than a K)
+ * and send it down the pike
+ */
+ so_seek(so, 0L, 0);
+ i = 0;
+ while(so_readc(&c, so))
+ if((tmp[i++] = c) == '\012' || i > sizeof(tmp) - 3){
+ tmp[i] = '\0';
+ if((f && !(*f)(s, tmp))
+ || !lmc_body_header_line(tmp, i <= sizeof(tmp) - 3))
+ return(pwbh_finish(0, so));
+
+ i = 0;
+ }
+
+ /* Finally, write blank line */
+ if((f && !(*f)(s, "\015\012")) || !lmc_body_header_finish())
+ return(pwbh_finish(0, so));
+
+ return(pwbh_finish(i == 0, so)); /* better of ended on LF */
+ }
+
+ return(0);
+}
+
+
+/*
+ * pine_write_header - convert, encode (if needed) and
+ * write "header-name: field-body"
+ */
+int
+pine_write_header_line(char *hdr, char *val, STORE_S *so)
+{
+ char *cv, *cs, *vp;
+ int rv;
+
+ cs = posting_characterset(val, NULL, HdrText);
+ cv = utf8_to_charset(val, cs, 0);
+ vp = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *) cv, cs);
+
+ rv = (so_puts(so, hdr) && so_puts(so, vp) && so_puts(so, "\015\012"));
+
+ if(cv && cv != val)
+ fs_give((void **) &cv);
+
+
+ return(rv);
+}
+
+
+/*
+ * pine_write_param - convert, encode and write MIME header-field parameters
+ */
+int
+pine_write_params(PARAMETER *param, STORE_S *so)
+{
+ for(; param; param = param->next){
+ int rv;
+ char *cv, *cs;
+ extern const char *tspecials;
+
+ cs = posting_characterset(param->value, NULL, HdrText);
+ cv = utf8_to_charset(param->value, cs, 0);
+ rv = (so_puts(so, "; ")
+ && rfc2231_output(so, param->attribute, cv, (char *) tspecials, cs));
+
+ if(cv && cv != param->value)
+ fs_give((void **) &cv);
+
+ if(!rv)
+ return(0);
+ }
+
+ return(1);
+}
+
+
+
+int
+lmc_body_header_line(char *line, int beginning)
+{
+ if(lmc.so && !lmc.all_written){
+ if(beginning && lmc.text_only && lmc.text_written
+ && (!struncmp(line, "content-type:", 13)
+ || !struncmp(line, "content-transfer-encoding:", 26)
+ || !struncmp(line, "content-disposition:", 20))){
+ /*
+ * "comment out" the real values since our comment isn't
+ * likely the same type, disposition nor encoding...
+ */
+ if(!so_puts(lmc.so, "X-"))
+ return(FALSE);
+ }
+
+ return(so_puts(lmc.so, line));
+ }
+
+ return(TRUE);
+}
+
+
+int
+lmc_body_header_finish(void)
+{
+ if(lmc.so && !lmc.all_written){
+ if(lmc.text_only && lmc.text_written
+ && !so_puts(lmc.so, "Content-Type: TEXT/PLAIN\015\012"))
+ return(FALSE);
+
+ return(so_puts(lmc.so, "\015\012"));
+ }
+
+ return(TRUE);
+}
+
+
+
+int
+pwbh_finish(int rv, STORE_S *so)
+{
+ if(so)
+ so_give(&so);
+
+ return(rv);
+}
+
+
+/*
+ * pine_free_body - c-client call wrapper so the body data pointer we
+ * we're using in a way c-client doesn't know about
+ * gets free'd appropriately.
+ */
+void
+pine_free_body(struct mail_bodystruct **body)
+{
+ /*
+ * Preempt c-client's contents.text.data clean up since we've
+ * usurped it's meaning for our own purposes...
+ */
+ pine_free_body_data (*body);
+
+ /* Then let c-client handle the rest... */
+ mail_free_body(body);
+}
+
+
+/*
+ * pine_free_body_data - free pine's interpretations of the body part's
+ * data pointer.
+ */
+void
+pine_free_body_data(struct mail_bodystruct *body)
+{
+ if(body){
+ if(body->type == TYPEMULTIPART){
+ PART *part = body->nested.part;
+ do /* for each part */
+ pine_free_body_data(&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ }
+ else if(body->contents.text.data)
+ so_give((STORE_S **) &body->contents.text.data);
+ }
+}
+
+
+long
+send_body_size(struct mail_bodystruct *body)
+{
+ long l = 0L;
+ PART *part;
+
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ part = body->nested.part; /* first body part */
+ do /* for each part */
+ l += send_body_size(&part->body);
+ while ((part = part->next) != NULL); /* until done */
+ return(l);
+ }
+
+ return(l + body->size.bytes);
+}
+
+
+int
+sent_percent(void)
+{
+ int i = (int) (((send_bytes_sent + gf_bytes_piped()) * 100)
+ / send_bytes_to_send);
+ return(MIN(i, 100));
+}
+
+
+/*
+ * pine_smtp_verbose_out - write
+ */
+void
+pine_smtp_verbose_out(char *s)
+{
+#ifdef _WINDOWS
+ LPTSTR slpt;
+#endif
+ if(verbose_send_output && s){
+ char *p, last = '\0';
+
+ for(p = s; *p; p++)
+ if(*p == '\015')
+ *p = ' ';
+ else
+ last = *p;
+
+#ifdef _WINDOWS
+ /*
+ * The stream is opened in Unicode mode, so we need to fix the
+ * argument to fputs.
+ */
+ slpt = utf8_to_lptstr((LPSTR) s);
+ if(slpt){
+ _fputts(slpt, verbose_send_output);
+ fs_give((void **) &slpt);
+ }
+
+ if(last != '\012')
+ _fputtc(L'\n', verbose_send_output);
+#else
+ fputs(s, verbose_send_output);
+ if(last != '\012')
+ fputc('\n', verbose_send_output);
+#endif
+ }
+
+}
+
+
+/*
+ * pine_header_forbidden - is this name a "forbidden" header?
+ *
+ * name - the header name to check
+ * We don't allow user to change these.
+ */
+int
+pine_header_forbidden(char *name)
+{
+ char **p;
+ static char *forbidden_headers[] = {
+ "sender",
+ "x-sender",
+ "x-x-sender",
+ "date",
+ "received",
+ "message-id",
+ "in-reply-to",
+ "path",
+ "resent-message-id",
+ "resent-date",
+ "resent-from",
+ "resent-sender",
+ "resent-to",
+ "resent-cc",
+ "resent-reply-to",
+ "mime-version",
+ "content-type",
+ "x-priority",
+ "user-agent",
+ NULL
+ };
+
+ for(p = forbidden_headers; *p; p++)
+ if(!strucmp(name, *p))
+ break;
+
+ return((*p) ? 1 : 0);
+}
+
+
+/*
+ * hdr_is_in_list - is there a custom value for this header?
+ *
+ * hdr - the header name to check
+ * custom - the list to check in
+ * Returns 1 if there is a custom value, 0 otherwise.
+ */
+int
+hdr_is_in_list(char *hdr, PINEFIELD *custom)
+{
+ PINEFIELD *pf;
+
+ for(pf = custom; pf && pf->name; pf = pf->next)
+ if(strucmp(pf->name, hdr) == 0)
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * count_custom_hdrs_pf - returns number of custom headers in arg
+ * custom -- the list to be counted
+ * only_nonstandard -- only count headers which aren't standard pine headers
+ */
+int
+count_custom_hdrs_pf(PINEFIELD *custom, int only_nonstandard)
+{
+ int ret = 0;
+
+ for(; custom && custom->name; custom = custom->next)
+ if(!only_nonstandard || !custom->standard)
+ ret++;
+
+ return(ret);
+}
+
+
+/*
+ * count_custom_hdrs_list - returns number of custom headers in arg
+ */
+int
+count_custom_hdrs_list(char **list)
+{
+ char **p;
+ char *q = NULL;
+ char *name;
+ char *t;
+ char save;
+ int ret = 0;
+
+ if(list){
+ for(p = list; (q = *p) != NULL; p++){
+ if(q[0]){
+ /* remove leading whitespace */
+ name = skip_white_space(q);
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ save = *t;
+ *t = '\0';
+ if(!pine_header_forbidden(name))
+ ret++;
+
+ *t = save;
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * set_default_hdrval - put the user's default value for this header
+ * into pf->textbuf.
+ * setthis - the pinefield to be set
+ * custom - where to look for the default
+ */
+CustomType
+set_default_hdrval(PINEFIELD *setthis, PINEFIELD *custom)
+{
+ PINEFIELD *pf;
+ CustomType ret = NoMatch;
+
+ if(!setthis || !setthis->name){
+ q_status_message(SM_ORDER,3,7,"Internal error setting default header");
+ return(ret);
+ }
+
+ setthis->textbuf = NULL;
+
+ for(pf = custom; pf && pf->name; pf = pf->next){
+ if(strucmp(pf->name, setthis->name) != 0)
+ continue;
+
+ ret = pf->cstmtype;
+
+ /* turn on editing */
+ if(strucmp(pf->name, "From") == 0 || strucmp(pf->name, "Reply-To") == 0)
+ setthis->canedit = 1;
+
+ if(pf->val)
+ setthis->textbuf = cpystr(pf->val);
+ }
+
+ if(!setthis->textbuf)
+ setthis->textbuf = cpystr("");
+
+ return(ret);
+}
+
+
+/*
+ * pine_header_standard - is this name a "standard" header?
+ *
+ * name - the header name to check
+ */
+FieldType
+pine_header_standard(char *name)
+{
+ int i;
+
+ /* check to see if this is a standard header */
+ for(i = 0; pf_template[i].name; i++)
+ if(!strucmp(name, pf_template[i].name))
+ return(pf_template[i].type);
+
+ return(TypeUnknown);
+}
+
+
+/*
+ * customized_hdr_setup - setup the PINEFIELDS for all the customized headers
+ * Allocates space for each name and addr ptr.
+ * Allocates space for default in textbuf, even if empty.
+ *
+ * head - the first PINEFIELD to fill in
+ * list - the list to parse
+ */
+void
+customized_hdr_setup(PINEFIELD *head, char **list, CustomType cstmtype)
+{
+ char **p, *q, *t, *name, *value, save;
+ PINEFIELD *pf;
+
+ pf = head;
+
+ if(list){
+ for(p = list; (q = *p) != NULL; p++){
+
+ if(q[0]){
+
+ /* anything after leading whitespace? */
+ if(!*(name = skip_white_space(q)))
+ continue;
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ /* if there is a space in the field-name, skip it */
+ if(isspace((unsigned char)*t)){
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Space not allowed in header name (%s)"),
+ name);
+ continue;
+ }
+
+ save = *t;
+ *t = '\0';
+
+ /* Don't allow any of the forbidden headers. */
+ if(pine_header_forbidden(name)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Not allowed to change header \"%s\""),
+ name);
+
+ *t = save;
+ continue;
+ }
+
+ if(pf){
+ if(pine_header_standard(name) != TypeUnknown)
+ pf->standard = 1;
+
+ pf->name = cpystr(name);
+ pf->type = FreeText;
+ pf->cstmtype = cstmtype;
+ pf->next = pf+1;
+
+#ifdef OLDWAY
+ /*
+ * Some mailers apparently break if we change
+ * user@domain into Fred <user@domain> for
+ * return-receipt-to,
+ * so we'll just call this a FreeText field, too.
+ */
+ /*
+ * For now, all custom headers are FreeText except for
+ * this one that we happen to know about. We might
+ * have to add some syntax to the config option so that
+ * people can tell us their custom header takes addresses.
+ */
+ if(!strucmp(pf->name, "Return-Receipt-to")){
+ pf->type = Address;
+ pf->addr = (ADDRESS **)fs_get(sizeof(ADDRESS *));
+ *pf->addr = (ADDRESS *)NULL;
+ }
+#endif /* OLDWAY */
+
+ *t = save;
+
+ /* remove space between name and colon */
+ value = skip_white_space(t);
+
+ /* give them an alloc'd default, even if empty */
+ pf->textbuf = cpystr((*value == ':')
+ ? skip_white_space(++value) : "");
+ if(pf->textbuf && pf->textbuf[0])
+ pf->val = cpystr(pf->textbuf);
+
+ pf++;
+ }
+ else
+ *t = save;
+ }
+ }
+ }
+
+ /* fix last next pointer */
+ if(head && pf != head)
+ (pf-1)->next = NULL;
+}
+
+
+/*
+ * add_defaults_from_list - the PINEFIELDS list given by "head" is already
+ * setup except that it doesn't have default values.
+ * Add those defaults if they exist in "list".
+ *
+ * head - the first PINEFIELD to add a default to
+ * list - the list to get the defaults from
+ */
+void
+add_defaults_from_list(PINEFIELD *head, char **list)
+{
+ char **p, *q, *t, *name, *value, save;
+ PINEFIELD *pf;
+
+ for(pf = head; pf && list; pf = pf->next){
+ if(!pf->name)
+ continue;
+
+ for(p = list; (q = *p) != NULL; p++){
+
+ if(q[0]){
+
+ /* anything after leading whitespace? */
+ if(!*(name = skip_white_space(q)))
+ continue;
+
+ /* look for colon or space or end */
+ for(t = name;
+ *t && !isspace((unsigned char)*t) && *t != ':'; t++)
+ ;/* do nothing */
+
+ /* if there is a space in the field-name, skip it */
+ if(isspace((unsigned char)*t))
+ continue;
+
+ save = *t;
+ *t = '\0';
+
+ if(strucmp(name, pf->name) != 0){
+ *t = save;
+ continue;
+ }
+
+ *t = save;
+
+ /*
+ * Found the right header. See if it has a default
+ * value set.
+ */
+
+ /* remove space between name and colon */
+ value = skip_white_space(t);
+
+ if(*value == ':'){
+ char *defval;
+
+ defval = skip_white_space(++value);
+ if(defval && *defval){
+ if(pf->val)
+ fs_give((void **)&pf->val);
+
+ pf->val = cpystr(defval);
+ }
+ }
+
+ break; /* on to next pf */
+ }
+ }
+ }
+}
+
+
+/*
+ * parse_custom_hdrs - allocate PINEFIELDS for custom headers
+ * fill in the defaults
+ * Args - list -- The list to parse.
+ * cstmtype -- Fill the in cstmtype field with this value
+ */
+PINEFIELD *
+parse_custom_hdrs(char **list, CustomType cstmtype)
+{
+ PINEFIELD *pfields;
+ int i;
+
+ /*
+ * add one for possible use by fcc
+ * What is this "possible use"? I don't see it. I don't
+ * think it exists anymore. I think +1 would be ok. hubert 2000-08-02
+ */
+ i = (count_custom_hdrs_list(list) + 2) * sizeof(PINEFIELD);
+ pfields = (PINEFIELD *)fs_get((size_t) i);
+ memset(pfields, 0, (size_t) i);
+
+ /* set up the custom header pfields */
+ customized_hdr_setup(pfields, list, cstmtype);
+
+ return(pfields);
+}
+
+
+/*
+ * Combine the two lists of headers into one list which is allocated here
+ * and freed by the caller. Eliminate duplicates with values from the role
+ * taking precedence over values from the default.
+ */
+PINEFIELD *
+combine_custom_headers(PINEFIELD *dflthdrs, PINEFIELD *rolehdrs)
+{
+ PINEFIELD *pfields, *pf, *pf2;
+ int max_hdrs, i;
+
+ max_hdrs = count_custom_hdrs_pf(rolehdrs,0) +
+ count_custom_hdrs_pf(dflthdrs,0);
+
+ pfields = (PINEFIELD *)fs_get((size_t)(max_hdrs+1)*sizeof(PINEFIELD));
+ memset(pfields, 0, (size_t)(max_hdrs+1)*sizeof(PINEFIELD));
+
+ pf = pfields;
+ for(pf2 = rolehdrs; pf2 && pf2->name; pf2 = pf2->next){
+ pf->name = pf2->name ? cpystr(pf2->name) : NULL;
+ pf->type = pf2->type;
+ pf->cstmtype = pf2->cstmtype;
+ pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
+ pf->val = pf2->val ? cpystr(pf2->val) : NULL;
+ pf->standard = pf2->standard;
+ pf->next = pf+1;
+ pf++;
+ }
+
+ /* if these aren't already there, add them */
+ for(pf2 = dflthdrs; pf2 && pf2->name; pf2 = pf2->next){
+ /* check for already there */
+ for(i = 0;
+ pfields[i].name && strucmp(pfields[i].name, pf2->name);
+ i++)
+ ;
+
+ if(!pfields[i].name){ /* this is a new one */
+ pf->name = pf2->name ? cpystr(pf2->name) : NULL;
+ pf->type = pf2->type;
+ pf->cstmtype = pf2->cstmtype;
+ pf->textbuf = pf2->textbuf ? cpystr(pf2->textbuf) : NULL;
+ pf->val = pf2->val ? cpystr(pf2->val) : NULL;
+ pf->standard = pf2->standard;
+ pf->next = pf+1;
+ pf++;
+ }
+ }
+
+ /* fix last next pointer */
+ if(pf != pfields)
+ (pf-1)->next = NULL;
+
+ return(pfields);
+}
+
+
+/*
+ * free_customs - free misc. resources associated with custom header fields
+ *
+ * pf - pointer to first custom field
+ */
+void
+free_customs(PINEFIELD *head)
+{
+ PINEFIELD *pf;
+
+ for(pf = head; pf && pf->name; pf = pf->next){
+
+ fs_give((void **)&pf->name);
+
+ if(pf->val)
+ fs_give((void **)&pf->val);
+
+ /* only true for FreeText */
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ /* only true for Address */
+ if(pf->addr && *pf->addr)
+ mail_free_address(pf->addr);
+ }
+
+ fs_give((void **)&head);
+}
+
+
+/*
+ * encode_whole_header
+ *
+ * Returns 1 if whole value should be encoded
+ * 0 to encode only within comments
+ */
+int
+encode_whole_header(char *field, METAENV *header)
+{
+ int retval = 0;
+ PINEFIELD *pf;
+
+ if(field && (!strucmp(field, "Subject") ||
+ !strucmp(field, "Comment") ||
+ !struncmp(field, "X-", 2)))
+ retval++;
+ else if(field && *field && header && header->custom){
+ for(pf = header->custom; pf && pf->name; pf = pf->next){
+
+ if(!pf->standard && !strucmp(pf->name, field)){
+ retval++;
+ break;
+ }
+ }
+ }
+
+ return(retval);
+}
+
+
+/*----------------------------------------------------------------------
+ Post news via NNTP or inews
+
+Args: env -- envelope of message to post
+ body -- body of message to post
+
+Returns: -1 if failed or cancelled, 1 if succeeded
+
+WARNING: This call function has the side effect of writing the message
+ to the lmc.so object.
+ ----*/
+int
+news_poster(METAENV *header, struct mail_bodystruct *body, char **alt_nntp_servers,
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *error_mess, error_buf[200], **news_servers;
+ char **servers_to_use;
+ int we_cancel = 0, server_no = 0, done_posting = 0;
+ void *orig_822_output;
+ BODY *bp = NULL;
+
+ error_buf[0] = '\0';
+ we_cancel = busy_cue("Posting news", NULL, 0);
+
+ dprint((4, "Posting: [%s]\n",
+ (header && header->env && header->env->newsgroups)
+ ? header->env->newsgroups : "?"));
+
+ if((alt_nntp_servers && alt_nntp_servers[0] && alt_nntp_servers[0][0])
+ || (ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
+ && ps_global->VAR_NNTP_SERVER[0][0])){
+ /*---------- NNTP server defined ----------*/
+ error_mess = NULL;
+ servers_to_use = (alt_nntp_servers && alt_nntp_servers[0]
+ && alt_nntp_servers[0][0])
+ ? alt_nntp_servers : ps_global->VAR_NNTP_SERVER;
+
+ /*
+ * Install our rfc822 output routine
+ */
+ orig_822_output = mail_parameters(NULL, GET_RFC822OUTPUT, NULL);
+ (void) mail_parameters(NULL, SET_RFC822OUTPUT,
+ (void *)post_rfc822_output);
+
+ server_no = 0;
+ news_servers = (char **)fs_get(2 * sizeof(char *));
+ news_servers[1] = NULL;
+ while(!done_posting && servers_to_use[server_no] &&
+ servers_to_use[server_no][0]){
+ news_servers[0] = servers_to_use[server_no];
+ ps_global->noshow_error = 1;
+#ifdef DEBUG
+ sending_stream = nntp_open(news_servers, debug ? NOP_DEBUG : 0L);
+#else
+ sending_stream = nntp_open(news_servers, 0L);
+#endif
+ ps_global->noshow_error = 0;
+
+ if(sending_stream != NULL) {
+ unsigned short save_encoding, added_encoding;
+
+ /*
+ * Fake that we've got clearance from the transport agent
+ * for 8bit transport for the benefit of our output routines...
+ */
+ if(F_ON(F_ENABLE_8BIT_NNTP, ps_global)
+ && (bp = first_text_8bit(body))){
+ int i;
+
+ for(i = 0; (i <= ENCMAX) && body_encodings[i]; i++)
+ ;
+
+ if(i > ENCMAX){ /* no empty encoding slots! */
+ bp = NULL;
+ }
+ else {
+ added_encoding = i;
+ body_encodings[added_encoding] = body_encodings[ENC8BIT];
+ save_encoding = bp->encoding;
+ bp->encoding = added_encoding;
+ }
+ }
+
+ /*
+ * Set global header pointer so we can get at it later...
+ */
+ send_header = header;
+ ps_global->noshow_error = 1;
+ if(nntp_mail(sending_stream, header->env, body) == 0)
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Error posting message: %s"),
+ sending_stream->reply);
+ else{
+ done_posting = 1;
+ error_buf[0] = '\0';
+ error_mess = NULL;
+ }
+
+ error_buf[sizeof(error_buf)-1] = '\0';
+ smtp_close(sending_stream);
+ ps_global->noshow_error = 0;
+ sending_stream = NULL;
+ if(F_ON(F_ENABLE_8BIT_NNTP, ps_global) && bp){
+ body_encodings[added_encoding] = NULL;
+ bp->encoding = save_encoding;
+ }
+
+ } else {
+ /*---- Open of NNTP connection failed ------ */
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Error connecting to news server: %s"),
+ ps_global->c_client_error);
+ error_buf[sizeof(error_buf)-1] = '\0';
+ dprint((1, error_buf));
+ }
+ server_no++;
+ }
+ fs_give((void **)&news_servers);
+ (void) mail_parameters (NULL, SET_RFC822OUTPUT, orig_822_output);
+ } else {
+ /*----- Post via local mechanism -------*/
+#ifdef _WINDOWS
+ snprintf(error_mess = error_buf, sizeof(error_buf),
+ _("Can't post, NNTP-server must be defined!"));
+#else /* UNIX */
+ error_mess = post_handoff(header, body, error_buf, sizeof(error_buf), NULL,
+ pipecb_f);
+#endif
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(error_mess){
+ if(lmc.so && !lmc.all_written)
+ so_give(&lmc.so); /* clean up any fcc data */
+
+ q_status_message(SM_ORDER | SM_DING, 4, 5, error_mess);
+ return(-1);
+ }
+
+ lmc.all_written = 1;
+ return(1);
+}
+
+
+/* ----------------------------------------------------------------------
+ Figure out command to start local SMTP agent
+
+ Args: errbuf -- buffer for reporting errors (assumed non-NULL)
+
+ Returns an alloc'd copy of the local SMTP agent invocation or NULL
+
+ ----*/
+char *
+smtp_command(char *errbuf, size_t errbuflen)
+{
+#ifdef _WINDOWS
+ if(!(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0]
+ && ps_global->VAR_SMTP_SERVER[0][0]))
+ strncpy(errbuf,_("SMTP-server must be defined!"),errbuflen);
+
+ errbuf[errbuflen-1] = '\0';
+#else /* UNIX */
+# if defined(SENDMAIL) && defined(SENDMAILFLAGS)
+ char tmp[256];
+
+ snprintf(tmp, sizeof(tmp), "%.*s %.*s", (sizeof(tmp)-3)/2, SENDMAIL,
+ (sizeof(tmp)-3)/2, SENDMAILFLAGS);
+ return(cpystr(tmp));
+# else
+ strncpy(errbuf, _("No default posting command."), errbuflen);
+ errbuf[errbuflen-1] = '\0';
+# endif
+#endif
+
+ return(NULL);
+}
+
+
+
+#ifndef _WINDOWS
+/*----------------------------------------------------------------------
+ Hand off given message to local posting agent
+
+ Args: envelope -- The envelope for the BCC and debugging
+ header -- The text of the message header
+ errbuf -- buffer for reporting errors (assumed non-NULL)
+ len -- Length of errbuf
+
+ ----*/
+int
+mta_handoff(METAENV *header, struct mail_bodystruct *body,
+ char *errbuf, size_t len,
+ void (*bigresult_f) (char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+#ifdef DF_SENDMAIL_PATH
+ char cmd_buf[256];
+#endif
+ char *cmd = NULL;
+
+ /*
+ * A bit of complicated policy implemented here.
+ * There are two posting variables sendmail-path and smtp-server.
+ * Precedence is in that order.
+ * They can be set one of 4 ways: fixed, command-line, user, or globally.
+ * Precedence is in that order.
+ * Said differently, the order goes something like what's below.
+ *
+ * NOTE: the fixed/command-line/user precendence handling is also
+ * indicated by what's pointed to by ps_global->VAR_*, but since
+ * that also includes the global defaults, it's not sufficient.
+ */
+
+ if(ps_global->FIX_SENDMAIL_PATH
+ && ps_global->FIX_SENDMAIL_PATH[0]){
+ cmd = ps_global->FIX_SENDMAIL_PATH;
+ }
+ else if(!(ps_global->FIX_SMTP_SERVER
+ && ps_global->FIX_SMTP_SERVER[0])){
+ if(ps_global->COM_SENDMAIL_PATH
+ && ps_global->COM_SENDMAIL_PATH[0]){
+ cmd = ps_global->COM_SENDMAIL_PATH;
+ }
+ else if(!(ps_global->COM_SMTP_SERVER
+ && ps_global->COM_SMTP_SERVER[0])){
+ if((ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0]) ||
+ (ps_global->vars[V_SENDMAIL_PATH].main_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].main_user_val.p[0])){
+ if(ps_global->vars[V_SENDMAIL_PATH].post_user_val.p
+ && ps_global->vars[V_SENDMAIL_PATH].post_user_val.p[0])
+ cmd = ps_global->vars[V_SENDMAIL_PATH].post_user_val.p;
+ else
+ cmd = ps_global->vars[V_SENDMAIL_PATH].main_user_val.p;
+ }
+ else if(!((ps_global->vars[V_SMTP_SERVER].post_user_val.l
+ && ps_global->vars[V_SMTP_SERVER].post_user_val.l[0]) ||
+ (ps_global->vars[V_SMTP_SERVER].main_user_val.l
+ && ps_global->vars[V_SMTP_SERVER].main_user_val.l[0]))){
+ if(ps_global->GLO_SENDMAIL_PATH
+ && ps_global->GLO_SENDMAIL_PATH[0]){
+ cmd = ps_global->GLO_SENDMAIL_PATH;
+ }
+#ifdef DF_SENDMAIL_PATH
+ /*
+ * This defines the default method of posting. So,
+ * unless we're told otherwise use it...
+ */
+ else if(!(ps_global->GLO_SMTP_SERVER
+ && ps_global->GLO_SMTP_SERVER[0])){
+ strncpy(cmd = cmd_buf, DF_SENDMAIL_PATH, sizeof(cmd_buf)-1);
+ cmd_buf[sizeof(cmd_buf)-1] = '\0';
+ }
+#endif
+ }
+ }
+ }
+
+ *errbuf = '\0';
+ if(cmd){
+ dprint((4, "call_mailer via cmd: %s\n", cmd ? cmd : "?"));
+
+ (void) mta_parse_post(header, body, cmd, errbuf, len, bigresult_f, pipecb_f);
+ return(1);
+ }
+ else
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Hand off given message to local posting agent
+
+ Args: envelope -- The envelope for the BCC and debugging
+ header -- The text of the message header
+ errbuf -- buffer for reporting errors (assumed non-NULL)
+ errbuflen -- Length of errbuf
+
+ Fork off mailer process and pipe the message into it
+ Called to post news via Inews when NNTP is unavailable
+
+ ----*/
+char *
+post_handoff(METAENV *header, struct mail_bodystruct *body, char *errbuf,
+ size_t errbuflen,
+ void (*bigresult_f) (char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *err = NULL;
+#ifdef SENDNEWS
+ char *s;
+ char tmp[200];
+
+ if(s = strstr(header->env->date," (")) /* fix the date format for news */
+ *s = '\0';
+
+ if(err = mta_parse_post(header, body, SENDNEWS, errbuf, errbuflen, bigresult_f, pipecb_f)){
+ strncpy(tmp, err, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ snprintf(err = errbuf, errbuflen, _("News not posted: \"%s\": %s"),
+ SENDNEWS, tmp);
+ }
+
+ if(s)
+ *s = ' '; /* restore the date */
+
+#else /* !SENDNEWS */ /* this is the default case */
+ strncpy(err = errbuf, _("Can't post, NNTP-server must be defined!"), errbuflen-1);
+ err[errbuflen-1] = '\0';
+#endif /* !SENDNEWS */
+ return(err);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Hand off message to local MTA; it parses recipients from 822 header
+
+ Args: header -- struct containing header data
+ body -- struct containing message body data
+ cmd -- command to use for handoff (%s says where file should go)
+ errs -- pointer to buf to hold errors
+
+ ----*/
+char *
+mta_parse_post(METAENV *header, struct mail_bodystruct *body, char *cmd,
+ char *errs, size_t errslen, void (*bigresult_f)(char *, int),
+ void (*pipecb_f)(PIPE_S *, int, void *))
+{
+ char *result = NULL;
+ PIPE_S *pipe;
+
+ dprint((1, "=== mta_parse_post(%s) ===\n", cmd ? cmd : "?"));
+
+ if((pipe = open_system_pipe(cmd, &result, NULL,
+ PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
+ 0, pipecb_f, pipe_report_error)) != NULL){
+ if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl,
+ (TCPSTREAM *) pipe)){
+ strncpy(errs, _("Error posting."), errslen-1);
+ errs[errslen-1] = '\0';
+ }
+
+ if(close_system_pipe(&pipe, NULL, pipecb_f) && !*errs){
+ snprintf(errs, errslen, _("Posting program %s returned error"), cmd);
+ if(result && bigresult_f)
+ (*bigresult_f)(result, CM_BR_ERROR);
+ }
+ }
+ else
+ snprintf(errs, errslen, _("Error running \"%s\""), cmd);
+
+ if(result){
+ our_unlink(result);
+ fs_give((void **)&result);
+ }
+
+ return(*errs ? errs : NULL);
+}
+
+
+/*
+ * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+pine_pipe_soutr_nl (void *stream, char *s)
+{
+ long rv = T;
+ char *p;
+ size_t n;
+
+ while(*s && rv){
+ if((n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) != 0)
+ while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n)
+ if(rv < 0){
+ if(errno != EINTR){
+ rv = 0;
+ break;
+ }
+ }
+ else{
+ s += rv;
+ n -= rv;
+ }
+
+ if(p && rv){
+ s = p + 2; /* write UNIX EOL */
+ while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1)
+ if(rv < 0 && errno != EINTR){
+ rv = 0;
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ return(rv);
+}
+#endif
+
+
+/**************** "PIPE" READING POSTING I/O ROUTINES ****************/
+
+
+/*
+ * helpful def's
+ */
+#define S(X) ((PIPE_S *)(X))
+#define GETBUFLEN (4 * MAILTMPLEN)
+
+
+void *
+piped_smtp_open (char *host, char *service, long unsigned int port)
+{
+ char *postcmd;
+ void *rv = NULL;
+
+ if(strucmp(host, "localhost")){
+ char tmp[MAILTMPLEN];
+
+ snprintf(tmp, sizeof(tmp), _("Unexpected hostname for piped SMTP: %.*s"),
+ sizeof(tmp)-50, host);
+ tmp[sizeof(tmp)-1] = '\0';
+ mm_log(tmp, ERROR);
+ }
+ else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){
+ rv = open_system_pipe(postcmd, NULL, NULL,
+ PIPE_READ|PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC,
+ 0, NULL, pipe_report_error);
+ fs_give((void **) &postcmd);
+ }
+ else
+ mm_log(ps_global->c_client_error, ERROR);
+
+ return(rv);
+}
+
+
+void *
+piped_aopen (NETMBX *mb, char *service, char *user)
+{
+ return(NULL);
+}
+
+
+/*
+ * piped_soutr - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+piped_soutr (void *stream, char *s)
+{
+ return(piped_sout(stream, s, strlen(s)));
+}
+
+
+/*
+ * piped_sout - Replacement for tcp_soutr that writes one of our
+ * pipes rather than a tcp stream
+ */
+long
+piped_sout (void *stream, char *s, long unsigned int size)
+{
+ int i, o;
+
+ if(S(stream)->out.d < 0)
+ return(0L);
+
+ if((i = (int) size) != 0){
+ while((o = write(S(stream)->out.d, s, i)) != i)
+ if(o < 0){
+ if(errno != EINTR){
+ piped_abort(stream);
+ return(0L);
+ }
+ }
+ else{
+ s += o; /* try again, fix up counts */
+ i -= o;
+ }
+ }
+
+ return(1L);
+}
+
+
+/*
+ * piped_getline - Replacement for tcp_getline that reads one
+ * of our pipes rather than a tcp pipe
+ *
+ * C-client expects that the \r\n will be stripped off.
+ */
+char *
+piped_getline (void *stream)
+{
+ static int cnt;
+ static char *ptr;
+ int n, m;
+ char *ret, *s, *sp, c = '\0', d;
+
+ if(S(stream)->in.d < 0)
+ return(NULL);
+
+ if(!S(stream)->tmp){ /* initialize! */
+ /* alloc space to collect input (freed in close_system_pipe) */
+ S(stream)->tmp = (char *) fs_get(GETBUFLEN);
+ memset(S(stream)->tmp, 0, GETBUFLEN);
+ cnt = -1;
+ }
+
+ while(cnt < 0){
+ while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
+ if(errno != EINTR){
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ if(cnt == 0){
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ ptr = S(stream)->tmp;
+ }
+
+ s = ptr;
+ n = 0;
+ while(cnt--){
+ d = *ptr++;
+ if((c == '\015') && (d == '\012')){
+ ret = (char *)fs_get (n--);
+ memcpy(ret, s, n);
+ ret[n] = '\0';
+ return(ret);
+ }
+
+ n++;
+ c = d;
+ }
+ /* copy partial string from buffer */
+ memcpy((ret = sp = (char *) fs_get (n)), s, n);
+ /* get more data */
+ while(cnt < 0){
+ memset(S(stream)->tmp, 0, GETBUFLEN);
+ while((cnt = read(S(stream)->in.d, S(stream)->tmp, GETBUFLEN)) < 0)
+ if(errno != EINTR){
+ fs_give((void **) &ret);
+ piped_abort(stream);
+ return(NULL);
+ }
+
+ if(cnt == 0){
+ if(n > 0)
+ ret[n-1] = '\0'; /* to try to get error message logged */
+ else{
+ piped_abort(stream);
+ return(NULL);
+ }
+ }
+
+ ptr = S(stream)->tmp;
+ }
+
+ if(c == '\015' && *ptr == '\012'){
+ ptr++;
+ cnt--;
+ ret[n - 1] = '\0'; /* tie off string with null */
+ }
+ else if ((s = piped_getline(stream)) != NULL) {
+ ret = (char *) fs_get(n + 1 + (m = strlen (s)));
+ memcpy(ret, sp, n); /* copy first part */
+ memcpy(ret + n, s, m); /* and second part */
+ fs_give((void **) &sp); /* flush first part */
+ fs_give((void **) &s); /* flush second part */
+ ret[n + m] = '\0'; /* tie off string with null */
+ }
+
+ return(ret);
+}
+
+
+/*
+ * piped_close - Replacement for tcp_close that closes pipes to our
+ * child rather than a tcp connection
+ */
+void
+piped_close(void *stream)
+{
+ /*
+ * Uninstall our hooks into smtp_send since it's being used by
+ * the nntp driver as well...
+ */
+ (void) close_system_pipe((PIPE_S **) &stream, NULL, NULL);
+}
+
+
+/*
+ * piped_abort - close down the pipe we were using to post
+ */
+void
+piped_abort(void *stream)
+{
+ if(S(stream)->in.d >= 0){
+ close(S(stream)->in.d);
+ S(stream)->in.d = -1;
+ }
+
+ if(S(stream)->out.d){
+ close(S(stream)->out.d);
+ S(stream)->out.d = -1;
+ }
+}
+
+
+char *
+piped_host(void *stream)
+{
+ return(ps_global->hostname ? ps_global->hostname : "localhost");
+}
+
+
+unsigned long
+piped_port(void *stream)
+{
+ return(0L);
+}
diff --git a/pith/send.h b/pith/send.h
new file mode 100644
index 00000000..69d763fc
--- /dev/null
+++ b/pith/send.h
@@ -0,0 +1,271 @@
+/*
+ * $Id: send.h 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SEND_INCLUDED
+#define PITH_SEND_INCLUDED
+
+
+#include "../pith/context.h"
+#include "../pith/pattern.h"
+#include "../pith/repltype.h"
+#include "../pith/store.h"
+#include "../pith/osdep/pipe.h"
+
+
+#ifndef TCPSTREAM
+#define TCPSTREAM void
+#endif
+
+#define MIME_VER "MIME-Version: 1.0\015\012"
+
+#define UNKNOWN_CHARSET "X-UNKNOWN"
+
+#define OUR_HDRS_LIST "X-Our-Headers"
+
+
+/*
+ * Redraft flags...
+ */
+#define REDRAFT_NONE 0
+#define REDRAFT_PPND 0x01
+#define REDRAFT_DEL 0x02
+#define REDRAFT_HTML 0x04
+
+
+/*
+ * Child posting control structure
+ */
+typedef struct post_s {
+ int pid; /* proc id of child doing posting */
+ int status; /* child's exit status */
+ char *fcc; /* fcc we may have copied */
+} POST_S;
+
+
+/*------------------------------
+ Structures and enum used to expand the envelope structure to
+ support user defined headers. PINEFIELDs are sort of used for two
+ different purposes. The main use is to store information about headers
+ in pine_send. There is a pf for every header. It is also used for the
+ related task of parsing the customized-hdrs and role->cstm headers and
+ storing information about those.
+ ----*/
+
+typedef enum {FreeText, Address, Fcc,
+ Attachment, Subject, TypeUnknown} FieldType;
+typedef enum {NoMatch = 0, /* no match for this header */
+ UseAsDef=1, /* use only if no value set yet */
+ Combine=2, /* combine if News, To, Cc, Bcc, else
+ replace existing value */
+ Replace=3} CustomType; /* replace existing value */
+
+typedef struct pine_field {
+ char *name; /* field's literal name */
+ FieldType type; /* field's type */
+ unsigned canedit:1; /* allow editing of this field */
+ unsigned writehdr:1; /* write rfc822 header for field */
+ unsigned localcopy:1; /* copy to fcc or postponed */
+ unsigned rcptto:1; /* send to this if type Address */
+ unsigned posterr:1; /* posting error occured in field */
+ /* the next three fields are used only for customized-hdrs and friends */
+ unsigned standard:1; /* this hdr already in pf_template */
+ CustomType cstmtype; /* for customized-hdrs and r->cstm */
+ char *val; /* field's config'd value */
+ ADDRESS **addr; /* used if type is Address */
+ char *scratch; /* scratch pointer for Address type */
+ char **text; /* field's free text form */
+ char *textbuf; /* need to free this when done */
+ void *extdata; /* hook for extra data pointer */
+ struct pine_field *next; /* next pine field */
+} PINEFIELD;
+
+
+typedef struct {
+ ENVELOPE *env; /* standard c-client envelope */
+ PINEFIELD *local; /* this is all of the headers */
+ PINEFIELD *custom; /* ptr to start of custom headers */
+ PINEFIELD **sending_order; /* array giving writing order of hdrs */
+} METAENV;
+
+
+/*
+ * Return values for check_address()
+ */
+#define CA_OK 0 /* Address is OK */
+#define CA_EMPTY 1 /* Address is OK, but no deliverable addrs */
+#define CA_BAD -1 /* Address is bogus */
+
+
+
+/*
+ * call_mailer bigresult_f flags
+ */
+#define CM_BR_VERBOSE 0x01
+#define CM_BR_ERROR 0x02
+
+
+/*
+ * call_mailer flags
+ */
+#define CM_NONE 0x00
+#define CM_VERBOSE 0x01 /* request verbose mode */
+#define CM_DSN_NEVER 0x02 /* request NO DSN */
+#define CM_DSN_DELAY 0x04 /* DSN delay notification */
+#define CM_DSN_SUCCESS 0x08 /* DSN success notification */
+#define CM_DSN_FULL 0x10 /* DSN full notificiation */
+#define CM_DSN_SHOW 0x20 /* show DSN result (place holder) */
+
+
+#ifdef DEBUG_PLUS
+#define TIME_STAMP(str, l) { \
+ struct timeval tp; \
+ struct timezone tzp; \
+ if(gettimeofday(&tp, &tzp) == 0) \
+ dprint((l, \
+ "\nKACHUNK (%s) : %.8s.%3.3ld\n", \
+ str, ctime(&tp.tv_sec) + 11, \
+ tp.tv_usec));\
+ }
+#else /* !DEBUG_PLUS */
+#define TIME_STAMP(str, l)
+#endif /* !DEBUG_PLUS */
+
+/*
+ * Most number of errors call_mailer should report
+ */
+#define MAX_ADDR_ERROR 2 /* Only display 2 address errors */
+
+
+#define FCC_SOURCE CharStar
+
+
+struct local_message_copy {
+ STORE_S *so;
+ unsigned text_only:1;
+ unsigned all_written:1;
+ unsigned text_written:1;
+};
+
+
+#define TONAME "To"
+#define CCNAME "cc"
+#define SUBJNAME "Subject"
+#define PRIORITYNAME "X-Priority"
+
+
+/*
+ * Note, these are in the same order in the he_template and pf_template arrays.
+ * The typedef is just a way to get these variables automatically defined in order.
+ */
+typedef enum { N_AUTHRCVD = 0
+ , N_FROM
+ , N_REPLYTO
+ , N_TO
+ , N_CC
+ , N_BCC
+ , N_NEWS
+ , N_FCC
+ , N_LCC
+ , N_ATTCH
+ , N_SUBJ
+ , N_REF
+ , N_DATE
+ , N_INREPLY
+ , N_MSGID
+ , N_PRIORITY
+ , N_USERAGENT
+ , N_NOBODY
+ , N_POSTERR
+ , N_RPLUID
+ , N_RPLMBOX
+ , N_SMTP
+ , N_NNTP
+ , N_CURPOS
+ , N_OURREPLYTO
+ , N_OURHDRS
+ , N_SENDER
+} dummy_enum;
+
+
+typedef struct {
+ int val;
+ char *desc;
+} PRIORITY_S;
+
+
+/* ugly globals we'd like to get rid of */
+extern struct local_message_copy lmc;
+extern char verbose_requested;
+extern unsigned char dsn_requested;
+extern PRIORITY_S priorities[];
+extern PINEFIELD pf_template[];
+
+
+/* exported protoypes */
+int postponed_stream(MAILSTREAM **, char *, char *, int);
+int redraft_work(MAILSTREAM **, long, ENVELOPE **, BODY **, char **, char **,
+ REPLY_S **, REDRAFT_POS_S **, PINEFIELD **, ACTION_S **, int, STORE_S *);
+int redraft_cleanup(MAILSTREAM **, int, int);
+void simple_header_parse(char *, char **, char **);
+REPLY_S *build_reply_uid(char *);
+METAENV *pine_new_env(ENVELOPE *, char **, char ***, PINEFIELD *);
+void pine_free_env(METAENV **);
+int check_addresses(METAENV *);
+void update_answered_flags(REPLY_S *);
+ADDRESS *phone_home_from(void);
+unsigned int phone_home_hash(char *);
+int call_mailer(METAENV *, BODY *, char **, int, void (*)(char *, int),
+ void (*)(PIPE_S *, int, void *));
+int write_postponed(METAENV *, BODY *);
+int commence_fcc(char *, CONTEXT_S **, int);
+int wrapup_fcc(char *, CONTEXT_S *, METAENV *, BODY *);
+STORE_S *open_fcc(char *, CONTEXT_S **, int, char *, char *);
+int write_fcc(char *, CONTEXT_S *, STORE_S *, MAILSTREAM *, char *, char *);
+BODY *first_text_8bit(BODY *);
+ADDRESS *generate_from(void);
+void set_mime_type_by_grope(BODY *);
+void set_charset_possibly_to_ascii(BODY *, char *);
+void set_parameter(PARAMETER **, char *, char *);
+void pine_encode_body(BODY *);
+int pine_header_line(char *, METAENV *, char *, soutr_t, TCPSTREAM *, int, int);
+char *encode_header_value(char *, size_t, unsigned char *, char *, int);
+int pine_address_line(char *, METAENV *, ADDRESS *, soutr_t, TCPSTREAM *, int, int);
+long pine_rfc822_header(METAENV *, BODY *, soutr_t, TCPSTREAM *);
+long pine_rfc822_output(METAENV *, BODY *, soutr_t, TCPSTREAM *);
+void pine_free_body(BODY **);
+long send_body_size(BODY *);
+void pine_smtp_verbose_out(char *);
+int pine_header_forbidden(char *);
+int hdr_is_in_list(char *, PINEFIELD *);
+int count_custom_hdrs_pf(PINEFIELD *, int);
+int count_custom_hdrs_list(char **);
+CustomType set_default_hdrval(PINEFIELD *, PINEFIELD *);
+FieldType pine_header_standard(char *);
+void customized_hdr_setup(PINEFIELD *, char **, CustomType);
+void add_defaults_from_list(PINEFIELD *, char **);
+PINEFIELD *parse_custom_hdrs(char **, CustomType);
+PINEFIELD *combine_custom_headers(PINEFIELD *, PINEFIELD *);
+void free_customs(PINEFIELD *);
+int encode_whole_header(char *, METAENV *);
+int news_poster(METAENV *, BODY *, char **, void (*)(PIPE_S *, int, void *));
+PINEFIELD *set_priority_header(METAENV *header, char *value);
+void pine_free_body_data(BODY *);
+void free_charsetchecker(void);
+long pine_rfc822_output_body(BODY *,soutr_t,TCPSTREAM *);
+int pine_write_body_header(BODY *, soutr_t, TCPSTREAM *);
+
+
+#endif /* PITH_SEND_INCLUDED */
diff --git a/pith/sequence.c b/pith/sequence.c
new file mode 100644
index 00000000..577041dc
--- /dev/null
+++ b/pith/sequence.c
@@ -0,0 +1,460 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: sequence.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/sequence.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/flag.h"
+#include "../pith/thread.h"
+#include "../pith/icache.h"
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*----------------------------------------------------------------------
+ Build comma delimited list of selected messages
+
+ Args: stream -- mail stream to use for flag testing
+ msgmap -- message number struct of to build selected messages in
+ count -- pointer to place to write number of comma delimited
+ mark -- mark manually undeleted so we don't refilter right away
+
+ Returns: malloc'd string containing sequence, else NULL if
+ no messages in msgmap with local "selected" flag.
+ ----*/
+char *
+selected_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count, int mark)
+{
+ long i;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(NULL);
+
+ /*
+ * The plan here is to use the c-client elt's "sequence" bit
+ * to work around any orderings or exclusions in pine's internal
+ * mapping that might cause the sequence to be artificially
+ * lengthy. It's probably cheaper to run down the elt list
+ * twice rather than call nm_raw2m() for each message as
+ * we run down the elt list once...
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ if(get_lflag(stream, msgmap, i, MN_SLCT)){
+ long rawno;
+ int exbits = 0;
+
+ /*
+ * Forget we knew about it, and set "add to sequence"
+ * bit...
+ */
+ clear_index_cache_ent(stream, i, 0);
+ rawno = mn_m2raw(msgmap, i);
+ if(rawno > 0L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno)))
+ mc->sequence = 1;
+
+ /*
+ * Mark this message manually flagged so we don't re-filter it
+ * with a filter which only sets flags.
+ */
+ if(mark){
+ if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
+ exbits |= MSG_EX_MANUNDEL;
+ else
+ exbits = MSG_EX_MANUNDEL;
+
+ msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
+ }
+ }
+
+ return(build_sequence(stream, NULL, count));
+}
+
+
+/*----------------------------------------------------------------------
+ Build comma delimited list of messages current in msgmap which have all
+ flags matching the arguments
+
+ Args: stream -- mail stream to use for flag testing
+ msgmap -- only consider messages selected in this msgmap
+ flag -- flags to match against
+ count -- pointer to place to return number of comma delimited
+ mark -- mark index cache entry changed, and count state change
+ kw_on -- if flag contains F_KEYWORD, this is
+ the array of keywords to be checked
+ kw_off -- if flag contains F_UNKEYWORD, this is
+ the array of keywords to be checked
+
+ Returns: malloc'd string containing sequence, else NULL if
+ no messages in msgmap with local "selected" flag (a flag
+ of zero means all current msgs).
+ ----*/
+char *
+currentf_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int flag,
+ long int *count, int mark, char **kw_on, char **kw_off)
+{
+ char *seq, **t;
+ long i, rawno;
+ int exbits;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(NULL);
+
+ /* First, make sure elts are valid for all the interesting messages */
+ if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
+ pine_mail_fetch_flags(stream, seq, NIL);
+ fs_give((void **) &seq);
+ }
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0; /* clear "sequence" bits */
+
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
+ /* if not already set, go on... */
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(!mc)
+ continue;
+
+ if((flag == 0)
+ || ((flag & F_DEL) && mc->deleted)
+ || ((flag & F_UNDEL) && !mc->deleted)
+ || ((flag & F_SEEN) && mc->seen)
+ || ((flag & F_UNSEEN) && !mc->seen)
+ || ((flag & F_ANS) && mc->answered)
+ || ((flag & F_UNANS) && !mc->answered)
+ || ((flag & F_FLAG) && mc->flagged)
+ || ((flag & F_UNFLAG) && !mc->flagged)){
+ mc->sequence = 1; /* set "sequence" flag */
+ }
+
+ /* check for forwarded status */
+ if(!mc->sequence
+ && (flag & F_FWD)
+ && user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
+ mc->sequence = 1;
+ }
+
+ if(!mc->sequence
+ && (flag & F_UNFWD)
+ && !user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
+ mc->sequence = 1;
+ }
+
+ /* check for user keywords or not */
+ if(!mc->sequence && flag & F_KEYWORD && kw_on){
+ for(t = kw_on; !mc->sequence && *t; t++)
+ if(user_flag_is_set(stream, rawno, *t))
+ mc->sequence = 1;
+ }
+ else if(!mc->sequence && flag & F_UNKEYWORD && kw_off){
+ for(t = kw_off; !mc->sequence && *t; t++)
+ if(!user_flag_is_set(stream, rawno, *t))
+ mc->sequence = 1;
+ }
+
+ if(mc->sequence){
+ if(mark){
+ if(THRD_INDX()){
+ PINETHRD_S *thrd;
+ long t;
+
+ /* clear thread index line instead of index index line */
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
+ if(thrd && thrd->top
+ && (thrd=fetch_thread(stream,thrd->top))
+ && (t = mn_raw2m(msgmap, thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+ }
+ else
+ clear_index_cache_ent(stream, i, 0);/* force new index line */
+
+ /*
+ * Mark this message manually flagged so we don't re-filter it
+ * with a filter which only sets flags.
+ */
+ exbits = 0;
+ if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
+ exbits |= MSG_EX_MANUNDEL;
+ else
+ exbits = MSG_EX_MANUNDEL;
+
+ msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
+ }
+ }
+ }
+
+ return(build_sequence(stream, NULL, count));
+}
+
+
+/*----------------------------------------------------------------------
+ Return sequence numbers of messages with invalid MESSAGECACHEs
+
+ Args: stream -- mail stream to use for flag testing
+ msgmap -- message number struct of to build selected messages in
+
+ Returns: malloc'd string containing sequence, else NULL if
+ no messages in msgmap with local "selected" flag (a flag
+ of zero means all current msgs).
+ ----*/
+char *
+invalid_elt_sequence(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long i, rawno;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(NULL);
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0; /* clear "sequence" bits */
+
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
+ if((rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno)) && !mc->valid)
+ mc->sequence = 1;
+
+ return(build_sequence(stream, NULL, NULL));
+}
+
+
+/*----------------------------------------------------------------------
+ Build comma delimited list of messages with elt "sequence" bit set
+
+ Args: stream -- mail stream to use for flag testing
+ msgmap -- struct containing sort to build sequence in
+ count -- pointer to place to write number of comma delimited
+ NOTE: if non-zero, it's a clue as to how many messages
+ have the sequence bit lit.
+
+ Returns: malloc'd string containing sequence, else NULL if
+ no messages in msgmap with elt's "sequence" bit set
+ ----*/
+char *
+build_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count)
+{
+#define SEQ_INCREMENT 128
+ long n = 0L, i, x, lastn = 0L, runstart = 0L;
+ size_t size = SEQ_INCREMENT;
+ char *seq = NULL, *p;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(NULL);
+
+ if(count){
+ if(*count > 0L)
+ size = MAX(size, MIN((*count) * 4, 16384));
+
+ *count = 0L;
+ }
+
+ for(x = 1L; x <= stream->nmsgs; x++){
+ if(msgmap){
+ if((i = mn_m2raw(msgmap, x)) == 0L)
+ continue;
+ }
+ else
+ i = x;
+
+ if(i > 0L && i <= stream->nmsgs
+ && (mc = mail_elt(stream, i)) && mc->sequence){
+ n++;
+ if(!seq) /* initialize if needed */
+ seq = p = fs_get(size);
+
+ /*
+ * This code will coalesce the ascending runs of
+ * sequence numbers, but fails to break sequences
+ * into a reasonably sensible length for imapd's to
+ * swallow (reasonable addtition to c-client?)...
+ */
+ if(lastn){ /* if may be in a run */
+ if(lastn + 1L == i){ /* and its the next raw num */
+ lastn = i; /* skip writing anything... */
+ continue;
+ }
+ else if(runstart != lastn){
+ *p++ = (runstart + 1L == lastn) ? ',' : ':';
+ sstrncpy(&p, long2string(lastn), size-(p-seq));
+ } /* wrote end of run */
+ }
+
+ runstart = lastn = i; /* remember last raw num */
+
+ if(n > 1L && (p-seq) < size) /* !first num, write delim */
+ *p++ = ',';
+
+ if(size - (p - seq) < 16){ /* room for two more nums? */
+ size_t offset = p - seq; /* grow the sequence array */
+ size += SEQ_INCREMENT;
+ fs_resize((void **)&seq, size);
+ p = seq + offset;
+ }
+
+ sstrncpy(&p, long2string(i), size-(p-seq)); /* write raw number */
+ }
+ }
+
+ if(lastn && runstart != lastn){ /* were in a run? */
+ if((p-seq) < size) /* !first num, write delim */
+ *p++ = (runstart + 1L == lastn) ? ',' : ':';
+
+ sstrncpy(&p, long2string(lastn), size-(p-seq)); /* write the trailing num */
+ }
+
+ if(seq && (p-seq) < size) /* if sequence, tie it off */
+ *p = '\0';
+
+ if(seq)
+ seq[size-1] = '\0';
+
+ if(count)
+ *count = n;
+
+ return(seq);
+}
+
+
+/*----------------------------------------------------------------------
+ If any messages flagged "selected", fake the "currently selected" array
+
+ Args: map -- message number struct of to build selected messages in
+
+ OK folks, here's the tradeoff: either all the functions have to
+ know if the user want's to deal with the "current" hilited message
+ or the list of currently "selected" messages, *or* we just
+ wrap the call to these functions with some glue that tweeks
+ what these functions see as the "current" message list, and let them
+ do their thing.
+ ----*/
+int
+pseudo_selected(MAILSTREAM *stream, MSGNO_S *map)
+{
+ long i, later = 0L;
+
+ if(any_lflagged(map, MN_SLCT)){
+ map->hilited = mn_m2raw(map, mn_get_cur(map));
+
+ for(i = 1L; i <= mn_get_total(map); i++)
+ if(get_lflag(stream, map, i, MN_SLCT)){
+ if(!later++){
+ mn_set_cur(map, i);
+ }
+ else{
+ mn_add_cur(map, i);
+ }
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Antidote for the monkey business committed above
+
+ Args: map -- message number struct of to build selected messages in
+
+ ----*/
+void
+restore_selected(MSGNO_S *map)
+{
+ if(map->hilited){
+ mn_reset_cur(map, mn_raw2m(map, map->hilited));
+ map->hilited = 0L;
+ }
+}
+
+
+/*
+ * Return a search set which can be used to limit the search to a smaller set,
+ * for performance reasons. The caller must still work correctly if we return
+ * the whole set (or NULL) here, because we may not be able to send the full
+ * search set over IMAP. In cases where the search set description is getting
+ * too large we send a search set which contains all of the relevant messages.
+ * It may contain more.
+ *
+ * Args stream
+ * narrow -- If set, we are narrowing our selection (restrict to
+ * messages with MN_SLCT already set) or if not set,
+ * we are broadening (so we may look only at messages
+ * with MN_SLCT not set)
+ *
+ * Returns - allocated search set or NULL. Caller is responsible for freeing it.
+ */
+SEARCHSET *
+limiting_searchset(MAILSTREAM *stream, int narrow)
+{
+ long n, run;
+ int cnt = 0;
+ SEARCHSET *full_set = NULL, **set;
+
+ /*
+ * If we're talking to anything other than a server older than
+ * imap 4rev1, build a searchset otherwise it'll choke.
+ */
+ if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
+ for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
+ /* test for end of run */
+ if(get_lflag(stream, NULL, n, MN_EXLD)
+ || (narrow && !get_lflag(stream, NULL, n, MN_SLCT))
+ || (!narrow && get_lflag(stream, NULL, n, MN_SLCT))){
+ if(run){ /* previous selected? */
+ set = &(*set)->next;
+ run = 0L;
+ }
+ }
+ else if(run++){ /* next in run */
+ (*set)->last = n;
+ }
+ else{ /* start of run */
+ *set = mail_newsearchset();
+ (*set)->first = (*set)->last = n;
+
+ /*
+ * Make this last set cover the rest of the messages.
+ * We could be fancier about this but it probably isn't
+ * worth the trouble.
+ */
+ if(++cnt > 100){
+ (*set)->last = stream->nmsgs;
+ break;
+ }
+ }
+ }
+
+ return(full_set);
+}
diff --git a/pith/sequence.h b/pith/sequence.h
new file mode 100644
index 00000000..49305e0b
--- /dev/null
+++ b/pith/sequence.h
@@ -0,0 +1,33 @@
+/*
+ * $Id: sequence.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SEQUENCE_INCLUDED
+#define PITH_SEQUENCE_INCLUDED
+
+
+#include "../pith/msgno.h"
+
+
+/* exported protoypes */
+char *selected_sequence(MAILSTREAM *, MSGNO_S *, long *, int);
+char *currentf_sequence(MAILSTREAM *, MSGNO_S *, long, long *, int, char **, char **);
+char *invalid_elt_sequence(MAILSTREAM *, MSGNO_S *);
+char *build_sequence(MAILSTREAM *, MSGNO_S *, long *);
+int pseudo_selected(MAILSTREAM *, MSGNO_S *);
+void restore_selected(MSGNO_S *);
+SEARCHSET *limiting_searchset(MAILSTREAM *, int);
+
+
+#endif /* PITH_SEQUENCE_INCLUDED */
diff --git a/pith/signal.h b/pith/signal.h
new file mode 100644
index 00000000..c55e351f
--- /dev/null
+++ b/pith/signal.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: signal.h 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SIGNAL_INCLUDED
+#define PITH_SIGNAL_INCLUDED
+
+
+/* exported protoypes */
+
+/* currently mandatory to implement stubs */
+
+int intr_handling_on(void);
+void intr_handling_off(void);
+
+
+#endif /* PITH_SIGNAL_INCLUDED */
diff --git a/pith/smime.c b/pith/smime.c
new file mode 100644
index 00000000..dc332a2a
--- /dev/null
+++ b/pith/smime.c
@@ -0,0 +1,2377 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: smime.c 1176 2008-09-29 21:16:42Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * This is based on a contribution from Jonathan Paisley
+ *
+ * File: smime.c
+ * Author: paisleyj@dcs.gla.ac.uk
+ * Date: 01/2001
+ */
+
+
+#include "../pith/headers.h"
+
+#ifdef SMIME
+
+#include "../pith/osdep/canaccess.h"
+#include "../pith/helptext.h"
+#include "../pith/store.h"
+#include "../pith/status.h"
+#include "../pith/detach.h"
+#include "../pith/conf.h"
+#include "../pith/smkeys.h"
+#include "../pith/smime.h"
+#include "../pith/mailpart.h"
+#include "../pith/reply.h"
+#include "../pith/tempfile.h"
+#include "../pith/readfile.h"
+#include "../pith/remote.h"
+
+#include <openssl/buffer.h>
+
+
+typedef enum {Public, Private, CACert} WhichCerts;
+
+
+/* internal prototypes */
+static void forget_private_keys(void);
+static int app_RAND_load_file(const char *file);
+static void openssl_extra_randomness(void);
+static int app_RAND_write_file(const char *file);
+static void smime_init(void);
+static const char *openssl_error_string(void);
+static void create_local_cache(char *base, BODY *b);
+static BIO *raw_part_to_bio(long msgno, const char *section);
+static long rfc822_output_func(void *b, char *string);
+static int load_private_key(PERSONAL_CERT *pcert);
+static void setup_pkcs7_body_for_signature(BODY *b, char *description,
+ char *type, char *filename);
+static BIO *body_to_bio(BODY *body);
+static BIO *bio_from_store(STORE_S *store);
+static STORE_S *get_part_contents(long msgno, const char *section);
+static PKCS7 *get_pkcs7_from_part(long msgno, const char *section);
+static int do_signature_verify(PKCS7 *p7, BIO *in, BIO *out);
+static int do_detached_signature_verify(BODY *b, long msgno, char *section);
+static PERSONAL_CERT *find_certificate_matching_pkcs7(PKCS7 *p7);
+static int do_decoding(BODY *b, long msgno, const char *section);
+static void free_smime_struct(SMIME_STUFF_S **smime);
+static void setup_storage_locations(void);
+static int copy_dir_to_container(WhichCerts which);
+static int copy_container_to_dir(WhichCerts which);
+
+
+int (*pith_opt_smime_get_passphrase)(void);
+
+
+static X509_STORE *s_cert_store;
+
+/* State management for randomness functions below */
+static int seeded = 0;
+static int egdsocket = 0;
+
+
+/*
+ * Forget any cached private keys
+ */
+static void
+forget_private_keys(void)
+{
+ PERSONAL_CERT *pcert;
+ size_t len;
+ volatile char *p;
+
+ dprint((9, "forget_private_keys()"));
+ if(ps_global->smime){
+ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs;
+ pcert;
+ pcert=pcert->next){
+
+ if(pcert->key){
+ EVP_PKEY_free(pcert->key);
+ pcert->key = NULL;
+ }
+ }
+
+ ps_global->smime->entered_passphrase = 0;
+ len = sizeof(ps_global->smime->passphrase);
+ p = ps_global->smime->passphrase;
+
+ while(len-- > 0)
+ *p++ = '\0';
+ }
+}
+
+
+/*
+ * taken from openssl/apps/app_rand.c
+ */
+static int
+app_RAND_load_file(const char *file)
+{
+ char buffer[200];
+
+ if(file == NULL)
+ file = RAND_file_name(buffer, sizeof buffer);
+ else if(RAND_egd(file) > 0){
+ /* we try if the given filename is an EGD socket.
+ if it is, we don't write anything back to the file. */
+ egdsocket = 1;
+ return 1;
+ }
+
+ if(file == NULL || !RAND_load_file(file, -1)){
+ if(RAND_status() == 0){
+ dprint((1, "unable to load 'random state'\n"));
+ dprint((1, "This means that the random number generator has not been seeded\n"));
+ dprint((1, "with much random data.\n"));
+ }
+
+ return 0;
+ }
+
+ seeded = 1;
+ return 1;
+}
+
+
+/*
+ * copied and fiddled from imap/src/osdep/unix/auth_ssl.c
+ */
+static void
+openssl_extra_randomness(void)
+{
+#if !defined(WIN32)
+ int fd;
+ unsigned long i;
+ char *tf = NULL;
+ char tmp[MAXPATH];
+ struct stat sbuf;
+ /* if system doesn't have /dev/urandom */
+ if(stat ("/dev/urandom", &sbuf)){
+ tmp[0] = '0';
+ tf = temp_nam(NULL, NULL);
+ if(tf){
+ strncpy(tmp, tf, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ fs_give((void **) &tf);
+ }
+
+ if((fd = open(tmp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0)
+ i = (unsigned long) tmp;
+ else{
+ unlink(tmp); /* don't need the file */
+ fstat(fd, &sbuf); /* get information about the file */
+ i = sbuf.st_ino; /* remember its inode */
+ close(fd); /* or its descriptor */
+ }
+ /* not great but it'll have to do */
+ snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp), "%.80s%lx%lx%lx",
+ tcp_serverhost (),i,
+ (unsigned long) (time (0) ^ gethostid ()),
+ (unsigned long) getpid ());
+ RAND_seed(tmp, strlen(tmp));
+ }
+#endif
+}
+
+
+/* taken from openssl/apps/app_rand.c */
+static int
+app_RAND_write_file(const char *file)
+{
+ char buffer[200];
+
+ if(egdsocket || !seeded)
+ /*
+ * If we did not manage to read the seed file,
+ * we should not write a low-entropy seed file back --
+ * it would suppress a crucial warning the next time
+ * we want to use it.
+ */
+ return 0;
+
+ if(file == NULL)
+ file = RAND_file_name(buffer, sizeof buffer);
+
+ if(file == NULL || !RAND_write_file(file)){
+ dprint((1, "unable to write 'random state'\n"));
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* Installed as an atexit() handler to save the random data */
+void
+smime_deinit(void)
+{
+ dprint((9, "smime_deinit()"));
+ app_RAND_write_file(NULL);
+ free_smime_struct(&ps_global->smime);
+}
+
+
+/* Initialise openssl stuff if needed */
+static void
+smime_init(void)
+{
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && !(ps_global->smime && ps_global->smime->inited)){
+
+ dprint((9, "smime_init()"));
+ if(!ps_global->smime)
+ ps_global->smime = new_smime_struct();
+
+ setup_storage_locations();
+
+ s_cert_store = get_ca_store();
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ app_RAND_load_file(NULL);
+
+ openssl_extra_randomness();
+ ps_global->smime->inited = 1;
+ }
+
+ ERR_clear_error();
+}
+
+
+static void
+setup_storage_locations(void)
+{
+ int publiccertcontainer = 0, privatekeycontainer = 0, cacertcontainer = 0;
+ char path[MAXPATH+1], *contents;
+
+ if(!ps_global->smime)
+ return;
+
+#ifdef APPLEKEYCHAIN
+ if(F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ ps_global->smime->publictype = Keychain;
+ }
+ else{
+#endif /* APPLEKEYCHAIN */
+ /* Public certificates in a container */
+ if(ps_global->VAR_PUBLICCERT_CONTAINER && ps_global->VAR_PUBLICCERT_CONTAINER[0]){
+
+ publiccertcontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_PUBLICCERT_CONTAINER, path, MAXPATH))
+ publiccertcontainer = 0;
+
+ if(publiccertcontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ publiccertcontainer = 0;
+ }
+
+ if(publiccertcontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ publiccertcontainer = 0;
+ }
+
+ if(publiccertcontainer && path[0]){
+ ps_global->smime->publictype = Container;
+ ps_global->smime->publicpath = cpystr(path);
+
+ if(contents){
+ ps_global->smime->publiccontent = contents;
+ ps_global->smime->publiccertlist = mem_to_certlist(contents);
+ }
+ }
+ }
+
+ /* Public certificates in a directory of files */
+ if(!publiccertcontainer){
+ ps_global->smime->publictype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->publictype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->publictype = Nada;
+ }
+ }
+
+ if(ps_global->smime->publictype == Directory)
+ ps_global->smime->publicpath = cpystr(path);
+ }
+
+#ifdef APPLEKEYCHAIN
+ }
+#endif /* APPLEKEYCHAIN */
+
+ /* private keys in a container */
+ if(ps_global->VAR_PRIVATEKEY_CONTAINER && ps_global->VAR_PRIVATEKEY_CONTAINER[0]){
+
+ privatekeycontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_PRIVATEKEY_CONTAINER, path, MAXPATH))
+ privatekeycontainer = 0;
+
+ if(privatekeycontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ privatekeycontainer = 0;
+ }
+
+ if(privatekeycontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ privatekeycontainer = 0;
+ }
+
+ if(privatekeycontainer && path[0]){
+ ps_global->smime->privatetype = Container;
+ ps_global->smime->privatepath = cpystr(path);
+
+ if(contents){
+ ps_global->smime->privatecontent = contents;
+ ps_global->smime->personal_certs = mem_to_personal_certs(contents);
+ }
+ }
+ }
+
+ /* private keys in a directory of files */
+ if(!privatekeycontainer){
+ PERSONAL_CERT *result = NULL;
+
+ ps_global->smime->privatetype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->privatetype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->privatetype = Nada;
+ }
+ }
+
+ if(ps_global->smime->privatetype == Directory){
+ char buf2[MAXPATH];
+ struct dirent *d;
+ DIR *dirp;
+
+ ps_global->smime->privatepath = cpystr(path);
+ dirp = opendir(path);
+ if(dirp){
+
+ while((d=readdir(dirp)) != NULL){
+ X509 *cert;
+ size_t ll;
+
+ if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, ".key")){
+
+ /* copy file name to temp buffer */
+ strncpy(buf2, d->d_name, sizeof(buf2)-1);
+ buf2[sizeof(buf2)-1] = '\0';
+ /* chop off ".key" trailier */
+ buf2[strlen(buf2)-4] = 0;
+ /* Look for certificate */
+ cert = get_cert_for(buf2);
+
+ if(cert){
+ PERSONAL_CERT *pc;
+
+ /* create a new PERSONAL_CERT, fill it in */
+
+ pc = (PERSONAL_CERT *) fs_get(sizeof(*pc));
+ pc->cert = cert;
+ pc->name = cpystr(buf2);
+
+ /* Try to load the key with an empty password */
+ pc->key = load_key(pc, "");
+
+ pc->next = result;
+ result = pc;
+ }
+ }
+ }
+
+ closedir(dirp);
+ }
+ }
+
+ ps_global->smime->personal_certs = result;
+ }
+
+ /* extra cacerts in a container */
+ if(ps_global->VAR_CACERT_CONTAINER && ps_global->VAR_CACERT_CONTAINER[0]){
+
+ cacertcontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_CACERT_CONTAINER, path, MAXPATH))
+ cacertcontainer = 0;
+
+ if(cacertcontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ cacertcontainer = 0;
+ }
+
+ if(cacertcontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ cacertcontainer = 0;
+ }
+
+ if(cacertcontainer && path[0]){
+ ps_global->smime->catype = Container;
+ ps_global->smime->capath = cpystr(path);
+ ps_global->smime->cacontent = contents;
+ }
+ }
+
+ if(!cacertcontainer){
+ ps_global->smime->catype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->catype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->catype = Nada;
+ }
+ }
+
+ if(ps_global->smime->catype == Directory)
+ ps_global->smime->capath = cpystr(path);
+ }
+}
+
+
+int
+copy_publiccert_dir_to_container(void)
+{
+ return(copy_dir_to_container(Public));
+}
+
+
+int
+copy_publiccert_container_to_dir(void)
+{
+ return(copy_container_to_dir(Public));
+}
+
+
+int
+copy_privatecert_dir_to_container(void)
+{
+ return(copy_dir_to_container(Private));
+}
+
+
+int
+copy_privatecert_container_to_dir(void)
+{
+ return(copy_container_to_dir(Private));
+}
+
+
+int
+copy_cacert_dir_to_container(void)
+{
+ return(copy_dir_to_container(CACert));
+}
+
+
+int
+copy_cacert_container_to_dir(void)
+{
+ return(copy_container_to_dir(CACert));
+}
+
+
+/*
+ * returns 0 on success, -1 on failure
+ */
+int
+copy_dir_to_container(WhichCerts which)
+{
+ int ret = 0;
+ BIO *bio_out = NULL, *bio_in = NULL;
+ char srcpath[MAXPATH+1], dstpath[MAXPATH+1], emailaddr[MAXPATH], file[MAXPATH], line[4096];
+ char *tempfile = NULL;
+ DIR *dirp;
+ struct dirent *d;
+ REMDATA_S *rd = NULL;
+ char *configdir = NULL;
+ char *configpath = NULL;
+ char *filesuffix = NULL;
+
+ dprint((9, "copy_dir_to_container(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?"));
+ smime_init();
+
+ srcpath[0] = '\0';
+ dstpath[0] = '\0';
+ file[0] = '\0';
+ emailaddr[0] = '\0';
+
+ if(which == Public){
+ configdir = ps_global->VAR_PUBLICCERT_DIR;
+ configpath = ps_global->smime->publicpath;
+ filesuffix = ".crt";
+ }
+ else if(which == Private){
+ configdir = ps_global->VAR_PRIVATEKEY_DIR;
+ configpath = ps_global->smime->privatepath;
+ filesuffix = ".key";
+ }
+ else if(which == CACert){
+ configdir = ps_global->VAR_CACERT_DIR;
+ configpath = ps_global->smime->capath;
+ filesuffix = ".crt";
+ }
+
+ if(!(configdir && configdir[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Directory not defined"));
+ return -1;
+ }
+
+ if(!(configpath && configpath[0])){
+#ifdef APPLEKEYCHAIN
+ if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first"));
+ return -1;
+ }
+#endif /* APPLEKEYCHAIN */
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ if(!(filesuffix && strlen(filesuffix) == 4)){
+ return -1;
+ }
+
+
+ /*
+ * If there is a legit directory to read from set up the
+ * container file to write to.
+ */
+ if(signature_path(configdir, srcpath, MAXPATH) && !IS_REMOTE(srcpath)){
+
+ if(IS_REMOTE(configpath)){
+ rd = rd_create_remote(RemImap, configpath, REMOTE_SMIME_SUBTYPE,
+ NULL, "Error: ",
+ _("Can't access remote smime configuration."));
+ if(!rd)
+ return -1;
+
+ (void) rd_read_metadata(rd);
+
+ if(rd->access == MaybeRorW){
+ if(rd->read_status == 'R')
+ rd->access = ReadOnly;
+ else
+ rd->access = ReadWrite;
+ }
+
+ if(rd->access != NoExists){
+
+ rd_check_remvalid(rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(rd->read_status == 'R'){
+ rd_check_readonly_access(rd);
+ if(rd->read_status == 'W'){
+ rd->access = ReadWrite;
+ rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ rd->access = ReadOnly;
+ }
+ }
+
+ if(rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(rd) != 0){
+
+ dprint((1, "copy_dir_to_container: rd_update_local failed\n"));
+ rd_close_remdata(&rd);
+ return -1;
+ }
+ }
+ else
+ rd_open_remote(rd);
+
+ if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
+ rd_close_remdata(&rd);
+ return -1;
+ }
+
+ rd->flags |= DO_REMTRIM;
+
+ strncpy(dstpath, rd->lf, sizeof(dstpath)-1);
+ dstpath[sizeof(dstpath)-1] = '\0';
+ }
+ else{
+ strncpy(dstpath, configpath, sizeof(dstpath)-1);
+ dstpath[sizeof(dstpath)-1] = '\0';
+ }
+
+ /*
+ * dstpath is either the local Container file or the local cache file
+ * for the remote Container file.
+ */
+ tempfile = tempfile_in_same_dir(dstpath, "az", NULL);
+ }
+
+ /*
+ * If there is a legit directory to read from and a tempfile
+ * to write to we continue.
+ */
+ if(tempfile && (bio_out=BIO_new_file(tempfile, "w")) != NULL){
+
+ dirp = opendir(srcpath);
+ if(dirp){
+
+ while((d=readdir(dirp)) && !ret){
+ size_t ll;
+
+ if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, filesuffix)){
+
+ /* copy file name to temp buffer */
+ strncpy(emailaddr, d->d_name, sizeof(emailaddr)-1);
+ emailaddr[sizeof(emailaddr)-1] = '\0';
+ /* chop off suffix trailier */
+ emailaddr[strlen(emailaddr)-4] = 0;
+
+ /*
+ * This is the separator between the contents of
+ * different files.
+ */
+ if(which == CACert){
+ if(!((BIO_puts(bio_out, CACERTSTORELEADER) > 0)
+ && (BIO_puts(bio_out, emailaddr) > 0)
+ && (BIO_puts(bio_out, "\n") > 0)))
+ ret = -1;
+ }
+ else{
+ if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0)
+ && (BIO_puts(bio_out, emailaddr) > 0)
+ && (BIO_puts(bio_out, "\n") > 0)))
+ ret = -1;
+ }
+
+ /* read then write contents of file */
+ build_path(file, srcpath, d->d_name, sizeof(file));
+ if(!(bio_in = BIO_new_file(file, "r")))
+ ret = -1;
+
+ if(!ret){
+ int good_stuff = 0;
+
+ while(BIO_gets(bio_in, line, sizeof(line)) > 0){
+ if(strncmp("-----BEGIN", line, strlen("-----BEGIN")) == 0)
+ good_stuff = 1;
+
+ if(good_stuff)
+ BIO_puts(bio_out, line);
+
+ if(strncmp("-----END", line, strlen("-----END")) == 0)
+ good_stuff = 0;
+ }
+ }
+
+ BIO_free(bio_in);
+ }
+ }
+
+ closedir(dirp);
+ }
+
+ BIO_free(bio_out);
+
+ if(!ret){
+ if(rename_file(tempfile, dstpath) < 0){
+ q_status_message2(SM_ORDER, 3, 3,
+ _("Can't rename %s to %s"), tempfile, dstpath);
+ ret = -1;
+ }
+
+ /* if the container is remote, copy it */
+ if(!ret && IS_REMOTE(configpath)){
+ int e;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+
+ if((e = rd_update_remote(rd, datebuf)) != 0){
+ if(e == -1){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary smime file %s: %s"),
+ rd->lf, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error opening temp file %s\n",
+ rd->lf ? rd->lf : "?"));
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %s: %s"),
+ rd->rn, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error copying from %s to %s\n",
+ rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of smime key to remote folder failed, NOT saved remotely"));
+ }
+ else{
+ rd_update_metadata(rd, datebuf);
+ rd->read_status = 'W';
+ }
+
+ rd_close_remdata(&rd);
+ }
+ }
+ }
+
+ if(tempfile)
+ fs_give((void **) &tempfile);
+
+ return ret;
+}
+
+
+/*
+ * returns 0 on success, -1 on failure
+ */
+int
+copy_container_to_dir(WhichCerts which)
+{
+ char path[MAXPATH+1], file[MAXPATH+1], buf[MAXPATH+1];
+ char iobuf[4096];
+ char *contents = NULL;
+ char *leader = NULL;
+ char *filesuffix = NULL;
+ char *configdir = NULL;
+ char *configpath = NULL;
+ char *tempfile = NULL;
+ char *p, *q, *line, *name, *certtext, *save_p;
+ int len;
+ BIO *in, *out;
+
+ dprint((9, "copy_container_to_dir(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?"));
+ smime_init();
+
+ path[0] = '\0';
+
+ if(which == Public){
+ leader = EMAILADDRLEADER;
+ contents = ps_global->smime->publiccontent;
+ configdir = ps_global->VAR_PUBLICCERT_DIR;
+ configpath = ps_global->smime->publicpath;
+ filesuffix = ".crt";
+ if(!(configpath && configpath[0])){
+#ifdef APPLEKEYCHAIN
+ if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first"));
+ return -1;
+ }
+#endif /* APPLEKEYCHAIN */
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->publicpath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->publicpath = cpystr(path);
+ configpath = ps_global->smime->publicpath;
+ }
+ else if(which == Private){
+ leader = EMAILADDRLEADER;
+ contents = ps_global->smime->privatecontent;
+ configdir = ps_global->VAR_PRIVATEKEY_DIR;
+ configpath = ps_global->smime->privatepath;
+ filesuffix = ".key";
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->privatepath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->privatepath = cpystr(path);
+ configpath = ps_global->smime->privatepath;
+ }
+ else if(which == CACert){
+ leader = CACERTSTORELEADER;
+ contents = ps_global->smime->cacontent;
+ configdir = ps_global->VAR_CACERT_DIR;
+ configpath = ps_global->smime->capath;
+ filesuffix = ".crt";
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->capath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->capath = cpystr(path);
+ configpath = ps_global->smime->capath;
+ }
+
+ if(!(configdir && configdir[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Directory not defined"));
+ return -1;
+ }
+
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ if(!(filesuffix && strlen(filesuffix) == 4)){
+ return -1;
+ }
+
+
+ if(contents && *contents){
+ for(p = contents; *p != '\0';){
+ line = p;
+
+ while(*p && *p != '\n')
+ p++;
+
+ save_p = NULL;
+ if(*p == '\n'){
+ save_p = p;
+ *p++ = '\0';
+ }
+
+ if(strncmp(leader, line, strlen(leader)) == 0){
+ name = line + strlen(leader);
+ certtext = p;
+ if(strncmp("-----BEGIN", certtext, strlen("-----BEGIN")) == 0){
+ if((q = strstr(certtext, leader)) != NULL){
+ p = q;
+ }
+ else{ /* end of file */
+ q = certtext + strlen(certtext);
+ p = q;
+ }
+
+ strncpy(buf, name, sizeof(buf)-5);
+ buf[sizeof(buf)-5] = '\0';
+ strncat(buf, filesuffix, 5);
+ build_path(file, configpath, buf, sizeof(file));
+
+ in = BIO_new_mem_buf(certtext, q-certtext);
+ if(in){
+ tempfile = tempfile_in_same_dir(file, "az", NULL);
+ out = NULL;
+ if(tempfile)
+ out = BIO_new_file(tempfile, "w");
+
+ if(out){
+ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
+ BIO_write(out, iobuf, len);
+
+ BIO_free(out);
+
+ if(rename_file(tempfile, file) < 0){
+ q_status_message2(SM_ORDER, 3, 3,
+ _("Can't rename %s to %s"),
+ tempfile, file);
+ return -1;
+ }
+
+ fs_give((void **) &tempfile);
+ }
+
+ BIO_free(in);
+ }
+ }
+ }
+
+ if(save_p)
+ *save_p = '\n';
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef APPLEKEYCHAIN
+
+int
+copy_publiccert_container_to_keychain(void)
+{
+ /* NOT IMPLEMNTED */
+ return -1;
+}
+
+int
+copy_publiccert_keychain_to_container(void)
+{
+ /* NOT IMPLEMNTED */
+ return -1;
+}
+
+#endif /* APPLEKEYCHAIN */
+
+
+/*
+ * Get a pointer to a string describing the most recent OpenSSL error.
+ * It's statically allocated, so don't change or attempt to free it.
+ */
+static const char *
+openssl_error_string(void)
+{
+ char *errs;
+ const char *data = NULL;
+ long errn;
+
+ errn = ERR_peek_error_line_data(NULL, NULL, &data, NULL);
+ errs = (char*) ERR_reason_error_string(errn);
+
+ if(errs)
+ return errs;
+ else if(data)
+ return data;
+
+ return "unknown error";
+}
+
+
+/* Return true if the body looks like a PKCS7 object */
+int
+is_pkcs7_body(BODY *body)
+{
+ int result;
+
+ result = body->type==TYPEAPPLICATION &&
+ body->subtype &&
+ (strucmp(body->subtype,"pkcs7-mime")==0 ||
+ strucmp(body->subtype,"x-pkcs7-mime")==0 ||
+ strucmp(body->subtype,"pkcs7-signature")==0 ||
+ strucmp(body->subtype,"x-pkcs7-signature")==0);
+
+ return result;
+}
+
+
+#ifdef notdef
+/*
+ * Somewhat useful debug utility to dump the contents of a BIO to a file.
+ * Note that a memory BIO will have its contents eliminated after they
+ * are read so this will break the next step.
+ */
+static void
+dump_bio_to_file(BIO *in, char *filename)
+{
+ char iobuf[4096];
+ int len;
+ BIO *out;
+
+ out = BIO_new_file(filename, "w");
+
+ if(out){
+ if(BIO_method_type(in) != BIO_TYPE_MEM)
+ BIO_reset(in);
+
+ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
+ BIO_write(out, iobuf, len);
+
+ BIO_free(out);
+ }
+
+ BIO_reset(in);
+}
+#endif
+
+
+/*
+ * Recursively stash a pointer to the decrypted data in our
+ * manufactured body.
+ */
+static void
+create_local_cache(char *base, BODY *b)
+{
+ if(b->type==TYPEMULTIPART){
+ PART *p;
+
+#if 0
+ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes);
+#else
+ /*
+ * We don't really want to copy the real body contents. It shouldn't be
+ * used, and in the case of a message with attachments, we'll be
+ * duplicating the files multiple times.
+ */
+ cpytxt(&b->contents.text, "BODY UNAVAILABLE", 16);
+#endif
+
+ for(p=b->nested.part; p; p=p->next)
+ create_local_cache(base, (BODY*) p);
+ }
+ else{
+ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes);
+ }
+}
+
+
+static long
+rfc822_output_func(void *b, char *string)
+{
+ BIO *bio = (BIO *) b;
+
+ return((BIO_puts(bio, string) > 0) ? 1L : 0L);
+}
+
+
+/*
+ * Attempt to load the private key for the given PERSONAL_CERT.
+ * This sets the appropriate passphrase globals in order to
+ * interact with the user correctly.
+ */
+static int
+load_private_key(PERSONAL_CERT *pcert)
+{
+ if(!pcert->key){
+
+ /* Try empty password by default */
+ char *password = "";
+
+ if(ps_global->smime
+ && (ps_global->smime->need_passphrase
+ || ps_global->smime->entered_passphrase)){
+ /* We've already been in here and discovered we need a different password */
+
+ if(ps_global->smime->entered_passphrase)
+ password = (char *) ps_global->smime->passphrase; /* already entered */
+ else
+ return 0;
+ }
+
+ ERR_clear_error();
+
+ if(!(pcert->key = load_key(pcert, password))){
+ long err = ERR_get_error();
+
+ /* Couldn't load key... */
+
+ if(ps_global->smime && ps_global->smime->entered_passphrase){
+
+ /* The user got the password wrong maybe? */
+
+ if((ERR_GET_LIB(err)==ERR_LIB_EVP && ERR_GET_REASON(err)==EVP_R_BAD_DECRYPT) ||
+ (ERR_GET_LIB(err)==ERR_LIB_PEM && ERR_GET_REASON(err)==PEM_R_BAD_DECRYPT))
+ q_status_message(SM_ORDER | SM_DING, 4, 4, _("Incorrect passphrase"));
+ else
+ q_status_message1(SM_ORDER, 4, 4, _("Couldn't read key: %s"),(char*)openssl_error_string());
+
+ /* This passphrase is no good; forget it */
+ ps_global->smime->entered_passphrase = 0;
+ }
+
+ /* Indicate to the UI that we need re-entry (see mailcmd.c:process_cmd())*/
+ if(ps_global->smime)
+ ps_global->smime->need_passphrase = 1;
+
+ if(ps_global->smime){
+ if(ps_global->smime->passphrase_emailaddr)
+ fs_give((void **) &ps_global->smime->passphrase_emailaddr);
+
+ ps_global->smime->passphrase_emailaddr = get_x509_subject_email(pcert->cert);
+ }
+
+ return 0;
+ }
+ else{
+ /* This key will be cached, so we won't be called again */
+ if(ps_global->smime){
+ ps_global->smime->entered_passphrase = 0;
+ ps_global->smime->need_passphrase = 0;
+ }
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void
+setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename)
+{
+ b->type = TYPEAPPLICATION;
+ b->subtype = cpystr(type);
+ b->encoding = ENCBINARY;
+ b->description = cpystr(description);
+
+ b->disposition.type = cpystr("attachment");
+ set_parameter(&b->disposition.parameter, "filename", filename);
+
+ set_parameter(&b->parameter, "name", filename);
+}
+
+
+/*
+ * Look for a personal certificate matching the
+ * given address
+ */
+PERSONAL_CERT *
+match_personal_cert_to_email(ADDRESS *a)
+{
+ PERSONAL_CERT *pcert = NULL;
+ char buf[MAXPATH];
+ char *email;
+
+ if(!a || !a->mailbox || !a->host)
+ return NULL;
+
+ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host);
+
+ if(ps_global->smime){
+ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs;
+ pcert;
+ pcert=pcert->next){
+
+ if(!pcert->cert)
+ continue;
+
+ email = get_x509_subject_email(pcert->cert);
+
+ if(email && strucmp(email,buf)==0){
+ fs_give((void**) &email);
+ break;
+ }
+
+ fs_give((void**) &email);
+ }
+ }
+
+ return pcert;
+}
+
+
+/*
+ * Look for a personal certificate matching the from
+ * (or reply_to? in the given envelope)
+ */
+PERSONAL_CERT *
+match_personal_cert(ENVELOPE *env)
+{
+ PERSONAL_CERT *pcert;
+
+ pcert = match_personal_cert_to_email(env->reply_to);
+ if(!pcert)
+ pcert = match_personal_cert_to_email(env->from);
+
+ return pcert;
+}
+
+
+/*
+ * Flatten the given body into its MIME representation.
+ * Return the result in a BIO.
+ */
+static BIO *
+body_to_bio(BODY *body)
+{
+ BIO *bio = NULL;
+ int len;
+
+ bio = BIO_new(BIO_s_mem());
+ if(!bio)
+ return NULL;
+
+ pine_encode_body(body); /* this attaches random boundary strings to multiparts */
+ pine_write_body_header(body, rfc822_output_func, bio);
+ pine_rfc822_output_body(body, rfc822_output_func, bio);
+
+ /*
+ * Now need to truncate by two characters since the above
+ * appends CRLF.
+ */
+ if((len=BIO_ctrl_pending(bio)) > 1){
+ BUF_MEM *biobuf = NULL;
+
+ BIO_get_mem_ptr(bio, &biobuf);
+ if(biobuf){
+ BUF_MEM_grow(biobuf, len-2); /* remove CRLF */
+ }
+ }
+
+ return bio;
+}
+
+
+static BIO *
+bio_from_store(STORE_S *store)
+{
+ BIO *ret = NULL;
+
+ if(store && store->src == BioType && store->txt){
+ ret = (BIO *) store->txt;
+ }
+
+ return(ret);
+}
+
+/*
+ * Encrypt file; given a path (char *) fp, replace the file
+ * by an encrypted version of it. If (char *) text is not null, then
+ * replace the text of (char *) fp by the encrypted version of (char *) text.
+ */
+int
+encrypt_file(char *fp, char *text)
+{
+ const EVP_CIPHER *cipher = NULL;
+ STACK_OF(X509) *encerts = NULL;
+ X509 *cert;
+ PERSONAL_CERT *pcert;
+ BIO *in;
+ PKCS7 *p7 = NULL;
+ FILE *fpp;
+ int rv = 0;
+
+ smime_init();
+ if((pcert = ps_global->smime->personal_certs) == NULL)
+ return 0;
+
+ cipher = EVP_aes_256_cbc();
+ encerts = sk_X509_new_null();
+
+ if((cert = get_cert_for(pcert->name)) != NULL)
+ sk_X509_push(encerts, cert);
+ else
+ goto end;
+
+ if(text){
+ in = BIO_new(BIO_s_mem());
+ if(in == NULL)
+ goto end;
+ (void) BIO_reset(in);
+ BIO_puts(in, text);
+ }
+ else{
+ if(!(in = BIO_new_file(fp, "rb")))
+ goto end;
+
+ BIO_read_filename(in, fp);
+ }
+
+ if((p7 = PKCS7_encrypt(encerts, in, cipher, 0)) == NULL)
+ goto end;
+ BIO_set_close(in, BIO_CLOSE);
+ BIO_free(in);
+ if(!(in = BIO_new_file(fp, "w")))
+ goto end;
+ BIO_reset(in);
+ rv = PEM_write_bio_PKCS7(in, p7);
+ BIO_flush(in);
+
+end:
+ BIO_free(in);
+ PKCS7_free(p7);
+ sk_X509_pop_free(encerts, X509_free);
+
+ return rv;
+}
+
+/*
+ * Encrypt a message on the way out. Called from call_mailer in send.c
+ * The body may be reallocated.
+ */
+int
+encrypt_outgoing_message(METAENV *header, BODY **bodyP)
+{
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+ BIO *out = NULL;
+ const EVP_CIPHER *cipher = NULL;
+ STACK_OF(X509) *encerts = NULL;
+ STORE_S *outs = NULL;
+ PINEFIELD *pf;
+ ADDRESS *a;
+ BODY *body = *bodyP;
+ BODY *newBody = NULL;
+ int result = 0;
+
+ dprint((9, "encrypt_outgoing_message()"));
+ smime_init();
+
+ cipher = EVP_des_cbc();
+
+ encerts = sk_X509_new_null();
+
+ /* Look for a certificate for each of the recipients */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
+ for(a=*pf->addr; a; a=a->next){
+ X509 *cert;
+ char buf[MAXPATH];
+
+ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host);
+
+ cert = get_cert_for(buf);
+ if(cert)
+ sk_X509_push(encerts,cert);
+ else{
+ q_status_message2(SM_ORDER, 1, 1,
+ _("Unable to find certificate for <%s@%s>"),
+ a->mailbox, a->host);
+ goto end;
+ }
+ }
+ }
+
+
+ in = body_to_bio(body);
+
+ p7 = PKCS7_encrypt(encerts, in, cipher, 0);
+
+ outs = so_get(BioType, NULL, EDIT_ACCESS);
+ out = bio_from_store(outs);
+
+ i2d_PKCS7_bio(out, p7);
+ (void) BIO_flush(out);
+
+ so_seek(outs, 0, SEEK_SET);
+
+ newBody = mail_newbody();
+
+ newBody->type = TYPEAPPLICATION;
+ newBody->subtype = cpystr("pkcs7-mime");
+ newBody->encoding = ENCBINARY;
+
+ newBody->disposition.type = cpystr("attachment");
+ set_parameter(&newBody->disposition.parameter, "filename", "smime.p7m");
+
+ newBody->description = cpystr("S/MIME Encrypted Message");
+ set_parameter(&newBody->parameter, "smime-type", "enveloped-data");
+ set_parameter(&newBody->parameter, "name", "smime.p7m");
+
+ newBody->contents.text.data = (unsigned char *) outs;
+
+ *bodyP = newBody;
+
+ result = 1;
+
+end:
+
+ BIO_free(in);
+ PKCS7_free(p7);
+ sk_X509_pop_free(encerts, X509_free);
+
+ dprint((9, "encrypt_outgoing_message returns %d", result));
+ return result;
+}
+
+
+/*
+ * Plonk the contents (mime headers and body) of the given
+ * section of a message to a BIO_s_mem BIO object.
+ */
+static BIO *
+raw_part_to_bio(long msgno, const char *section)
+{
+ unsigned long len;
+ char *text;
+ BIO *bio;
+
+ bio = BIO_new(BIO_s_mem());
+
+ if(bio){
+
+ (void) BIO_reset(bio);
+
+ /* First grab headers of the chap */
+ text = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) section, &len, 0);
+
+ if(text){
+ BIO_write(bio, text, len);
+
+ /** Now grab actual body */
+ text = mail_fetch_body (ps_global->mail_stream, msgno, (char*) section, &len, 0);
+ if(text){
+ BIO_write(bio, text, len);
+ }
+ else{
+ BIO_free(bio);
+ bio = NULL;
+ }
+
+ }
+ else{
+ BIO_free(bio);
+ bio = NULL;
+ }
+ }
+
+ return bio;
+}
+
+
+/*
+ Get (and decode) the body of the given section of msg
+ */
+static STORE_S*
+get_part_contents(long msgno, const char *section)
+{
+ long len;
+ gf_io_t pc;
+ STORE_S *store = NULL;
+ char *err;
+
+ store = so_get(CharStar, NULL, EDIT_ACCESS);
+ if(store){
+ gf_set_so_writec(&pc,store);
+
+ err = detach(ps_global->mail_stream, msgno, (char*) section, 0L, &len, pc, NULL, 0L);
+
+ gf_clear_so_writec(store);
+
+ so_seek(store, 0, SEEK_SET);
+
+ if(err)
+ so_give(&store);
+ }
+
+ return store;
+}
+
+
+static PKCS7 *
+get_pkcs7_from_part(long msgno,const char *section)
+{
+ STORE_S *store = NULL;
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+
+ store = get_part_contents(msgno, section);
+
+ if(store){
+ if(store->src == CharStar){
+ int len;
+
+ /*
+ * We're reaching inside the STORE_S structure. We should
+ * probably have a way to get the length, instead.
+ */
+ len = (int) (store->eod - store->dp);
+ in = BIO_new_mem_buf(store->txt, len);
+ }
+ else{ /* just copy it */
+ unsigned char c;
+
+ in = BIO_new(BIO_s_mem());
+ (void) BIO_reset(in);
+
+ so_seek(store, 0L, 0);
+ while(so_readc(&c, store)){
+ BIO_write(in, &c, 1);
+ }
+ }
+
+ if(in){
+/* dump_bio_to_file(in, "/tmp/decoded-signature"); */
+ if((p7=d2i_PKCS7_bio(in,NULL)) == NULL){
+ /* error */
+ }
+
+ BIO_free(in);
+ }
+
+ so_give(&store);
+ }
+
+ return p7;
+}
+
+
+/*
+ * Try to verify a signature.
+ *
+ * p7 - the pkcs7 object to verify
+ * in - the plain data to verify (NULL if not detached)
+ * out - BIO to which to write the opaque data
+ */
+static int
+do_signature_verify(PKCS7 *p7, BIO *in, BIO *out)
+{
+ STACK_OF(X509) *otherCerts = NULL;
+ int result;
+ const char *data;
+ long err;
+
+#if 0
+ dump_bio_to_file(in,"/tmp/verified-data");
+#endif
+
+ if(!s_cert_store){
+ q_status_message(SM_ORDER | SM_DING, 2, 2,
+ _("Couldn't verify S/MIME signature: No CA Certs were loaded"));
+
+ return -1;
+ }
+
+ result = PKCS7_verify(p7, otherCerts, s_cert_store, in, out, 0);
+
+ if(result){
+ q_status_message(SM_ORDER, 1, 1, _("S/MIME signature verified ok"));
+ }
+ else{
+ err = ERR_peek_error_line_data(NULL, NULL, &data, NULL);
+
+ if(out && err==ERR_PACK(ERR_LIB_PKCS7,PKCS7_F_PKCS7_VERIFY,PKCS7_R_CERTIFICATE_VERIFY_ERROR)){
+
+ /* Retry verification so we can get the plain text */
+ /* Might be better to reimplement PKCS7_verify here? */
+
+ PKCS7_verify(p7, otherCerts, s_cert_store, in, out, PKCS7_NOVERIFY);
+ }
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Couldn't verify S/MIME signature: %s"), (char*) openssl_error_string());
+ }
+
+ /* now try to extract the certificates of any signers */
+ {
+ STACK_OF(X509) *signers;
+ int i;
+
+ signers = PKCS7_get0_signers(p7, NULL, 0);
+
+ if(signers)
+ for(i=0; i<sk_X509_num(signers); i++){
+ char *email;
+ X509 *x = sk_X509_value(signers,i);
+ X509 *cert;
+
+ if(!x)
+ continue;
+
+ email = get_x509_subject_email(x);
+
+ if(email){
+ cert = get_cert_for(email);
+ if(cert)
+ X509_free(cert);
+ else
+ save_cert_for(email, x);
+
+ fs_give((void**) &email);
+ }
+ }
+
+ sk_X509_free(signers);
+ }
+
+ return result;
+}
+
+
+void
+free_smime_body_sparep(void **sparep)
+{
+ if(sparep && *sparep){
+ PKCS7_free((PKCS7 *) (*sparep));
+ *sparep = NULL;
+ }
+}
+
+
+/*
+ * Given a multipart body of type multipart/signed, attempt to verify it.
+ * Returns non-zero if the body was changed.
+ */
+static int
+do_detached_signature_verify(BODY *b, long msgno, char *section)
+{
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+ PART *p;
+ int result, modified_the_body = 0;
+ char newSec[100];
+ char *what_we_did;
+
+ dprint((9, "do_detached_signature_verify(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+ smime_init();
+
+ snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : "");
+ in = raw_part_to_bio(msgno, newSec);
+
+ if(in){
+
+ snprintf(newSec, sizeof(newSec), "%s%s2", section ? section : "", (section && *section) ? "." : "");
+ p7 = get_pkcs7_from_part(msgno, newSec);
+
+ if(!p7)
+ goto end;
+
+ result = do_signature_verify(p7, in, NULL);
+
+ if(b->subtype)
+ fs_give((void**) &b->subtype);
+
+ b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE);
+ b->encoding = ENC8BIT;
+
+ if(b->description)
+ fs_give ((void**) &b->description);
+
+ what_we_did = result ? _("This message was cryptographically signed.") :
+ _("This message was cryptographically signed but the signature could not be verified.");
+
+ b->description = cpystr(what_we_did);
+
+ b->sparep = p7;
+ p7 = NULL;
+
+ p = b->nested.part;
+
+ /* p is signed plaintext */
+ if(p && p->next)
+ mail_free_body_part(&p->next); /* hide the pkcs7 from the viewer */
+
+ BIO_free(in);
+
+ modified_the_body = 1;
+ }
+
+end:
+ PKCS7_free(p7);
+
+ return modified_the_body;
+}
+
+
+PERSONAL_CERT *
+find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri)
+{
+ PERSONAL_CERT *x = NULL;
+
+ if(ps_global->smime){
+ for(x = (PERSONAL_CERT *) ps_global->smime->personal_certs; x; x=x->next){
+ X509 *mine;
+
+ mine = x->cert;
+
+ if(!X509_NAME_cmp(ri->issuer_and_serial->issuer,mine->cert_info->issuer) &&
+ !ASN1_INTEGER_cmp(ri->issuer_and_serial->serial,mine->cert_info->serialNumber)){
+ break;
+ }
+ }
+ }
+
+ return x;
+}
+
+
+static PERSONAL_CERT *
+find_certificate_matching_pkcs7(PKCS7 *p7)
+{
+ int i;
+ STACK_OF(PKCS7_RECIP_INFO) *recips;
+ PERSONAL_CERT *x = NULL;
+
+ recips = p7->d.enveloped->recipientinfo;
+
+ for(i=0; i<sk_PKCS7_RECIP_INFO_num(recips); i++){
+ PKCS7_RECIP_INFO *ri;
+
+ ri = sk_PKCS7_RECIP_INFO_value(recips, i);
+
+ if((x=find_certificate_matching_recip_info(ri))!=0){
+ break;
+ }
+ }
+
+ return x;
+}
+
+/* decrypt an encrypted file.
+ Args: fp - the path to the encrypted file.
+ rv - a code that thells the caller what happened inside the function
+ Returns the decoded text allocated in a char *, whose memory must be
+ freed by caller
+ */
+
+char *
+decrypt_file(char *fp, int *rv)
+{
+ PKCS7 *p7 = NULL;
+ char *text, *tmp;
+ BIO *in = NULL, *out = NULL;
+ EVP_PKEY *pkey = NULL, *key = NULL;
+ PERSONAL_CERT *pcert = NULL;
+ X509 *recip, *cert;
+ STORE_S *outs = NULL, *store, *ins;
+ int i, j;
+ long unsigned int len;
+ void *ret;
+
+ smime_init();
+
+ if((text = read_file(fp, 0)) == NULL)
+ return NULL;
+
+ tmp = fs_get(strlen(text) + (strlen(text) << 6) + 1);
+ for(j = 0, i = strlen("-----BEGIN PKCS7-----") + 1; text[i] != '\0'
+ && text[i] != '-'; j++, i++)
+ tmp[j] = text[i];
+ tmp[j] = '\0';
+
+ ret = rfc822_base64(tmp, strlen(tmp), &len);
+
+ if((in = BIO_new_mem_buf((char *)ret, len)) != NULL){
+ p7 = d2i_PKCS7_bio(in, NULL);
+ BIO_free(in);
+ }
+
+ if(text) fs_give((void **)&text);
+ if(ret) fs_give((void **)&ret);
+
+ if((pcert = ps_global->smime->personal_certs) == NULL)
+ goto end;
+
+ if((i = load_private_key(pcert)) == 0
+ && ps_global->smime
+ && ps_global->smime->need_passphrase
+ && !ps_global->smime->already_auto_asked)
+ for(; i == 0;){
+ ps_global->smime->already_auto_asked = 1;
+ if(pith_opt_smime_get_passphrase){
+ switch((*pith_opt_smime_get_passphrase)()){
+ case 0 : i = load_private_key(pcert);
+ break;
+
+ case 1 : i = -1;
+ break;
+
+ default: break; /* repeat until we cancel */
+ }
+ }
+ else
+ i = -2;
+ }
+
+ if(rv) *rv = i;
+
+ if((key = pcert->key) == NULL)
+ goto end;
+
+ recip = get_cert_for(pcert->name);
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+
+ i = PKCS7_decrypt(p7, key, recip, out, 0);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(i == 0){
+ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"),
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ BIO_get_mem_data(out, &tmp);
+
+ text = cpystr(tmp);
+ BIO_free(out);
+
+end:
+ PKCS7_free(p7);
+
+ return text;
+}
+
+/*
+ * Try to decode (decrypt or verify a signature) a PKCS7 body
+ * Returns non-zero if something was changed.
+ */
+static int
+do_decoding(BODY *b, long msgno, const char *section)
+{
+ int modified_the_body = 0;
+ BIO *out = NULL;
+ PKCS7 *p7 = NULL;
+ X509 *recip = NULL;
+ EVP_PKEY *key = NULL;
+ PERSONAL_CERT *pcert = NULL;
+ char *what_we_did = "";
+ char null[1];
+
+ dprint((9, "do_decoding(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+ null[0] = '\0';
+ smime_init();
+
+ /*
+ * Extract binary data from part to an in-memory store
+ */
+
+ if(b->sparep){ /* already done */
+ p7 = (PKCS7*) b->sparep;
+ }
+ else{
+
+ p7 = get_pkcs7_from_part(msgno, section && *section ? section : "1");
+ if(!p7){
+ q_status_message1(SM_ORDER, 2, 2, "Couldn't load PKCS7 object: %s",
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ /*
+ * Save the PKCS7 object for later dealings by the user interface.
+ * It will be cleaned up when the body is garbage collected.
+ */
+ b->sparep = p7;
+ }
+
+ if(PKCS7_type_is_signed(p7)){
+ int sigok;
+
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+ BIO_puts(out, "MIME-Version: 1.0\r\n"); /* needed so rfc822_parse_msg_full believes it's MIME */
+
+ sigok = do_signature_verify(p7, NULL, out);
+
+ /* shouldn't really duplicate these messages */
+ what_we_did = sigok ? _("This message was cryptographically signed.") :
+ _("This message was cryptographically signed but the signature could not be verified.");
+
+ /* make sure it's null terminated */
+ BIO_write(out, null, 1);
+ }
+ else if(!PKCS7_type_is_enveloped(p7)){
+ q_status_message(SM_ORDER, 1, 1, "PKCS7 object not recognised.");
+ goto end;
+ }
+ else{ /* It *is* enveloped */
+ int decrypt_result;
+
+ what_we_did = _("This message was encrypted.");
+
+ /* now need to find a cert that can decrypt this */
+ pcert = find_certificate_matching_pkcs7(p7);
+
+ if(!pcert){
+ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to decrypt."));
+ goto end;
+ }
+
+ recip = pcert->cert;
+
+ if(!load_private_key(pcert)
+ && ps_global->smime
+ && ps_global->smime->need_passphrase
+ && !ps_global->smime->already_auto_asked){
+ /* Couldn't load key with blank password, ask user */
+ ps_global->smime->already_auto_asked = 1;
+ if(pith_opt_smime_get_passphrase){
+ (*pith_opt_smime_get_passphrase)();
+ load_private_key(pcert);
+ }
+ }
+
+ key = pcert->key;
+ if(!key)
+ goto end;
+
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+ BIO_puts(out, "MIME-Version: 1.0\r\n");
+
+ decrypt_result = PKCS7_decrypt(p7, key, recip, out, 0);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(!decrypt_result){
+ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"),
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ BIO_write(out, null, 1);
+ }
+
+ /*
+ * We've now produced a flattened MIME object in BIO out.
+ * It needs to be turned back into a BODY.
+ */
+
+ if(out){
+ BODY *body;
+ ENVELOPE *env;
+ char *h = NULL;
+ char *bstart;
+ STRING s;
+ BUF_MEM *bptr = NULL;
+
+ BIO_get_mem_ptr(out, &bptr);
+ if(bptr)
+ h = bptr->data;
+
+ /* look for start of body */
+ bstart = strstr(h, "\r\n\r\n");
+
+ if(!bstart){
+ q_status_message(SM_ORDER, 3, 3, _("Encrypted data couldn't be parsed."));
+ }
+ else{
+ bstart += 4; /* skip over CRLF*2 */
+
+ INIT(&s, mail_string, bstart, strlen(bstart));
+ rfc822_parse_msg_full(&env, &body, h, bstart-h-2, &s, BADHOST, 0, 0);
+ mail_free_envelope(&env); /* Don't care about this */
+
+ /*
+ * Now convert original body (application/pkcs7-mime)
+ * to a multipart body with one sub-part (the decrypted body).
+ * Note that the sub-part may also be multipart!
+ */
+
+ b->type = TYPEMULTIPART;
+ if(b->subtype)
+ fs_give((void**) &b->subtype);
+
+ /*
+ * This subtype is used in mailview.c to annotate the display of
+ * encrypted or signed messages. We know for sure then that it's a PKCS7
+ * part because the sparep field is set to the PKCS7 object (see above).
+ */
+ b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE);
+ b->encoding = ENC8BIT;
+
+ if(b->description)
+ fs_give((void**) &b->description);
+
+ b->description = cpystr(what_we_did);
+
+ if(b->disposition.type)
+ fs_give((void **) &b->disposition.type);
+
+ if(b->contents.text.data)
+ fs_give((void **) &b->contents.text.data);
+
+ if(b->parameter)
+ mail_free_body_parameter(&b->parameter);
+
+ /* Allocate mem for the sub-part, and copy over the contents of our parsed body */
+ b->nested.part = fs_get(sizeof(PART));
+ b->nested.part->body = *body;
+ b->nested.part->next = NULL;
+
+ fs_give((void**) &body);
+
+ /*
+ * IMPORTANT BIT: set the body->contents.text.data elements to contain the decrypted
+ * data. Otherwise, it'll try to load it from the original data. Eek.
+ */
+ create_local_cache(bstart, &b->nested.part->body);
+
+ modified_the_body = 1;
+ }
+ }
+
+end:
+ if(out)
+ BIO_free(out);
+
+ return modified_the_body;
+}
+
+
+/*
+ * Recursively handle PKCS7 bodies in our message.
+ *
+ * Returns non-zero if some fiddling was done.
+ */
+static int
+do_fiddle_smime_message(BODY *b, long msgno, char *section)
+{
+ int modified_the_body = 0;
+
+ if(!b)
+ return 0;
+
+ dprint((9, "do_fiddle_smime_message(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+
+ if(is_pkcs7_body(b)){
+
+ if(do_decoding(b, msgno, section)){
+ /*
+ * b should now be a multipart message:
+ * fiddle it too in case it's been multiply-encrypted!
+ */
+
+ /* fallthru */
+ modified_the_body = 1;
+ }
+ }
+
+ if(b->type==TYPEMULTIPART || MIME_MSG(b->type, b->subtype)){
+
+ PART *p;
+ int partNum;
+ char newSec[100];
+
+ if(MIME_MULT_SIGNED(b->type, b->subtype)){
+
+
+ /*
+ * Ahah. We have a multipart signed entity.
+ *
+ * Multipart/signed
+ * part 1 (signed thing)
+ * part 2 (the pkcs7 signature)
+ *
+ * We're going to convert that to
+ *
+ * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE
+ * part 1 (signed thing)
+ * part 2 has been freed
+ *
+ * We also extract the signature from part 2 and save it
+ * in the multipart body->sparep, and we add a description
+ * in the multipart body->description.
+ *
+ *
+ * The results of a decrypted message will be similar. It
+ * will be
+ *
+ * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE
+ * part 1 (decrypted thing)
+ */
+
+ modified_the_body += do_detached_signature_verify(b, msgno, section);
+ }
+ else if(MIME_MSG(b->type, b->subtype)){
+ modified_the_body += do_fiddle_smime_message(b->nested.msg->body, msgno, section);
+ }
+ else{
+
+ for(p=b->nested.part,partNum=1; p; p=p->next,partNum++){
+
+ /* Append part number to the section string */
+
+ snprintf(newSec, sizeof(newSec), "%s%s%d", section, *section ? "." : "", partNum);
+
+ modified_the_body += do_fiddle_smime_message(&p->body, msgno, newSec);
+ }
+ }
+ }
+
+ return modified_the_body;
+}
+
+
+/*
+ * Fiddle a message in-place by decrypting/verifying S/MIME entities.
+ * Returns non-zero if something was changed.
+ */
+int
+fiddle_smime_message(BODY *b, long msgno)
+{
+ return do_fiddle_smime_message(b, msgno, "");
+}
+
+
+/********************************************************************************/
+
+
+/*
+ * Output a string in a distinctive style
+ */
+void
+gf_puts_uline(char *txt, gf_io_t pc)
+{
+ pc(TAG_EMBED); pc(TAG_BOLDON);
+ gf_puts(txt, pc);
+ pc(TAG_EMBED); pc(TAG_BOLDOFF);
+}
+
+
+/*
+ * Sign a message. Called from call_mailer in send.c.
+ *
+ * This takes the header for the outgoing message as well as a pointer
+ * to the current body (which may be reallocated).
+ */
+int
+sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach)
+{
+ STORE_S *outs = NULL;
+ BODY *body = *bodyP;
+ BODY *newBody = NULL;
+ PART *p1 = NULL;
+ PART *p2 = NULL;
+ PERSONAL_CERT *pcert;
+ BIO *in = NULL;
+ BIO *out = NULL;
+ PKCS7 *p7 = NULL;
+ int result = 0;
+ int flags = dont_detach ? 0 : PKCS7_DETACHED;
+
+ dprint((9, "sign_outgoing_message()"));
+
+ smime_init();
+
+ /* Look for a private key matching the sender address... */
+
+ pcert = match_personal_cert(header->env);
+
+ if(!pcert){
+ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to sign."));
+ goto end;
+ }
+
+ if(!load_private_key(pcert) && ps_global->smime && ps_global->smime->need_passphrase){
+ /* Couldn't load key with blank password, try again */
+ if(pith_opt_smime_get_passphrase){
+ (*pith_opt_smime_get_passphrase)();
+ load_private_key(pcert);
+ }
+ }
+
+ if(!pcert->key)
+ goto end;
+
+ in = body_to_bio(body);
+
+#ifdef notdef
+ dump_bio_to_file(in,"/tmp/signed-data");
+#endif
+
+ p7 = PKCS7_sign(pcert->cert, pcert->key, NULL, in, flags);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(!p7){
+ q_status_message(SM_ORDER, 1, 1, _("Error creating signed object."));
+ goto end;
+ }
+
+ outs = so_get(BioType, NULL, EDIT_ACCESS);
+ out = bio_from_store(outs);
+
+ i2d_PKCS7_bio(out, p7);
+ (void) BIO_flush(out);
+
+ so_seek(outs, 0, SEEK_SET);
+
+ if((flags&PKCS7_DETACHED)==0){
+
+ /* the simple case: the signed data is in the pkcs7 object */
+
+ newBody = mail_newbody();
+
+ setup_pkcs7_body_for_signature(newBody, "S/MIME Cryptographically Signed Message", "pkcs7-mime", "smime.p7m");
+
+ newBody->contents.text.data = (unsigned char *) outs;
+ *bodyP = newBody;
+
+ result = 1;
+ }
+ else{
+
+ /*
+ * OK.
+ * We have to create a new body as follows:
+ *
+ * multipart/signed; blah blah blah
+ * reference to existing body
+ *
+ * pkcs7 object
+ */
+
+ newBody = mail_newbody();
+
+ newBody->type = TYPEMULTIPART;
+ newBody->subtype = cpystr("signed");
+ newBody->encoding = ENC7BIT;
+
+ set_parameter(&newBody->parameter, "protocol", "application/pkcs7-signature");
+ set_parameter(&newBody->parameter, "micalg", "sha1");
+
+ p1 = mail_newbody_part();
+ p2 = mail_newbody_part();
+
+ /*
+ * This is nasty. We're just copying the body in here,
+ * but since our newBody is freed at the end of call_mailer,
+ * we mustn't let this body (the original one) be freed twice.
+ */
+ p1->body = *body; /* ARRGH. This is special cased at the end of call_mailer */
+
+ p1->next = p2;
+
+ setup_pkcs7_body_for_signature(&p2->body, "S/MIME Cryptographic Signature", "pkcs7-signature", "smime.p7s");
+ p2->body.contents.text.data = (unsigned char *) outs;
+
+ newBody->nested.part = p1;
+
+ *bodyP = newBody;
+
+ result = 1;
+ }
+
+end:
+
+ PKCS7_free(p7);
+ BIO_free(in);
+
+ dprint((9, "sign_outgoing_message returns %d", result));
+ return result;
+}
+
+
+SMIME_STUFF_S *
+new_smime_struct(void)
+{
+ SMIME_STUFF_S *ret = NULL;
+
+ ret = (SMIME_STUFF_S *) fs_get(sizeof(*ret));
+ memset((void *) ret, 0, sizeof(*ret));
+ ret->publictype = Nada;
+
+ return ret;
+}
+
+
+static void
+free_smime_struct(SMIME_STUFF_S **smime)
+{
+ if(smime && *smime){
+ if((*smime)->passphrase_emailaddr)
+ fs_give((void **) &(*smime)->passphrase_emailaddr);
+
+ if((*smime)->publicpath)
+ fs_give((void **) &(*smime)->publicpath);
+
+ if((*smime)->publiccertlist)
+ free_certlist(&(*smime)->publiccertlist);
+
+ if((*smime)->publiccontent)
+ fs_give((void **) &(*smime)->publiccontent);
+
+ if((*smime)->privatepath)
+ fs_give((void **) &(*smime)->privatepath);
+
+ if((*smime)->personal_certs){
+ PERSONAL_CERT *pc;
+
+ pc = (PERSONAL_CERT *) (*smime)->personal_certs;
+ free_personal_certs(&pc);
+ (*smime)->personal_certs = NULL;
+ }
+
+ if((*smime)->privatecontent)
+ fs_give((void **) &(*smime)->privatecontent);
+
+ if((*smime)->capath)
+ fs_give((void **) &(*smime)->capath);
+
+ if((*smime)->cacontent)
+ fs_give((void **) &(*smime)->cacontent);
+
+ fs_give((void **) smime);
+ }
+}
+
+#endif /* SMIME */
diff --git a/pith/smime.h b/pith/smime.h
new file mode 100644
index 00000000..5a89b372
--- /dev/null
+++ b/pith/smime.h
@@ -0,0 +1,58 @@
+/*
+ * $Id: smime.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifdef SMIME
+#ifndef PITH_SMIME_INCLUDED
+#define PITH_SMIME_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/send.h"
+#include "../pith/filttype.h"
+#include "../pith/smkeys.h"
+
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+
+#define OUR_PKCS7_ENCLOSURE_SUBTYPE "x-pkcs7-enclosure"
+
+
+/* exported protoypes */
+int encrypt_file(char *fp, char *text);
+char *decrypt_file(char *fp, int *rv);
+int is_pkcs7_body(BODY *b);
+int fiddle_smime_message(BODY *b, long msgno);
+int encrypt_outgoing_message(METAENV *header, BODY **bodyP);
+void free_smime_body_sparep(void **sparep);
+int sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach);
+void gf_puts_uline(char *txt, gf_io_t pc);
+PERSONAL_CERT *find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri);
+void smime_deinit(void);
+SMIME_STUFF_S *new_smime_struct(void);
+int copy_publiccert_dir_to_container(void);
+int copy_publiccert_container_to_dir(void);
+int copy_privatecert_dir_to_container(void);
+int copy_privatecert_container_to_dir(void);
+int copy_cacert_dir_to_container(void);
+int copy_cacert_container_to_dir(void);
+#ifdef APPLEKEYCHAIN
+int copy_publiccert_container_to_keychain(void);
+int copy_publiccert_keychain_to_container(void);
+#endif /* APPLEKEYCHAIN */
+
+
+#endif /* PITH_SMIME_INCLUDED */
+#endif /* SMIME */
diff --git a/pith/smkeys.c b/pith/smkeys.c
new file mode 100644
index 00000000..5a827070
--- /dev/null
+++ b/pith/smkeys.c
@@ -0,0 +1,925 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * This is based on a contribution from Jonathan Paisley, see smime.c
+ */
+
+
+#include "../pith/headers.h"
+
+#ifdef SMIME
+
+#include "../pith/status.h"
+#include "../pith/conf.h"
+#include "../pith/remote.h"
+#include "../pith/tempfile.h"
+#include "../pith/busy.h"
+#include "../pith/osdep/lstcmpnt.h"
+#include "smkeys.h"
+
+#ifdef APPLEKEYCHAIN
+#include <Security/SecKeychain.h>
+#include <Security/SecKeychainItem.h>
+#include <Security/SecKeychainSearch.h>
+#include <Security/SecCertificate.h>
+#endif /* APPLEKEYCHAIN */
+
+
+/* internal prototypes */
+static char *emailstrclean(char *string);
+static int add_certs_in_dir(X509_LOOKUP *lookup, char *path);
+static int certlist_to_file(char *filename, CertList *certlist);
+static int mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup);
+
+
+/*
+ * Remove leading whitespace, trailing whitespace and convert
+ * to lowercase. Also remove slash characters
+ *
+ * Args: s, -- The string to clean
+ *
+ * Result: the cleaned string
+ */
+static char *
+emailstrclean(char *string)
+{
+ char *s = string, *sc = NULL, *p = NULL;
+
+ for(; *s; s++){ /* single pass */
+ if(!isspace((unsigned char) (*s))){
+ p = NULL; /* not start of blanks */
+ if(!sc) /* first non-blank? */
+ sc = string; /* start copying */
+ }
+ else if(!p) /* it's OK if sc == NULL */
+ p = sc; /* start of blanks? */
+
+ if(sc && *s!='/' && *s!='\\') /* if copying, copy */
+ *sc++ = isupper((unsigned char) (*s))
+ ? (unsigned char) tolower((unsigned char) (*s))
+ : (unsigned char) (*s);
+ }
+
+ if(p) /* if ending blanks */
+ *p = '\0'; /* tie off beginning */
+ else if(!sc) /* never saw a non-blank */
+ *string = '\0'; /* so tie whole thing off */
+
+ return(string);
+}
+
+
+/*
+ * Add a lookup for each "*.crt" file in the given directory.
+ */
+static int
+add_certs_in_dir(X509_LOOKUP *lookup, char *path)
+{
+ char buf[MAXPATH];
+ struct direct *d;
+ DIR *dirp;
+ int ret = 0;
+
+ dirp = opendir(path);
+ if(dirp){
+
+ while(!ret && (d=readdir(dirp)) != NULL){
+ if(srchrstr(d->d_name, ".crt")){
+ build_path(buf, path, d->d_name, sizeof(buf));
+
+ if(!X509_LOOKUP_load_file(lookup, buf, X509_FILETYPE_PEM)){
+ q_status_message1(SM_ORDER, 3, 3, _("Error loading file %s"), buf);
+ ret = -1;
+ }
+ }
+
+ }
+
+ closedir(dirp);
+ }
+
+ return ret;
+}
+
+
+/*
+ * Get an X509_STORE. This consists of the system
+ * certs directory and any certificates in the user's
+ * ~/.alpine-smime/ca directory.
+ */
+X509_STORE *
+get_ca_store(void)
+{
+ X509_LOOKUP *lookup;
+ X509_STORE *store = NULL;
+
+ dprint((9, "get_ca_store()"));
+
+ if(!(store=X509_STORE_new())){
+ dprint((9, "X509_STORE_new() failed"));
+ return store;
+ }
+
+ if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_file()))){
+ dprint((9, "X509_STORE_add_lookup() failed"));
+ X509_STORE_free(store);
+ return NULL;
+ }
+
+ if(ps_global->smime && ps_global->smime->catype == Container
+ && ps_global->smime->cacontent){
+
+ if(!mem_add_extra_cacerts(ps_global->smime->cacontent, lookup)){
+ X509_STORE_free(store);
+ return NULL;
+ }
+ }
+ else if(ps_global->smime && ps_global->smime->catype == Directory
+ && ps_global->smime->capath){
+ if(add_certs_in_dir(lookup, ps_global->smime->capath) < 0){
+ X509_STORE_free(store);
+ return NULL;
+ }
+ }
+
+ if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()))){
+ X509_STORE_free(store);
+ return NULL;
+ }
+
+#ifdef SMIME_SSLCERTS
+ dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS));
+ X509_LOOKUP_add_dir(lookup, SMIME_SSLCERTS, X509_FILETYPE_PEM);
+#endif
+
+ return store;
+}
+
+
+EVP_PKEY *
+load_key(PERSONAL_CERT *pc, char *pass)
+{
+ BIO *in;
+ EVP_PKEY *key = NULL;
+ char buf[MAXPATH], file[MAXPATH];
+
+ if(!(ps_global->smime && pc && pc->name))
+ return key;
+
+ if(ps_global->smime->privatetype == Container){
+ char *q;
+
+ if(pc->keytext && (q = strstr(pc->keytext, "-----END")) != NULL){
+ while(*q && *q != '\n')
+ q++;
+
+ if(*q == '\n')
+ q++;
+
+ if((in = BIO_new_mem_buf(pc->keytext, q-pc->keytext)) != NULL){
+ key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
+ BIO_free(in);
+ }
+ }
+ }
+ else if(ps_global->smime->privatetype == Directory){
+ /* filename is path/name.key */
+ strncpy(buf, pc->name, sizeof(buf)-5);
+ buf[sizeof(buf)-5] = '\0';
+ strncat(buf, ".key", 5);
+ build_path(file, ps_global->smime->privatepath, buf, sizeof(file));
+
+ if(!(in = BIO_new_file(file, "r")))
+ return NULL;
+
+ key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
+ BIO_free(in);
+ }
+
+ return key;
+}
+
+
+#ifdef notdef
+static char *
+get_x509_name_entry(const char *key, X509_NAME *name)
+{
+ int i, c, n;
+ char buf[256];
+ char *id;
+
+ if(!name)
+ return NULL;
+
+ c = X509_NAME_entry_count(name);
+
+ for(i=0; i<c; i++){
+ X509_NAME_ENTRY *e;
+
+ e = X509_NAME_get_entry(name, i);
+ if(!e)
+ continue;
+
+ buf[0] = 0;
+ id = buf;
+
+ n = OBJ_obj2nid(e->object);
+ if((n == NID_undef) || ((id=(char*) OBJ_nid2sn(n)) == NULL)){
+ i2t_ASN1_OBJECT(buf, sizeof(buf), e->object);
+ id = buf;
+ }
+
+ if((strucmp(id, "email")==0) || (strucmp(id, "emailAddress")==0)){
+ X509_NAME_get_text_by_OBJ(name, e->object, buf, sizeof(buf)-1);
+ return cpystr(buf);
+ }
+ }
+
+ return NULL;
+}
+
+
+char *
+get_x509_subject_email(X509 *x)
+{
+ char* result;
+ result = get_x509_name_entry("email", X509_get_subject_name(x));
+ if( !result ){
+ result = get_x509_name_entry("emailAddress", X509_get_subject_name(x));
+ }
+
+ return result;
+}
+#endif /* notdef */
+
+#include <openssl/x509v3.h>
+/*
+ * This newer version is from Adrian Vogel. It looks for the email
+ * address not only in the email address field, but also in an
+ * X509v3 extension field, Subject Altenative Name.
+ */
+char *
+get_x509_subject_email(X509 *x)
+{
+ char *result = NULL;
+ STACK_OF(OPENSSL_STRING) *emails = X509_get1_email(x);
+ if (sk_OPENSSL_STRING_num(emails) > 0) {
+ /* take the first one on the stack */
+ result = cpystr(sk_OPENSSL_STRING_value(emails, 0));
+ }
+ X509_email_free(emails);
+ return result;
+}
+
+
+/*
+ * Save the certificate for the given email address in
+ * ~/.alpine-smime/public.
+ *
+ * Should consider the security hazards in making a file with
+ * the email address that has come from the certificate.
+ *
+ * The argument email is destroyed.
+ */
+void
+save_cert_for(char *email, X509 *cert)
+{
+ if(!ps_global->smime)
+ return;
+
+ dprint((9, "save_cert_for(%s)", email ? email : "?"));
+ emailstrclean(email);
+
+ if(ps_global->smime->publictype == Keychain){
+#ifdef APPLEKEYCHAIN
+
+ OSStatus rc;
+ SecCertificateRef secCertificateRef;
+ CSSM_DATA certData;
+
+ memset((void *) &certData, 0, sizeof(certData));
+ memset((void *) &secCertificateRef, 0, sizeof(secCertificateRef));
+
+ /* convert OpenSSL X509 cert data to MacOS certData */
+ if((certData.Length = i2d_X509(cert, &(certData.Data))) > 0){
+
+ /*
+ * Put that certData into a SecCertificateRef.
+ * Version 3 should work for versions 1-3.
+ */
+ if(!(rc=SecCertificateCreateFromData(&certData,
+ CSSM_CERT_X_509v3,
+ CSSM_CERT_ENCODING_DER,
+ &secCertificateRef))){
+
+ /* add it to the default keychain */
+ if(!(rc=SecCertificateAddToKeychain(secCertificateRef, NULL))){
+ /* ok */
+ }
+ else if(rc == errSecDuplicateItem){
+ dprint((9, "save_cert_for: certificate for %s already in keychain", email));
+ }
+ else{
+ dprint((9, "SecCertificateAddToKeychain failed"));
+ }
+ }
+ else{
+ dprint((9, "SecCertificateCreateFromData failed"));
+ }
+ }
+ else{
+ dprint((9, "i2d_X509 failed"));
+ }
+
+#endif /* APPLEKEYCHAIN */
+ }
+ else if(ps_global->smime->publictype == Container){
+ REMDATA_S *rd = NULL;
+ char path[MAXPATH];
+ char *tempfile = NULL;
+ int err = 0;
+
+ add_to_end_of_certlist(&ps_global->smime->publiccertlist, email, X509_dup(cert));
+
+ if(!ps_global->smime->publicpath)
+ return;
+
+ if(IS_REMOTE(ps_global->smime->publicpath)){
+ rd = rd_create_remote(RemImap, ps_global->smime->publicpath, REMOTE_SMIME_SUBTYPE,
+ NULL, "Error: ",
+ _("Can't access remote smime configuration."));
+ if(!rd)
+ return;
+
+ (void) rd_read_metadata(rd);
+
+ if(rd->access == MaybeRorW){
+ if(rd->read_status == 'R')
+ rd->access = ReadOnly;
+ else
+ rd->access = ReadWrite;
+ }
+
+ if(rd->access != NoExists){
+
+ rd_check_remvalid(rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(rd->read_status == 'R'){
+ rd_check_readonly_access(rd);
+ if(rd->read_status == 'W'){
+ rd->access = ReadWrite;
+ rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ rd->access = ReadOnly;
+ }
+ }
+
+ if(rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(rd) != 0){
+
+ dprint((1, "save_cert_for: rd_update_local failed\n"));
+ rd_close_remdata(&rd);
+ return;
+ }
+ }
+ else
+ rd_open_remote(rd);
+
+ if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
+ rd_close_remdata(&rd);
+ return;
+ }
+
+ rd->flags |= DO_REMTRIM;
+
+ strncpy(path, rd->lf, sizeof(path)-1);
+ path[sizeof(path)-1] = '\0';
+ }
+ else{
+ strncpy(path, ps_global->smime->publicpath, sizeof(path)-1);
+ path[sizeof(path)-1] = '\0';
+ }
+
+ tempfile = tempfile_in_same_dir(path, "az", NULL);
+ if(tempfile){
+ if(certlist_to_file(tempfile, ps_global->smime->publiccertlist))
+ err++;
+
+ if(!err){
+ if(rename_file(tempfile, path) < 0){
+ q_status_message2(SM_ORDER, 3, 3,
+ _("Can't rename %s to %s"), tempfile, path);
+ err++;
+ }
+ }
+
+ if(!err && IS_REMOTE(ps_global->smime->publicpath)){
+ int e, we_cancel;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+
+ we_cancel = busy_cue(_("Copying to remote smime container"), NULL, 1);
+ if((e = rd_update_remote(rd, datebuf)) != 0){
+ if(e == -1){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary smime file %s: %s"),
+ rd->lf, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error opening temp file %s\n",
+ rd->lf ? rd->lf : "?"));
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %s: %s"),
+ rd->rn, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error copying from %s to %s\n",
+ rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
+ }
+ else{
+ rd_update_metadata(rd, datebuf);
+ rd->read_status = 'W';
+ }
+
+ rd_close_remdata(&rd);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+
+ fs_give((void **) &tempfile);
+ }
+ }
+ else if(ps_global->smime->publictype == Directory){
+ char certfilename[MAXPATH];
+ BIO *bio_out;
+
+ build_path(certfilename, ps_global->smime->publicpath,
+ email, sizeof(certfilename));
+ strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename));
+ certfilename[sizeof(certfilename)-1] = 0;
+
+ bio_out = BIO_new_file(certfilename, "w");
+ if(bio_out){
+ PEM_write_bio_X509(bio_out, cert);
+ BIO_free(bio_out);
+ q_status_message1(SM_ORDER, 1, 1, _("Saved certificate for <%s>"), email);
+ }
+ else{
+ q_status_message1(SM_ORDER, 1, 1, _("Couldn't save certificate for <%s>"), email);
+ }
+ }
+}
+
+
+/*
+ * Try to retrieve the certificate for the given email address.
+ * The caller should free the cert.
+ */
+X509 *
+get_cert_for(char *email)
+{
+ char *path;
+ char certfilename[MAXPATH];
+ char emailaddr[MAXPATH];
+ X509 *cert = NULL;
+ BIO *in;
+
+ if(!ps_global->smime)
+ return cert;
+
+ dprint((9, "get_cert_for(%s)", email ? email : "?"));
+
+ strncpy(emailaddr, email, sizeof(emailaddr)-1);
+ emailaddr[sizeof(emailaddr)-1] = 0;
+
+ /* clean it up (lowercase, space removal) */
+ emailstrclean(emailaddr);
+
+ if(ps_global->smime->publictype == Keychain){
+#ifdef APPLEKEYCHAIN
+
+ OSStatus rc;
+ SecKeychainItemRef itemRef = nil;
+ SecKeychainAttributeList attrList;
+ SecKeychainAttribute attrib;
+ SecKeychainSearchRef searchRef = nil;
+ CSSM_DATA certData;
+
+ /* low-level form of MacOS data */
+ memset((void *) &certData, 0, sizeof(certData));
+
+ attrList.count = 1;
+ attrList.attr = &attrib;
+
+ /* kSecAlias means email address for a certificate */
+ attrib.tag = kSecAlias;
+ attrib.data = emailaddr;
+ attrib.length = strlen(attrib.data);
+
+ /* Find the certificate in the default keychain */
+ if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
+ kSecCertificateItemClass,
+ &attrList,
+ &searchRef))){
+
+ if(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef))){
+
+ /* extract the data portion of the certificate */
+ if(!(rc=SecCertificateGetData((SecCertificateRef) itemRef, &certData))){
+
+ /*
+ * Convert it from MacOS form to OpenSSL form.
+ * The input is certData from above and the output
+ * is the X509 *cert.
+ */
+ if(!d2i_X509(&cert, &(certData.Data), certData.Length)){
+ dprint((9, "d2i_X509 failed"));
+ }
+ }
+ else{
+ dprint((9, "SecCertificateGetData failed"));
+ }
+ }
+ else if(rc == errSecItemNotFound){
+ dprint((9, "get_cert_for: Public cert for %s not found", emailaddr));
+ }
+ else{
+ dprint((9, "SecKeychainSearchCopyNext failed"));
+ }
+ }
+ else{
+ dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
+ }
+
+ if(searchRef)
+ CFRelease(searchRef);
+
+#endif /* APPLEKEYCHAIN */
+ }
+ else if(ps_global->smime->publictype == Container){
+ if(ps_global->smime->publiccertlist){
+ CertList *cl;
+
+ for(cl = ps_global->smime->publiccertlist; cl; cl = cl->next){
+ if(cl->name && !strucmp(emailaddr, cl->name))
+ break;
+ }
+
+ if(cl)
+ cert = X509_dup((X509 *) cl->x509_cert);
+ }
+ }
+ else if(ps_global->smime->publictype == Directory){
+ path = ps_global->smime->publicpath;
+ build_path(certfilename, path, emailaddr, sizeof(certfilename));
+ strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename));
+ certfilename[sizeof(certfilename)-1] = 0;
+
+ if((in = BIO_new_file(certfilename, "r"))!=0){
+
+ cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
+
+ if(cert){
+ /* could check email addr in cert matches */
+ }
+
+ BIO_free(in);
+ }
+ }
+
+ return cert;
+}
+
+
+PERSONAL_CERT *
+mem_to_personal_certs(char *contents)
+{
+ PERSONAL_CERT *result = NULL;
+ char *p, *q, *line, *name, *keytext, *save_p;
+ X509 *cert = NULL;
+
+ if(contents && *contents){
+ for(p = contents; *p != '\0';){
+ line = p;
+
+ while(*p && *p != '\n')
+ p++;
+
+ save_p = NULL;
+ if(*p == '\n'){
+ save_p = p;
+ *p++ = '\0';
+ }
+
+ if(strncmp(EMAILADDRLEADER, line, strlen(EMAILADDRLEADER)) == 0){
+ name = line + strlen(EMAILADDRLEADER);
+ cert = get_cert_for(name);
+ keytext = p;
+
+ /* advance p past this record */
+ if((q = strstr(keytext, "-----END")) != NULL){
+ while(*q && *q != '\n')
+ q++;
+
+ if(*q == '\n')
+ q++;
+
+ p = q;
+ }
+ else{
+ p = p + strlen(p);
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error in privatekey container, missing END"));
+ }
+
+ if(cert){
+ PERSONAL_CERT *pc;
+
+ pc = (PERSONAL_CERT *) fs_get(sizeof(*pc));
+ pc->cert = cert;
+ pc->name = cpystr(name);
+ pc->keytext = keytext; /* a pointer into contents */
+
+ pc->key = load_key(pc, "");
+
+ pc->next = result;
+ result = pc;
+ }
+ }
+
+ if(save_p)
+ *save_p = '\n';
+ }
+ }
+
+ return result;
+}
+
+
+CertList *
+mem_to_certlist(char *contents)
+{
+ CertList *ret = NULL;
+ char *p, *q, *line, *name, *certtext, *save_p;
+ X509 *cert = NULL;
+ BIO *in;
+
+ if(contents && *contents){
+ for(p = contents; *p != '\0';){
+ line = p;
+
+ while(*p && *p != '\n')
+ p++;
+
+ save_p = NULL;
+ if(*p == '\n'){
+ save_p = p;
+ *p++ = '\0';
+ }
+
+ if(strncmp(EMAILADDRLEADER, line, strlen(EMAILADDRLEADER)) == 0){
+ name = line + strlen(EMAILADDRLEADER);
+ cert = NULL;
+ certtext = p;
+ if(strncmp("-----BEGIN", certtext, strlen("-----BEGIN")) == 0){
+ if((q = strstr(certtext, "-----END")) != NULL){
+ while(*q && *q != '\n')
+ q++;
+
+ if(*q == '\n')
+ q++;
+
+ p = q;
+
+ if((in = BIO_new_mem_buf(certtext, q-certtext)) != 0){
+ cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
+
+ BIO_free(in);
+ }
+ }
+ }
+ else{
+ p = p + strlen(p);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in publiccert container, missing BEGIN, certtext=%s"), certtext);
+ }
+
+ if(name && cert){
+ add_to_end_of_certlist(&ret, name, cert);
+ }
+ }
+
+ if(save_p)
+ *save_p = '\n';
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ * Add the CACert Container contents into the CACert store.
+ *
+ * Returns > 0 for success, 0 for failure
+ */
+int
+mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup)
+{
+ char *p, *q, *line, *certtext, *save_p;
+ BIO *in, *out;
+ int len, failed = 0;
+ char *tempfile;
+ char iobuf[4096];
+
+ /*
+ * The most straight-forward way to do this is to write
+ * the container contents to a temp file and then load the
+ * contents of the file with X509_LOOKUP_load_file(), like
+ * is done in add_certs_in_dir(). What we don't know is if
+ * each file should consist of one cacert or if they can all
+ * just be jammed together into one file. To be safe, we'll use
+ * one file per and do each in a separate operation.
+ */
+
+ if(contents && *contents){
+ for(p = contents; *p != '\0';){
+ line = p;
+
+ while(*p && *p != '\n')
+ p++;
+
+ save_p = NULL;
+ if(*p == '\n'){
+ save_p = p;
+ *p++ = '\0';
+ }
+
+ /* look for separator line */
+ if(strncmp(CACERTSTORELEADER, line, strlen(CACERTSTORELEADER)) == 0){
+ /* certtext is the content that should go in a file */
+ certtext = p;
+ if(strncmp("-----BEGIN", certtext, strlen("-----BEGIN")) == 0){
+ if((q = strstr(certtext, CACERTSTORELEADER)) != NULL){
+ p = q;
+ }
+ else{ /* end of file */
+ q = certtext + strlen(certtext);
+ p = q;
+ }
+
+ in = BIO_new_mem_buf(certtext, q-certtext);
+ if(in){
+ tempfile = temp_nam(NULL, "az");
+ out = NULL;
+ if(tempfile)
+ out = BIO_new_file(tempfile, "w");
+
+ if(out){
+ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
+ BIO_write(out, iobuf, len);
+
+ BIO_free(out);
+ if(!X509_LOOKUP_load_file(lookup, tempfile, X509_FILETYPE_PEM))
+ failed++;
+
+ fs_give((void **) &tempfile);
+ }
+
+ BIO_free(in);
+ }
+ }
+ else{
+ p = p + strlen(p);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext);
+ }
+ }
+ else{
+ p = p + strlen(p);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing separator, line=%s"), line);
+ }
+
+ if(save_p)
+ *save_p = '\n';
+ }
+ }
+
+ return(!failed);
+}
+
+
+int
+certlist_to_file(char *filename, CertList *certlist)
+{
+ CertList *cl;
+ BIO *bio_out = NULL;
+ int ret = -1;
+
+ if(filename && (bio_out=BIO_new_file(filename, "w")) != NULL){
+ ret = 0;
+ for(cl = certlist; cl; cl = cl->next){
+ if(cl->name && cl->name[0] && cl->x509_cert){
+ if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0)
+ && (BIO_puts(bio_out, cl->name) > 0)
+ && (BIO_puts(bio_out, "\n") > 0)))
+ ret = -1;
+
+ if(!PEM_write_bio_X509(bio_out, (X509 *) cl->x509_cert))
+ ret = -1;
+ }
+ }
+
+ BIO_free(bio_out);
+ }
+
+ return ret;
+}
+
+
+void
+add_to_end_of_certlist(CertList **cl, char *name, X509 *cert)
+{
+ CertList *new, *clp;
+
+ if(!cl)
+ return;
+
+ new = (CertList *) fs_get(sizeof(*new));
+ memset((void *) new, 0, sizeof(*new));
+ new->x509_cert = cert;
+ new->name = name ? cpystr(name) : NULL;
+
+ if(!*cl){
+ *cl = new;
+ }
+ else{
+ for(clp = (*cl); clp->next; clp = clp->next)
+ ;
+
+ clp->next = new;
+ }
+}
+
+
+void
+free_certlist(CertList **cl)
+{
+ if(cl && *cl){
+ free_certlist(&(*cl)->next);
+ if((*cl)->name)
+ fs_give((void **) &(*cl)->name);
+
+ if((*cl)->x509_cert)
+ X509_free((X509 *) (*cl)->x509_cert);
+
+ fs_give((void **) cl);
+ }
+}
+
+
+void
+free_personal_certs(PERSONAL_CERT **pc)
+{
+ if(pc && *pc){
+ free_personal_certs(&(*pc)->next);
+ if((*pc)->name)
+ fs_give((void **) &(*pc)->name);
+
+ if((*pc)->name)
+ fs_give((void **) &(*pc)->name);
+
+ if((*pc)->cert)
+ X509_free((*pc)->cert);
+
+ if((*pc)->key)
+ EVP_PKEY_free((*pc)->key);
+
+ fs_give((void **) pc);
+ }
+}
+
+#endif /* SMIME */
diff --git a/pith/smkeys.h b/pith/smkeys.h
new file mode 100644
index 00000000..0c6db8eb
--- /dev/null
+++ b/pith/smkeys.h
@@ -0,0 +1,61 @@
+/*
+ * $Id: smkeys.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifdef SMIME
+#ifndef PITH_SMKEYS_INCLUDED
+#define PITH_SMKEYS_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/send.h"
+
+#include <openssl/objects.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/pkcs7.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/bio.h>
+
+
+#define EMAILADDRLEADER "emailAddress="
+#define CACERTSTORELEADER "cacert="
+
+
+typedef struct personal_cert {
+ X509 *cert;
+ EVP_PKEY *key;
+ char *name;
+ char *keytext;
+ struct personal_cert *next;
+} PERSONAL_CERT;
+
+
+/* exported protoypes */
+X509_STORE *get_ca_store(void);
+PERSONAL_CERT *get_personal_certs(char *d);
+X509 *get_cert_for(char *email);
+void save_cert_for(char *email, X509 *cert);
+char *get_x509_subject_email(X509 *x);
+EVP_PKEY *load_key(PERSONAL_CERT *pc, char *pass);
+CertList *mem_to_certlist(char *contents);
+void add_to_end_of_certlist(CertList **cl, char *name, X509 *cert);
+void free_certlist(CertList **cl);
+PERSONAL_CERT *mem_to_personal_certs(char *contents);
+void free_personal_certs(PERSONAL_CERT **pc);
+
+
+#endif /* PITH_SMKEYS_INCLUDED */
+#endif /* SMIME */
diff --git a/pith/sort.c b/pith/sort.c
new file mode 100644
index 00000000..68a9c10c
--- /dev/null
+++ b/pith/sort.c
@@ -0,0 +1,710 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: sort.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/sort.h"
+#include "../pith/state.h"
+#include "../pith/status.h"
+#include "../pith/msgno.h"
+#include "../pith/flag.h"
+#include "../pith/pineelt.h"
+#include "../pith/thread.h"
+#include "../pith/search.h"
+#include "../pith/pattern.h"
+#include "../pith/util.h"
+#include "../pith/signal.h"
+#include "../pith/busy.h"
+#include "../pith/icache.h"
+
+
+/*
+ * global place to store mail_sort and mail_thread results
+ */
+struct global_sort_data g_sort;
+
+
+/*
+ * Internal prototypes
+ */
+void sort_sort_callback(MAILSTREAM *, unsigned long *, unsigned long);
+int percent_sorted(void);
+int pine_compare_long(const qsort_t *, const qsort_t *);
+int pine_compare_long_rev(const qsort_t *, const qsort_t *);
+int pine_compare_scores(const qsort_t *, const qsort_t *);
+void build_score_array(MAILSTREAM *, MSGNO_S *);
+void free_score_array(void);
+
+
+/*----------------------------------------------------------------------
+ Map sort types to names
+ ----*/
+char *
+sort_name(SortOrder so)
+{
+ /*
+ * Make sure the first upper case letter of any new sort name is
+ * unique. The command char and label for sort selection is
+ * derived from this name and its first upper case character.
+ * See mailcmd.c:select_sort().
+ */
+ return((so == SortArrival) ? "Arrival" :
+ (so == SortDate) ? "Date" :
+ (so == SortSubject) ? "Subject" :
+ (so == SortCc) ? "Cc" :
+ (so == SortFrom) ? "From" :
+ (so == SortTo) ? "To" :
+ (so == SortSize) ? "siZe" :
+ (so == SortSubject2) ? "OrderedSubj" :
+ (so == SortScore) ? "scorE" :
+ (so == SortThread) ? "tHread" :
+ "BOTCH");
+}
+
+
+/*----------------------------------------------------------------------
+ Sort the current folder into the order set in the msgmap
+
+Args: msgmap --
+ new_sort --
+ new_rev --
+
+ The idea of the deferred sort is to let the user interrupt a long sort
+ and have a chance to do a different command, such as a sort by arrival
+ or a Goto. The next newmail call will increment the deferred variable,
+ then the user may do a command, then the newmail call after that
+ causes the sort to happen if it is still needed.
+ ----*/
+void
+sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort,
+ int new_rev, unsigned int flags)
+{
+ long raw_current, i, j;
+ unsigned long *sort = NULL;
+ int we_cancel = 0;
+ char sort_msg[MAX_SCREEN_COLS+1];
+ SortOrder current_sort;
+ int current_rev;
+ MESSAGECACHE *mc;
+
+ dprint((2, "Sorting by %s%s\n",
+ sort_name(new_sort), new_rev ? "/reverse" : ""));
+
+ if(!msgmap)
+ return;
+
+ current_sort = mn_get_sort(msgmap);
+ current_rev = mn_get_revsort(msgmap);
+ /*
+ * If we were previously threading (spare == 1) and now we're switching
+ * sorts (other than just a rev switch) then erase the information
+ * about the threaded state (collapsed and so forth).
+ */
+ if(stream && stream->spare && (current_sort != new_sort))
+ erase_threading_info(stream, msgmap);
+
+ if(mn_get_total(msgmap) <= 1L
+ && !(mn_get_total(msgmap) == 1L
+ && (new_sort == SortThread || new_sort == SortSubject2))){
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ if(!mn_get_mansort(msgmap))
+ mn_set_mansort(msgmap, (flags & SRT_MAN) ? 1 : 0);
+
+ return;
+ }
+
+ raw_current = mn_m2raw(msgmap, mn_get_cur(msgmap));
+
+ if(new_sort == SortArrival){
+ /*
+ * NOTE: RE c-client sorting, our idea of arrival is really
+ * just the natural sequence order. C-client, and probably
+ * rightly so, considers "arrival" the order based on the
+ * message's internal date. This is more costly to compute
+ * since it means touching the message store (say "nntp"?),
+ * so we just preempt it here.
+ *
+ * Someday c-client will support "unsorted" and do what
+ * we're doing here. That day this gets scrapped.
+ */
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+
+ if(current_sort != new_sort || current_rev != new_rev ||
+ any_lflagged(msgmap, MN_EXLD))
+ clear_index_cache(stream, 0);
+
+ if(any_lflagged(msgmap, MN_EXLD)){
+ /*
+ * BEWARE: "exclusion" may leave holes in the unsorted sort order
+ * so we have to do a real sort if that is the case.
+ */
+ qsort(msgmap->sort+1, (size_t) mn_get_total(msgmap),
+ sizeof(long),
+ new_rev ? pine_compare_long_rev : pine_compare_long);
+ }
+ else if(mn_get_total(msgmap) > 0L){
+ if(new_rev){
+ clear_index_cache(stream, 0);
+ for(i = 1L, j = mn_get_total(msgmap); j >= 1; i++, j--)
+ msgmap->sort[i] = j;
+ }
+ else
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ msgmap->sort[i] = i;
+ }
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+ }
+ else if(new_sort == SortScore){
+
+ /*
+ * We have to build a temporary array which maps raw msgno to
+ * score. We use the index cache machinery to build the array.
+ */
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+
+ if(flags & SRT_VRB){
+ /* TRANSLATORS: tell user they are waiting for Sorting of %s, the foldername */
+ snprintf(sort_msg, sizeof(sort_msg), _("Sorting \"%s\""),
+ strsquish(tmp_20k_buf + 500, 500, ps_global->cur_folder,
+ ps_global->ttyo->screen_cols - 20));
+ we_cancel = busy_cue(sort_msg, NULL, 0);
+ }
+
+ /*
+ * We do this so that we don't have to lookup the scores with function
+ * calls for each qsort compare.
+ */
+ build_score_array(stream, msgmap);
+
+ qsort(msgmap->sort+1, (size_t) mn_get_total(msgmap),
+ sizeof(long), pine_compare_scores);
+ free_score_array();
+ clear_index_cache(stream, 0);
+
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ /*
+ * Flip the sort if necessary (cheaper to do it once than for
+ * every comparison in pine_compare_scores.
+ */
+ if(new_rev && mn_get_total(msgmap) > 1L){
+ long *ep = &msgmap->sort[mn_get_total(msgmap)],
+ *sp = &msgmap->sort[1], tmp;
+
+ do{
+ tmp = *sp;
+ *sp++ = *ep;
+ *ep-- = tmp;
+ }
+ while(ep > sp);
+ }
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+ }
+ else{
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ clear_index_cache(stream, 0);
+
+ if(flags & SRT_VRB){
+ int (*sort_func)() = NULL;
+
+ /*
+ * IMAP sort doesn't give us any way to get progress,
+ * so just spin the bar rather than show zero percent
+ * forever while a slow sort's going on...
+ */
+ if(!(stream && stream->dtb && stream->dtb->name
+ && !strucmp(stream->dtb->name, "imap")
+ && LEVELSORT(stream)))
+ sort_func = percent_sorted;
+
+ snprintf(sort_msg, sizeof(sort_msg), _("Sorting \"%s\""),
+ strsquish(tmp_20k_buf + 500, 500, ps_global->cur_folder,
+ ps_global->ttyo->screen_cols - 20));
+ we_cancel = busy_cue(sort_msg, sort_func, 0);
+ }
+
+ /*
+ * Limit the sort/thread if messages are hidden from view
+ * by lighting searched bit of every interesting msg in
+ * the folder and call c-client thread/sort to do the dirty work.
+ *
+ * Unfortunately it isn't that easy. IMAP servers are not able to
+ * handle medium to large sized sequence sets (more than 1000
+ * characters in the command line breaks some) so we have to try
+ * to handle it locally. By lighting the searched bits and
+ * providing a NULL search program we get a special c-client
+ * interface. This means that c-client will attempt to send the
+ * sequence set with the SORT or THREAD but it may get back
+ * a BAD response because of long command lines. In that case,
+ * if it is a SORT call, c-client will issue the full SORT
+ * without the sequence sets and will then filter the results
+ * locally. So sort_sort_callback will see the correctly
+ * filtered results. If it is a mail_thread call, a similar thing
+ * will be done. If a BAD is received, then there is no way to
+ * easily filter the results. C-client (in this special case where
+ * we provide a NULL search program) will set tree->num to zero
+ * for nodes of the thread tree which were supposed to be
+ * filtered out of the thread. Then pine, in sort_thread_callback,
+ * will treat those as dummy nodes (nodes which are part of the
+ * tree logically but where we don't have access to the messages).
+ * This will give us a different answer than we would have gotten
+ * if the restricted thread would have worked, but it's something.
+ *
+ * It isn't in general possible to give some shorter search set
+ * in place of the long sequence set because the long sequence set
+ * may be the result of several filter rules or of excluded
+ * messages in news (in this 2nd case maybe we could give
+ * a shorter search set).
+ *
+ * We note also that the too-long commands in imap is a general
+ * imap deficiency. It comes up in particular also in SEARCH
+ * commands. Pine likes to exclude the hidden messages from the
+ * SEARCH. SEARCH will be handled transparently by the local
+ * c-client by first issuing the full SEARCH command, if that
+ * comes back with a BAD and there is a pgm->msgno at the top
+ * level of pgm, then c-client will re-issue the SEARCH command
+ * but without the msgno sequence set in hopes that the resulting
+ * command will now be short enough, and then it will filter out
+ * the sequence set locally. If that doesn't work, it will
+ * download the messages and do the SEARCH locally. That is
+ * controllable by a flag bit.
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = !get_lflag(stream, NULL, i, MN_EXLD);
+
+ g_sort.msgmap = msgmap;
+ if(new_sort == SortThread || new_sort == SortSubject2){
+ THREADNODE *thread;
+
+ /*
+ * Install callback to collect thread results
+ * and update sort mapping. Problem this solves
+ * is that of receiving exists/expunged events
+ * within sort/thread response. Since we update
+ * the sorted table within those handlers, we
+ * can get out of sync when we replace possibly
+ * stale sort/thread results once the function
+ * call's returned. Make sense? Thought so.
+ */
+ mail_parameters(NULL, SET_THREADRESULTS,
+ (void *) sort_thread_callback);
+
+ thread = mail_thread(stream,
+ (new_sort == SortThread)
+ ? "REFERENCES" : "ORDEREDSUBJECT",
+ NULL, NULL, 0L);
+
+ mail_parameters(NULL, SET_THREADRESULTS, (void *) NULL);
+
+ if(!thread){
+ new_sort = current_sort;
+ new_rev = current_rev;
+ q_status_message1(SM_ORDER, 3, 3,
+ "Sort Failed! Restored %.200s sort.",
+ sort_name(new_sort));
+ }
+
+ if(thread)
+ mail_free_threadnode(&thread);
+ }
+ else{
+ /*
+ * Set up the sort program.
+ * NOTE: we deal with reverse bit below.
+ */
+ g_sort.prog = mail_newsortpgm();
+ g_sort.prog->function = (new_sort == SortSubject)
+ ? SORTSUBJECT
+ : (new_sort == SortFrom)
+ ? SORTFROM
+ : (new_sort == SortTo)
+ ? SORTTO
+ : (new_sort == SortCc)
+ ? SORTCC
+ : (new_sort == SortDate)
+ ? SORTDATE
+ : (new_sort == SortSize)
+ ? SORTSIZE
+ : SORTARRIVAL;
+
+ mail_parameters(NULL, SET_SORTRESULTS,
+ (void *) sort_sort_callback);
+
+ /* Where the rubber meets the road. */
+ sort = mail_sort(stream, NULL, NULL, g_sort.prog, 0L);
+
+ mail_parameters(NULL, SET_SORTRESULTS, (void *) NULL);
+
+ if(!sort){
+ new_sort = current_sort;
+ new_rev = current_rev;
+ q_status_message1(SM_ORDER, 3, 3,
+ "Sort Failed! Restored %s sort.",
+ sort_name(new_sort));
+ }
+
+ if(sort)
+ fs_give((void **) &sort);
+
+ mail_free_sortpgm(&g_sort.prog);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ /*
+ * Flip the sort if necessary (cheaper to do it once than for
+ * every comparison underneath mail_sort()
+ */
+ if(new_rev && mn_get_total(msgmap) > 1L){
+ long *ep = &msgmap->sort[mn_get_total(msgmap)],
+ *sp = &msgmap->sort[1], tmp;
+
+ do{
+ tmp = *sp;
+ *sp++ = *ep;
+ *ep-- = tmp;
+ }
+ while(ep > sp);
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+
+ /*
+ * Flip the thread numbers around.
+ * This puts us in a weird state that requires keeping track
+ * of. The direction of the thread list hasn't changed, but the
+ * thrdnos have and the display direction has.
+ *
+ * For Sort thrdno 1 thread list head
+ * thrdno 2 |
+ * thrdno . v nextthd this direction
+ * thrdno .
+ * thrdno n thread list tail
+ *
+ * Rev Sort thrdno 1 thread list tail
+ * thrdno 2
+ * thrdno . ^ nextthd this direction
+ * thrdno . |
+ * thrdno n thread list head
+ */
+ if(new_sort == SortThread || new_sort == SortSubject2){
+ PINETHRD_S *thrd;
+
+ thrd = fetch_head_thread(stream);
+ for(j = msgmap->max_thrdno; thrd && j >= 1L; j--){
+ thrd->thrdno = j;
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream,
+ thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ }
+
+ /* Fix up sort structure */
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ /*
+ * Once a folder has been sorted manually, we continue treating it
+ * as manually sorted until it is closed.
+ */
+ if(!mn_get_mansort(msgmap))
+ mn_set_mansort(msgmap, (flags & SRT_MAN) ? 1 : 0);
+
+ if(!msgmap->hilited){
+ /*
+ * If current is hidden, change current to visible parent.
+ * It can only be hidden if we are threading.
+ *
+ * Don't do this if hilited is set, because it means we're in the
+ * middle of an aggregate op, and this will mess up our selection.
+ * "hilited" means we've done a pseudo_selected, which we'll later
+ * fix with restore_selected.
+ */
+ if(THREADING())
+ mn_reset_cur(msgmap, first_sorted_flagged(new_rev ? F_NONE : F_SRCHBACK,
+ stream,
+ mn_raw2m(msgmap, raw_current),
+ FSF_SKIP_CHID));
+ else
+ mn_reset_cur(msgmap, mn_raw2m(msgmap, raw_current));
+ }
+
+ msgmap->top = -1L;
+
+ if(!sp_mail_box_changed(stream))
+ sp_set_unsorted_newmail(stream, 0);
+
+ /*
+ * Turn off the MN_USOR flag. Don't bother going through the
+ * function call and the message number mappings.
+ */
+ if(THREADING()){
+ PINELT_S *pelt;
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL && (pelt = mc->sparep) != NULL)
+ pelt->unsorted = 0;
+ }
+}
+
+
+void
+sort_sort_callback(MAILSTREAM *stream, long unsigned int *list, long unsigned int nmsgs)
+{
+ long i;
+
+ dprint((2, "sort_sort_callback\n"));
+
+ if(mn_get_total(g_sort.msgmap) < nmsgs)
+ panic("Message count shrank after sort!");
+
+ /* copy ulongs to array of longs */
+ for(i = nmsgs; i > 0; i--)
+ g_sort.msgmap->sort[i] = list ? (long) list[i-1] : i-1;
+
+ /* reset the inverse array */
+ msgno_reset_isort(g_sort.msgmap);
+
+ dprint((2, "sort_sort_callback done\n"));
+}
+
+
+/*
+ * Return value for use by progress bar.
+ */
+int
+percent_sorted(void)
+{
+ /*
+ * C-client's sort routine exports two types of status
+ * indicators. One's the progress thru loading the cache (typically
+ * the elephantine bulk of the delay, and the progress thru the
+ * actual sort (typically qsort). Our job is to balance the two
+ */
+
+ return(g_sort.prog && g_sort.prog->nmsgs
+ ? (((((g_sort.prog->progress.cached * 100)
+ / g_sort.prog->nmsgs) * 9) / 10)
+ + (((g_sort.prog->progress.sorted) * 10)
+ / g_sort.prog->nmsgs))
+ : 0);
+}
+
+
+/*
+ * This is only used at startup time. It sets ps->def_sort and
+ * ps->def_sort_rev. The syntax of the sort_spec is type[/reverse].
+ * A reverse without a type is the same as arrival/reverse. A blank
+ * argument also means arrival/reverse.
+ */
+int
+decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev)
+{
+ char *sep;
+ char *fix_this = NULL;
+ int x, reverse;
+
+ if(!sort_spec || !*sort_spec){
+ *def_sort = SortArrival;
+ *def_sort_rev = 0;
+ return(0);
+ }
+
+ if(struncmp(sort_spec, "reverse", strlen(sort_spec)) == 0){
+ *def_sort = SortArrival;
+ *def_sort_rev = 1;
+ return(0);
+ }
+
+ reverse = 0;
+ if((sep = strindex(sort_spec, '/')) != NULL){
+ *sep = '\0';
+ fix_this = sep;
+ sep++;
+ if(struncmp(sep, "reverse", strlen(sep)) == 0)
+ reverse = 1;
+ else{
+ *fix_this = '/';
+ return(-1);
+ }
+ }
+
+ for(x = 0; ps_global->sort_types[x] != EndofList; x++)
+ if(struncmp(sort_name(ps_global->sort_types[x]),
+ sort_spec, strlen(sort_spec)) == 0)
+ break;
+
+ if(fix_this)
+ *fix_this = '/';
+
+ if(ps_global->sort_types[x] == EndofList)
+ return(-1);
+
+ *def_sort = ps_global->sort_types[x];
+ *def_sort_rev = reverse;
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Compare raw message numbers
+ ----*/
+int
+pine_compare_long(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+
+ return((mdiff = *mess_a - *mess_b) ? ((mdiff > 0L) ? 1 : -1) : 0);
+}
+
+/*
+ * reverse version of pine_compare_long
+ */
+int
+pine_compare_long_rev(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+
+ return((mdiff = *mess_a - *mess_b) ? ((mdiff < 0L) ? 1 : -1) : 0);
+}
+
+
+long *g_score_arr;
+
+/*
+ * This calculate all of the scores and also puts them into a temporary array
+ * for speed when sorting.
+ */
+void
+build_score_array(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ SEARCHSET *searchset;
+ long msgno, cnt, nmsgs, rawmsgno;
+ long score;
+ MESSAGECACHE *mc;
+
+ nmsgs = mn_get_total(msgmap);
+ g_score_arr = (long *) fs_get((nmsgs+1) * sizeof(score));
+ memset(g_score_arr, 0, (nmsgs+1) * sizeof(score));
+
+ /*
+ * Build a searchset that contains everything except those that have
+ * already been looked up.
+ */
+
+ for(msgno=1L; msgno <= stream->nmsgs; msgno++)
+ if((mc = mail_elt(stream, msgno)) != NULL)
+ mc->sequence = 0;
+
+ for(cnt=0L, msgno=1L; msgno <= nmsgs; msgno++){
+ rawmsgno = mn_m2raw(msgmap, msgno);
+ if(get_msg_score(stream, rawmsgno) == SCORE_UNDEF
+ && rawmsgno > 0L && stream && rawmsgno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawmsgno))){
+ mc->sequence = 1;
+ cnt++;
+ }
+ }
+
+ if(cnt){
+ searchset = build_searchset(stream);
+ (void)calculate_some_scores(stream, searchset, 0);
+ mail_free_searchset(&searchset);
+ }
+
+ /*
+ * Copy scores to g_score_arr. They should all be defined now but if
+ * they aren't assign score zero.
+ */
+ for(rawmsgno = 1L; rawmsgno <= nmsgs; rawmsgno++){
+ score = get_msg_score(stream, rawmsgno);
+ g_score_arr[rawmsgno] = (score == SCORE_UNDEF) ? 0L : score;
+ }
+}
+
+
+void
+free_score_array(void)
+{
+ if(g_score_arr)
+ fs_give((void **) &g_score_arr);
+}
+
+
+/*----------------------------------------------------------------------
+ Compare scores
+ ----*/
+int
+pine_compare_scores(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+ long sdiff;
+
+ return((sdiff = g_score_arr[*mess_a] - g_score_arr[*mess_b])
+ ? ((sdiff > 0L) ? 1 : -1)
+ : ((mdiff = *mess_a - *mess_b) ? ((mdiff > 0) ? 1 : -1) : 0));
+}
+
+
+void
+reset_sort_order(unsigned int flags)
+{
+ long rflags = ROLE_DO_OTHER;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ SortOrder the_sort_order;
+ int sort_is_rev;
+
+ /* set default order */
+ the_sort_order = ps_global->def_sort;
+ sort_is_rev = ps_global->def_sort_rev;
+
+ if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
+ NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
+ break;
+ }
+
+ if(pat && pat->action && !pat->action->bogus
+ && pat->action->sort_is_set){
+ the_sort_order = pat->action->sortorder;
+ sort_is_rev = pat->action->revsort;
+ }
+ }
+
+ sort_folder(ps_global->mail_stream, ps_global->msgmap,
+ the_sort_order, sort_is_rev, flags);
+}
diff --git a/pith/sort.h b/pith/sort.h
new file mode 100644
index 00000000..ce383a04
--- /dev/null
+++ b/pith/sort.h
@@ -0,0 +1,49 @@
+/*
+ * $Id: sort.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SORT_INCLUDED
+#define PITH_SORT_INCLUDED
+
+
+#include "../pith/sorttype.h"
+#include "../pith/msgno.h"
+
+
+#define refresh_sort(S,M,F) sort_folder((S), (M), mn_get_sort(M), \
+ mn_get_revsort(M), (F))
+
+struct global_sort_data {
+ MSGNO_S *msgmap;
+ SORTPGM *prog;
+};
+
+
+/* sort flags */
+#define SRT_NON 0x0 /* None; no options set */
+#define SRT_VRB 0x1 /* Verbose */
+#define SRT_MAN 0x2 /* Sorted manually since opened */
+
+
+extern struct global_sort_data g_sort;
+
+
+/* exported protoypes */
+char *sort_name(SortOrder);
+void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned);
+int decode_sort(char *, SortOrder *, int *);
+void reset_sort_order(unsigned);
+
+
+#endif /* PITH_SORT_INCLUDED */
diff --git a/pith/sorttype.h b/pith/sorttype.h
new file mode 100644
index 00000000..9d487ec7
--- /dev/null
+++ b/pith/sorttype.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: sorttype.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_SORTTYPE_INCLUDED
+#define PITH_SORTTYPE_INCLUDED
+
+
+/*
+ * Code exists that is sensitive to this order. Don't change
+ * it unless you know what you're doing.
+ */
+typedef enum {SortSubject = 0, SortArrival, SortFrom, SortTo,
+ SortCc, SortDate, SortSize,
+ SortSubject2, SortScore, SortThread, EndofList} SortOrder;
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_SORTTYPE_INCLUDED */
diff --git a/pith/state.c b/pith/state.c
new file mode 100644
index 00000000..e88c27f6
--- /dev/null
+++ b/pith/state.c
@@ -0,0 +1,318 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: state.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ state.c
+ Implements the Pine state management routines
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/init.h"
+#include "../pith/sort.h"
+#include "../pith/atttype.h"
+#include "../pith/util.h"
+#include "../pith/mailindx.h"
+#include "../pith/remote.h"
+#include "../pith/list.h"
+#include "../pith/smime.h"
+
+
+/*
+ * Globals referenced throughout pine...
+ */
+struct pine *ps_global; /* THE global variable! */
+
+#ifdef DEBUG
+/*
+ * Debug level and output file defined here, referenced globally.
+ * The debug file is opened and initialized below...
+ */
+int debug = DEFAULT_DEBUG;
+#endif
+
+
+/*----------------------------------------------------------------------
+ General use big buffer. It is used in the following places:
+ compose_mail: while parsing header of postponed message
+ append_message2: while writing header into folder
+ q_status_messageX: while doing printf formatting
+ addr_book: Used to return expanded address in. (Can only use here
+ because mm_log doesn't q_status on PARSE errors !)
+ alpine.c: When address specified on command line
+ init.c: When expanding variable values
+ and many many more...
+
+ ----*/
+char tmp_20k_buf[SIZEOF_20KBUF];
+
+
+/*
+ * new_pine_struct - allocate and fill in with default values a new pine struct
+ */
+struct pine *
+new_pine_struct(void)
+{
+ struct pine *p;
+
+ p = (struct pine *)fs_get(sizeof (struct pine));
+ memset((void *) p, 0, sizeof(struct pine));
+ p->def_sort = SortArrival;
+ p->sort_types[0] = SortSubject;
+ p->sort_types[1] = SortArrival;
+ p->sort_types[2] = SortFrom;
+ p->sort_types[3] = SortTo;
+ p->sort_types[4] = SortCc;
+ p->sort_types[5] = SortDate;
+ p->sort_types[6] = SortSize;
+ p->sort_types[7] = SortSubject2;
+ p->sort_types[8] = SortScore;
+ p->sort_types[9] = SortThread;
+ p->sort_types[10] = EndofList;
+#ifdef SMIME
+ /*
+ * We need to have access to p->smime even before calling
+ * smime_init() so that we can set do_encrypt and do_sign.
+ */
+ p->smime = new_smime_struct();
+#endif /* SMIME */
+ p->atmts = (ATTACH_S *) fs_get(sizeof(ATTACH_S));
+ p->atmts_allocated = 1;
+ p->atmts->description = NULL;
+ p->low_speed = 1;
+ p->init_context = -1;
+ msgno_init(&p->msgmap, 0L, SortArrival, 0);
+ init_init_vars(p);
+
+ return(p);
+}
+
+
+
+/*
+ * free_pine_struct -- free up allocated data in pine struct and then the
+ * struct itself
+ */
+void
+free_pine_struct(struct pine **pps)
+{
+ if(!(pps && (*pps)))
+ return;
+
+ if((*pps)->hostname != NULL)
+ fs_give((void **)&(*pps)->hostname);
+
+ if((*pps)->localdomain != NULL)
+ fs_give((void **)&(*pps)->localdomain);
+
+ if((*pps)->ttyo != NULL)
+ fs_give((void **)&(*pps)->ttyo);
+
+ if((*pps)->home_dir != NULL)
+ fs_give((void **)&(*pps)->home_dir);
+
+ if((*pps)->folders_dir != NULL)
+ fs_give((void **)&(*pps)->folders_dir);
+
+ if((*pps)->ui.homedir)
+ fs_give((void **)&(*pps)->ui.homedir);
+
+ if((*pps)->ui.login)
+ fs_give((void **)&(*pps)->ui.login);
+
+ if((*pps)->ui.fullname)
+ fs_give((void **)&(*pps)->ui.fullname);
+
+ free_index_format(&(*pps)->index_disp_format);
+
+ if((*pps)->conv_table){
+ if((*pps)->conv_table->table)
+ fs_give((void **) &(*pps)->conv_table->table);
+
+ if((*pps)->conv_table->from_charset)
+ fs_give((void **) &(*pps)->conv_table->from_charset);
+
+ if((*pps)->conv_table->to_charset)
+ fs_give((void **) &(*pps)->conv_table->to_charset);
+
+ fs_give((void **)&(*pps)->conv_table);
+ }
+
+ if((*pps)->pinerc)
+ fs_give((void **)&(*pps)->pinerc);
+
+#if defined(DOS) || defined(OS2)
+ if((*pps)->pine_dir)
+ fs_give((void **)&(*pps)->pine_dir);
+
+ if((*pps)->aux_files_dir)
+ fs_give((void **)&(*pps)->aux_files_dir);
+#endif
+
+ if((*pps)->display_charmap)
+ fs_give((void **)&(*pps)->display_charmap);
+
+ if((*pps)->keyboard_charmap)
+ fs_give((void **)&(*pps)->keyboard_charmap);
+
+ if((*pps)->posting_charmap)
+ fs_give((void **)&(*pps)->posting_charmap);
+
+#ifdef PASSFILE
+ if((*pps)->passfile)
+ fs_give((void **)&(*pps)->passfile);
+#endif /* PASSFILE */
+
+ if((*pps)->hdr_colors)
+ free_spec_colors(&(*pps)->hdr_colors);
+
+ if((*pps)->keywords)
+ free_keyword_list(&(*pps)->keywords);
+
+ if((*pps)->kw_colors)
+ free_spec_colors(&(*pps)->kw_colors);
+
+ if((*pps)->atmts){
+ int i;
+
+ for(i = 0; (*pps)->atmts[i].description; i++){
+ fs_give((void **) &(*pps)->atmts[i].description);
+ fs_give((void **) &(*pps)->atmts[i].number);
+ }
+
+ fs_give((void **) &(*pps)->atmts);
+ }
+
+ if((*pps)->msgmap)
+ msgno_give(&(*pps)->msgmap);
+
+ free_vars(*pps);
+
+ fs_give((void **) pps);
+}
+
+
+void
+free_pinerc_strings(struct pine **pps)
+{
+ if((*pps)->prc){
+ if((*pps)->prc->outstanding_pinerc_changes)
+ write_pinerc((*pps), Main, WRP_NONE);
+
+ if((*pps)->prc->rd)
+ rd_close_remdata(&(*pps)->prc->rd);
+
+ free_pinerc_s(&(*pps)->prc);
+ }
+
+ if((*pps)->pconf)
+ free_pinerc_s(&(*pps)->pconf);
+
+ if((*pps)->post_prc){
+ if((*pps)->post_prc->outstanding_pinerc_changes)
+ write_pinerc((*pps), Post, WRP_NONE);
+
+ if((*pps)->post_prc->rd)
+ rd_close_remdata(&(*pps)->post_prc->rd);
+
+ free_pinerc_s(&(*pps)->post_prc);
+ }
+}
+
+
+/*
+ * free_vars -- give back resources acquired when we defined the
+ * variables list
+ */
+void
+free_vars(struct pine *ps)
+{
+ register int i;
+
+ for(i = 0; ps && i <= V_LAST_VAR; i++)
+ free_variable_values(&ps->vars[i]);
+}
+
+
+void
+free_variable_values(struct variable *var)
+{
+ if(var){
+ if(var->is_list){
+ free_list_array(&var->current_val.l);
+ free_list_array(&var->main_user_val.l);
+ free_list_array(&var->post_user_val.l);
+ free_list_array(&var->global_val.l);
+ free_list_array(&var->fixed_val.l);
+ free_list_array(&var->cmdline_val.l);
+ }
+ else{
+ if(var->current_val.p)
+ fs_give((void **)&var->current_val.p);
+ if(var->main_user_val.p)
+ fs_give((void **)&var->main_user_val.p);
+ if(var->post_user_val.p)
+ fs_give((void **)&var->post_user_val.p);
+ if(var->global_val.p)
+ fs_give((void **)&var->global_val.p);
+ if(var->fixed_val.p)
+ fs_give((void **)&var->fixed_val.p);
+ if(var->cmdline_val.p)
+ fs_give((void **)&var->cmdline_val.p);
+ }
+ }
+}
+
+
+PINERC_S *
+new_pinerc_s(char *name)
+{
+ PINERC_S *prc = NULL;
+
+ if(name){
+ prc = (PINERC_S *)fs_get(sizeof(*prc));
+ memset((void *)prc, 0, sizeof(*prc));
+ prc->name = cpystr(name);
+ if(IS_REMOTE(name))
+ prc->type = RemImap;
+ else
+ prc->type = Loc;
+ }
+
+ return(prc);
+}
+
+
+void
+free_pinerc_s(PINERC_S **prc)
+{
+ if(prc && *prc){
+ if((*prc)->name)
+ fs_give((void **)&(*prc)->name);
+
+ if((*prc)->rd)
+ rd_free_remdata(&(*prc)->rd);
+
+ if((*prc)->pinerc_lines)
+ free_pinerc_lines(&(*prc)->pinerc_lines);
+
+ fs_give((void **)prc);
+ }
+}
diff --git a/pith/state.h b/pith/state.h
new file mode 100644
index 00000000..93db4f4b
--- /dev/null
+++ b/pith/state.h
@@ -0,0 +1,374 @@
+/*
+ * $Id: state.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STATE_INCLUDED
+#define PITH_STATE_INCLUDED
+
+
+#include "../pith/conftype.h"
+#include "../pith/indxtype.h"
+#include "../pith/bitmap.h"
+#include "../pith/charset.h"
+#include "../pith/context.h"
+#include "../pith/keyword.h"
+#include "../pith/atttype.h"
+#include "../pith/msgno.h"
+#include "../pith/pattern.h"
+#include "../pith/pipe.h"
+#include "../pith/send.h"
+#include "../pith/sorttype.h"
+#include "../pith/stream.h"
+#include "../pith/color.h"
+#include "../pith/user.h"
+
+
+/*
+ * Printing control structure
+ */
+typedef struct print_ctrl {
+#ifndef DOS
+ PIPE_S *pipe; /* control struct for pipe to write text */
+ FILE *fp; /* file pointer to write printed text into */
+ char *result; /* file containing print command's output */
+#endif
+#ifdef OS2
+ int ispipe;
+#endif
+ int err; /* bit indicating something went awry */
+} PRINT_S;
+
+
+/*
+ * Keeps track of display dimensions
+ */
+struct ttyo {
+ int screen_rows,
+ screen_cols,
+ header_rows, /* number of rows for titlebar and whitespace */
+ footer_rows; /* number of rows for status and keymenu */
+};
+
+/*
+ * HEADER_ROWS is always 2. 1 for the titlebar and 1 for the
+ * blank line after the titlebar. We should probably make it go down
+ * to 0 when the screen shrinks but instead we're just figuring out
+ * if there is enough room by looking at screen_rows.
+ * FOOTER_ROWS is either 3 or 1. Normally it is 3, 2 for the keymenu plus 1
+ * for the status line. If the NoKeyMenu command has been given, then it is 1.
+ */
+#define HEADER_ROWS(X) ((X)->ttyo->header_rows)
+#define FOOTER_ROWS(X) ((X)->ttyo->footer_rows)
+
+
+/*----------------------------------------------------------------------
+ This structure sort of takes the place of global variables or perhaps
+is the global variable. (It can be accessed globally as ps_global. One
+advantage to this is that as soon as you see a reference to the structure
+you know it is a global variable.
+ In general it is treated as global by the lower level and utility
+routines, but it is not treated so by the main screen driving routines.
+Each of them receives it as an argument and then sets ps_global to the
+argument they received. This is sort of with the thought that things
+might be coupled more loosely one day and that Pine might run where there
+is more than one window and more than one instance. But we haven't kept
+up with this convention very well.
+ ----*/
+
+struct pine {
+ void (*next_screen)(struct pine *); /* See loop at end of main() for how */
+ void (*prev_screen)(struct pine *); /* these are used... */
+ void (*redrawer)(void); /* NULL means stay in current screen */
+
+ CONTEXT_S *context_list; /* list of user defined contexts */
+ CONTEXT_S *context_current; /* default open context */
+ CONTEXT_S *context_last; /* most recently open context */
+
+ SP_S s_pool; /* stream pool */
+
+ char inbox_name[MAXFOLDER+1];
+ char pine_pre_vers[10]; /* highest version previously run */
+ char vers_internal[10];
+
+ MAILSTREAM *mail_stream; /* ptr to current folder stream */
+ MSGNO_S *msgmap; /* ptr to current message map */
+
+ unsigned read_predicted:1;
+
+ char cur_folder[MAXPATH+1];
+ QUOTALIST *quota;
+ char last_unambig_folder[MAXPATH+1];
+ char last_save_folder[MAXPATH+1];
+ CONTEXT_S *last_save_context;
+ ATTACH_S *atmts;
+ int atmts_allocated;
+ int remote_abook_validity; /* minutes, -1=never, 0=only on opens */
+
+ INDEX_COL_S *index_disp_format;
+
+ char *folders_dir;
+
+ unsigned mangled_footer:1; /* footer needs repainting */
+ unsigned mangled_header:1; /* header needs repainting */
+ unsigned mangled_body:1; /* body of screen needs repainting */
+ unsigned mangled_screen:1; /* whole screen needs repainting */
+
+ unsigned in_init_seq:1; /* executing initial cmd list */
+ unsigned save_in_init_seq:1;
+ unsigned dont_use_init_cmds:1; /* use keyboard input when true */
+
+ unsigned give_fixed_warning:1; /* warn user about "fixed" vars */
+ unsigned fix_fixed_warning:1; /* offer to fix it */
+
+ unsigned user_says_cancel:1; /* user typed ^C to abort open */
+
+ unsigned unseen_in_view:1;
+ unsigned start_in_context:1; /* start fldr_scrn in current cntxt */
+ unsigned def_sort_rev:1; /* true if reverse sort is default */
+ unsigned restricted:1;
+
+ unsigned save_msg_rule:5;
+ unsigned fcc_rule:3;
+ unsigned ab_sort_rule:3;
+ unsigned color_style:3;
+ unsigned index_color_style:3;
+ unsigned titlebar_color_style:3;
+ unsigned fld_sort_rule:3;
+ unsigned inc_startup_rule:3;
+ unsigned pruning_rule:3;
+ unsigned reopen_rule:4;
+ unsigned goto_default_rule:3;
+ unsigned thread_disp_style:3;
+ unsigned thread_index_style:3;
+
+ unsigned full_header:2; /* display full headers */
+ /* 0 means normal */
+ /* 1 means display all quoted text */
+ /* 2 means full headers */
+ unsigned some_quoting_was_suppressed:1;
+ unsigned orig_use_fkeys:1;
+ unsigned try_to_create:1; /* Save should try mail_create */
+ unsigned low_speed:1; /* various opt's 4 low connect speed */
+ unsigned postpone_no_flow:1; /* don't set flowed when we postpone */
+ /* and don't reflow when we resume. */
+ unsigned mm_log_error:1;
+ unsigned show_new_version:1;
+ unsigned pre441:1;
+ unsigned first_time_user:1;
+ unsigned intr_pending:1; /* received SIGINT and haven't acted */
+ unsigned expunge_in_progress:1; /* don't want to re-enter c-client */
+ unsigned never_allow_changing_from:1; /* not even for roles */
+ unsigned newthread:1; /* start a new thread on composing */
+
+ unsigned readonly_pinerc:1;
+ unsigned view_all_except:1;
+ unsigned start_in_index:1; /* cmd line flag modified on startup */
+ unsigned noshow_error:1; /* c-client error callback controls */
+ unsigned noshow_warn:1;
+ unsigned noshow_timeout:1;
+ unsigned conceal_sensitive_debugging:1;
+ unsigned turn_off_threading_temporarily:1;
+ unsigned view_skipped_index:1;
+ unsigned a_format_contains_score:1;
+ unsigned ugly_consider_advancing_bit:1;
+ unsigned dont_count_flagchanges:1;
+ unsigned in_folder_screen:1;
+ unsigned noticed_change_in_unseen:1;
+ unsigned first_open_was_attempted:1;
+ unsigned force_prefer_plain:1;
+ unsigned force_no_prefer_plain:1;
+
+ unsigned phone_home:1;
+ unsigned painted_body_on_startup:1;
+ unsigned painted_footer_on_startup:1;
+ unsigned open_readonly_on_startup:1;
+ unsigned exit_if_no_pinerc:1;
+ unsigned pass_ctrl_chars:1;
+ unsigned pass_c1_ctrl_chars:1;
+ unsigned display_keywords_in_subject:1;
+ unsigned display_keywordinits_in_subject:1;
+ unsigned beginning_of_month:1;
+ unsigned beginning_of_year:1;
+
+ unsigned viewer_overlap:8;
+ unsigned scroll_margin:8;
+ unsigned remote_abook_history:8;
+
+#if defined(DOS) || defined(OS2)
+ unsigned blank_user_id:1;
+ unsigned blank_personal_name:1;
+ unsigned blank_user_domain:1;
+#ifdef _WINDOWS
+ unsigned update_registry:2;
+ unsigned install_flag:1;
+#endif
+#endif
+
+ unsigned debug_malloc:6;
+ unsigned debug_timestamp:1;
+ unsigned debug_flush:1;
+ unsigned debug_tcp:1;
+ unsigned debug_imap:3;
+ unsigned debug_nfiles:5;
+ unsigned debugmem:1;
+#ifdef LOCAL_PASSWD_CACHE
+ unsigned nowrite_password_cache:1;
+#endif
+
+ unsigned convert_sigs:1;
+ unsigned dump_supported_options:1;
+
+ unsigned noexpunge_on_close:1;
+
+ unsigned no_newmail_check_from_optionally_enter:1;
+
+ unsigned post_utf8:1;
+
+ unsigned start_entry; /* cmd line arg: msg # to start on */
+
+ bitmap_t feature_list; /* a bitmap of all the features */
+ char **feat_list_back_compat;
+
+ SPEC_COLOR_S *hdr_colors; /* list of configed colors for view */
+
+ short init_context;
+
+ int *initial_cmds; /* cmds to execute on startup */
+ int *free_initial_cmds; /* used to free when done */
+
+ char c_client_error[300]; /* when nowhow_error is set and PARSE */
+
+ struct ttyo *ttyo;
+
+ USER_S ui; /* system derived user info */
+
+ POST_S *post;
+
+ char *home_dir,
+ *hostname, /* Fully qualified hostname */
+ *localdomain, /* The (DNS) domain this host resides in */
+ *userdomain, /* The per user domain from .pinerc or */
+ *maildomain, /* Domain name for most uses */
+#if defined(DOS) || defined(OS2)
+ *pine_dir, /* argv[0] as provided by DOS */
+ *aux_files_dir, /* User's auxiliary files directory */
+#endif
+#ifdef PASSFILE
+ *passfile,
+#endif /* PASSFILE */
+ *pinerc, /* Location of user's pinerc */
+ *exceptions, /* Location of user's exceptions */
+ *pine_name; /* name we were invoked under */
+ PINERC_S *prc, /* structure for personal pinerc */
+ *post_prc, /* structure for post-loaded pinerc */
+ *pconf; /* structure for global pinerc */
+
+ EditWhich ew_for_except_vars;
+ EditWhich ew_for_role_take;
+ EditWhich ew_for_score_take;
+ EditWhich ew_for_filter_take;
+ EditWhich ew_for_incol_take;
+ EditWhich ew_for_other_take;
+ EditWhich ew_for_srch_take;
+
+ SortOrder def_sort, /* Default sort type */
+ sort_types[22];
+
+ int last_expire_year, last_expire_month;
+
+ int printer_category;
+
+ int status_msg_delay;
+
+ int active_status_interval;
+
+ int composer_fillcol;
+
+ int nmw_width;
+
+ int hours_to_timeout;
+
+ int tcp_query_timeout;
+
+ int inc_check_timeout;
+ int inc_check_interval; /* for local and IMAP */
+ int inc_second_check_interval; /* for other */
+
+ time_t check_interval_for_noncurr;
+
+ time_t last_nextitem_forcechk;
+
+ MAILSTREAM *cur_uid_stream;
+ imapuid_t cur_uid;
+
+ int deadlets;
+
+ int quote_suppression_threshold;
+
+ char *display_charmap; /* needs to be freed */
+ char *keyboard_charmap; /* needs to be freed */
+ void *input_cs;
+
+ char *posting_charmap; /* needs to be freed */
+
+ CONV_TABLE *conv_table;
+
+ /*
+ * Optional tools Pine Data Engine caller might provide
+ */
+ struct {
+ char *(*display_filter)(char *, STORE_S *, gf_io_t, FILTLIST_S *);
+ char *(*display_filter_trigger)(BODY *, char *, size_t);
+ } tools;
+
+ KEYWORD_S *keywords;
+ SPEC_COLOR_S *kw_colors;
+
+ ACTION_S *default_role; /* pointer to one of regular roles */
+
+ char last_error[500];
+ INIT_ERR_S *init_errs;
+
+ PRINT_S *print;
+
+#ifdef SMIME
+ SMIME_STUFF_S *smime;
+#endif /* SMIME */
+
+ struct variable *vars;
+};
+
+
+/*----------------------------------------------------------------------
+ The few global variables we use in Pine Data Engine
+ ----*/
+
+extern struct pine *ps_global;
+
+#define SIZEOF_20KBUF (20480)
+extern char tmp_20k_buf[];
+
+
+/* exported protoypes */
+struct pine *new_pine_struct(void);
+void free_pine_struct(struct pine **);
+void free_pinerc_strings(struct pine **);
+void free_vars(struct pine *);
+void free_variable_values(struct variable *);
+PINERC_S *new_pinerc_s(char *);
+void free_pinerc_s(PINERC_S **);
+
+
+#endif /* PITH_STATE_INCLUDED */
diff --git a/pith/status.c b/pith/status.c
new file mode 100644
index 00000000..b2c8dc00
--- /dev/null
+++ b/pith/status.c
@@ -0,0 +1,163 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: status.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/status.h"
+#include "../pith/state.h"
+
+
+/*----------------------------------------------------------------------
+ Put a message with 1 printf argument on queue for status line
+
+ Args: min_t -- minimum time to display message for
+ max_t -- minimum time to display message for
+ s -- printf style control string
+ a -- argument for printf
+
+ Result: message queued
+ ----*/
+
+/*VARARGS1*/
+void
+q_status_message1(int flags, int min_t, int max_t, char *s, void *a)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Put a message with 2 printf argument on queue for status line
+
+ Args: min_t -- minimum time to display message for
+ max_t -- maximum time to display message for
+ s -- printf style control string
+ a1 -- argument for printf
+ a2 -- argument for printf
+
+ Result: message queued
+ ---*/
+
+/*VARARGS1*/
+void
+q_status_message2(int flags, int min_t, int max_t, char *s, void *a1, void *a2)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Put a message with 3 printf argument on queue for status line
+
+ Args: min_t -- minimum time to display message for
+ max_t -- maximum time to display message for
+ s -- printf style control string
+ a1 -- argument for printf
+ a2 -- argument for printf
+ a3 -- argument for printf
+
+ Result: message queued
+ ---*/
+
+/*VARARGS1*/
+void
+q_status_message3(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Put a message with 4 printf argument on queue for status line
+
+
+ Args: min_t -- minimum time to display message for
+ max_t -- maximum time to display message for
+ s -- printf style control string
+ a1 -- argument for printf
+ a2 -- argument for printf
+ a3 -- argument for printf
+ a4 -- argument for printf
+
+ Result: message queued
+ ----------------------------------------------------------------------*/
+/*VARARGS1*/
+void
+q_status_message4(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3, void *a4)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3, a4);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+/*VARARGS1*/
+void
+q_status_message5(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3, void *a4, void *a5)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3, a4, a5);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+/*VARARGS1*/
+void
+q_status_message6(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3, void *a4, void *a5, void *a6)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3, a4, a5, a6);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+/*----------------------------------------------------------------------
+ Put a message with 7 printf argument on queue for status line
+
+
+ Args: min_t -- minimum time to display message for
+ max_t -- maximum time to display message for
+ s -- printf style control string
+ a1 -- argument for printf
+ a2 -- argument for printf
+ a3 -- argument for printf
+ a4 -- argument for printf
+ a5 -- argument for printf
+ a6 -- argument for printf
+ a7 -- argument for printf
+
+
+ Result: message queued
+ ----------------------------------------------------------------------*/
+/*VARARGS1*/
+void
+q_status_message7(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3, a4, a5, a6, a7);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
+
+
+/*VARARGS1*/
+void
+q_status_message8(int flags, int min_t, int max_t, char *s, void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, s, a1, a2, a3, a4, a5, a6, a7, a8);
+ q_status_message(flags, min_t, max_t, tmp_20k_buf);
+}
diff --git a/pith/status.h b/pith/status.h
new file mode 100644
index 00000000..1673edd5
--- /dev/null
+++ b/pith/status.h
@@ -0,0 +1,54 @@
+/*
+ * $Id: status.h 770 2007-10-24 00:23:09Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STATUS_INCLUDED
+#define PITH_STATUS_INCLUDED
+
+
+#include <general.h>
+
+
+/*
+ * Status message types
+ */
+#define SM_DING 0x01 /* ring bell when displayed */
+#define SM_ASYNC 0x02 /* display any time */
+#define SM_ORDER 0x04 /* ordered, flush on prompt */
+#define SM_MODAL 0x08 /* display, wait for user */
+#define SM_INFO 0x10 /* Handy, but discardable */
+
+
+/* exported protoypes */
+void q_status_message1(int, int, int, char *, void *);
+void q_status_message2(int, int, int, char *, void *, void *);
+void q_status_message3(int, int, int, char *, void *, void *, void *);
+void q_status_message4(int, int, int, char *, void *, void *, void *, void *);
+void q_status_message5(int, int, int, char *, void *, void *, void *, void *, void *);
+void q_status_message6(int, int, int, char *, void *, void *, void *, void *, void *, void *);
+void q_status_message7(int, int, int, char *, void *, void *,
+ void *, void *, void *, void *, void *);
+void q_status_message8(int, int, int, char *, void *, void *,
+ void *, void *, void *, void *, void *, void *);
+
+
+/* currently mandatory to implement stubs */
+void q_status_message(int, int, int, char *);
+int status_message_remaining(void);
+int display_message(UCS);
+void flush_status_messages(int);
+
+
+#endif /* PITH_STATUS_INCLUDED */
diff --git a/pith/store.c b/pith/store.c
new file mode 100644
index 00000000..e8508257
--- /dev/null
+++ b/pith/store.c
@@ -0,0 +1,1021 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: store.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * GENERALIZED STORAGE FUNCTIONS. Idea is to allow creation of
+ * storage objects that can be written into and read from without
+ * the caller knowing if the storage is core or in a file
+ * or whatever.
+ */
+
+
+#include "../pith/headers.h"
+#include "../pith/store.h"
+#include "../pith/status.h"
+#include "../pith/state.h"
+#include "../pico/keydefs.h"
+#ifdef SMIME
+#include <openssl/buffer.h>
+#endif /* SMIME */
+
+
+/*
+ * Internal prototypes
+ */
+void *so_file_open(STORE_S *);
+int so_cs_writec(int, STORE_S *);
+int so_cs_writec_locale(int, STORE_S *);
+int so_file_writec(int, STORE_S *);
+int so_file_writec_locale(int, STORE_S *);
+int so_cs_readc(unsigned char *, STORE_S *);
+int so_cs_readc_locale(unsigned char *, STORE_S *);
+int so_cs_readc_getchar(unsigned char *c, void *extraarg);
+int so_file_readc(unsigned char *, STORE_S *);
+int so_file_readc_locale(unsigned char *, STORE_S *);
+int so_file_readc_getchar(unsigned char *c, void *extraarg);
+int so_cs_puts(STORE_S *, char *);
+int so_cs_puts_locale(STORE_S *, char *);
+int so_file_puts(STORE_S *, char *);
+int so_file_puts_locale(STORE_S *, char *);
+int so_reaquire(STORE_S *);
+#ifdef _WINDOWS
+int so_file_readc_windows(unsigned char *, STORE_S *);
+#endif /* _WINDOWS */
+#ifdef SMIME
+int so_bio_writec(int, STORE_S *);
+int so_bio_readc(unsigned char *, STORE_S *);
+int so_bio_puts(STORE_S *, char *);
+#endif /* SMIME */
+
+
+/*
+ * place holders for externally defined storage object driver
+ */
+static struct externalstoreobjectdata {
+ STORE_S *(*get)(void);
+ int (*give)(STORE_S **);
+ int (*writec)(int, STORE_S *);
+ int (*readc)(unsigned char *, STORE_S *);
+ int (*puts)(STORE_S *, char *);
+ int (*seek)(STORE_S *, long, int);
+ int (*truncate)(STORE_S *, off_t);
+ int (*tell)(STORE_S *);
+} etsod;
+
+
+#define MSIZE_INIT 8192
+#define MSIZE_INC 4096
+
+
+#ifdef S_IREAD
+#define OP_MD_USER (S_IREAD | S_IWRITE)
+#else
+#define OP_MD_USER 0600
+#endif
+
+#ifdef S_IRUSR
+#define OP_MD_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | \
+ S_IROTH | S_IWOTH)
+#else
+#define OP_MD_ALL 0666
+#endif
+
+
+/*
+ * allocate resources associated with the specified type of
+ * storage. If requesting a named file object, open it for
+ * appending, else just open a temp file.
+ *
+ * return the filled in storage object
+ */
+STORE_S *
+so_get(SourceType source, char *name, int rtype)
+ /* requested storage type */
+ /* file name */
+ /* file access type */
+{
+ STORE_S *so = (STORE_S *)fs_get(sizeof(STORE_S));
+
+ memset(so, 0, sizeof(STORE_S));
+ so->flags |= rtype;
+
+ so->cb.cbuf[0] = '\0';
+ so->cb.cbufp = so->cb.cbuf;
+ so->cb.cbufend = so->cb.cbuf;
+
+ if(name) /* stash the name */
+ so->name = cpystr(name);
+#ifdef DOS
+ else if(source == TmpFileStar || source == FileStar){
+ /*
+ * Coerce to TmpFileStar. The MSC library's "tmpfile()"
+ * doesn't observe the "TMP" or "TEMP" environment vars and
+ * always wants to write "\". This is problematic in shared,
+ * networked environments.
+ */
+ source = TmpFileStar;
+ so->name = temp_nam(NULL, "pi");
+ }
+#else
+ else if(source == TmpFileStar) /* make one up! */
+ so->name = temp_nam(NULL, "pine-tmp");
+#endif
+
+ so->src = source;
+ if(so->src == FileStar || so->src == TmpFileStar){
+#ifdef _WINDOWS
+ so->writec = so_file_writec;
+ so->readc = (rtype & READ_FROM_LOCALE) ? so_file_readc_windows
+ : so_file_readc;
+#else /* UNIX */
+ so->writec = (rtype & WRITE_TO_LOCALE) ? so_file_writec_locale
+ : so_file_writec;
+ so->readc = (rtype & READ_FROM_LOCALE) ? so_file_readc_locale
+ : so_file_readc;
+#endif /* UNIX */
+ so->puts = (rtype & WRITE_TO_LOCALE) ? so_file_puts_locale
+ : so_file_puts;
+
+ /*
+ * The reason for both FileStar and TmpFileStar types is
+ * that, named or unnamed, TmpFileStar's are unlinked
+ * when the object is given back to the system. This is
+ * useful for keeping us from running out of file pointers as
+ * the pointer associated with the object can be temporarily
+ * returned to the system without destroying the object.
+ *
+ * The programmer is warned to be careful not to assign the
+ * TmpFileStar type to any files that are expected to remain
+ * after the dust has settled!
+ */
+ if(so->name){
+ if(!(so->txt = so_file_open(so))){
+ dprint((1, "so_get error: %s : %s",
+ so->name ? so->name : "?",
+ error_description(errno)));
+ if(source == TmpFileStar)
+ our_unlink(so->name);
+
+ fs_give((void **)&so->name);
+ fs_give((void **)&so); /* so freed & set to NULL */
+ }
+ }
+ else{
+ if(!(so->txt = (void *) create_tmpfile())){
+ dprint((1, "so_get error: tmpfile : %s",
+ error_description(errno)));
+ fs_give((void **)&so); /* so freed & set to NULL */
+ }
+ }
+ }
+ else if(so->src == ExternalText && etsod.get){
+ so->writec = etsod.writec;
+ so->readc = etsod.readc;
+ so->puts = etsod.puts;
+ if(!(so->txt = (*etsod.get)())){
+ dprint((1, "so_get error: external driver allocation error"));
+
+ if(so->name)
+ fs_give((void **)&so->name);
+
+ fs_give((void **)&so); /* so freed & set to NULL */
+ }
+ }
+#ifdef SMIME
+ else if(so->src == BioType){
+ so->writec = so_bio_writec;
+ so->readc = so_bio_readc;
+ so->puts = so_bio_puts;
+
+ if(!(so->txt = BIO_new(BIO_s_mem()))){
+ dprint((1, "so_get error: BIO driver allocation error"));
+
+ if(so->name)
+ fs_give((void **) &so->name);
+
+ fs_give((void **) &so); /* so freed & set to NULL */
+ }
+ }
+#endif /* SMIME */
+ else{
+ so->writec = (rtype & WRITE_TO_LOCALE) ? so_cs_writec_locale
+ : so_cs_writec;
+ so->readc = (rtype & READ_FROM_LOCALE) ? so_cs_readc_locale
+ : so_cs_readc;
+ so->puts = (rtype & WRITE_TO_LOCALE) ? so_cs_puts_locale
+ : so_cs_puts;
+
+ so->txt = (void *)fs_get((size_t) MSIZE_INIT * sizeof(char));
+ so->dp = so->eod = (unsigned char *) so->txt;
+ so->eot = so->dp + MSIZE_INIT;
+ memset(so->eod, 0, so->eot - so->eod);
+ }
+
+ return(so);
+}
+
+
+/*
+ * so_give - free resources associated with a storage object and then
+ * the object itself.
+ */
+int
+so_give(STORE_S **so)
+{
+ int ret = 0;
+
+ if(!(so && (*so)))
+ return(ret);
+
+ if((*so)->src == FileStar || (*so)->src == TmpFileStar){
+ if((*so)->txt) /* disassociate from storage */
+ ret = fclose((FILE *)(*so)->txt) == EOF ? -1 : 0;
+
+ if((*so)->name && (*so)->src == TmpFileStar)
+ our_unlink((*so)->name); /* really disassociate! */
+ }
+ else if((*so)->txt && (*so)->src == ExternalText){
+ if(etsod.give)
+ (*etsod.give)((*so)->txt);
+ }
+#ifdef SMIME
+ else if((*so)->txt && (*so)->src == BioType){
+ BIO *b = (BIO *) (*so)->txt;
+
+ BIO_free(b);
+ }
+#endif /* SMIME */
+ else if((*so)->txt)
+ fs_give((void **)&((*so)->txt));
+
+ if((*so)->name)
+ fs_give((void **)&((*so)->name)); /* blast the name */
+
+ /* release attribute list */
+ mail_free_body_parameter(&(*so)->attr);
+
+ fs_give((void **)so); /* release the object */
+
+ return(ret);
+}
+
+
+/*
+ * so_register_external_driver - hokey way to get pico-dependent storage object
+ * support out of pith library
+ */
+void
+so_register_external_driver(STORE_S *(*get)(void),
+ int (*give)(STORE_S **),
+ int (*writec)(int, STORE_S *),
+ int (*readc)(unsigned char *, STORE_S *),
+ int (*puts)(STORE_S *, char *),
+ int (*seek)(STORE_S *, long int, int),
+ int (*truncate)(STORE_S *, off_t),
+ int (*tell)(STORE_S *))
+{
+ memset(&etsod, 0, sizeof(etsod));
+ if(get)
+ etsod.get = get;
+
+ if(give)
+ etsod.give = give;
+
+ if(writec)
+ etsod.writec = writec;
+
+ if(readc)
+ etsod.readc = readc;
+
+ if(puts)
+ etsod.puts = puts;
+
+ if(seek)
+ etsod.seek = seek;
+
+ if(truncate)
+ etsod.truncate = truncate;
+
+ if(tell)
+ etsod.tell = tell;
+}
+
+
+void *
+so_file_open(STORE_S *so)
+{
+ char *type = ((so->flags) & WRITE_ACCESS) ? "a+" : "r";
+ int flags, fd,
+ mode = (((so->flags) & OWNER_ONLY) || so->src == TmpFileStar)
+ ? OP_MD_USER : OP_MD_ALL;
+
+ /*
+ * Careful. EDIT_ACCESS and WRITE_TO_LOCALE/READ_FROM_LOCALE will
+ * not work together.
+ */
+ if(so->flags & WRITE_ACCESS){
+ flags = O_RDWR | O_CREAT | O_APPEND | O_BINARY;
+ }
+ else{
+ flags = O_RDONLY;
+ if(so->flags & READ_FROM_LOCALE){
+ flags |= _O_WTEXT;
+ }
+ else{
+ flags |= O_BINARY;
+ }
+ }
+
+ /*
+ * Use open instead of fopen so we can make temp files private.
+ * I believe the "b" or "t" in the type isn't necessary in the fdopen call
+ * because we already set O_BINARY or _O_U8TEXT or _O_WTEXT in the open.
+ */
+ return(((fd = our_open(so->name, flags, mode)) > -1)
+ ? (so->txt = (void *) fdopen(fd, type)) : NULL);
+}
+
+
+/*
+ * put a character into the specified storage object,
+ * expanding if neccessary
+ *
+ * return 1 on success and 0 on failure
+ */
+int
+so_cs_writec(int c, STORE_S *so)
+{
+ if(so->dp >= so->eot){
+ size_t incr;
+ size_t cur_o = so->dp - (unsigned char *) so->txt;
+ size_t data_o = so->eod - (unsigned char *) so->txt;
+ size_t size = (so->eot - (unsigned char *) so->txt);
+
+ /*
+ * We estimate the size we're going to need at the beginning
+ * so it shouldn't normally happen that we run out of space and
+ * come here. If we do come here, it is probably because there
+ * are lots of handles in the message or lots of quote coloring.
+ * In either case, the overhead of those is high so we don't want
+ * to keep adding little bits and resizing. Add 50% each time
+ * instead.
+ */
+ incr = MAX(size/2, MSIZE_INC);
+ size += incr;
+
+ fs_resize(&so->txt, size * sizeof(char));
+ so->dp = (unsigned char *) so->txt + cur_o;
+ so->eod = (unsigned char *) so->txt + data_o;
+ so->eot = (unsigned char *) so->txt + size;
+ memset(so->eod, 0, so->eot - so->eod);
+ }
+
+ *so->dp++ = (unsigned char) c;
+ if(so->dp > so->eod)
+ so->eod = so->dp;
+
+ return(1);
+}
+
+
+/*
+ * The locale version converts from UTF-8 to user's locale charset
+ * before writing the characters.
+ */
+int
+so_cs_writec_locale(int c, STORE_S *so)
+{
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if((outchars = utf8_to_locale(c, &so->cb, obuf, sizeof(obuf))) != 0){
+ for(i = 0; i < outchars; i++)
+ if(so_cs_writec(obuf[i], so) != 1){
+ rv = 0;
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+int
+so_file_writec(int c, STORE_S *so)
+{
+ unsigned char ch = (unsigned char) c;
+ int rv = 0;
+
+ if(so->txt || so_reaquire(so))
+ do
+ rv = fwrite(&ch,sizeof(unsigned char),(size_t)1,(FILE *)so->txt);
+ while(!rv && ferror((FILE *)so->txt) && errno == EINTR);
+
+ return(rv);
+}
+
+
+/*
+ * The locale version converts from UTF-8 to user's locale charset
+ * before writing the characters.
+ */
+int
+so_file_writec_locale(int c, STORE_S *so)
+{
+ int rv = 1;
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if((outchars = utf8_to_locale(c, &so->cb, obuf, sizeof(obuf))) != 0){
+ for(i = 0; i < outchars; i++)
+ if(so_file_writec(obuf[i], so) != 1){
+ rv = 0;
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * get a character from the specified storage object.
+ *
+ * return 1 on success and 0 on failure
+ */
+int
+so_cs_readc(unsigned char *c, STORE_S *so)
+{
+ return((so->dp < so->eod) ? *c = *(so->dp)++, 1 : 0);
+}
+
+
+/*
+ * The locale version converts from user's locale charset to UTF-8
+ * after reading the characters and before returning to the caller.
+ */
+int
+so_cs_readc_locale(unsigned char *c, STORE_S *so)
+{
+ return(generic_readc_locale(c, so_cs_readc_getchar, so, &so->cb));
+}
+
+
+int
+so_cs_readc_getchar(unsigned char *c, void *extraarg)
+{
+ STORE_S *so;
+
+ so = (STORE_S *) extraarg;
+ return(so_cs_readc(c, so));
+}
+
+
+int
+so_file_readc(unsigned char *c, STORE_S *so)
+{
+ int rv = 0;
+
+ if(so->txt || so_reaquire(so))
+ do
+ rv = fread(c, sizeof(char), (size_t)1, (FILE *)so->txt);
+ while(!rv && ferror((FILE *)so->txt) && errno == EINTR);
+
+ return(rv);
+}
+
+
+/*
+ * The locale version converts from user's locale charset to UTF-8
+ * after reading the characters and before returning to the caller.
+ */
+int
+so_file_readc_locale(unsigned char *c, STORE_S *so)
+{
+ return(generic_readc_locale(c, so_file_readc_getchar, so, &so->cb));
+}
+
+
+int
+so_file_readc_getchar(unsigned char *c, void *extraarg)
+{
+ STORE_S *so;
+
+ so = (STORE_S *) extraarg;
+ return(so_file_readc(c, so));
+}
+
+
+#ifdef _WINDOWS
+/*
+ * Read unicode characters from windows filesystem and return
+ * them as a stream of UTF-8 characters. The stream is assumed
+ * opened so that it will know how to put together the unicode.
+ */
+int
+so_file_readc_windows(unsigned char *c, STORE_S *so)
+{
+ int rv = 0;
+ UCS ucs;
+
+ /* already got some from previous call? */
+ if(so->cb.cbufend > so->cb.cbuf){
+ *c = *so->cb.cbufp;
+ so->cb.cbufp++;
+ rv++;
+ if(so->cb.cbufp >= so->cb.cbufend){
+ so->cb.cbufend = so->cb.cbuf;
+ so->cb.cbufp = so->cb.cbuf;
+ }
+
+ return(rv);
+ }
+
+ if(so->txt || so_reaquire(so)){
+ /* windows only so second arg is ignored */
+ ucs = read_a_wide_char((FILE *) so->txt, NULL);
+ rv = (ucs == CCONV_EOF) ? 0 : 1;
+ }
+
+ if(rv){
+ /*
+ * Now we need to convert the UCS character to UTF-8
+ * and dole out the UTF-8 one char at a time.
+ */
+ so->cb.cbufend = utf8_put(so->cb.cbuf, (unsigned long) ucs);
+ so->cb.cbufp = so->cb.cbuf;
+ if(so->cb.cbufend > so->cb.cbuf){
+ *c = *so->cb.cbufp;
+ so->cb.cbufp++;
+ if(so->cb.cbufp >= so->cb.cbufend){
+ so->cb.cbufend = so->cb.cbuf;
+ so->cb.cbufp = so->cb.cbuf;
+ }
+ }
+ else
+ *c = '?';
+ }
+
+ return(rv);
+}
+#endif /* _WINDOWS */
+
+
+/*
+ * write a string into the specified storage object,
+ * expanding if necessary (and cheating if the object
+ * happens to be a file!)
+ *
+ * return 1 on success and 0 on failure
+ */
+int
+so_cs_puts(STORE_S *so, char *s)
+{
+ int slen = strlen(s);
+
+ if(so->dp + slen >= so->eot){
+ register size_t cur_o = so->dp - (unsigned char *) so->txt;
+ register size_t data_o = so->eod - (unsigned char *) so->txt;
+ register size_t len = so->eot - (unsigned char *) so->txt;
+ while(len <= cur_o + slen + 1){
+ size_t incr;
+
+ incr = MAX(len/2, MSIZE_INC);
+ len += incr;
+ }
+
+ fs_resize(&so->txt, len * sizeof(char));
+ so->dp = (unsigned char *)so->txt + cur_o;
+ so->eod = (unsigned char *)so->txt + data_o;
+ so->eot = (unsigned char *)so->txt + len;
+ memset(so->eod, 0, so->eot - so->eod);
+ }
+
+ memcpy(so->dp, s, slen);
+ so->dp += slen;
+ if(so->dp > so->eod)
+ so->eod = so->dp;
+
+ return(1);
+}
+
+
+int
+so_cs_puts_locale(STORE_S *so, char *s)
+{
+ int slen = strlen(s);
+
+ while(slen--)
+ if(!so_cs_writec_locale((unsigned char) *s++, so))
+ return(0);
+
+ return(1);
+}
+
+
+int
+so_file_puts(STORE_S *so, char *s)
+{
+ int rv = *s ? 0 : 1;
+
+ if(!rv && (so->txt || so_reaquire(so)))
+ do
+ rv = fwrite(s, strlen(s)*sizeof(char), (size_t)1, (FILE *)so->txt);
+ while(!rv && ferror((FILE *)so->txt) && errno == EINTR);
+
+ return(rv);
+}
+
+
+int
+so_file_puts_locale(STORE_S *so, char *s)
+{
+ int slen = strlen(s);
+
+ while(slen--)
+ if(!so_file_writec_locale((unsigned char) *s++, so))
+ return(0);
+
+ return(1);
+}
+
+
+#ifdef SMIME
+/*
+ * put a character into the specified storage object,
+ * expanding if neccessary
+ *
+ * return 1 on success and 0 on failure
+ */
+int
+so_bio_writec(int c, STORE_S *so)
+{
+ if(so->txt && so->src == BioType){
+ unsigned char ch[1];
+ BIO *b = (BIO *) so->txt;
+
+ ch[0] = (unsigned char) (c & 0xff);
+
+ if(BIO_write(b, ch, 1) >= 1)
+ return(1);
+ }
+
+ return(0);
+}
+
+
+int
+so_bio_readc(unsigned char *c, STORE_S *so)
+{
+ if(so->txt && so->src == BioType){
+ unsigned char ch[1];
+ BIO *b = (BIO *) so->txt;
+
+ if(BIO_read(b, ch, 1) >= 1){
+ *c = ch[0];
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+
+/*
+ * write a string into the specified storage object,
+ * expanding if necessary (and cheating if the object
+ * happens to be a file!)
+ *
+ * return 1 on success and 0 on failure
+ */
+int
+so_bio_puts(STORE_S *so, char *s)
+{
+
+ if(so->txt && so->src == BioType){
+ BIO *b = (BIO *) so->txt;
+ int slen = strlen(s);
+
+ if(BIO_puts(b, s) >= slen)
+ return(1);
+ }
+
+ return(1);
+}
+#endif /* SMIME */
+
+
+/*
+ *
+ */
+int
+so_nputs(STORE_S *so, char *s, long int n)
+{
+ while(n--)
+ if(!so_writec((unsigned char) *s++, so))
+ return(0); /* ERROR putting char ! */
+
+ return(1);
+}
+
+
+/*
+ * Position the storage object's pointer to the given offset
+ * from the start of the object's data.
+ */
+int
+so_seek(STORE_S *so, long int pos, int orig)
+{
+ if(so->src == CharStar){
+ switch(orig){
+ case 0 : /* SEEK_SET */
+ return((pos < so->eod - (unsigned char *) so->txt)
+ ? so->dp = (unsigned char *)so->txt + pos, 0 : -1);
+ case 1 : /* SEEK_CUR */
+ return((pos > 0)
+ ? ((pos < so->eod - so->dp) ? so->dp += pos, 0: -1)
+ : ((pos < 0)
+ ? ((-pos < so->dp - (unsigned char *)so->txt)
+ ? so->dp += pos, 0 : -1)
+ : 0));
+ case 2 : /* SEEK_END */
+ return((pos > 0)
+ ? -1
+ : ((-pos <= so->eod - (unsigned char *) so->txt)
+ ? so->dp = so->eod + pos, 0 : -1));
+ default :
+ return(-1);
+ }
+ }
+ else if(so->src == ExternalText){
+ if(etsod.seek)
+ return((*etsod.seek)(so->txt, pos, orig));
+
+ fatal("programmer botch: unsupported so_seek call");
+ /*NOTREACHED*/
+ return(0); /* suppress dumb compiler warnings */
+ }
+#ifdef SMIME
+ else if(so->src == BioType){
+ BIO *b = (BIO *) so->txt;
+
+ if(b && BIO_method_type(b) != BIO_TYPE_MEM)
+ (void) BIO_reset(b);
+
+ return(0);
+ }
+#endif /* SMIME */
+ else /* FileStar or TmpFileStar */
+ return((so->txt || so_reaquire(so))
+ ? fseek((FILE *)so->txt,pos,orig)
+ : -1);
+}
+
+
+/*
+ * Change the given storage object's size to that specified. If size
+ * is less than the current size, the internal pointer is adjusted and
+ * all previous data beyond the given size is lost.
+ *
+ * Returns 0 on failure.
+ */
+int
+so_truncate(STORE_S *so, long int size)
+{
+ if(so->src == CharStar){
+ if(so->eod < (unsigned char *) so->txt + size){ /* alloc! */
+ unsigned char *newtxt = (unsigned char *) so->txt;
+ register size_t len = so->eot - (unsigned char *) so->txt;
+
+ while(len <= size)
+ len += MSIZE_INC; /* need to resize! */
+
+ if(len > so->eot - (unsigned char *) newtxt){
+ fs_resize((void **) &newtxt, len * sizeof(char));
+ so->eot = newtxt + len;
+ so->eod = newtxt + (so->eod - (unsigned char *) so->txt);
+ memset(so->eod, 0, so->eot - so->eod);
+ }
+
+ so->eod = newtxt + size;
+ so->dp = newtxt + (so->dp - (unsigned char *) so->txt);
+ so->txt = newtxt;
+ }
+ else if(so->eod > (unsigned char *) so->txt + size){
+ if(so->dp > (so->eod = (unsigned char *)so->txt + size))
+ so->dp = so->eod;
+
+ memset(so->eod, 0, so->eot - so->eod);
+ }
+
+ return(1);
+ }
+ else if(so->src == ExternalText){
+ if(etsod.truncate)
+ return((*etsod.truncate)(so, (off_t) size));
+
+ fatal("programmer botch: unsupported so_truncate call");
+ /*NOTREACHED*/
+ return(0); /* suppress dumb compiler warnings */
+ }
+#ifdef SMIME
+ else if(so->src == BioType){
+ fatal("programmer botch: unsupported so_truncate call for BioType");
+ /*NOTREACHED*/
+ return(0); /* suppress dumb compiler warnings */
+
+#ifdef notdef
+ long len;
+ BIO *b = (BIO *) so->txt;
+
+ if(b){
+ BUF_MEM *biobuf = NULL;
+
+ BIO_get_mem_ptr(b, &biobuf);
+ if(biobuf){
+ BUF_MEM_grow(biobuf, size);
+ return(1);
+ }
+ }
+
+ return(0);
+#endif /* notdef */
+ }
+#endif /* SMIME */
+ else /* FileStar or TmpFileStar */
+ return(fflush((FILE *) so->txt) != EOF
+ && fseek((FILE *) so->txt, size, 0) == 0
+ && ftruncate(fileno((FILE *)so->txt), (off_t) size) == 0);
+}
+
+
+/*
+ * Report given storage object's position indicator.
+ * Returns 0 on failure.
+ */
+long
+so_tell(STORE_S *so)
+{
+ if(so->src == CharStar){
+ return((long) (so->dp - (unsigned char *) so->txt));
+ }
+ else if(so->src == ExternalText){
+ if(etsod.tell)
+ return((*etsod.tell)(so));
+
+ fatal("programmer botch: unsupported so_tell call");
+ /*NOTREACHED*/
+ return(0); /* suppress dumb compiler warnings */
+ }
+#ifdef SMIME
+ else if(so->src == BioType){
+ fatal("programmer botch: unsupported so_tell call for BioType");
+ /*NOTREACHED*/
+ return(0); /* suppress dumb compiler warnings */
+ }
+#endif /* SMIME */
+ else /* FileStar or TmpFileStar */
+ return(ftell((FILE *) so->txt));
+}
+
+
+/*
+ * so_attr - hook to hang random attributes onto the storage object
+ */
+char *
+so_attr(STORE_S *so, char *key, char *value)
+{
+ if(so && key){
+ if(value){
+ PARAMETER **pp = &so->attr;
+
+ while(1){
+ if(*pp){
+ if((*pp)->attribute && !strcmp(key, (*pp)->attribute)){
+ if((*pp)->value)
+ fs_give((void **)&(*pp)->value);
+
+ break;
+ }
+
+ pp = &(*pp)->next;
+ }
+ else{
+ *pp = mail_newbody_parameter();
+ (*pp)->attribute = cpystr(key);
+ break;
+ }
+ }
+
+ return((*pp)->value = cpystr(value));
+ }
+ else{
+ PARAMETER *p;
+
+ for(p = so->attr; p; p = p->next)
+ if(p->attribute && !strcmp(key, p->attribute))
+ return(p->value);
+ }
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * so_release - a rather misnamed function. the idea is to release
+ * what system resources we can (e.g., open files).
+ * while maintaining a reference to it.
+ * it's up to the functions that deal with this object
+ * next to re-aquire those resources.
+ */
+int
+so_release(STORE_S *so)
+{
+ if(so->txt && so->name && (so->src == FileStar || so->src == TmpFileStar)){
+ if(fget_pos((FILE *)so->txt, (fpos_t *)&(so->used)) == 0){
+ fclose((FILE *)so->txt); /* free the handle! */
+ so->txt = NULL;
+ }
+ }
+
+ return(1);
+}
+
+
+/*
+ * so_reaquire - get any previously released system resources we
+ * may need for the given storage object.
+ * NOTE: at the moment, only FILE * types of objects are
+ * effected, so it only needs to be called before
+ * references to them.
+ *
+ */
+int
+so_reaquire(STORE_S *so)
+{
+ int rv = 1;
+
+ if(!so->txt && (so->src == FileStar || so->src == TmpFileStar)){
+ if(!(so->txt = so_file_open(so))){
+ q_status_message2(SM_ORDER,3,5, "ERROR reopening %.200s : %.200s",
+ so->name, error_description(errno));
+ rv = 0;
+ }
+ else if(fset_pos((FILE *)so->txt, (fpos_t *)&(so->used))){
+ q_status_message2(SM_ORDER, 3, 5,
+ "ERROR positioning in %.200s : %.200s",
+ so->name, error_description(errno));
+ rv = 0;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * so_text - return a pointer to the text the store object passed
+ */
+void *
+so_text(STORE_S *so)
+{
+ return((so) ? so->txt : NULL);
+}
+
+
+/*
+ * Similar to fgets but reading from a storage object.
+ */
+char *
+so_fgets(STORE_S *so, char *s, size_t size)
+{
+ unsigned char c;
+ char *p = s;
+
+ while(--size > 0 && so_readc(&c, so) > 0){
+ *p++ = (char) c;
+ if(c == '\n')
+ break;
+ }
+
+ *p = '\0';
+
+ return((p>s) ? s : NULL);
+}
diff --git a/pith/store.h b/pith/store.h
new file mode 100644
index 00000000..5496c452
--- /dev/null
+++ b/pith/store.h
@@ -0,0 +1,80 @@
+/*
+ * $Id: store.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STORE_INCLUDED
+#define PITH_STORE_INCLUDED
+
+
+#include "../pith/filttype.h"
+#ifdef SMIME
+#include <openssl/bio.h>
+#endif /* SMIME */
+
+
+typedef enum {CharStarStar, CharStar, FileStar,
+#ifdef SMIME
+ BioType,
+#endif /* SMIME */
+ TmpFileStar, PipeStar, ExternalText} SourceType;
+
+
+/*
+ * typedef used by storage object routines
+ */
+
+typedef struct store_object {
+ unsigned char *dp; /* current position in data */
+ unsigned char *eod; /* end of current data */
+ void *txt; /* container's data */
+ unsigned char *eot; /* end of space alloc'd for data */
+ int (*writec)(int, struct store_object *);
+ int (*readc)(unsigned char *, struct store_object *);
+ int (*puts)(struct store_object *, char *);
+ fpos_t used; /* amount of object in use */
+ char *name; /* optional object name */
+ SourceType src; /* what we're copying into */
+ short flags; /* flags relating to object use */
+ CBUF_S cb;
+ PARAMETER *attr; /* attribute list */
+} STORE_S;
+
+#define so_writec(c, so) ((*(so)->writec)((c), (so)))
+#define so_readc(c, so) ((*(so)->readc)((c), (so)))
+#define so_puts(so, s) ((*(so)->puts)((so), (s)))
+
+
+/* exported protoypes */
+STORE_S *so_get(SourceType, char *, int);
+int so_give(STORE_S **);
+int so_nputs(STORE_S *, char *, long);
+int so_seek(STORE_S *, long, int);
+int so_truncate(STORE_S *, long);
+long so_tell(STORE_S *);
+char *so_attr(STORE_S *, char *, char *);
+int so_release(STORE_S *);
+void *so_text(STORE_S *);
+char *so_fgets(STORE_S *, char *, size_t);
+void so_register_external_driver(STORE_S *(*)(void),
+ int (*)(STORE_S **),
+ int (*)(int, STORE_S *),
+ int (*)(unsigned char *, STORE_S *),
+ int (*)(STORE_S *, char *),
+ int (*)(STORE_S *, long, int),
+ int (*)(STORE_S *, off_t),
+ int (*)(STORE_S *));
+
+
+#endif /* PITH_STORE_INCLUDED */
diff --git a/pith/stream.c b/pith/stream.c
new file mode 100644
index 00000000..f13fd73f
--- /dev/null
+++ b/pith/stream.c
@@ -0,0 +1,3392 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: stream.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ stream.c
+ Implements the Pine mail stream management routines
+ and c-client wrapper functions
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/stream.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/flag.h"
+#include "../pith/msgno.h"
+#include "../pith/adrbklib.h"
+#include "../pith/status.h"
+#include "../pith/newmail.h"
+#include "../pith/detach.h"
+#include "../pith/folder.h"
+#include "../pith/mailcmd.h"
+#include "../pith/util.h"
+#include "../pith/news.h"
+#include "../pith/sequence.h"
+#include "../pith/options.h"
+#include "../pith/mimedesc.h"
+
+
+void (*pith_opt_closing_stream)(MAILSTREAM *);
+
+
+/*
+ * Internal prototypes
+ */
+void reset_stream_view_state(MAILSTREAM *);
+void carefully_reset_sp_flags(MAILSTREAM *, unsigned long);
+char *partial_text_gets(readfn_t, void *, unsigned long, GETS_DATA *);
+void mail_list_internal(MAILSTREAM *, char *, char *);
+int recent_activity(MAILSTREAM *);
+int hibit_in_searchpgm(SEARCHPGM *);
+int hibit_in_strlist(STRINGLIST *);
+int hibit_in_header(SEARCHHEADER *);
+int hibit_in_sizedtext(SIZEDTEXT *);
+int sp_nusepool_notperm(void);
+int sp_add(MAILSTREAM *, int);
+void sp_delete(MAILSTREAM *);
+void sp_free(PER_STREAM_S **);
+
+
+static FETCH_READC_S *g_pft_desc;
+
+
+MAILSTATUS *pine_cached_status; /* implement status for #move folder */
+
+
+
+/*
+ * Pine wrapper around mail_open. If we have the PREFER_ALT_AUTH flag turned
+ * on, we need to set the TRYALT flag before trying the open.
+ * This routine manages the stream pool, too. It tries to re-use existing
+ * streams instead of opening new ones, or maybe it will leave one open and
+ * use a new one if that seems to make more sense. Pine_mail_close leaves
+ * streams open so that they may be re-used. Each pine_mail_open should have
+ * a matching pine_mail_close (or possible pine_mail_actually_close) somewhere
+ * that goes with it.
+ *
+ * Args:
+ * stream -- A possible stream for recycling. This isn't usually the
+ * way recycling happens. Usually it is automatic.
+ * mailbox -- The mailbox to be opened.
+ * openflags -- Flags passed here to modify the behavior.
+ * retflags -- Flags returned from here. SP_MATCH will be lit if that is
+ * what happened. If SP_MATCH is lit then SP_LOCKED may also
+ * be lit if the matched stream was already locked when
+ * we got here.
+ */
+MAILSTREAM *
+pine_mail_open(MAILSTREAM *stream, char *mailbox, long int openflags, long int *retflags)
+{
+ MAILSTREAM *retstream = NULL;
+ DRIVER *d;
+ int permlocked = 0, is_inbox = 0, usepool = 0, tempuse = 0, uf = 0;
+ unsigned long flags;
+ char **lock_these, *target = NULL;
+ static unsigned long streamcounter = 0;
+
+ dprint((7,
+ "pine_mail_open: opening \"%s\"%s openflags=0x%x %s%s%s%s%s%s%s%s%s (%s)\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)",
+ openflags,
+ openflags & OP_HALFOPEN ? " OP_HALFOPEN" : "",
+ openflags & OP_READONLY ? " OP_READONLY" : "",
+ openflags & OP_SILENT ? " OP_SILENT" : "",
+ openflags & OP_DEBUG ? " OP_DEBUG" : "",
+ openflags & SP_PERMLOCKED ? " SP_PERMLOCKED" : "",
+ openflags & SP_INBOX ? " SP_INBOX" : "",
+ openflags & SP_USERFLDR ? " SP_USERFLDR" : "",
+ openflags & SP_USEPOOL ? " SP_USEPOOL" : "",
+ openflags & SP_TEMPUSE ? " SP_TEMPUSE" : "",
+ debug_time(1, ps_global->debug_timestamp)));
+
+ if(retflags)
+ *retflags = 0L;
+
+ if(ps_global->user_says_cancel){
+ dprint((7, "pine_mail_open: cancelled by user\n"));
+ return(retstream);
+ }
+
+ is_inbox = openflags & SP_INBOX;
+ uf = openflags & SP_USERFLDR;
+
+ /* inbox is still special, assume that we want to permlock it */
+ permlocked = (is_inbox || openflags & SP_PERMLOCKED) ? 1 : 0;
+
+ /* check to see if user wants this folder permlocked */
+ for(lock_these = ps_global->VAR_PERMLOCKED;
+ uf && !permlocked && lock_these && *lock_these; lock_these++){
+ char *p = NULL, *dummy = NULL, *lt, *lname, *mname;
+ char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN];
+
+ /* there isn't really a pair, it just dequotes for us */
+ get_pair(*lock_these, &dummy, &p, 0, 0);
+
+ /*
+ * Check to see if this is an incoming nickname and replace it
+ * with the full name.
+ */
+ if(!(p && ps_global->context_list
+ && ps_global->context_list->use & CNTXT_INCMNG
+ && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), 0))))
+ lt = p;
+
+ if(dummy)
+ fs_give((void **) &dummy);
+
+ if(lt && mailbox
+ && (same_remote_mailboxes(mailbox, lt)
+ ||
+ (!IS_REMOTE(mailbox)
+ && (lname=mailboxfile(tmp1, lt))
+ && (mname=mailboxfile(tmp2, mailbox))
+ && !strcmp(lname, mname))))
+ permlocked++;
+
+ if(p)
+ fs_give((void **) &p);
+ }
+
+ /*
+ * Only cache if remote, not nntp, not pop, and caller asked us to.
+ * It might make sense to do some caching for nntp and pop, as well, but
+ * we aren't doing it right now. For example, an nntp stream open to
+ * one group could be reused for another group. An open pop stream could
+ * be used for mail_copy_full.
+ *
+ * An implication of doing only imap here is that sp_stream_get will only
+ * be concerned with imap streams.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){
+ usepool = openflags & SP_USEPOOL;
+ tempuse = openflags & SP_TEMPUSE;
+ }
+ else{
+ if(IS_REMOTE(mailbox)){
+ dprint((9, "pine_mail_open: not cacheable: %s\n", !d ? "no driver?" : d->name ? d->name : "?" ));
+ }
+ else{
+ if(permlocked || (openflags & OP_READONLY)){
+ /*
+ * This is a strange case. We want to allow stay-open local
+ * folders, but they don't fit into the rest of the framework
+ * well. So we'll look for it being already open in this case
+ * and special-case it (the already_open_stream() case
+ * below).
+ */
+ dprint((9,
+ "pine_mail_open: not cacheable: not remote, but check for local stream\n"));
+ }
+ else{
+ dprint((9,
+ "pine_mail_open: not cacheable: not remote\n"));
+ }
+ }
+ }
+
+ /* If driver doesn't support halfopen, just open it. */
+ if(d && (openflags & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)){
+ openflags &= ~OP_HALFOPEN;
+ dprint((9,
+ "pine_mail_open: turning off OP_HALFOPEN flag\n"));
+ }
+
+ /*
+ * Some of the flags are pine's, the rest are meant for mail_open.
+ * We've noted the pine flags, now remove them before we call mail_open.
+ */
+ openflags &= ~(SP_USEPOOL | SP_TEMPUSE | SP_INBOX
+ | SP_PERMLOCKED | SP_USERFLDR);
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 3 || ps_global->debugmem)
+ openflags |= OP_DEBUG;
+#endif
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global)){
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap"))
+ openflags |= OP_TRYALT;
+ }
+
+ if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)){
+ char source[MAILTMPLEN];
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ DRIVER *d;
+ if((d = mail_valid(NIL, source, (char *) NIL))
+ && (!strcmp(d->name, "news")
+ || !strcmp(d->name, "nntp")))
+ openflags |= OP_MULNEWSRC;
+ }
+ else if((d = mail_valid(NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "nntp"))
+ openflags |= OP_MULNEWSRC;
+ }
+
+ /*
+ * One of the problems is that the new-style stream caching (the
+ * sp_stream_get stuff) may conflict with some of the old-style caching
+ * (the passed in argument stream) that is still in the code. We should
+ * probably eliminate the old-style caching, but some of it is still useful,
+ * especially if it deals with something other than IMAP. We want to prevent
+ * mistakes caused by conflicts between the two styles. In particular, we
+ * don't want to have a new-style cached stream re-opened because of the
+ * old-style caching code. This can happen if a stream is passed in that
+ * is not useable, and then a new stream is opened because the passed in
+ * stream causes us to bypass the new caching code. Play it safe. If it
+ * is an IMAP stream, just close it. This should leave it in the new-style
+ * cache anyway, causing no loss. Maybe not if the cache wasn't large
+ * enough to have it in there in the first place, in which case we get
+ * a possibly unnecessary close and open. If it isn't IMAP we still have
+ * to worry about it because it will cause us to bypass the caching code.
+ * So if the stream isn't IMAP but the mailbox we're opening is, close it.
+ * The immediate alternative would be to try to emulate the code in
+ * mail_open that checks whether it is re-usable or not, but that is
+ * dangerous if that code changes on us.
+ */
+ if(stream){
+ if(is_imap_stream(stream)
+ || ((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap"))){
+ if(is_imap_stream(stream)){
+ dprint((7,
+ "pine_mail_open: closing passed in IMAP stream %s\n",
+ stream->mailbox ? stream->mailbox : "?"));
+ }
+ else{
+ dprint((7,
+ "pine_mail_open: closing passed in non-IMAP stream %s\n",
+ stream->mailbox ? stream->mailbox : "?"));
+ }
+
+ pine_mail_close(stream);
+ stream = NULL;
+ }
+ }
+
+ /*
+ * Maildrops are special. The mailbox name will be a #move name. If the
+ * target of the maildrop is an IMAP folder we want to be sure it isn't
+ * already open in another cached stream, to avoid double opens. This
+ * could have happened if the user opened it manually as the target
+ * instead of as a maildrop. It could also be a side-effect of marking
+ * an answered flag after a reply.
+ */
+ target = NULL;
+ if(check_for_move_mbox(mailbox, NULL, 0, &target)){
+ MAILSTREAM *targetstream = NULL;
+
+ if((d = mail_valid (NIL, target, (char *) NIL)) && !strcmp(d->name, "imap")){
+ targetstream = sp_stream_get(target, SP_MATCH | SP_RO_OK);
+ if(targetstream){
+ dprint((9, "pine_mail_open: close previously opened target of maildrop\n"));
+ pine_mail_actually_close(targetstream);
+ }
+ }
+ }
+
+ if((usepool && !stream && permlocked)
+ || (!usepool && (permlocked || (openflags & OP_READONLY))
+ && (retstream = already_open_stream(mailbox, AOS_NONE)))){
+ if(retstream)
+ stream = retstream;
+ else
+ stream = sp_stream_get(mailbox,
+ SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0));
+ if(stream){
+ flags = SP_LOCKED
+ | (usepool ? SP_USEPOOL : 0)
+ | (permlocked ? SP_PERMLOCKED : 0)
+ | (is_inbox ? SP_INBOX : 0)
+ | (uf ? SP_USERFLDR : 0)
+ | (tempuse ? SP_TEMPUSE : 0);
+
+ /*
+ * If the stream wasn't already locked, then we reset it so it
+ * looks like we are reopening it. We have to worry about recent
+ * messages since they will still be recent, if that affects us.
+ */
+ if(!(sp_flags(stream) & SP_LOCKED))
+ reset_stream_view_state(stream);
+
+ if(retflags){
+ *retflags |= SP_MATCH;
+ if(sp_flags(stream) & SP_LOCKED)
+ *retflags |= SP_LOCKED;
+ }
+
+ if(sp_flags(stream) & SP_LOCKED
+ && sp_flags(stream) & SP_USERFLDR
+ && !(flags & SP_USERFLDR)){
+ sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
+ dprint((7,
+ "pine_mail_open: permlocked: ref cnt up to %d\n",
+ sp_ref_cnt(stream)));
+ }
+ else if(sp_ref_cnt(stream) <= 0){
+ sp_set_ref_cnt(stream, 1);
+ dprint((7,
+ "pine_mail_open: permexact: ref cnt set to %d\n",
+ sp_ref_cnt(stream)));
+ }
+
+ carefully_reset_sp_flags(stream, flags);
+
+ if(stream->silent && !(openflags & OP_SILENT))
+ stream->silent = NIL;
+
+ dprint((9, "pine_mail_open: stream was already open\n"));
+ if(stream && stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap")){
+ dprint((7, "pine_mail_open: next TAG %08lx\n",
+ stream->gensym));
+ }
+
+ return(stream);
+ }
+ }
+
+ if(usepool && !stream){
+ /*
+ * First, we look for an exact match, a stream which is already
+ * open to the mailbox we are trying to re-open, and we use that.
+ * Skip permlocked only because we did it above already.
+ */
+ if(!permlocked)
+ stream = sp_stream_get(mailbox,
+ SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0));
+
+ if(stream){
+ flags = SP_LOCKED
+ | (usepool ? SP_USEPOOL : 0)
+ | (permlocked ? SP_PERMLOCKED : 0)
+ | (is_inbox ? SP_INBOX : 0)
+ | (uf ? SP_USERFLDR : 0)
+ | (tempuse ? SP_TEMPUSE : 0);
+
+ /*
+ * If the stream wasn't already locked, then we reset it so it
+ * looks like we are reopening it. We have to worry about recent
+ * messages since they will still be recent, if that affects us.
+ */
+ if(!(sp_flags(stream) & SP_LOCKED))
+ reset_stream_view_state(stream);
+
+ if(retflags){
+ *retflags |= SP_MATCH;
+ if(sp_flags(stream) & SP_LOCKED)
+ *retflags |= SP_LOCKED;
+ }
+
+ if(sp_flags(stream) & SP_LOCKED
+ && sp_flags(stream) & SP_USERFLDR
+ && !(flags & SP_USERFLDR)){
+ sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
+ dprint((7,
+ "pine_mail_open: matched: ref cnt up to %d\n",
+ sp_ref_cnt(stream)));
+ }
+ else if(sp_ref_cnt(stream) <= 0){
+ sp_set_ref_cnt(stream, 1);
+ dprint((7,
+ "pine_mail_open: exact: ref cnt set to %d\n",
+ sp_ref_cnt(stream)));
+ }
+
+ carefully_reset_sp_flags(stream, flags);
+
+ /*
+ * We may be re-using a stream that was previously open
+ * with OP_SILENT and now we don't want OP_SILENT.
+ * We don't turn !silent into silent because the !silentness
+ * could be important in the original context (for example,
+ * silent suppresses mm_expunged calls).
+ *
+ * WARNING: we're messing with c-client internals.
+ */
+ if(stream->silent && !(openflags & OP_SILENT))
+ stream->silent = NIL;
+
+ dprint((9, "pine_mail_open: stream already open\n"));
+ if(stream && stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap")){
+ dprint((7, "pine_mail_open: next TAG %08lx\n",
+ stream->gensym));
+ }
+
+ return(stream);
+ }
+
+ /*
+ * No exact match, look for a stream which is open to the same
+ * server and marked for TEMPUSE.
+ */
+ stream = sp_stream_get(mailbox, SP_SAME | SP_TEMPUSE);
+ if(stream){
+ dprint((9,
+ "pine_mail_open: attempting to re-use TEMP stream\n"));
+ }
+ /*
+ * No SAME/TEMPUSE stream so we look to see if there is an
+ * open slot available (we're not yet at max_remstream). If there
+ * is an open slot, we'll just open a new stream and put it there.
+ * If not, we'll go inside this conditional.
+ */
+ else if(!permlocked
+ && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){
+ dprint((9,
+ "pine_mail_open: no empty slots\n"));
+ /*
+ * No empty remote slots available. See if there is a
+ * TEMPUSE stream that is open but to the wrong server.
+ */
+ stream = sp_stream_get(mailbox, SP_TEMPUSE);
+ if(stream){
+ /*
+ * We will close this stream and use the empty slot
+ * that that creates.
+ */
+ dprint((9,
+ "pine_mail_open: close a TEMPUSE stream and re-use that slot\n"));
+ pine_mail_actually_close(stream);
+ stream = NULL;
+ }
+ else{
+
+ /*
+ * Still no luck. Look for a stream open to the same
+ * server that is just not locked. This would be a
+ * stream that might be reusable in the future, but we
+ * need it now instead.
+ */
+ stream = sp_stream_get(mailbox, SP_SAME | SP_UNLOCKED);
+ if(stream){
+ dprint((9,
+ "pine_mail_open: attempting to re-use stream\n"));
+ }
+ else{
+ /*
+ * We'll take any UNLOCKED stream and re-use it.
+ */
+ stream = sp_stream_get(mailbox, 0);
+ if(stream){
+ /*
+ * We will close this stream and use the empty slot
+ * that that creates.
+ */
+ dprint((9,
+ "pine_mail_open: close an UNLOCKED stream and re-use the slot\n"));
+ pine_mail_actually_close(stream);
+ stream = NULL;
+ }
+ else{
+ if(ps_global->s_pool.max_remstream){
+ dprint((9, "pine_mail_open: all USEPOOL slots full of LOCKED streams, nothing to use\n"));
+ }
+ else{
+ dprint((9, "pine_mail_open: no caching, max_remstream == 0\n"));
+ }
+
+ usepool = 0;
+ tempuse = 0;
+ if(permlocked){
+ permlocked = 0;
+ dprint((2,
+ "pine_mail_open5: Can't mark folder Stay-Open: at max-remote limit\n"));
+ q_status_message1(SM_ORDER, 3, 5,
+ "Can't mark folder Stay-Open: reached max-remote limit (%s)",
+ comatose((long) ps_global->s_pool.max_remstream));
+ }
+ }
+ }
+ }
+ }
+ else{
+ dprint((9,
+ "pine_mail_open: there is an empty slot to use\n"));
+ }
+
+ /*
+ * We'll make an assumption here. If we were asked to halfopen a
+ * stream then we'll assume that the caller doesn't really care if
+ * the stream is halfopen or if it happens to be open to some mailbox
+ * already. They are just saying halfopen because they don't need to
+ * SELECT a mailbox. That's the assumption, anyway.
+ */
+ if(openflags & OP_HALFOPEN && stream){
+ dprint((9,
+ "pine_mail_open: asked for HALFOPEN so returning stream as is\n"));
+ sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
+ dprint((7,
+ "pine_mail_open: halfopen: ref cnt up to %d\n",
+ sp_ref_cnt(stream)));
+ if(stream && stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap")){
+ dprint((7, "pine_mail_open: next TAG %08lx\n",
+ stream->gensym));
+ }
+
+ if(stream->silent && !(openflags & OP_SILENT))
+ stream->silent = NIL;
+
+ return(stream);
+ }
+
+ /*
+ * We're going to SELECT another folder with this open stream.
+ */
+ if(stream){
+ /*
+ * We will have just pinged the stream to make sure it is
+ * still alive. That ping may have discovered some new mail.
+ * Before unselecting the folder, we should process the filters
+ * for that new mail.
+ */
+ if(!sp_flagged(stream, SP_LOCKED)
+ && !sp_flagged(stream, SP_USERFLDR)
+ && sp_new_mail_count(stream))
+ process_filter_patterns(stream, sp_msgmap(stream),
+ sp_new_mail_count(stream));
+
+ if(stream && stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap")){
+ dprint((7,
+ "pine_mail_open: cancel idle timer: TAG %08lx (%s)\n",
+ stream->gensym, debug_time(1, ps_global->debug_timestamp)));
+ }
+
+ /*
+ * We need to reset the counters and everything.
+ * The easiest way to do that is just to delete all of the
+ * sp_s data and let the open fill it in correctly for
+ * the new folder.
+ */
+ sp_free((PER_STREAM_S **) &stream->sparep);
+ }
+ }
+
+ /*
+ * When we pass a stream to mail_open, it will either re-use it or
+ * close it.
+ */
+ retstream = mail_open(stream, mailbox, openflags);
+
+ if(retstream){
+
+ dprint((7, "pine_mail_open: mail_open returns stream:\n original_mailbox=%s\n mailbox=%s\n driver=%s rdonly=%d halfopen=%d secure=%d nmsgs=%ld recent=%ld\n", retstream->original_mailbox ? retstream->original_mailbox : "?", retstream->mailbox ? retstream->mailbox : "?", (retstream->dtb && retstream->dtb->name) ? retstream->dtb->name : "?", retstream->rdonly, retstream->halfopen, retstream->secure, retstream->nmsgs, retstream->recent));
+
+ /*
+ * So it is easier to figure out which command goes with which
+ * stream when debugging, change the tag associated with each stream.
+ * Of course, this will come after the SELECT, so the startup IMAP
+ * commands will have confusing tags.
+ */
+ if(retstream != stream && retstream->dtb && retstream->dtb->name
+ && !strcmp(retstream->dtb->name, "imap")){
+ retstream->gensym += (streamcounter * 0x1000000);
+ streamcounter = (streamcounter + 1) % (8 * 16);
+ }
+
+ if(retstream && retstream->dtb && retstream->dtb->name
+ && !strcmp(retstream->dtb->name, "imap")){
+ dprint((7, "pine_mail_open: next TAG %08lx\n",
+ retstream->gensym));
+ }
+
+ /*
+ * Catch the case where our test up above (where usepool was set)
+ * did not notice that this was news, but that we can tell once
+ * we've opened the stream. (One such case would be an imap proxy
+ * to an nntp server.) Remove it from being cached here. There was
+ * a possible penalty for not noticing sooner. If all the usepool
+ * slots were full, we will have closed one of the UNLOCKED streams
+ * above, preventing us from future re-use of that stream.
+ * We could figure out how to do the test better with just the
+ * name. We could open the stream and then close the other one
+ * after the fact. Or we could just not worry about it since it is
+ * rare.
+ */
+ if(IS_NEWS(retstream)){
+ usepool = 0;
+ tempuse = 0;
+ }
+
+ /* make sure the message map has been instantiated */
+ (void) sp_msgmap(retstream);
+
+ /*
+ * If a referral during the mail_open above causes a stream
+ * re-use which involves a BYE on an IMAP stream, then that
+ * BYE will have caused an mm_notify which will have
+ * instantiated the sp_data and set dead_stream! Trust that
+ * it is alive up to here and reset it to zero.
+ */
+ sp_set_dead_stream(retstream, 0);
+
+ flags = SP_LOCKED
+ | (usepool ? SP_USEPOOL : 0)
+ | (permlocked ? SP_PERMLOCKED : 0)
+ | (is_inbox ? SP_INBOX : 0)
+ | (uf ? SP_USERFLDR : 0)
+ | (tempuse ? SP_TEMPUSE : 0);
+
+ sp_flag(retstream, flags);
+ sp_set_recent_since_visited(retstream, retstream->recent);
+
+ /* initialize the reference count */
+ sp_set_ref_cnt(retstream, 1);
+ dprint((7, "pine_mail_open: reset: ref cnt set to %d\n",
+ sp_ref_cnt(retstream)));
+
+ if(sp_add(retstream, usepool) != 0 && usepool){
+ usepool = 0;
+ tempuse = 0;
+ flags = SP_LOCKED
+ | (usepool ? SP_USEPOOL : 0)
+ | (permlocked ? SP_PERMLOCKED : 0)
+ | (is_inbox ? SP_INBOX : 0)
+ | (uf ? SP_USERFLDR : 0)
+ | (tempuse ? SP_TEMPUSE : 0);
+
+ sp_flag(retstream, flags);
+ (void) sp_add(retstream, usepool);
+ }
+
+
+ /*
+ * When opening a newsgroup, c-client marks the messages up to the
+ * last Deleted as Unseen. If the feature news-approximates-new-status
+ * is on, we'd rather they be treated as Seen. That way, selecting New
+ * messages will give us the ones past the last Deleted. So we're going
+ * to change them to Seen. Since Seen is a session flag for news, making
+ * this change won't have any permanent effect. C-client also marks the
+ * messages after the last deleted Recent, which is the bit of
+ * information we'll use to find the messages we want to change.
+ */
+ if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global) &&
+ retstream->nmsgs > 0 && IS_NEWS(retstream)){
+ char *seq;
+ long i, mflags = ST_SET;
+ MESSAGECACHE *mc;
+
+ /*
+ * Search for !recent messages to set the searched bit for
+ * those messages we want to change. Then we'll flip the bits.
+ */
+ (void)count_flagged(retstream, F_UNRECENT);
+
+ for(i = 1L; i <= retstream->nmsgs; i++)
+ if((mc = mail_elt(retstream,i)) && mc->searched)
+ mc->sequence = 1;
+ else
+ mc->sequence = 0;
+
+ if(!is_imap_stream(retstream))
+ mflags |= ST_SILENT;
+ if((seq = build_sequence(retstream, NULL, NULL)) != NULL){
+ mail_flag(retstream, seq, "\\SEEN", mflags);
+ fs_give((void **)&seq);
+ }
+ }
+ }
+
+ return(retstream);
+}
+
+
+void
+reset_stream_view_state(MAILSTREAM *stream)
+{
+ MSGNO_S *mm;
+
+ if(!stream)
+ return;
+
+ mm = sp_msgmap(stream);
+
+ if(!mm)
+ return;
+
+ sp_set_viewing_a_thread(stream, 0);
+ sp_set_need_to_rethread(stream, 0);
+
+ mn_reset_cur(mm, stream->nmsgs > 0L ? stream->nmsgs : 0L); /* default */
+
+ mm->visible_threads = -1L;
+ mm->top = 0L;
+ mm->max_thrdno = 0L;
+ mm->top_after_thrd = 0L;
+
+ mn_set_mansort(mm, 0);
+
+ /*
+ * Get rid of zooming and selections, but leave filtering flags. All the
+ * flag counts and everything should still be correct because set_lflag
+ * preserves them correctly.
+ */
+ if(any_lflagged(mm, MN_SLCT | MN_HIDE)){
+ long i;
+
+ for(i = 1L; i <= mn_get_total(mm); i++)
+ set_lflag(stream, mm, i, MN_SLCT | MN_HIDE, 0);
+ }
+
+ /*
+ * We could try to set up a default sort order, but the caller is going
+ * to re-sort anyway if they are interested in sorting. So we won't do
+ * it here.
+ */
+}
+
+
+/*
+ * We have to be careful when we change the flags of an already
+ * open stream, because there may be more than one section of
+ * code actively using the stream.
+ * We allow turning on (but not off) SP_LOCKED
+ * SP_PERMLOCKED
+ * SP_USERFLDR
+ * SP_INBOX
+ * We allow turning off (but not on) SP_TEMPUSE
+ */
+void
+carefully_reset_sp_flags(MAILSTREAM *stream, long unsigned int flags)
+{
+ if(sp_flags(stream) != flags){
+ /* allow turning on but not off */
+ if(sp_flags(stream) & SP_LOCKED && !(flags & SP_LOCKED))
+ flags |= SP_LOCKED;
+
+ if(sp_flags(stream) & SP_PERMLOCKED && !(flags & SP_PERMLOCKED))
+ flags |= SP_PERMLOCKED;
+
+ if(sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR))
+ flags |= SP_USERFLDR;
+
+ if(sp_flags(stream) & SP_INBOX && !(flags & SP_INBOX))
+ flags |= SP_INBOX;
+
+
+ /* allow turning off but not on */
+ if(!(sp_flags(stream) & SP_TEMPUSE) && flags & SP_TEMPUSE)
+ flags &= ~SP_TEMPUSE;
+
+
+ /* leave the way they already are */
+ if((sp_flags(stream) & SP_FILTERED) != (flags & SP_FILTERED))
+ flags = (flags & ~SP_FILTERED) | (sp_flags(stream) & SP_FILTERED);
+
+
+ if(sp_flags(stream) != flags)
+ sp_flag(stream, flags);
+ }
+}
+
+
+/*
+ * Pine wrapper around mail_create. If we have the PREFER_ALT_AUTH flag turned
+ * on we don't want to pass a NULL stream to c-client because it will open
+ * a non-ssl connection when we want it to be ssl.
+ */
+long
+pine_mail_create(MAILSTREAM *stream, char *mailbox)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ char source[MAILTMPLEN], *target = NULL;
+ DRIVER *d;
+
+ dprint((7, "pine_mail_create: creating \"%s\"%s\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ mailbox = target;
+ dprint((7,
+ "pine_mail_create: #move special case, creating \"%s\"\n",
+ mailbox ? mailbox : "(NULL)"));
+ }
+
+ /*
+ * We don't really need this anymore, since we are now using IMAPTRYALT.
+ * We'll leave it since it works.
+ */
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_MATCH);
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ return_val = mail_create(stream, mailbox);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*
+ * Pine wrapper around mail_delete.
+ */
+long
+pine_mail_delete(MAILSTREAM *stream, char *mailbox)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ char source[MAILTMPLEN], *target = NULL;
+ DRIVER *d;
+
+ dprint((7, "pine_mail_delete: deleting \"%s\"%s\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ mailbox = target;
+ dprint((7,
+ "pine_mail_delete: #move special case, deleting \"%s\"\n",
+ mailbox ? mailbox : "(NULL)"));
+ }
+
+ /*
+ * We don't really need this anymore, since we are now using IMAPTRYALT.
+ */
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ /* oops, we seem to be deleting a selected stream */
+ if(!stream && (stream = sp_stream_get(mailbox, SP_MATCH))){
+ pine_mail_actually_close(stream);
+ stream = NULL;
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ return_val = mail_delete(stream, mailbox);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*
+ * Pine wrapper around mail_append.
+ */
+long
+pine_mail_append_full(MAILSTREAM *stream, char *mailbox, char *flags, char *date, STRING *message)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ char source[MAILTMPLEN], *target = NULL;
+ char mailbox_nodelim[MAILTMPLEN];
+ int delim;
+ DRIVER *d;
+
+ dprint((7, "pine_mail_append_full: appending to \"%s\"%s\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ /* strip delimiter, it has no meaning in an APPEND and could cause trouble */
+ if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){
+ size_t len;
+
+ len = strlen(mailbox);
+ if(mailbox[len-1] == delim){
+ strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1));
+ mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0';
+ mailbox = mailbox_nodelim;
+ }
+ }
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ mailbox = target;
+ dprint((7,
+ "pine_mail_append_full: #move special case, appending to \"%s\"\n",
+ mailbox ? mailbox : "(NULL)"));
+ }
+
+ /*
+ * We don't really need this anymore, since we are now using IMAPTRYALT.
+ */
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_MATCH);
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ return_val = mail_append_full(stream, mailbox, flags, date, message);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*
+ * Pine wrapper around mail_append.
+ */
+long
+pine_mail_append_multiple(MAILSTREAM *stream, char *mailbox, append_t af,
+ APPENDPACKAGE *data, MAILSTREAM *not_this_stream)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ char source[MAILTMPLEN], *target = NULL;
+ DRIVER *d;
+ int we_blocked_reuse = 0;
+
+ dprint((7, "pine_mail_append_multiple: appending to \"%s\"%s\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ mailbox = target;
+ dprint((7,
+ "pine_mail_append_multiple: #move special case, appending to \"%s\"\n",
+ mailbox ? mailbox : "(NULL)"));
+ }
+
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ /*
+ * We have to be careful re-using streams for multiappend, because of
+ * the way it works. We call into c-client below but part of the call
+ * is data containing a callback function to us to supply the data to
+ * be appended. That function may need to get the data from the server.
+ * If that uses the same stream as we're trying to append on, we're
+ * in trouble. We can't call back into c-client from c-client on the same
+ * stream. (Just think about it, we're in the middle of an APPEND command.
+ * We can't issue a FETCH before the APPEND completes in order to complete
+ * the APPEND.) We can re-use a stream if it is a different stream from
+ * the one we are reading from, so that's what the not_this_stream
+ * stuff is for. If we mark it !SP_USEPOOL, it won't get reused.
+ */
+ if(sp_flagged(not_this_stream, SP_USEPOOL)){
+ we_blocked_reuse++;
+ sp_unflag(not_this_stream, SP_USEPOOL);
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_MATCH);
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ if(we_blocked_reuse)
+ sp_set_flags(not_this_stream, sp_flags(not_this_stream) | SP_USEPOOL);
+
+ return_val = mail_append_multiple(stream, mailbox, af, (void *) data);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*
+ * Pine wrapper around mail_copy.
+ */
+long
+pine_mail_copy_full(MAILSTREAM *stream, char *sequence, char *mailbox, long int options)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ char source[MAILTMPLEN], *target = NULL;
+ char mailbox_nodelim[MAILTMPLEN];
+ int delim;
+ DRIVER *d;
+
+ dprint((7, "pine_mail_copy_full: copying to \"%s\"%s\n",
+ mailbox ? mailbox : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ /* strip delimiter, it has no meaning in a COPY and could cause trouble */
+ if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){
+ size_t len;
+
+ len = strlen(mailbox);
+ if(mailbox[len-1] == delim){
+ strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1));
+ mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0';
+ mailbox = mailbox_nodelim;
+ }
+ }
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ mailbox = target;
+ dprint((7,
+ "pine_mail_copy_full: #move special case, copying to \"%s\"\n",
+ mailbox ? mailbox : "(NULL)"));
+ }
+
+ /*
+ * We don't really need this anymore, since we are now using IMAPTRYALT.
+ */
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_MATCH);
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ * Actually, mail_copy_full is the case where it might also be
+ * useful to provide a stream in the nntp and pop3 cases. If we
+ * cache such streams, then we will probably want to open one
+ * here so that it gets cached.
+ */
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ return_val = mail_copy_full(stream, sequence, mailbox, options);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*
+ * Pine wrapper around mail_rename.
+ */
+long
+pine_mail_rename(MAILSTREAM *stream, char *old, char *new)
+{
+ MAILSTREAM *ourstream = NULL;
+ long return_val;
+ long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+ DRIVER *d;
+
+ dprint((7, "pine_mail_rename(%s,%s)\n", old ? old : "",
+ new ? new : ""));
+
+ /*
+ * We don't really need this anymore, since we are now using IMAPTRYALT.
+ */
+ if((F_ON(F_PREFER_ALT_AUTH, ps_global)
+ || (ps_global->debug_imap > 3 || ps_global->debugmem))){
+
+ if((d = mail_valid (NIL, old, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+ }
+ }
+
+ /* oops, we seem to be renaming a selected stream */
+ if(!stream && (stream = sp_stream_get(old, SP_MATCH))){
+ pine_mail_actually_close(stream);
+ stream = NULL;
+ }
+
+ if(!stream)
+ stream = sp_stream_get(old, SP_SAME);
+
+ if(!stream){
+ /*
+ * It is only useful to open a stream in the imap case.
+ */
+ if((d = mail_valid (NIL, old, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+
+ stream = pine_mail_open(NULL, old, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+
+ return_val = mail_rename(stream, old, new);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(return_val);
+}
+
+
+/*----------------------------------------------------------------------
+ Our mail_close wrapper to clean up anything on the mailstream we may have
+ added to it. mostly in the unused bits of the elt's.
+ ----*/
+void
+pine_mail_close(MAILSTREAM *stream)
+{
+ unsigned long uid_last, last_uid;
+ int refcnt;
+
+ if(!stream)
+ return;
+
+ dprint((7, "pine_mail_close: %s (%s)\n",
+ stream && stream->mailbox ? stream->mailbox : "(NULL)",
+ debug_time(1, ps_global->debug_timestamp)));
+
+ if(sp_flagged(stream, SP_USEPOOL) && !sp_dead_stream(stream)){
+
+ refcnt = sp_ref_cnt(stream);
+ dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
+
+ /*
+ * Instead of checkpointing here, which takes time that the user
+ * definitely notices, we checkpoint in new_mail at the next
+ * opportune time, hopefully when the user is idle.
+ */
+#if 0
+ if(sp_flagged(stream, SP_LOCKED) && sp_flagged(stream, SP_USERFLDR)
+ && !stream->halfopen && refcnt <= 1){
+ if(changes_to_checkpoint(stream))
+ pine_mail_check(stream);
+ else{
+ dprint((7,
+ "pine_mail_close: dont think we need to checkpoint\n"));
+ }
+ }
+#endif
+
+ /*
+ * Uid_last is valid when we first open a stream, but not always
+ * valid after that. So if we know the last uid should be higher
+ * than uid_last (!#%) use that instead.
+ */
+ uid_last = stream->uid_last;
+ if(stream->nmsgs > 0L
+ && (last_uid=mail_uid(stream,stream->nmsgs)) > uid_last)
+ uid_last = last_uid;
+
+ sp_set_saved_uid_validity(stream, stream->uid_validity);
+ sp_set_saved_uid_last(stream, uid_last);
+
+ /*
+ * If the reference count is down to 0, unlock it.
+ * In any case, don't actually do any real closing.
+ */
+ if(refcnt > 0)
+ sp_set_ref_cnt(stream, refcnt-1);
+
+ refcnt = sp_ref_cnt(stream);
+ dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
+ if(refcnt <= 0){
+ dprint((7,
+ "pine_mail_close: unlocking: start idle timer: TAG %08lx (%s)\n",
+ stream->gensym, debug_time(1, ps_global->debug_timestamp)));
+ sp_set_last_use_time(stream, time(0));
+
+ /*
+ * Logically, we ought to be unflagging SP_INBOX, too. However,
+ * the filtering code uses SP_INBOX when deciding if it should
+ * filter some things, and we keep filtering after the mailbox
+ * is closed. So leave SP_INBOX alone. This (the closing of INBOX)
+ * usually only happens in goodnight_gracey when we're
+ * shutting everything down.
+ */
+ sp_unflag(stream, SP_LOCKED | SP_PERMLOCKED | SP_USERFLDR);
+ }
+ else{
+ dprint((7, "pine_mail_close: ref cnt is now %d\n",
+ refcnt));
+ }
+ }
+ else{
+ dprint((7, "pine_mail_close: %s\n",
+ sp_flagged(stream, SP_USEPOOL) ? "dead stream" : "no pool"));
+
+ refcnt = sp_ref_cnt(stream);
+ dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
+
+ /*
+ * If the reference count is down to 0, unlock it.
+ * In any case, don't actually do any real closing.
+ */
+ if(refcnt > 0)
+ sp_set_ref_cnt(stream, refcnt-1);
+
+ refcnt = sp_ref_cnt(stream);
+ if(refcnt <= 0){
+ pine_mail_actually_close(stream);
+ }
+ else{
+ dprint((7, "pine_mail_close: ref cnt is now %d\n",
+ refcnt));
+ }
+
+ }
+}
+
+
+void
+pine_mail_actually_close(MAILSTREAM *stream)
+{
+ if(!stream)
+ return;
+
+ if(!sp_closing(stream)){
+ dprint((7, "pine_mail_actually_close: %s (%s)\n",
+ stream && stream->mailbox ? stream->mailbox : "(NULL)",
+ debug_time(1, ps_global->debug_timestamp)));
+
+ sp_set_closing(stream, 1);
+
+ if(!sp_flagged(stream, SP_LOCKED)
+ && !sp_flagged(stream, SP_USERFLDR)
+ && !sp_dead_stream(stream)
+ && sp_new_mail_count(stream))
+ process_filter_patterns(stream, sp_msgmap(stream),
+ sp_new_mail_count(stream));
+ sp_delete(stream);
+
+ /*
+ * let sp_free_callback() free the sp_s stuff and the callbacks to
+ * free_pine_elt free the per-elt pine stuff.
+ */
+ mail_close(stream);
+ }
+}
+
+
+/*
+ * If we haven't used a stream for a while, we may want to logout.
+ */
+void
+maybe_kill_old_stream(MAILSTREAM *stream)
+{
+#define KILL_IF_IDLE_TIME (25 * 60)
+ if(stream
+ && !sp_flagged(stream, SP_LOCKED)
+ && !sp_flagged(stream, SP_USERFLDR)
+ && time(0) - sp_last_use_time(stream) > KILL_IF_IDLE_TIME){
+
+ dprint((7,
+ "killing idle stream: %s (%s): idle timer = %ld secs\n",
+ stream && stream->mailbox ? stream->mailbox : "(NULL)",
+ debug_time(1, ps_global->debug_timestamp),
+ (long) (time(0)-sp_last_use_time(stream))));
+
+ /*
+ * Another thing we could do here instead is to unselect the
+ * mailbox, leaving the stream open to the server.
+ */
+ pine_mail_actually_close(stream);
+ }
+}
+
+
+/*
+ * Catch searches that don't need to go to the server.
+ * (Not anymore, now c-client does this for us.)
+ */
+long
+pine_mail_search_full(MAILSTREAM *stream, char *charset,
+ SEARCHPGM *pgm, long int flags)
+{
+ /*
+ * The charset should either be UTF-8 or NULL for alpine.
+ * If it is UTF-8 we may be able to change it to NULL if
+ * everything in the search is actually ascii. We try to
+ * do this because not all servers understand the CHARSET
+ * parameter.
+ */
+ if(charset && !strucmp(charset, "utf-8")
+ && is_imap_stream(stream) && !hibit_in_searchpgm(pgm))
+ charset = NULL;
+
+ if(F_ON(F_NNTP_SEARCH_USES_OVERVIEW, ps_global))
+ flags |= SO_OVERVIEW;
+
+ return(stream ? mail_search_full(stream, charset, pgm, flags) : NIL);
+}
+
+
+int
+hibit_in_searchpgm(SEARCHPGM *pgm)
+{
+ SEARCHOR *or;
+ SEARCHPGMLIST *not;
+
+ if(!pgm)
+ return 0;
+
+ if((pgm->subject && hibit_in_strlist(pgm->subject))
+ || (pgm->text && hibit_in_strlist(pgm->text))
+ || (pgm->body && hibit_in_strlist(pgm->body))
+ || (pgm->cc && hibit_in_strlist(pgm->cc))
+ || (pgm->from && hibit_in_strlist(pgm->from))
+ || (pgm->to && hibit_in_strlist(pgm->to))
+ || (pgm->bcc && hibit_in_strlist(pgm->bcc))
+ || (pgm->keyword && hibit_in_strlist(pgm->keyword))
+ || (pgm->unkeyword && hibit_in_strlist(pgm->return_path))
+ || (pgm->sender && hibit_in_strlist(pgm->sender))
+ || (pgm->reply_to && hibit_in_strlist(pgm->reply_to))
+ || (pgm->in_reply_to && hibit_in_strlist(pgm->in_reply_to))
+ || (pgm->message_id && hibit_in_strlist(pgm->message_id))
+ || (pgm->newsgroups && hibit_in_strlist(pgm->newsgroups))
+ || (pgm->followup_to && hibit_in_strlist(pgm->followup_to))
+ || (pgm->references && hibit_in_strlist(pgm->references))
+ || (pgm->header && hibit_in_header(pgm->header)))
+ return 1;
+
+ for(or = pgm->or; or; or = or->next)
+ if(hibit_in_searchpgm(or->first) || hibit_in_searchpgm(or->second))
+ return 1;
+
+ for(not = pgm->not; not; not = not->next)
+ if(hibit_in_searchpgm(not->pgm))
+ return 1;
+
+ return 0;
+}
+
+
+int
+hibit_in_strlist(STRINGLIST *sl)
+{
+ for(; sl; sl = sl->next)
+ if(hibit_in_sizedtext(&sl->text))
+ return 1;
+
+ return 0;
+}
+
+
+int
+hibit_in_header(SEARCHHEADER *header)
+{
+ SEARCHHEADER *hdr;
+
+ for(hdr = header; hdr; hdr = hdr->next)
+ if(hibit_in_sizedtext(&hdr->line) || hibit_in_sizedtext(&hdr->text))
+ return 1;
+
+ return 0;
+}
+
+
+int
+hibit_in_sizedtext(SIZEDTEXT *st)
+{
+ unsigned char *p, *end;
+
+ p = st ? st->data : NULL;
+ if(p)
+ for(end = p + st->size; p < end; p++)
+ if(*p & 0x80)
+ return 1;
+
+ return 0;
+}
+
+
+void
+pine_mail_fetch_flags(MAILSTREAM *stream, char *sequence, long int flags)
+{
+ ps_global->dont_count_flagchanges = 1;
+ mail_fetch_flags(stream, sequence, flags);
+ ps_global->dont_count_flagchanges = 0;
+}
+
+
+ENVELOPE *
+pine_mail_fetchenvelope(MAILSTREAM *stream, long unsigned int msgno)
+{
+ ENVELOPE *env = NULL;
+
+ ps_global->dont_count_flagchanges = 1;
+ if(stream && msgno > 0L && msgno <= stream->nmsgs)
+ env = mail_fetchenvelope(stream, msgno);
+
+ ps_global->dont_count_flagchanges = 0;
+ return(env);
+}
+
+
+ENVELOPE *
+pine_mail_fetch_structure(MAILSTREAM *stream, long unsigned int msgno,
+ struct mail_bodystruct **body, long int flags)
+{
+ ENVELOPE *env = NULL;
+
+ ps_global->dont_count_flagchanges = 1;
+ if(stream && (flags & FT_UID || (msgno > 0L && msgno <= stream->nmsgs)))
+ env = mail_fetch_structure(stream, msgno, body, flags);
+
+ ps_global->dont_count_flagchanges = 0;
+ return(env);
+}
+
+
+ENVELOPE *
+pine_mail_fetchstructure(MAILSTREAM *stream, long unsigned int msgno, struct mail_bodystruct **body)
+{
+ ENVELOPE *env = NULL;
+
+ ps_global->dont_count_flagchanges = 1;
+ if(stream && msgno > 0L && msgno <= stream->nmsgs)
+ env = mail_fetchstructure(stream, msgno, body);
+
+ ps_global->dont_count_flagchanges = 0;
+ return(env);
+}
+
+
+/*
+ * Wrapper around mail_fetch_body.
+ * Currently only used to turn on partial fetching if trying
+ * to work around the microsoft ssl bug.
+ */
+char *
+pine_mail_fetch_body(MAILSTREAM *stream, long unsigned int msgno, char *section,
+ long unsigned int *len, long int flags)
+{
+#ifdef _WINDOWS
+ if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
+ return(pine_mail_partial_fetch_wrapper(stream, msgno,
+ section, len, flags, 0, NULL, 0));
+ else
+#endif
+ return(mail_fetch_body(stream, msgno, section, len, flags));
+}
+
+/*
+ * Wrapper around mail_fetch_text.
+ * Currently the only purpose this wrapper serves is to turn
+ * on partial fetching for quell-ssl-largeblocks.
+ */
+char *
+pine_mail_fetch_text(MAILSTREAM *stream, long unsigned int msgno, char *section,
+ long unsigned int *len, long int flags)
+{
+#ifdef _WINDOWS
+ if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
+ return(pine_mail_partial_fetch_wrapper(stream, msgno,
+ section, len, flags,
+ 0, NULL, 1));
+ else
+#endif
+ return(mail_fetch_text(stream, msgno, section, len, flags));
+}
+
+
+/*
+ * Determine whether to do partial-fetching or not, and do it
+ * args - same as c-client functions being wrapped around
+ * get_n_bytes - try to partial fetch for the first n bytes.
+ * makes no guarantees, might wind up fetching
+ * the entire text anyway.
+ * str_to_free - pointer to string to free if we only get
+ * (non-cachable) partial text (required for
+ * get_n_bytes).
+ * is_text_fetch -
+ * set, tells us to do mail_fetch_text and mail_partial_text
+ * unset, tells us to do mail_fetch_body and mail_partial_body
+ */
+char *
+pine_mail_partial_fetch_wrapper(MAILSTREAM *stream, long unsigned int msgno,
+ char *section, long unsigned int *len,
+ long int flags, long unsigned int get_n_bytes,
+ char **str_to_free, int is_text_fetch)
+{
+ BODY *body;
+ unsigned long size, firstbyte, lastbyte;
+ void *old_gets;
+ FETCH_READC_S *pftc;
+ char imap_cache_section[MAILTMPLEN];
+ SIZEDTEXT new_text;
+ MESSAGECACHE *mc;
+ char *(*fetch_full)(MAILSTREAM *, unsigned long, char *,
+ unsigned long *, long);
+ long (*fetch_partial)(MAILSTREAM *, unsigned long, char *,
+ unsigned long, unsigned long, long);
+
+ fetch_full = is_text_fetch ? mail_fetch_text : mail_fetch_body;
+ fetch_partial = is_text_fetch ? mail_partial_text : mail_partial_body;
+ if(str_to_free)
+ *str_to_free = NULL;
+#ifdef _WINDOWS
+ if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global) || get_n_bytes){
+#else
+ if(get_n_bytes){
+#endif /* _WINDOWS */
+ if(section && *section)
+ body = mail_body(stream, msgno, (unsigned char *) section);
+ else
+ pine_mail_fetch_structure(stream, msgno, &body, flags);
+ if(!body)
+ return NULL;
+ if(body->type != TYPEMULTIPART)
+ size = body->size.bytes;
+ else if((!section || !*section) && msgno > 0L
+ && stream && msgno <= stream->nmsgs
+ && (mc = mail_elt(stream, msgno)))
+ size = mc->rfc822_size; /* upper bound */
+ else /* just a guess, can't get actual size */
+ size = fcc_size_guess(body) + 2048;
+
+ /*
+ * imap_cache, originally intended for c-client internal use,
+ * takes a section argument that is different from one we
+ * would pass to mail_body. Typically in this function
+ * section is NULL, which translates to "TEXT", but in other
+ * cases we would want to append ".TEXT" to the section
+ */
+ if(is_text_fetch)
+ snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10,
+ section && *section ? section : "",
+ section && *section ? "." : "");
+ else
+ snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10,
+ section && *section ? section : "");
+
+ if(modern_imap_stream(stream)
+#ifdef _WINDOWS
+ && ((size > AVOID_MICROSOFT_SSL_CHUNKING_BUG)
+ || (get_n_bytes && size > get_n_bytes))
+#else
+ && (get_n_bytes && size > get_n_bytes)
+#endif /* _WINDOWS */
+ && !imap_cache(stream, msgno, imap_cache_section,
+ NULL, NULL)){
+ if(get_n_bytes == 0){
+ dprint((8,
+ "fetch_wrapper: doing partial fetching to work around microsoft bug\n"));
+ }
+ else{
+ dprint((8,
+ "fetch_wrapper: partial fetching due to %lu get_n_bytes\n", get_n_bytes));
+ }
+ pftc = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S));
+ memset(g_pft_desc = pftc, 0, sizeof(FETCH_READC_S));
+#ifdef _WINDOWS
+ if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)){
+ if(get_n_bytes)
+ pftc->chunksize = MIN(get_n_bytes,
+ AVOID_MICROSOFT_SSL_CHUNKING_BUG);
+ else
+ pftc->chunksize = AVOID_MICROSOFT_SSL_CHUNKING_BUG;
+ }
+ else
+#endif /* _WINDOWS */
+ pftc->chunksize = get_n_bytes;
+
+ pftc->chunk = (char *) fs_get((pftc->chunksize+1)
+ * sizeof(char));
+ pftc->cache = so_get(CharStar, NULL, EDIT_ACCESS);
+ pftc->read = 0L;
+ so_truncate(pftc->cache, size + 1);
+ old_gets = mail_parameters(stream, GET_GETS, (void *)NULL);
+ mail_parameters(stream, SET_GETS, (void *) partial_text_gets);
+ /* start fetching */
+ do{
+ firstbyte = pftc->read ;
+ lastbyte = firstbyte + pftc->chunksize;
+ if(get_n_bytes > firstbyte && get_n_bytes < lastbyte){
+ pftc->chunksize = get_n_bytes - firstbyte;
+ lastbyte = get_n_bytes;
+ }
+ (*fetch_partial)(stream, msgno, section, firstbyte,
+ pftc->chunksize, flags);
+
+ if(pftc->read != lastbyte)
+ break;
+ } while((pftc->read == lastbyte)
+ && (!get_n_bytes || (pftc->read < get_n_bytes)));
+ dprint((8,
+ "fetch_wrapper: anticipated size=%lu read=%lu\n",
+ size, pftc->read));
+ mail_parameters(stream, SET_GETS, old_gets);
+ new_text.size = pftc->read;
+ new_text.data = (unsigned char *)so_text(pftc->cache);
+
+ if(get_n_bytes && pftc->read == get_n_bytes){
+ /*
+ * don't write to cache if we're only dealing with
+ * partial text.
+ */
+ if(!str_to_free)
+ panic("Programmer botch: partial fetch attempt w/o string pointer");
+ else
+ *str_to_free = (char *) new_text.data;
+ }
+ else {
+ /* ugh, rewrite string in case it got stomped on last call */
+ if(is_text_fetch)
+ snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10,
+ section && *section ? section : "",
+ section && *section ? "." : "");
+ else
+ snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10,
+ section && *section ? section : "");
+
+ imap_cache(stream, msgno, imap_cache_section, NULL, &new_text);
+ }
+
+ pftc->cache->txt = (void *)NULL;
+ so_give(&pftc->cache);
+ fs_give((void **)&pftc->chunk);
+ fs_give((void **)&pftc);
+ g_pft_desc = NULL;
+ if(len)
+ *len = new_text.size;
+ return ((char *)new_text.data);
+ }
+ else
+ return((*fetch_full)(stream, msgno, section, len, flags));
+ }
+ else
+ return((*fetch_full)(stream, msgno, section, len, flags));
+}
+
+/*
+ * c-client callback that handles getting the text
+ */
+char *
+partial_text_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md)
+{
+ unsigned long n;
+
+ n = MIN(g_pft_desc->chunksize, size);
+ g_pft_desc->read +=n;
+
+ (*f) (stream, n, g_pft_desc->chunk);
+
+ if(g_pft_desc->cache)
+ so_nputs(g_pft_desc->cache, g_pft_desc->chunk, (long) n);
+
+
+ return(NULL);
+}
+
+
+/*
+ * Pings the stream. Returns 0 if the stream is dead, non-zero otherwise.
+ */
+long
+pine_mail_ping(MAILSTREAM *stream)
+{
+ time_t now;
+ long ret = 0L;
+
+ if(!sp_dead_stream(stream)){
+ ret = mail_ping(stream);
+ if(ret && sp_dead_stream(stream))
+ ret = 0L;
+ }
+
+ if(ret){
+ now = time(0);
+ sp_set_last_ping(stream, now);
+ sp_set_last_expunged_reaper(stream, now);
+ }
+
+ return(ret);
+}
+
+
+void
+pine_mail_check(MAILSTREAM *stream)
+{
+ reset_check_point(stream);
+ mail_check(stream);
+}
+
+
+/*
+ * Unlike mail_list, this version returns a value. The returned value is
+ * TRUE if the stream is opened ok, and FALSE if we failed opening the
+ * stream. This allows us to differentiate between a LIST which returns
+ * no matches and a failure opening the stream. We do this by pre-opening
+ * the stream ourselves.
+ */
+int
+pine_mail_list(MAILSTREAM *stream, char *ref, char *pat, unsigned int *options)
+{
+ int we_open = 0;
+ char *halfopen_target;
+ char source[MAILTMPLEN], *target = NULL;
+ MAILSTREAM *ourstream = NULL;
+
+ dprint((7, "pine_mail_list: ref=%s pat=%s%s\n",
+ ref ? ref : "(NULL)",
+ pat ? pat : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+
+ if((!ref && check_for_move_mbox(pat, source, sizeof(source), &target))
+ ||
+ check_for_move_mbox(ref, source, sizeof(source), &target)){
+ ref = NIL;
+ pat = target;
+ if(options)
+ *options |= PML_IS_MOVE_MBOX;
+
+ dprint((7,
+ "pine_mail_list: #move special case, listing \"%s\"%s\n",
+ pat ? pat : "(NULL)",
+ stream ? "" : " (stream was NULL)"));
+ }
+
+ if(!stream && ((pat && *pat == '{') || (ref && *ref == '{'))){
+ we_open++;
+ if(pat && *pat == '{'){
+ ref = NIL;
+ halfopen_target = pat;
+ }
+ else
+ halfopen_target = ref;
+ }
+
+ if(we_open){
+ long flags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
+
+ stream = sp_stream_get(halfopen_target, SP_MATCH);
+ if(!stream)
+ stream = sp_stream_get(halfopen_target, SP_SAME);
+
+ if(!stream){
+ DRIVER *d;
+
+ /*
+ * POP is a special case. We don't need to have a stream
+ * to call mail_list for a POP name. The else part is the
+ * POP part.
+ */
+ if((d = mail_valid(NIL, halfopen_target, (char *) NIL))
+ && (d->flags & DR_HALFOPEN)){
+ stream = pine_mail_open(NIL, halfopen_target, flags, NULL);
+ ourstream = stream;
+ if(!stream)
+ return(FALSE);
+ }
+ else
+ stream = NULL;
+ }
+
+ mail_list_internal(stream, ref, pat);
+ }
+ else
+ mail_list_internal(stream, ref, pat);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return(TRUE);
+}
+
+
+/*
+ * mail_list_internal -- A monument to software religion and those who
+ * would force it upon us.
+ */
+void
+mail_list_internal(MAILSTREAM *s, char *r, char *p)
+{
+ if(F_ON(F_FIX_BROKEN_LIST, ps_global)
+ && ((s && s->mailbox && *s->mailbox == '{')
+ || (!s && ((r && *r == '{') || (p && *p == '{'))))){
+ char tmp[2*MAILTMPLEN];
+
+ snprintf(tmp, sizeof(tmp), "%.*s%.*s", sizeof(tmp)/2-1, r ? r : "",
+ sizeof(tmp)/2-1, p);
+ mail_list(s, "", tmp);
+ }
+ else
+ mail_list(s, r, p);
+}
+
+
+long
+pine_mail_status(MAILSTREAM *stream, char *mailbox, long int flags)
+{
+ return(pine_mail_status_full(stream, mailbox, flags, NULL, NULL));
+}
+
+
+long
+pine_mail_status_full(MAILSTREAM *stream, char *mailbox, long int flags,
+ imapuid_t *uidvalidity, imapuid_t *uidnext)
+{
+ char source[MAILTMPLEN], *target = NULL;
+ long ret = NIL;
+ MAILSTATUS cache, status;
+ MAILSTREAM *ourstream = NULL;
+
+ if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
+ memset(&status, 0, sizeof(status));
+ memset(&cache, 0, sizeof(cache));
+
+ /* tell mm_status() to write partial return here */
+ pine_cached_status = &status;
+
+ flags |= (SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES);
+
+ /* do status of destination */
+
+ stream = sp_stream_get(target, SP_SAME);
+
+ /* should never be news, don't worry about mulnewrsc flag*/
+ if((ret = pine_mail_status_full(stream, target, flags, uidvalidity,
+ uidnext))
+ && !status.recent){
+
+ /* do status of source */
+ pine_cached_status = &cache;
+ stream = sp_stream_get(source, SP_SAME);
+
+ if(!stream){
+ DRIVER *d;
+
+ if((d = mail_valid (NIL, source, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+ long openflags =OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE;
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+
+ stream = pine_mail_open(NULL, source, openflags, NULL);
+ ourstream = stream;
+ }
+ else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)
+ && d && (!strucmp(d->name, "news")
+ || !strucmp(d->name, "nntp")))
+ flags |= SA_MULNEWSRC;
+
+ }
+
+ if(!ps_global->user_says_cancel && mail_status(stream, source, flags)){
+ DRIVER *d;
+ int is_news = 0;
+
+ /*
+ * If the target has recent messages, then we don't come
+ * through here. We just use the answer from the target.
+ *
+ * If not, then we leave the target results in "status" and
+ * run a mail_status on the source that puts its results
+ * in "cache". Combine the results from cache with the
+ * results that were already in status.
+ *
+ * We count all messages in the source mailbox as recent and
+ * unseen, even if they are not recent or unseen in the source,
+ * because they will be recent and unseen in the target
+ * when we open it. (Not quite true. It is possible that some
+ * messages from a POP server will end up seen instead
+ * of unseen.
+ * It is also possible that it is correct. If we add unseen, or
+ * if we add messages, we could get it wrong. As far as I
+ * can tell, Pine doesn't ever even use status.unseen, so it
+ * is currently academic anyway.) Hubert 2003-03-05
+ * (Does now 2004-06-02 in next_folder.)
+ *
+ * However, we don't want to count all messages as recent if
+ * the source mailbox is NNTP or NEWS, because we won't be
+ * deleting those messages from the source.
+ * We only count recent.
+ *
+ * There are other cases that are trouble. One in particular
+ * is an IMAP-to-NNTP proxy, where the messages can't be removed
+ * from the mailbox but they can be deleted. If we count
+ * messages in the source as being recent and it turns out they
+ * were all deleted already, then we incorrectly say the folder
+ * has recent messages when it doesn't. We can recover from that
+ * case at some cost by actually opening the folder the first
+ * time if there are not recents, and then checking to see if
+ * everything is deleted. Subsequently, we store the uid values
+ * (which are returned by status) so that we can know if the
+ * mailbox changed or not. The problem being solved is that
+ * the TAB command indicates new messages in a folder when there
+ * really aren't any. An alternative would be to use the is_news
+ * half of the if-else in all cases. A problem with that is
+ * that there could be non-recent messages sitting in the
+ * source mailbox that we never discover. Hubert 2003-03-28
+ */
+
+ if((d = mail_valid (NIL, source, (char *) NIL))
+ && (!strcmp(d->name, "nntp") || !strcmp(d->name, "news")))
+ is_news++;
+
+ if(is_news && cache.flags & SA_RECENT){
+ status.messages += cache.recent;
+ status.recent += cache.recent;
+ status.unseen += cache.recent;
+ status.uidnext += cache.recent;
+ }
+ else{
+ if(uidvalidity && *uidvalidity
+ && uidnext && *uidnext
+ && cache.flags & SA_UIDVALIDITY
+ && cache.uidvalidity == *uidvalidity
+ && cache.flags & SA_UIDNEXT
+ && cache.uidnext == *uidnext){
+ ; /* nothing changed in source mailbox */
+ }
+ else if(cache.flags & SA_RECENT && cache.recent){
+ status.messages += cache.recent;
+ status.recent += cache.recent;
+ status.unseen += cache.recent;
+ status.uidnext += cache.recent;
+ }
+ else if(!(cache.flags & SA_MESSAGES) || cache.messages){
+ long openflags = OP_SILENT | SP_USEPOOL | SP_TEMPUSE;
+ long delete_count, not_deleted = 0L;
+
+ /* Actually open it up and check */
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+
+ if(!ourstream)
+ stream = NULL;
+
+ if(ourstream
+ && !same_stream_and_mailbox(source, ourstream)){
+ pine_mail_close(ourstream);
+ ourstream = stream = NULL;
+ }
+
+ if(!stream){
+ stream = pine_mail_open(stream, source, openflags,
+ NULL);
+ ourstream = stream;
+ }
+
+ if(stream){
+ delete_count = count_flagged(stream, F_DEL);
+ not_deleted = stream->nmsgs - delete_count;
+ }
+
+ status.messages += not_deleted;
+ status.recent += not_deleted;
+ status.unseen += not_deleted;
+ status.uidnext += not_deleted;
+ }
+
+ if(uidvalidity && cache.flags & SA_UIDVALIDITY)
+ *uidvalidity = cache.uidvalidity;
+
+ if(uidnext && cache.flags & SA_UIDNEXT)
+ *uidnext = cache.uidnext;
+ }
+ }
+ }
+
+ /*
+ * Do the regular mm_status callback which we've been intercepting
+ * into different locations above.
+ */
+ pine_cached_status = NIL;
+ if(ret)
+ mm_status(NULL, mailbox, &status);
+ }
+ else{
+ if(!stream){
+ DRIVER *d;
+
+ if((d = mail_valid (NIL, mailbox, (char *) NIL))
+ && !strcmp(d->name, "imap")){
+ long openflags = OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE;
+
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ openflags |= OP_TRYALT;
+
+ /*
+ * We just use this to find the answer.
+ * We're asking for trouble if we do a STATUS on a
+ * selected mailbox. I don't believe this happens in pine.
+ * It does now (2004-06-02) in next_folder if the
+ * F_TAB_USES_UNSEEN option is set and the folder was
+ * already opened.
+ */
+ stream = sp_stream_get(mailbox, SP_MATCH);
+ if(stream){
+ memset(&status, 0, sizeof(status));
+ if(flags & SA_MESSAGES){
+ status.flags |= SA_MESSAGES;
+ status.messages = stream->nmsgs;
+ }
+
+ if(flags & SA_RECENT){
+ status.flags |= SA_RECENT;
+ status.recent = stream->recent;
+ }
+
+ if(flags & SA_UNSEEN){
+ long i;
+ SEARCHPGM *srchpgm;
+ MESSAGECACHE *mc;
+
+ srchpgm = mail_newsearchpgm();
+ srchpgm->unseen = 1;
+
+ pine_mail_search_full(stream, NULL, srchpgm,
+ SE_NOPREFETCH | SE_FREE);
+ status.flags |= SA_UNSEEN;
+ status.unseen = 0L;
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) && mc->searched)
+ status.unseen++;
+ }
+
+ if(flags & SA_UIDVALIDITY){
+ status.flags |= SA_UIDVALIDITY;
+ status.uidvalidity = stream->uid_validity;
+ }
+
+ if(flags & SA_UIDNEXT){
+ status.flags |= SA_UIDNEXT;
+ status.uidnext = stream->uid_last + 1L;
+ }
+
+ mm_status(NULL, mailbox, &status);
+ return T; /* that's what c-client returns when success */
+ }
+
+ if(!stream)
+ stream = sp_stream_get(mailbox, SP_SAME);
+
+ if(!stream){
+ stream = pine_mail_open(NULL, mailbox, openflags, NULL);
+ ourstream = stream;
+ }
+ }
+ else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)
+ && d && (!strucmp(d->name, "news")
+ || !strucmp(d->name, "nntp")))
+ flags |= SA_MULNEWSRC;
+ }
+
+ if(!ps_global->user_says_cancel)
+ ret = mail_status(stream, mailbox, flags); /* non #move case */
+ }
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+
+ return ret;
+}
+
+
+/*
+ * Check for a mailbox name that is a legitimate #move mailbox.
+ *
+ * Args mbox -- The mailbox name to check
+ * sourcebuf -- Copy the source mailbox name into this buffer
+ * sbuflen -- Length of sourcebuf
+ * targetptr -- Set the pointer this points to to point to the
+ * target mailbox name in the original string
+ *
+ * Returns 1 - is a #move mailbox
+ * 0 - not
+ */
+int
+check_for_move_mbox(char *mbox, char *sourcebuf, size_t sbuflen, char **targetptr)
+{
+ char delim, *s;
+ int i;
+
+ if(mbox && (mbox[0] == '#')
+ && ((mbox[1] == 'M') || (mbox[1] == 'm'))
+ && ((mbox[2] == 'O') || (mbox[2] == 'o'))
+ && ((mbox[3] == 'V') || (mbox[3] == 'v'))
+ && ((mbox[4] == 'E') || (mbox[4] == 'e'))
+ && (delim = mbox[5])
+ && (s = strchr(mbox+6, delim))
+ && (i = s++ - (mbox + 6))
+ && (!sourcebuf || i < sbuflen)){
+
+ if(sourcebuf){
+ strncpy(sourcebuf, mbox+6, i); /* copy source mailbox name */
+ sourcebuf[i] = '\0';
+ }
+
+ if(targetptr)
+ *targetptr = s;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Checks through stream cache for a stream pointer already open to
+ * this mailbox, read/write. Very similar to sp_stream_get, but we want
+ * to look at all streams, not just imap streams.
+ * Right now it is very specialized. If we want to use it more generally,
+ * generalize it or combine it with sp_stream_get somehow.
+ */
+MAILSTREAM *
+already_open_stream(char *mailbox, int flags)
+{
+ int i;
+ MAILSTREAM *m;
+
+ if(!mailbox)
+ return(NULL);
+
+ if(*mailbox == '{'){
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && !(flags & AOS_RW_ONLY && m->rdonly)
+ && (*m->mailbox == '{') && !sp_dead_stream(m)
+ && same_stream_and_mailbox(mailbox, m))
+ return(m);
+ }
+ }
+ else{
+ char *cn, tmp[MAILTMPLEN];
+
+ cn = mailboxfile(tmp, mailbox);
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && !(flags & AOS_RW_ONLY && m->rdonly)
+ && (*m->mailbox != '{') && !sp_dead_stream(m)
+ && ((cn && *cn && !strcmp(cn, m->mailbox))
+ || !strcmp(mailbox, m->original_mailbox)
+ || !strcmp(mailbox, m->mailbox)))
+ return(m);
+ }
+ }
+
+ return(NULL);
+}
+
+
+void
+pine_imap_cmd_happened(MAILSTREAM *stream, char *cmd, long int flags)
+{
+ dprint((9, "imap_cmd(%s, %s, 0x%lx)\n",
+ STREAMNAME(stream), cmd ? cmd : "?", flags));
+
+ if(cmd && !strucmp(cmd, "CHECK"))
+ reset_check_point(stream);
+
+ if(is_imap_stream(stream)){
+ time_t now;
+
+ now = time(0);
+ sp_set_last_ping(stream, now);
+ sp_set_last_activity(stream, now);
+ if(!(flags & SC_EXPUNGEDEFERRED))
+ sp_set_last_expunged_reaper(stream, now);
+ }
+}
+
+
+/*
+ * Tells us whether we ought to check for a dead stream or not.
+ * We assume that we ought to check if it is not IMAP and if it is IMAP we
+ * don't need to check if the last activity was within the last 5 minutes.
+ */
+int
+recent_activity(MAILSTREAM *stream)
+{
+ if(is_imap_stream(stream) && !sp_dead_stream(stream)
+ && (time(0) - sp_last_activity(stream) < 5L * 60L))
+ return 1;
+ else
+ return 0;
+}
+
+void
+sp_cleanup_dead_streams(void)
+{
+ int i;
+ MAILSTREAM *m;
+
+ (void) streams_died(); /* tell user in case they don't know yet */
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_dead_stream(m))
+ pine_mail_close(m);
+ }
+}
+
+
+/*
+ * Returns 0 if stream flags not set, non-zero if they are.
+ */
+int
+sp_flagged(MAILSTREAM *stream, long unsigned int flags)
+{
+ return(sp_flags(stream) & flags);
+}
+
+
+void
+sp_set_fldr(MAILSTREAM *stream, char *folder)
+{
+ PER_STREAM_S **pss;
+
+ pss = sp_data(stream);
+ if(pss && *pss){
+ if((*pss)->fldr)
+ fs_give((void **) &(*pss)->fldr);
+
+ if(folder)
+ (*pss)->fldr = cpystr(folder);
+ }
+}
+
+
+void
+sp_set_saved_cur_msg_id(MAILSTREAM *stream, char *id)
+{
+ PER_STREAM_S **pss;
+
+ pss = sp_data(stream);
+ if(pss && *pss){
+ if((*pss)->saved_cur_msg_id)
+ fs_give((void **) &(*pss)->saved_cur_msg_id);
+
+ if(id)
+ (*pss)->saved_cur_msg_id = cpystr(id);
+ }
+}
+
+
+/*
+ * Sets flags absolutely, erasing old flags.
+ */
+void
+sp_flag(MAILSTREAM *stream, long unsigned int flags)
+{
+ if(!stream)
+ return;
+
+ dprint((9, "sp_flag(%s, 0x%x): %s%s%s%s%s%s%s%s\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?",
+ flags,
+ flags ? "set" : "clear",
+ (flags & SP_LOCKED) ? " SP_LOCKED" : "",
+ (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
+ (flags & SP_INBOX) ? " SP_INBOX" : "",
+ (flags & SP_USERFLDR) ? " SP_USERFLDR" : "",
+ (flags & SP_USEPOOL) ? " SP_USEPOOL" : "",
+ (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "",
+ !flags ? " ALL" : ""));
+
+ sp_set_flags(stream, flags);
+}
+
+
+/*
+ * Clear individual stream flags.
+ */
+void
+sp_unflag(MAILSTREAM *stream, long unsigned int flags)
+{
+ if(!stream || !flags)
+ return;
+
+ dprint((9, "sp_unflag(%s, 0x%x): unset%s%s%s%s%s%s\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?",
+ flags,
+ (flags & SP_LOCKED) ? " SP_LOCKED" : "",
+ (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
+ (flags & SP_INBOX) ? " SP_INBOX" : "",
+ (flags & SP_USERFLDR) ? " SP_USERFLDR" : "",
+ (flags & SP_USEPOOL) ? " SP_USEPOOL" : "",
+ (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : ""));
+
+ sp_set_flags(stream, sp_flags(stream) & ~flags);
+
+ flags = sp_flags(stream);
+ dprint((9, "sp_unflag(%s, 0x%x): result:%s%s%s%s%s%s\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?",
+ flags,
+ (flags & SP_LOCKED) ? " SP_LOCKED" : "",
+ (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
+ (flags & SP_INBOX) ? " SP_INBOX" : "",
+ (flags & SP_USERFLDR) ? " SP_USERFLDR" : "",
+ (flags & SP_USEPOOL) ? " SP_USEPOOL" : "",
+ (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : ""));
+}
+
+
+/*
+ * Set dead stream indicator and close if not locked.
+ */
+void
+sp_mark_stream_dead(MAILSTREAM *stream)
+{
+ if(!stream)
+ return;
+
+ dprint((9, "sp_mark_stream_dead(%s)\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?"));
+
+ /*
+ * If the stream isn't locked, it is no longer useful. Get rid of it.
+ */
+ if(!sp_flagged(stream, SP_LOCKED))
+ pine_mail_actually_close(stream);
+ else{
+ /*
+ * If it is locked, then we have to worry about references to it
+ * that still exist. For example, it might be a permlocked stream
+ * or it might be the current stream. We need to let it be discovered
+ * by those referencers instead of just eliminating it, so that they
+ * can clean up the mess they need to clean up.
+ */
+ sp_set_dead_stream(stream, 1);
+ }
+}
+
+
+/*
+ * Returns the number of streams in the stream pool which are
+ * SP_USEPOOL but not SP_PERMLOCKED.
+ */
+int
+sp_nusepool_notperm(void)
+{
+ int i, cnt = 0;
+ MAILSTREAM *m;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_PERMLOCKED))
+ cnt++;
+ }
+
+ return(cnt);
+}
+
+
+/*
+ * Returns the number of folders that the user has marked to be PERMLOCKED
+ * folders (plus INBOX) that are remote IMAP folders.
+ *
+ * This routine depends on the fact that VAR_INBOX_PATH, VAR_PERMLOCKED,
+ * and the ps_global->context_list are correctly set.
+ */
+int
+sp_nremote_permlocked(void)
+{
+ int cnt = 0;
+ char **lock_these, *p = NULL, *dummy = NULL, *lt;
+ DRIVER *d;
+
+ /* first check if INBOX is remote */
+ lt = ps_global->VAR_INBOX_PATH;
+ if(lt && (d=mail_valid(NIL, lt, (char *) NIL))
+ && !strcmp(d->name, "imap"))
+ cnt++;
+
+ /* then count the user-specified permlocked folders */
+ for(lock_these = ps_global->VAR_PERMLOCKED; lock_these && *lock_these;
+ lock_these++){
+
+ /*
+ * Skip inbox, already done above. Should do this better so that we
+ * catch the case where the user puts the technical spec of the inbox
+ * in the list, or where the user lists one folder twice.
+ */
+ if(*lock_these && !strucmp(*lock_these, ps_global->inbox_name))
+ continue;
+
+ /* there isn't really a pair, it just dequotes for us */
+ get_pair(*lock_these, &dummy, &p, 0, 0);
+
+ /*
+ * Check to see if this is an incoming nickname and replace it
+ * with the full name.
+ */
+ if(!(p && ps_global->context_list
+ && ps_global->context_list->use & CNTXT_INCMNG
+ && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list),
+ FN_WHOLE_NAME))))
+ lt = p;
+
+ if(dummy)
+ fs_give((void **) &dummy);
+
+ if(lt && (d=mail_valid(NIL, lt, (char *) NIL))
+ && !strcmp(d->name, "imap"))
+ cnt++;
+
+ if(p)
+ fs_give((void **) &p);
+ }
+
+ return(cnt);
+}
+
+
+/*
+ * Look for an already open stream that can be used for a new purpose.
+ * (Note that we only look through streams flagged SP_USEPOOL.)
+ *
+ * Args: mailbox
+ * flags
+ *
+ * Flags is a set of values or'd together which tells us what the request
+ * is looking for.
+ *
+ * Returns: a live stream from the stream pool or NULL.
+ */
+MAILSTREAM *
+sp_stream_get(char *mailbox, long unsigned int flags)
+{
+ int i;
+ MAILSTREAM *m;
+
+ dprint((7, "sp_stream_get(%s):%s%s%s%s%s\n",
+ mailbox ? mailbox : "?",
+ (flags & SP_MATCH) ? " SP_MATCH" : "",
+ (flags & SP_RO_OK) ? " SP_RO_OK" : "",
+ (flags & SP_SAME) ? " SP_SAME" : "",
+ (flags & SP_UNLOCKED) ? " SP_UNLOCKED" : "",
+ (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : ""));
+
+ /* look for stream already open to this mailbox */
+ if(flags & SP_MATCH){
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL)
+ && (!m->rdonly || (flags & SP_RO_OK)) && !sp_dead_stream(m)
+ && same_stream_and_mailbox(mailbox, m)){
+ if((sp_flagged(m, SP_LOCKED) && recent_activity(m))
+ || pine_mail_ping(m)){
+ dprint((7,
+ "sp_stream_get: found exact match, slot %d\n", i));
+ if(!sp_flagged(m, SP_LOCKED)){
+ dprint((7,
+ "reset idle timer1: next TAG %08lx (%s)\n",
+ m->gensym,
+ debug_time(1, ps_global->debug_timestamp)));
+ sp_set_last_use_time(m, time(0));
+ }
+
+ return(m);
+ }
+
+ sp_mark_stream_dead(m);
+ }
+ }
+ }
+
+ /*
+ * SP_SAME will not match if an SP_MATCH match would have worked.
+ * If the caller is interested in SP_MATCH streams as well as SP_SAME
+ * streams then the caller should make two separate calls to this
+ * routine.
+ */
+ if(flags & SP_SAME){
+ /*
+ * If the flags arg does not have either SP_TEMPUSE or SP_UNLOCKED
+ * set, then we'll accept any stream, even if locked.
+ * We want to prefer the LOCKED case so that we don't have to ping.
+ */
+ if(!(flags & SP_UNLOCKED) && !(flags & SP_TEMPUSE)){
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL)
+ && sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
+ && same_stream(mailbox, m)
+ && !same_stream_and_mailbox(mailbox, m)){
+ if(recent_activity(m) || pine_mail_ping(m)){
+ dprint((7,
+ "sp_stream_get: found SAME match, slot %d\n", i));
+ return(m);
+ }
+
+ sp_mark_stream_dead(m);
+ }
+ }
+
+ /* consider the unlocked streams */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL)
+ && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
+ && same_stream(mailbox, m)
+ && !same_stream_and_mailbox(mailbox, m)){
+ /* always ping unlocked streams */
+ if(pine_mail_ping(m)){
+ dprint((7,
+ "sp_stream_get: found SAME match, slot %d\n", i));
+ dprint((7,
+ "reset idle timer4: next TAG %08lx (%s)\n",
+ m->gensym,
+ debug_time(1, ps_global->debug_timestamp)));
+ sp_set_last_use_time(m, time(0));
+
+ return(m);
+ }
+
+ sp_mark_stream_dead(m);
+ }
+ }
+ }
+
+ /*
+ * Prefer streams marked SP_TEMPUSE and not LOCKED.
+ * If SP_TEMPUSE is set in the flags arg then this is the
+ * only loop we try.
+ */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE)
+ && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
+ && same_stream(mailbox, m)
+ && !same_stream_and_mailbox(mailbox, m)){
+ if(pine_mail_ping(m)){
+ dprint((7,
+ "sp_stream_get: found SAME/TEMPUSE match, slot %d\n", i));
+ dprint((7,
+ "reset idle timer2: next TAG %08lx (%s)\n",
+ m->gensym,
+ debug_time(1, ps_global->debug_timestamp)));
+ sp_set_last_use_time(m, time(0));
+ return(m);
+ }
+
+ sp_mark_stream_dead(m);
+ }
+ }
+
+ /*
+ * If SP_TEMPUSE is not set in the flags arg but SP_UNLOCKED is,
+ * then we will consider
+ * streams which are not marked SP_TEMPUSE (but are still not
+ * locked). We go through these in reverse order so that we'll get
+ * the last one added instead of the first one. It's not clear if
+ * that is a good idea or if a more complex search would somehow
+ * be better. Maybe we should use a round-robin sort of search
+ * here so that we don't leave behind unused streams. Or maybe
+ * we should keep track of when we used it and look for the LRU stream.
+ */
+ if(!(flags & SP_TEMPUSE)){
+ for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL)
+ && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
+ && same_stream(mailbox, m)
+ && !same_stream_and_mailbox(mailbox, m)){
+ if(pine_mail_ping(m)){
+ dprint((7,
+ "sp_stream_get: found SAME/UNLOCKED match, slot %d\n", i));
+ dprint((7,
+ "reset idle timer3: next TAG %08lx (%s)\n",
+ m->gensym,
+ debug_time(1, ps_global->debug_timestamp)));
+ sp_set_last_use_time(m, time(0));
+ return(m);
+ }
+
+ sp_mark_stream_dead(m);
+ }
+ }
+ }
+ }
+
+ /*
+ * If we can't find a useful stream to use in pine_mail_open, we may
+ * want to re-use one that is not actively being used even though it
+ * is not on the same server. We'll have to close it and then re-use
+ * the slot.
+ */
+ if(!(flags & (SP_SAME | SP_MATCH))){
+ /*
+ * Prefer streams marked SP_TEMPUSE and not LOCKED.
+ * If SP_TEMPUSE is set in the flags arg then this is the
+ * only loop we try.
+ */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE)
+ && !sp_flagged(m, SP_LOCKED)){
+ dprint((7,
+ "sp_stream_get: found Not-SAME/TEMPUSE match, slot %d\n", i));
+ /*
+ * We ping it in case there is new mail that we should
+ * pass through our filters. Pine_mail_actually_close will
+ * do that.
+ */
+ (void) pine_mail_ping(m);
+ return(m);
+ }
+ }
+
+ /*
+ * If SP_TEMPUSE is not set in the flags arg, then we will consider
+ * streams which are not marked SP_TEMPUSE (but are still not
+ * locked). Maybe we should use a round-robin sort of search
+ * here so that we don't leave behind unused streams. Or maybe
+ * we should keep track of when we used it and look for the LRU stream.
+ */
+ if(!(flags & SP_TEMPUSE)){
+ for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED)){
+ dprint((7,
+ "sp_stream_get: found Not-SAME/UNLOCKED match, slot %d\n", i));
+ /*
+ * We ping it in case there is new mail that we should
+ * pass through our filters. Pine_mail_actually_close will
+ * do that.
+ */
+ (void) pine_mail_ping(m);
+ return(m);
+ }
+ }
+ }
+ }
+
+ dprint((7, "sp_stream_get: no match found\n"));
+
+ return(NULL);
+}
+
+
+void
+sp_end(void)
+{
+ int i;
+ MAILSTREAM *m;
+
+ dprint((7, "sp_end\n"));
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m)
+ pine_mail_actually_close(m);
+ }
+
+ if(ps_global->s_pool.streams)
+ fs_give((void **) &ps_global->s_pool.streams);
+
+ ps_global->s_pool.nstream = 0;
+}
+
+
+/*
+ * Find a vacant slot to put this new stream in.
+ * We are willing to close and kick out another stream as long as it isn't
+ * LOCKED. However, we may find that there is no place to put this one
+ * because all the slots are used and locked. For now, we'll return -1
+ * in that case and leave the new stream out of the pool.
+ */
+int
+sp_add(MAILSTREAM *stream, int usepool)
+{
+ int i, slot = -1;
+ MAILSTREAM *m;
+
+ dprint((7, "sp_add(%s, %d)\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?", usepool));
+
+ if(!stream){
+ dprint((7, "sp_add: NULL stream\n"));
+ return -1;
+ }
+
+ /* If this stream is already there, don't add it again */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m == stream){
+ slot = i;
+ dprint((7,
+ "sp_add: stream was already in slot %d\n", slot));
+ return 0;
+ }
+ }
+
+ if(usepool && !sp_flagged(stream, SP_PERMLOCKED)
+ && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){
+ dprint((7,
+ "sp_add: reached max implicit SP_USEPOOL of %d\n",
+ ps_global->s_pool.max_remstream));
+ return -1;
+ }
+
+ /* Look for an unused slot */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(!m){
+ slot = i;
+ dprint((7,
+ "sp_add: using empty slot %d\n", slot));
+ break;
+ }
+ }
+
+ /* else, allocate more space */
+ if(slot < 0){
+ ps_global->s_pool.nstream++;
+ slot = ps_global->s_pool.nstream - 1;
+ if(ps_global->s_pool.streams){
+ fs_resize((void **) &ps_global->s_pool.streams,
+ ps_global->s_pool.nstream *
+ sizeof(*ps_global->s_pool.streams));
+ ps_global->s_pool.streams[slot] = NULL;
+ }
+ else{
+ ps_global->s_pool.streams =
+ (MAILSTREAM **) fs_get(ps_global->s_pool.nstream *
+ sizeof(*ps_global->s_pool.streams));
+ memset(ps_global->s_pool.streams, 0,
+ ps_global->s_pool.nstream *
+ sizeof(*ps_global->s_pool.streams));
+ }
+
+ dprint((7,
+ "sp_add: allocate more space, using new slot %d\n", slot));
+ }
+
+ if(slot >= 0 && slot < ps_global->s_pool.nstream){
+ ps_global->s_pool.streams[slot] = stream;
+ return 0;
+ }
+ else{
+ dprint((7, "sp_add: failed to find a slot!\n"));
+ return -1;
+ }
+}
+
+
+/*
+ * Simply remove this stream from the stream pool.
+ */
+void
+sp_delete(MAILSTREAM *stream)
+{
+ int i;
+ MAILSTREAM *m;
+
+ if(!stream)
+ return;
+
+ dprint((7, "sp_delete(%s)\n",
+ (stream && stream->mailbox) ? stream->mailbox : "?"));
+
+ /*
+ * There are some global stream pointers that we have to worry
+ * about before deleting the stream.
+ */
+
+ /* first, mail_stream is the global currently open folder */
+ if(ps_global->mail_stream == stream)
+ ps_global->mail_stream = NULL;
+
+ /* remote address books may have open stream pointers */
+ note_closed_adrbk_stream(stream);
+
+ if(pith_opt_closing_stream)
+ (*pith_opt_closing_stream)(stream);
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m == stream){
+ ps_global->s_pool.streams[i] = NULL;
+ dprint((7,
+ "sp_delete: stream removed from slot %d\n", i));
+ return;
+ }
+ }
+}
+
+
+/*
+ * Returns 1 if any locked userfldr is dead, 0 if all alive.
+ */
+int
+sp_a_locked_stream_is_dead(void)
+{
+ int i, ret = 0;
+ MAILSTREAM *m;
+
+ for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && sp_dead_stream(m))
+ ret++;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns 1 if any locked stream is changed, 0 otherwise
+ */
+int
+sp_a_locked_stream_changed(void)
+{
+ int i, ret = 0;
+ MAILSTREAM *m;
+
+ for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && sp_mail_box_changed(m))
+ ret++;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns the inbox stream or NULL.
+ */
+MAILSTREAM *
+sp_inbox_stream(void)
+{
+ int i;
+ MAILSTREAM *m, *ret = NULL;
+
+ for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_INBOX))
+ ret = m;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Make sure that the sp_data per-stream data storage area exists.
+ *
+ * Returns a handle to the sp_data data unless stream is NULL,
+ * in which case NULL is returned
+ */
+PER_STREAM_S **
+sp_data(MAILSTREAM *stream)
+{
+ PER_STREAM_S **pss = NULL;
+
+ if(stream){
+ if(*(pss = (PER_STREAM_S **) &stream->sparep) == NULL){
+ *pss = (PER_STREAM_S *) fs_get(sizeof(PER_STREAM_S));
+ memset(*pss, 0, sizeof(PER_STREAM_S));
+ reset_check_point(stream);
+ }
+ }
+
+ return(pss);
+}
+
+
+/*
+ * Returns a pointer to the msgmap associated with the argument stream.
+ *
+ * If the PER_STREAM_S data or the msgmap does not already exist, it will be
+ * created.
+ */
+MSGNO_S *
+sp_msgmap(MAILSTREAM *stream)
+{
+ MSGNO_S **msgmap = NULL;
+ PER_STREAM_S **pss = NULL;
+
+ pss = sp_data(stream);
+
+ if(pss && *pss
+ && (*(msgmap = (MSGNO_S **) &(*pss)->msgmap) == NULL))
+ mn_init(msgmap, stream->nmsgs);
+
+ return(msgmap ? *msgmap : NULL);
+}
+
+
+void
+sp_free_callback(void **sparep)
+{
+ PER_STREAM_S **pss;
+ MAILSTREAM *stream = NULL, *m;
+ int i;
+
+ pss = (PER_STREAM_S **) sparep;
+
+ if(pss && *pss){
+ /*
+ * It is possible that this has been called from c-client when
+ * we weren't expecting it. We need to clean up the stream pool
+ * entries if the stream that goes with this pointer is in the
+ * stream pool somewhere.
+ */
+ for(i = 0; !stream && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(sparep && *sparep && m && m->sparep == *sparep)
+ stream = m;
+ }
+
+ if(stream){
+ if(ps_global->mail_stream == stream)
+ ps_global->mail_stream = NULL;
+
+ sp_delete(stream);
+ }
+
+ sp_free(pss);
+ }
+}
+
+
+/*
+ * Free the data but don't mess with the stream pool.
+ */
+void
+sp_free(PER_STREAM_S **pss)
+{
+ if(pss && *pss){
+ if((*pss)->msgmap){
+ if(ps_global->msgmap == (*pss)->msgmap)
+ ps_global->msgmap = NULL;
+
+ mn_give(&(*pss)->msgmap);
+ }
+
+ if((*pss)->fldr)
+ fs_give((void **) &(*pss)->fldr);
+
+ fs_give((void **) pss);
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ See if stream can be used for a mailbox name
+
+ Accepts: mailbox name
+ candidate stream
+ Returns: stream if it can be used, else NIL
+
+ This is called to weed out unnecessary use of c-client streams. In other
+ words, to help facilitate re-use of streams.
+
+ This code is very similar to the same_remote_mailboxes code below, which
+ is used in pine_mail_open. That code compares two mailbox names. One is
+ usually from the config file and the other is either from the config file
+ or is typed in. Here and in same_stream_and_mailbox below, we're comparing
+ an open stream to a name instead of two names. We could conceivably use
+ same_remote_mailboxes to compare stream->mailbox to name, but it isn't
+ exactly the same and the differences may be important. Some stuff that
+ happens here seems wrong, but it isn't easy to fix.
+ Having !mb_n.port count as a match to any mb_s.port isn't right. It should
+ only match if mb_s.port is equal to the default, but the default isn't
+ something that is available to us. The same thing is done in c-client in
+ the mail_usable_network_stream() routine, and it isn't right there, either.
+ The semantics of a missing user are also suspect, because just like with
+ port, a default is used.
+ ----*/
+MAILSTREAM *
+same_stream(char *name, MAILSTREAM *stream)
+{
+ NETMBX mb_s, mb_n, mb_o;
+
+ if(stream && stream->mailbox && *stream->mailbox && name && *name
+ && !(sp_dead_stream(stream))
+ && mail_valid_net_parse(stream->mailbox, &mb_s)
+ && mail_valid_net_parse(stream->original_mailbox, &mb_o)
+ && mail_valid_net_parse(name, &mb_n)
+ && !strucmp(mb_n.service, mb_s.service)
+ && (!strucmp(mb_n.host, mb_o.host) /* s is already canonical */
+ || !strucmp(canonical_name(mb_n.host), mb_s.host))
+ && (!mb_n.port || mb_n.port == mb_s.port)
+ && mb_n.anoflag == stream->anonymous
+ && ((mb_n.user && *mb_n.user &&
+ mb_s.user && !strcmp(mb_n.user, mb_s.user))
+ ||
+ ((!mb_n.user || !*mb_n.user)
+ && mb_s.user
+ && ((ps_global->VAR_USER_ID
+ && !strcmp(ps_global->VAR_USER_ID, mb_s.user))
+ ||
+ (!ps_global->VAR_USER_ID
+ && ps_global->ui.login[0]
+ && !strcmp(ps_global->ui.login, mb_s.user))))
+ ||
+ (!((mb_n.user && *mb_n.user) || (mb_s.user && *mb_s.user))
+ && stream->anonymous))
+ && (struncmp(mb_n.service, "imap", 4) ? 1 : strcmp(imap_host(stream), ".NO-IMAP-CONNECTION."))){
+ dprint((7, "same_stream: name->%s == stream->%s: yes\n",
+ name ? name : "?",
+ (stream && stream->mailbox) ? stream->mailbox : "NULL"));
+ return(stream);
+ }
+
+ dprint((7, "same_stream: name->%s == stream->%s: no dice\n",
+ name ? name : "?",
+ (stream && stream->mailbox) ? stream->mailbox : "NULL"));
+ return(NULL);
+}
+
+
+
+/*----------------------------------------------------------------------
+ See if this stream has the named mailbox selected.
+
+ Accepts: mailbox name
+ candidate stream
+ Returns: stream if it can be used, else NIL
+ ----*/
+MAILSTREAM *
+same_stream_and_mailbox(char *name, MAILSTREAM *stream)
+{
+ NETMBX mb_s, mb_n;
+
+ if(same_stream(name, stream)
+ && mail_valid_net_parse(stream->mailbox, &mb_s)
+ && mail_valid_net_parse(name, &mb_n)
+ && (mb_n.mailbox && mb_s.mailbox
+ && (!strcmp(mb_n.mailbox,mb_s.mailbox) /* case depend except INBOX */
+ || (!strucmp(mb_n.mailbox,"INBOX")
+ && !strucmp(mb_s.mailbox,"INBOX"))))){
+ dprint((7,
+ "same_stream_and_mailbox: name->%s == stream->%s: yes\n",
+ name ? name : "?",
+ (stream && stream->mailbox) ? stream->mailbox : "NULL"));
+ return(stream);
+ }
+
+ dprint((7,
+ "same_stream_and_mailbox: name->%s == stream->%s: no dice\n",
+ name ? name : "?",
+ (stream && stream->mailbox) ? stream->mailbox : "NULL"));
+ return(NULL);
+}
+
+/*
+ * Args -- name1 and name2 are remote mailbox names.
+ *
+ * Returns -- True if names refer to same mailbox accessed in same way
+ * False if not
+ *
+ * This has some very similar code to same_stream_and_mailbox but we're not
+ * quite ready to discard the differences.
+ * The treatment of the port and the user is not quite the same.
+ */
+int
+same_remote_mailboxes(char *name1, char *name2)
+{
+ NETMBX mb1, mb2;
+ char *cn1;
+
+ /*
+ * Probably we should allow !port equal to default port, but we don't
+ * know how to get the default port. To match what c-client does we
+ * allow !port to be equal to anything.
+ */
+ return(name1 && IS_REMOTE(name1)
+ && name2 && IS_REMOTE(name2)
+ && mail_valid_net_parse(name1, &mb1)
+ && mail_valid_net_parse(name2, &mb2)
+ && !strucmp(mb1.service, mb2.service)
+ && (!strucmp(mb1.host, mb2.host) /* just to save DNS lookups */
+ || !strucmp(cn1=canonical_name(mb1.host), mb2.host)
+ || !strucmp(cn1, canonical_name(mb2.host)))
+ && (!mb1.port || !mb2.port || mb1.port == mb2.port)
+ && mb1.anoflag == mb2.anoflag
+ && mb1.mailbox && mb2.mailbox
+ && (!strcmp(mb1.mailbox, mb2.mailbox)
+ || (!strucmp(mb1.mailbox,"INBOX")
+ && !strucmp(mb2.mailbox,"INBOX")))
+ && ((mb1.user && *mb1.user && mb2.user && *mb2.user
+ && !strcmp(mb1.user, mb2.user))
+ ||
+ (!(mb1.user && *mb1.user) && !(mb2.user && *mb2.user))
+ ||
+ (!(mb1.user && *mb1.user)
+ && ((ps_global->VAR_USER_ID
+ && !strcmp(ps_global->VAR_USER_ID, mb2.user))
+ ||
+ (!ps_global->VAR_USER_ID
+ && ps_global->ui.login[0]
+ && !strcmp(ps_global->ui.login, mb2.user))))
+ ||
+ (!(mb2.user && *mb2.user)
+ && ((ps_global->VAR_USER_ID
+ && !strcmp(ps_global->VAR_USER_ID, mb1.user))
+ ||
+ (!ps_global->VAR_USER_ID
+ && ps_global->ui.login[0]
+ && !strcmp(ps_global->ui.login, mb1.user))))));
+}
+
+
+int
+is_imap_stream(MAILSTREAM *stream)
+{
+ return(stream && stream->dtb && stream->dtb->name
+ && !strcmp(stream->dtb->name, "imap"));
+}
+
+
+int
+modern_imap_stream(MAILSTREAM *stream)
+{
+ return(is_imap_stream(stream) && LEVELIMAP4rev1(stream));
+}
+
+
+/*----------------------------------------------------------------------
+ Check and see if all the stream are alive
+
+Returns: 0 if there was no change
+ >0 if streams have died since last call
+
+Also outputs a message that the streams have died
+ ----*/
+int
+streams_died(void)
+{
+ int rv = 0;
+ int i;
+ MAILSTREAM *m;
+ char *folder;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_dead_stream(m)){
+ if(sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)){
+ if(!sp_noticed_dead_stream(m)){
+ rv++;
+ sp_set_noticed_dead_stream(m, 1);
+ folder = STREAMNAME(m);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("MAIL FOLDER \"%s\" CLOSED DUE TO ACCESS ERROR"),
+ short_str(pretty_fn(folder) ? pretty_fn(folder) : "?",
+ tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots));
+ dprint((6, "streams_died: locked: \"%s\"\n",
+ folder));
+ if(rv == 1){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Folder \"%s\" is Closed"),
+ short_str(pretty_fn(folder) ? pretty_fn(folder) : "?",
+ tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots));
+ if(pith_opt_icon_text)
+ (*pith_opt_icon_text)(tmp_20k_buf, IT_MCLOSED);
+ }
+ }
+ }
+ else{
+ if(!sp_noticed_dead_stream(m)){
+ sp_set_noticed_dead_stream(m, 1);
+ folder = STREAMNAME(m);
+ /*
+ * If a cached stream died and then we tried to use it
+ * it could cause problems. We could warn about it here
+ * but it may be confusing because it might be
+ * unrelated to what the user is doing and not cause
+ * any problem at all.
+ */
+#if 0
+ if(sp_flagged(m, SP_USEPOOL))
+ q_status_message(SM_ORDER, 3, 3,
+ "Warning: Possible problem accessing remote data, connection died.");
+#endif
+
+ dprint((6, "streams_died: not locked: \"%s\"\n",
+ folder));
+ }
+
+ pine_mail_actually_close(m);
+ }
+ }
+ }
+
+ return(rv);
+}
+
+
+/* Some stream is locked checks to see if there is any stream for which we
+ * are in a callback from c-client
+ */
+
+int
+some_stream_is_locked(void)
+{
+ int rv = 0, i;
+ MAILSTREAM *m;
+
+ for(i = 0; rv == 0 && i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && m->lock)
+ rv++;
+ }
+
+ return(rv);
+}
+
+/*
+ * Very simple version of appenduid_cb until we need something
+ * more complex.
+ */
+
+static imapuid_t last_append_uid;
+
+void
+appenduid_cb(char *mailbox,unsigned long uidvalidity, SEARCHSET *set)
+{
+ last_append_uid = set ? set->first : 0L;
+}
+
+
+imapuid_t
+get_last_append_uid(void)
+{
+ return last_append_uid;
+}
+
+
+/*
+ * mail_cmd_stream - return a stream suitable for mail_lsub,
+ * mail_subscribe, and mail_unsubscribe
+ *
+ */
+MAILSTREAM *
+mail_cmd_stream(CONTEXT_S *context, int *closeit)
+{
+ char tmp[MAILTMPLEN];
+
+ *closeit = 1;
+ (void) context_apply(tmp, context, "x", sizeof(tmp));
+
+ return(pine_mail_open(NULL, tmp,
+ OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
+ NULL));
+}
+
+
+/*
+ * This is so we can replace the old rfc822_ routines like rfc822_header_line
+ * with the new version that checks bounds, like rfc822_output_header_line.
+ * This routine is called when would be a bounds overflow, which we simply log
+ * and go on with the truncated data.
+ */
+long
+dummy_soutr(void *stream, char *string)
+{
+ dprint((2, "dummy_soutr unexpected call, caught overflow\n"));
+ return LONGT;
+}
diff --git a/pith/stream.h b/pith/stream.h
new file mode 100644
index 00000000..eafe6f24
--- /dev/null
+++ b/pith/stream.h
@@ -0,0 +1,471 @@
+/*
+ * $Id: stream.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STREAM_INCLUDED
+#define PITH_STREAM_INCLUDED
+
+
+#include "../pith/context.h"
+#include "../pith/msgno.h"
+#include "../pith/savetype.h"
+
+
+#define AOS_NONE 0x00 /* alredy_open_stream: no flag */
+#define AOS_RW_ONLY 0x01 /* don't match readonly streams */
+
+/* pine_mail_list flags */
+#define PML_IS_MOVE_MBOX 0x01
+
+
+/*
+ * The stream pool is where we keep pointers to open streams. Some of them are
+ * actively being used, some are connected to a folder but aren't actively
+ * in use, some are random temporary use streams left open for possible
+ * re-use. Each open stream should be in the streams array, which is of
+ * size nstream altogether. Streams which are not to be re-used (don't have
+ * the flag SP_USEPOOL set) are in the array anyway.
+ */
+
+/*
+ * Structure holds global information about the stream pool. The per-stream
+ * information is stored in a PER_STREAM_S struct attached to each stream.
+ */
+typedef struct stream_pool {
+ int max_remstream; /* max implicitly cached remote streams */
+ int nstream; /* size of streams array */
+ MAILSTREAM **streams; /* the array of streams in stream pool */
+} SP_S;
+
+/*
+ * Pine's private per-stream data stored on the c-client's stream
+ * spare pointer.
+ */
+typedef struct pine_per_stream_data {
+ MSGNO_S *msgmap;
+ CONTEXT_S *context; /* context fldr was interpreted in */
+ char *fldr; /* folder name, alloced copy */
+ unsigned long flags;
+ unsigned long icache_flags;
+ int ref_cnt;
+ long new_mail_count; /* new mail since the last new_mail check */
+ long mail_since_cmd; /* new mail since last key pressed */
+ long expunge_count;
+ long first_unseen;
+ long recent_since_visited;
+ int check_cnt;
+ time_t first_status_change;
+ time_t last_ping; /* Keeps track of when the last */
+ /* command was. The command wasn't */
+ /* necessarily a ping. */
+ time_t last_expunged_reaper; /* Some IMAP commands defer the */
+ /* return of EXPUNGE responses. */
+ /* This is the time of the last */
+ /* command which did not defer. */
+ time_t last_chkpnt_done;
+ time_t last_use_time;
+ time_t last_activity;
+ imapuid_t saved_uid_validity;
+ imapuid_t saved_uid_last;
+ char *saved_cur_msg_id;
+ unsigned unsorted_newmail:1;
+ unsigned need_to_rethread:1;
+ unsigned io_error_on_stream:1;
+ unsigned mail_box_changed:1;
+ unsigned viewing_a_thread:1;
+ unsigned dead_stream:1;
+ unsigned noticed_dead_stream:1;
+ unsigned closing:1;
+} PER_STREAM_S;
+
+/*
+ * Complicated set of flags for stream pool cache.
+ * LOCKED, PERMLOCKED, TEMPUSE, and USEPOOL are flags stored in the stream
+ * flags of the PER_STREAM_S structure.
+ *
+ * SP_LOCKED -- In use, don't re-use this.
+ * That isn't a good description of SP_LOCKED. Every time
+ * we pine_mail_open a stream it is SP_LOCKED and a ref_cnt
+ * is incremented. Pine_mail_close decrements the ref_cnt
+ * and unlocks it when we get to zero.
+ * SP_PERMLOCKED -- Should always be kept open, like INBOX. Right now the
+ * only significance of this is that the expunge_and_close
+ * won't happen if this is set (like the way INBOX works).
+ * If a stream is PERMLOCKED it should no doubt be LOCKED
+ * as well (it isn't done implicitly in the tests).
+ * SP_INBOX -- This stream is open on the INBOX.
+ * SP_USERFLDR -- This stream was opened by the user explicitly, not
+ * implicitly like would happen with a save or a remote
+ * address book, etc.
+ * SP_FILTERED -- This stream was opened by the user explicitly and
+ * filtered.
+ * SP_TEMPUSE -- If a stream is not SP_LOCKED, that says we can re-use
+ * it if need be but we should prefer to use another unused
+ * slot if there is one. If a stream is marked TEMPUSE we
+ * should consider re-using it before we consider re-using
+ * a stream which is not LOCKED but not marked TEMPUSE.
+ * This flag is not only stored in the PER_STREAM_S flags,
+ * it is also an input argument to sp_stream_get.
+ * It may make sense to mark a stream both SP_LOCKED and
+ * SP_TEMPUSE. That way, when we close the stream it will
+ * be SP_TEMPUSE and more re-usable than if we didn't.
+ * SP_USEPOOL -- Passed to pine_mail_open, it means to consider the
+ * stream pool when opening and to put it into the stream
+ * pool after we open it. If this is not set when we open,
+ * we do an honest open and an honest close when we close.
+ *
+ * These flags are input flags to sp_stream_get.
+ * SP_MATCH -- We're looking for a stream that is already open on
+ * this mailbox. This is good if we are reopening the
+ * same mailbox we already had opened.
+ * SP_SAME -- We're looking for a stream that is open to the same
+ * server. For example, we might want to do a STATUS
+ * command or a DELETE. We could use any stream that
+ * is already open for this. Unless SP_MATCH is also
+ * set, this will not return exact matches. (For example,
+ * it is a bad idea to do a STATUS command on an already
+ * selected mailbox. There may be locking problems if you
+ * try to delete a folder that is selected...)
+ * SP_TEMPUSE -- The checking for SP_SAME streams is controlled by these
+ * SP_UNLOCKED two flags. If SP_TEMPUSE is set then we will only match
+ * streams which are marked TEMPUSE and not LOCKED.
+ * If TEMPUSE is not set but UNLOCKED is, then we will
+ * match on any same stream that is not locked. We'll choose
+ * SP_TEMPUSE streams in preference to those that aren't
+ * SP_TEMPUSE. If neither SP_TEMPUSE or SP_UNLOCKED is set,
+ * then we'll consider any stream, even if it is locked.
+ * We'll still prefer TEMPUSE first, then UNLOCKED, then any.
+ *
+ * Careful with the values of these flags. Some of them should really be
+ * in separate name spaces, but we've combined all of them for convenience.
+ * In particular, SP_USERFLDR, SP_INBOX, SP_USEPOOL, and SP_TEMPUSE are
+ * all passed in the pine_mail_open flags argument, alongside OP_DEBUG and
+ * friends from c-client. So they have to have different values than
+ * those OP_ flags. SP_PERMLOCKED was passed at one time but isn't anymore.
+ * Still, include it in the careful set. C-client reserves the bits
+ * 0xff000000 for client flags.
+ */
+
+#define SP_USERFLDR 0x01000000
+#define SP_INBOX 0x02000000
+#define SP_USEPOOL 0x04000000
+#define SP_TEMPUSE 0x08000000
+
+#define SP_PERMLOCKED 0x10000000
+#define SP_LOCKED 0x20000000
+
+#define SP_MATCH 0x00100000
+#define SP_SAME 0x00200000
+#define SP_UNLOCKED 0x00400000
+#define SP_FILTERED 0x00800000
+#define SP_RO_OK 0x01000000 /* Readonly stream ok for SP_MATCH */
+
+/* these are for icache_flags */
+#define SP_NEED_FORMAT_SETUP 0x01
+#define SP_FORMAT_INCLUDES_MSGNO 0x02
+#define SP_FORMAT_INCLUDES_SMARTDATE 0x04
+
+
+/* access value of first_unseen, but don't set it with this */
+#define sp_first_unseen(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->first_unseen : 0L)
+
+/* use this to set it */
+#define sp_set_first_unseen(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->first_unseen = (val);}while(0)
+
+#define sp_flags(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->flags : 0L)
+
+#define sp_set_flags(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->flags = (val);}while(0)
+
+#define sp_icache_flags(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->icache_flags : 0L)
+
+#define sp_set_icache_flags(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->icache_flags = (val);}while(0)
+
+#define sp_ref_cnt(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->ref_cnt : 0L)
+
+#define sp_set_ref_cnt(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->ref_cnt = (val);}while(0)
+
+#define sp_expunge_count(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->expunge_count : 0L)
+
+#define sp_set_expunge_count(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->expunge_count = (val);}while(0)
+
+#define sp_new_mail_count(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->new_mail_count : 0L)
+
+#define sp_set_new_mail_count(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->new_mail_count = (val);}while(0)
+
+#define sp_mail_since_cmd(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->mail_since_cmd : 0L)
+
+#define sp_set_mail_since_cmd(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->mail_since_cmd = (val);}while(0)
+
+#define sp_recent_since_visited(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->recent_since_visited : 0L)
+
+#define sp_set_recent_since_visited(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->recent_since_visited = (val);}while(0)
+
+#define sp_check_cnt(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->check_cnt : 0L)
+
+#define sp_set_check_cnt(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->check_cnt = (val);}while(0)
+
+#define sp_first_status_change(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->first_status_change : 0L)
+
+#define sp_set_first_status_change(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->first_status_change = (val);}while(0)
+
+#define sp_last_ping(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->last_ping : 0L)
+
+#define sp_set_last_ping(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->last_ping = (val);}while(0)
+
+#define sp_last_expunged_reaper(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->last_expunged_reaper : 0L)
+
+#define sp_set_last_expunged_reaper(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->last_expunged_reaper = (val);}while(0)
+
+#define sp_last_chkpnt_done(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->last_chkpnt_done : 0L)
+
+#define sp_set_last_chkpnt_done(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->last_chkpnt_done = (val);}while(0)
+
+#define sp_last_use_time(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->last_use_time : 0L)
+
+#define sp_set_last_use_time(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->last_use_time = (val);}while(0)
+
+#define sp_last_activity(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->last_activity : 0L)
+
+#define sp_set_last_activity(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->last_activity = (val);}while(0)
+
+#define sp_saved_uid_validity(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->saved_uid_validity : 0L)
+
+#define sp_set_saved_uid_validity(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->saved_uid_validity = (val);}while(0)
+
+#define sp_saved_uid_last(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->saved_uid_last : 0L)
+
+#define sp_set_saved_uid_last(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->saved_uid_last = (val);}while(0)
+
+#define sp_mail_box_changed(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->mail_box_changed : 0L)
+
+#define sp_set_mail_box_changed(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->mail_box_changed = (val);}while(0)
+
+#define sp_unsorted_newmail(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->unsorted_newmail : 0L)
+
+#define sp_set_unsorted_newmail(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->unsorted_newmail = (val);}while(0)
+
+#define sp_need_to_rethread(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->need_to_rethread : 0L)
+
+#define sp_set_need_to_rethread(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->need_to_rethread = (val);}while(0)
+
+#define sp_viewing_a_thread(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->viewing_a_thread : 0L)
+
+#define sp_set_viewing_a_thread(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->viewing_a_thread = (val);}while(0)
+
+#define sp_dead_stream(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->dead_stream : 0L)
+
+#define sp_set_dead_stream(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->dead_stream = (val);}while(0)
+
+#define sp_noticed_dead_stream(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->noticed_dead_stream : 0L)
+
+#define sp_set_noticed_dead_stream(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->noticed_dead_stream = (val);}while(0)
+
+#define sp_closing(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->closing : 0L)
+
+#define sp_set_closing(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->closing = (val);}while(0)
+
+#define sp_io_error_on_stream(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->io_error_on_stream : 0L)
+
+#define sp_set_io_error_on_stream(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->io_error_on_stream = (val);}while(0)
+
+#define sp_fldr(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->fldr : (char *) NULL)
+
+#define sp_saved_cur_msg_id(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->saved_cur_msg_id : (char *) NULL)
+
+#define sp_context(stream) \
+ ((sp_data(stream) && *sp_data(stream)) \
+ ? (*sp_data(stream))->context : 0L)
+
+#define sp_set_context(stream,val) do{ \
+ if(sp_data(stream) && *sp_data(stream)) \
+ (*sp_data(stream))->context = (val);}while(0)
+
+
+extern MAILSTATUS *pine_cached_status;
+
+
+/*
+ * Macros to help fetch specific fields
+ */
+#define pine_fetchheader_lines(S,N,M,F) pine_fetch_header(S,N,M,F,0L)
+#define pine_fetchheader_lines_not(S,N,M,F) pine_fetch_header(S,N,M,F,FT_NOT)
+
+
+/* exported protoypes */
+MAILSTREAM *pine_mail_open(MAILSTREAM *, char *, long, long *);
+long pine_mail_create(MAILSTREAM *, char *);
+long pine_mail_delete(MAILSTREAM *, char *);
+long pine_mail_append_full(MAILSTREAM *, char *, char *, char *, STRING *);
+#define pine_mail_append(stream,mailbox,message) \
+ pine_mail_append_full(stream,mailbox,NULL,NULL,message)
+long pine_mail_append_multiple(MAILSTREAM *, char *, append_t, APPENDPACKAGE *, MAILSTREAM *);
+long pine_mail_copy_full(MAILSTREAM *, char *, char *, long);
+#define pine_mail_copy(stream,sequence,mailbox) \
+ pine_mail_copy_full(stream,sequence,mailbox,0)
+long pine_mail_rename(MAILSTREAM *, char *, char *);
+void pine_mail_close(MAILSTREAM *);
+void pine_mail_actually_close(MAILSTREAM *);
+void maybe_kill_old_stream(MAILSTREAM *);
+long pine_mail_search_full(MAILSTREAM *, char *, SEARCHPGM *, long);
+void pine_mail_fetch_flags(MAILSTREAM *, char *, long);
+ENVELOPE *pine_mail_fetchenvelope(MAILSTREAM *, unsigned long);
+ENVELOPE *pine_mail_fetch_structure(MAILSTREAM *, unsigned long, BODY **, long);
+ENVELOPE *pine_mail_fetchstructure(MAILSTREAM *, unsigned long, BODY **);
+char *pine_mail_fetch_body(MAILSTREAM *, unsigned long, char *, unsigned long *, long);
+char *pine_mail_fetch_text(MAILSTREAM *, unsigned long, char *, unsigned long *, long);
+char *pine_mail_partial_fetch_wrapper(MAILSTREAM *, unsigned long, char *, unsigned long *,
+ long, unsigned long, char **, int);
+long pine_mail_ping(MAILSTREAM *);
+void pine_mail_check(MAILSTREAM *);
+int pine_mail_list(MAILSTREAM *, char *, char *, unsigned *);
+long pine_mail_status(MAILSTREAM *, char *, long);
+long pine_mail_status_full(MAILSTREAM *, char *, long, imapuid_t *, imapuid_t *);
+int check_for_move_mbox(char *, char *, size_t, char **);
+MAILSTREAM *already_open_stream(char *, int);
+void pine_imap_cmd_happened(MAILSTREAM *, char *, long);
+void sp_cleanup_dead_streams(void);
+int sp_flagged(MAILSTREAM *, unsigned long);
+void sp_set_fldr(MAILSTREAM *, char *);
+void sp_set_saved_cur_msg_id(MAILSTREAM *, char *);
+void sp_flag(MAILSTREAM *, unsigned long);
+void sp_unflag(MAILSTREAM *, unsigned long);
+void sp_mark_stream_dead(MAILSTREAM *);
+int sp_nremote_permlocked(void);
+MAILSTREAM *sp_stream_get(char *, unsigned long);
+void sp_end(void);
+int sp_a_locked_stream_is_dead(void);
+int sp_a_locked_stream_changed(void);
+MAILSTREAM *sp_inbox_stream(void);
+PER_STREAM_S **sp_data(MAILSTREAM *);
+MSGNO_S *sp_msgmap(MAILSTREAM *);
+void sp_free_callback(void **);
+MAILSTREAM *same_stream(char *, MAILSTREAM *);
+MAILSTREAM *same_stream_and_mailbox(char *, MAILSTREAM *);
+int same_remote_mailboxes(char *, char *);
+int is_imap_stream(MAILSTREAM *);
+int modern_imap_stream(MAILSTREAM *);
+int streams_died(void);
+int some_stream_is_locked(void);
+void appenduid_cb(char *mailbox,unsigned long uidvalidity, SEARCHSET *set);
+imapuid_t get_last_append_uid(void);
+MAILSTREAM *mail_cmd_stream(CONTEXT_S *, int *);
+long dummy_soutr(void *, char *);
+
+
+#endif /* PITH_STREAM_INCLUDED */
diff --git a/pith/string.c b/pith/string.c
new file mode 100644
index 00000000..84717c3e
--- /dev/null
+++ b/pith/string.c
@@ -0,0 +1,2862 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: string.c 910 2008-01-14 22:28:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ string.c
+ Misc extra and useful string functions
+ - rplstr replace a substring with another string
+ - sqzspaces Squeeze out the extra blanks in a string
+ - sqznewlines Squeeze out \n and \r.
+ - removing_trailing_white_space
+ - short_str Replace part of string with ... for display
+ - removing_leading_white_space
+ Remove leading or trailing white space
+ - removing_double_quotes
+ Remove surrounding double quotes
+ - strclean
+ both of above plus convert to lower case
+ - skip_white_space
+ return pointer to first non-white-space char
+ - skip_to_white_space
+ return pointer to first white-space char
+ - srchstr Search a string for first occurrence of a sub string
+ - srchrstr Search a string for last occurrence of a sub string
+ - strindex Replacement for strchr/index
+ - strrindex Replacement for strrchr/rindex
+ - sstrncpy Copy one string onto another, advancing dest'n pointer
+ - istrncpy Copy n chars between bufs, making ctrl chars harmless
+ - month_abbrev Return three letter abbreviations for months
+ - month_num Calculate month number from month/year string
+ - cannon_date Formalize format of a some what formatted date
+ - repeat_char Returns a string n chars long
+ - fold Inserts newlines for folding at whitespace.
+ - byte_string Format number of bytes with Kb, Mb, Gb or bytes
+ - enth-string Format number i.e. 1: 1st, 983: 983rd....
+ - string_to_cstring Convert string to C-style constant string with \'s
+ - cstring_to_hexstring Convert cstring to hex string
+ - cstring_to_string Convert C-style string to string
+ - add_backslash_escapes Escape / and \ with \
+ - remove_backslash_escapes Undo the \ escaping, and stop string at /.
+
+ ====*/
+
+#include "../pith/headers.h"
+#include "../pith/string.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/escapes.h"
+#include "../pith/util.h"
+
+
+void char_to_octal_triple(int, char *);
+char *dollar_escape_dollars(char *);
+time_t date_to_local_time_t(char *date);
+
+
+
+/*----------------------------------------------------------------------
+ Replace dl characters in one string with another given string
+
+ args: os -- pointer into output string
+ oslen -- size of output string starting at os
+ dl -- the number of character to delete starting at os
+ is -- The string to insert
+
+ Result: returns pointer in originl string to end of string just inserted
+ ---*/
+char *
+rplstr(char *os, size_t oslen, int dl, char *is)
+{
+ char *x1, *x2, *x3;
+ int diff;
+
+ if(os == NULL)
+ return(NULL);
+
+ x1 = os + strlen(os);
+
+ /* can't delete more characters than there are */
+ if(dl > x1 - os)
+ dl = x1 - os;
+
+ x2 = is;
+ if(is != NULL)
+ x2 = is + strlen(is);
+
+ diff = (x2 - is) - dl;
+
+ if(diff < 0){ /* String shrinks */
+ x3 = os;
+ if(is != NULL)
+ for(x2 = is; *x2; *x3++ = *x2++) /* copy new string in */
+ ;
+
+ for(x2 = x3 - diff; *x2; *x3++ = *x2++) /* shift for delete */
+ ;
+
+ *x3 = *x2; /* the null */
+ }
+ else{ /* String grows */
+ /* make room for insert */
+ x3 = x1 + diff;
+ if(x3 >= os + oslen) /* just protecting ourselves */
+ x3 = os + oslen - 1;
+
+ for(; x3 >= os + (x2 - is); *x3-- = *x1--); /* shift*/
+ ;
+
+ if(is != NULL)
+ for(x1 = os, x2 = is; *x2 ; *x1++ = *x2++)
+ ;
+
+ while(*x3) x3++;
+ }
+
+ os[oslen-1] = '\0';
+
+ return(x3);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Squeeze out blanks
+ ----------------------------------------------------------------------*/
+void
+sqzspaces(char *string)
+{
+ char *p = string;
+
+ while((*string = *p++) != '\0') /* while something to copy */
+ if(!isspace((unsigned char)*string)) /* only really copy if non-blank */
+ string++;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Squeeze out CR's and LF's
+ ----------------------------------------------------------------------*/
+void
+sqznewlines(char *string)
+{
+ char *p = string;
+
+ while((*string = *p++) != '\0') /* while something to copy */
+ if(*string != '\r' && *string != '\n') /* only copy if non-newline */
+ string++;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove leading white space from a string in place
+
+ Args: string -- string to remove space from
+ ----*/
+void
+removing_leading_white_space(char *string)
+{
+ register char *p;
+
+ if(!string || !*string || !isspace(*string))
+ return;
+
+ for(p = string; *p; p++) /* find the first non-blank */
+ if(!isspace((unsigned char) *p)){
+ while((*string++ = *p++) != '\0') /* copy back from there... */
+ ;
+
+ return;
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove trailing white space from a string in place
+
+ Args: string -- string to remove space from
+ ----*/
+void
+removing_trailing_white_space(char *string)
+{
+ char *p = NULL;
+
+ if(!string || !*string)
+ return;
+
+ for(; *string; string++) /* remember start of whitespace */
+ p = (!isspace((unsigned char)*string)) ? NULL : (!p) ? string : p;
+
+ if(p) /* if whitespace, blast it */
+ *p = '\0';
+}
+
+
+void
+removing_leading_and_trailing_white_space(char *string)
+{
+ register char *p, *q = NULL;
+
+ if(!string || !*string)
+ return;
+
+ for(p = string; *p; p++) /* find the first non-blank */
+ if(!isspace((unsigned char)*p)){
+ if(p == string){ /* don't have to copy in this case */
+ for(; *string; string++)
+ q = (!isspace((unsigned char)*string)) ? NULL : (!q) ? string : q;
+ }
+ else{
+ for(; (*string = *p++) != '\0'; string++)
+ q = (!isspace((unsigned char)*string)) ? NULL : (!q) ? string : q;
+ }
+
+ if(q)
+ *q = '\0';
+
+ return;
+ }
+
+ if(*string != '\0')
+ *string = '\0';
+}
+
+
+/*----------------------------------------------------------------------
+ Remove one set of double quotes surrounding string in place
+ Returns 1 if quotes were removed
+
+ Args: string -- string to remove quotes from
+ ----*/
+int
+removing_double_quotes(char *string)
+{
+ register char *p;
+ int ret = 0;
+
+ if(string && string[0] == '"' && string[1] != '\0'){
+ p = string + strlen(string) - 1;
+ if(*p == '"'){
+ ret++;
+ *p = '\0';
+ for(p = string; *p; p++)
+ *p = *(p+1);
+ }
+ }
+
+ return(ret);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return a pointer to first non-whitespace char in string
+
+ Args: string -- string to scan
+ ----*/
+char *
+skip_white_space(char *string)
+{
+ while(*string && isspace((unsigned char) *string))
+ string++;
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return a pointer to first whitespace char in string
+
+ Args: string -- string to scan
+ ----*/
+char *
+skip_to_white_space(char *string)
+{
+ while(*string && !isspace((unsigned char) *string))
+ string++;
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove quotes from a string in place
+
+ Args: string -- string to remove quotes from
+ Rreturns: string passed us, but with quotes gone
+ ----*/
+char *
+removing_quotes(char *string)
+{
+ char *p, *q;
+
+ if(*(p = q = string) == '\"'){
+ q++;
+ do
+ if(*q == '\"' || *q == '\\')
+ q++;
+ while((*p++ = *q++) != '\0');
+ }
+
+ return(string);
+}
+
+
+
+/*---------------------------------------------------
+ Remove leading whitespace, trailing whitespace and convert
+ to lowercase
+
+ Args: s, -- The string to clean
+
+ Result: the cleaned string
+ ----*/
+char *
+strclean(char *string)
+{
+ char *s = string, *sc = NULL, *p = NULL;
+
+ for(; *s; s++){ /* single pass */
+ if(!isspace((unsigned char)*s)){
+ p = NULL; /* not start of blanks */
+ if(!sc) /* first non-blank? */
+ sc = string; /* start copying */
+ }
+ else if(!p) /* it's OK if sc == NULL */
+ p = sc; /* start of blanks? */
+
+ if(sc) /* if copying, copy */
+ *sc++ = isupper((unsigned char)(*s))
+ ? (unsigned char)tolower((unsigned char)(*s))
+ : (unsigned char)(*s);
+ }
+
+ if(p) /* if ending blanks */
+ *p = '\0'; /* tie off beginning */
+ else if(!sc) /* never saw a non-blank */
+ *string = '\0'; /* so tie whole thing off */
+
+ return(string);
+}
+
+
+/*
+ * Returns a pointer to a short version of the string.
+ * If src is not longer than wid, pointer points to src.
+ * If longer than wid, a version which is wid long is made in
+ * buf and the pointer points there.
+ *
+ * Wid refers to UTF-8 screen width, not strlen width.
+ *
+ * Args src -- The string to be shortened
+ * buf -- A place to put the short version
+ * wid -- Desired width of shortened string
+ * where -- Where should the dots be in the shortened string. Can be
+ * FrontDots, MidDots, EndDots.
+ *
+ * FrontDots ...stuvwxyz
+ * EndDots abcdefgh...
+ * MidDots abcd...wxyz
+ */
+char *
+short_str(char *src, char *buf, size_t buflen, int wid, WhereDots where)
+{
+ char *ans;
+ unsigned alen, first, second;
+
+ if(wid <= 0){
+ ans = buf;
+ if(buflen > 0)
+ buf[0] = '\0';
+ }
+ else if((alen = utf8_width(src)) <= wid)
+ ans = src;
+ else{
+ ans = buf;
+ if(wid < 5){
+ if(buflen > wid){
+ strncpy(buf, "....", buflen);
+ buf[wid] = '\0';
+ }
+ }
+ else{
+ char *q;
+ unsigned got_width;
+
+ /*
+ * first == length of preellipsis text
+ * second == length of postellipsis text
+ */
+ if(where == FrontDots){
+ first = 0;
+ second = wid - 3;
+ }
+ else if(where == MidDots){
+ first = (wid - 3)/2;
+ second = wid - 3 - first;
+ }
+ else if(where == EndDots){
+ first = wid - 3;
+ second = 0;
+ }
+
+ q = buf;
+ if(first){
+ q += utf8_to_width(q, src, buflen, first, &got_width);
+ if(got_width != first){
+ if(second)
+ second++;
+ else
+ while(got_width < first && buflen-(q-buf) > 0)
+ *q++ = '.';
+ }
+ }
+
+ if(buflen - (q-buf) > 3){
+ strncpy(q, "...", buflen - (q-buf));
+ buf[buflen-1] = '\0';
+ q += strlen(q);
+ }
+
+ if(second){
+ char *p;
+
+ p = utf8_count_back_width(src, src+strlen(src), second, &got_width);
+ if(buflen - (q-buf) > strlen(p)){
+ strncpy(q, p, buflen - (q-buf));
+ buf[buflen-1] = '\0';
+ q += strlen(q);
+ }
+ }
+
+ if(buflen - (q-buf) > 0)
+ *q = '\0';
+
+ buf[buflen-1] = '\0';
+ }
+ }
+
+ return(ans);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Search one string for another
+
+ Args: haystack -- The string to search in, the larger string
+ needle -- The string to search for, the smaller string
+
+ Search for first occurrence of needle in the haystack, and return a pointer
+ into the string haystack when it is found. The text we are dealing with is
+ UTF-8. We'd like the search to be case-independent but we're not sure what
+ that means for UTF-8. We're not even sure what matching means. We're not going
+ to worry about composed characters and canonical forms and anything like that
+ for now. Instead, we'll do the case-independent thing for ascii but exact
+ equality for the rest of the character space.
+ ----*/
+char *
+srchstr(char *haystack, char *needle)
+{
+ char *p, *q;
+
+#define CMPNOCASE(x, y) (((isascii((unsigned char) (x)) && isupper((unsigned char) (x))) \
+ ? tolower((unsigned char) (x)) \
+ : (unsigned char) (x)) \
+ == ((isascii((unsigned char) (y)) && isupper((unsigned char) (y))) \
+ ? tolower((unsigned char) (y)) \
+ : (unsigned char) (y)))
+
+ if(needle && haystack)
+ for(; *haystack; haystack++)
+ for(p = needle, q = haystack; ; p++, q++){
+ if(!*p)
+ return(haystack); /* winner! */
+ else if(!*q)
+ return(NULL); /* len(needle) > len(haystack)! */
+ else if(*p != *q && !CMPNOCASE(*p, *q))
+ break;
+ }
+
+ return(NULL);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Search one string for another, from right
+
+ Args: is -- The string to search in, the larger string
+ ss -- The string to search for, the smaller string
+
+ Search for last occurrence of ss in the is, and return a pointer
+ into the string is when it is found. The search is case indepedent.
+ ----*/
+
+char *
+srchrstr(register char *is, register char *ss)
+{
+ register char *sx, *sy;
+ char *ss_store, *rv;
+ char *begin_is;
+ char temp[251];
+
+ if(is == NULL || ss == NULL)
+ return(NULL);
+
+ if(strlen(ss) > sizeof(temp) - 2)
+ ss_store = (char *)fs_get(strlen(ss) + 1);
+ else
+ ss_store = temp;
+
+ for(sx = ss, sy = ss_store; *sx != '\0' ; sx++, sy++)
+ *sy = isupper((unsigned char)(*sx))
+ ? (unsigned char)tolower((unsigned char)(*sx))
+ : (unsigned char)(*sx);
+ *sy = *sx;
+
+ begin_is = is;
+ is = is + strlen(is) - strlen(ss_store);
+ rv = NULL;
+ while(is >= begin_is){
+ for(sx = is, sy = ss_store;
+ ((*sx == *sy)
+ || ((isupper((unsigned char)(*sx))
+ ? (unsigned char)tolower((unsigned char)(*sx))
+ : (unsigned char)(*sx)) == (unsigned char)(*sy))) && *sy;
+ sx++, sy++)
+ ;
+
+ if(!*sy){
+ rv = is;
+ break;
+ }
+
+ is--;
+ }
+
+ if(ss_store != temp)
+ fs_give((void **)&ss_store);
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ A replacement for strchr or index ...
+
+ Returns a pointer to the first occurrence of the character
+ 'ch' in the specified string or NULL if it doesn't occur
+
+ ....so we don't have to worry if it's there or not. We bring our own.
+If we really care about efficiency and think the local one is more
+efficient the local one can be used, but most of the things that take
+a long time are in the c-client and not in pine.
+ ----*/
+char *
+strindex(char *buffer, int ch)
+{
+ do
+ if(*buffer == ch)
+ return(buffer);
+ while (*buffer++ != '\0');
+
+ return(NULL);
+}
+
+
+/* Returns a pointer to the last occurrence of the character
+ * 'ch' in the specified string or NULL if it doesn't occur
+ */
+char *
+strrindex(char *buffer, int ch)
+{
+ char *address = NULL;
+
+ do
+ if(*buffer == ch)
+ address = buffer;
+ while (*buffer++ != '\0');
+ return(address);
+}
+
+
+/*----------------------------------------------------------------------
+ copy at most n chars of the UTF-8 source string onto the destination string
+ returning pointer to start of destination and converting any undisplayable
+ characters to harmless character equivalents.
+ ----*/
+char *
+iutf8ncpy(char *d, char *s, int n)
+{
+ register int i;
+
+ if(!d || !s)
+ return(NULL);
+
+ /*
+ * BUG: this needs to get improved to actually count the
+ * character "cell" positions. For now, at least don't break
+ * a multi-byte character.
+ */
+ for(i = 0; i < n && (d[i] = *s) != '\0'; s++, i++)
+ if((unsigned char)(*s) < 0x80 && FILTER_THIS(*s)){
+ if(i+1 < n){
+ d[i] = '^';
+ d[++i] = (*s == 0x7f) ? '?' : *s + '@';
+ }
+ else{
+ d[i] = '\0';
+ break; /* don't fit */
+ }
+ }
+ else if(*s & 0x80){
+ /* multi-byte character */
+ if((*s & 0xE0) == 0xC0){
+ if(i+1 < n){
+ if(((d[++i] = *++s) & 0xC0) != 0x80){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* too long */
+ }
+ }
+ else if((*s & 0xF0) == 0xE0){
+ if(i+2 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xF8) == 0xF0){
+ if(i+3 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xFC) == 0xF8){
+ if(i+4 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xFE) == 0xFC){
+ if(i+5 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* don't fit */
+ }
+ }
+
+ return(d);
+}
+
+
+/*----------------------------------------------------------------------
+ copy at most n chars of the source string onto the destination string
+ returning pointer to start of destination and converting any undisplayable
+ characters to harmless character equivalents.
+ ----*/
+char *
+istrncpy(char *d, char *s, int n)
+{
+ char *rv = d;
+ unsigned char c;
+
+ if(!d || !s)
+ return(NULL);
+
+ do
+ if(*s && (unsigned char)(*s) < 0x80 && FILTER_THIS(*s)
+ && !(*(s+1) && *s == ESCAPE && match_escapes(s+1))){
+ if(n-- > 0){
+ c = (unsigned char) *s;
+ *d++ = c >= 0x80 ? '~' : '^';
+
+ if(n-- > 0){
+ s++;
+ *d = (c == 0x7f) ? '?' : (c & 0x1f) + '@';
+ }
+ }
+ }
+ else{
+ if(n-- > 0)
+ *d = *s++;
+ }
+ while(n > 0 && *d++);
+
+ return(rv);
+}
+
+
+char *xdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};
+
+char *
+month_abbrev(int month_num)
+{
+ static char *xmonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
+ if(month_num < 1 || month_num > 12)
+ return("xxx");
+ return(xmonths[month_num - 1]);
+}
+
+char *
+month_abbrev_locale(int month_num)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(month_num < 1 || month_num > 12)
+ return("xxx");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 107;
+ tm.tm_mon = month_num-1;
+ our_strftime(buf, sizeof(buf), "%b", &tm);
+
+ /*
+ * If it is all digits, then use the English
+ * words instead. Look for
+ * "<digit>"
+ * "<digit><digit>" or
+ * "<space><digit>"
+ */
+ if((buf[0] && !(buf[0] & 0x80)
+ && isdigit((unsigned char)buf[0]) && !buf[1])
+ ||
+ (buf[0] && !(buf[0] & 0x80)
+ && (isdigit((unsigned char)buf[0]) || buf[0] == ' ')
+ && buf[1] && !(buf[1] & 0x80)
+ && isdigit((unsigned char)buf[1]) && !buf[2]))
+ return(month_abbrev(month_num));
+
+ /*
+ * If buf[0] is a digit then assume that there should be a leading
+ * space if it leads off with a single digit.
+ */
+ if(buf[0] && !(buf[0] & 0x80) && isdigit((unsigned char) buf[0])
+ && !(buf[1] && !(buf[1] & 0x80) && isdigit((unsigned char) buf[1]))){
+ char *p;
+
+ /* insert space at start of buf */
+ p = buf+strlen(buf) + 1;
+ if(p > buf + sizeof(buf) - 1)
+ p = buf + sizeof(buf) - 1;
+
+ for(; p > buf; p--)
+ *p = *(p-1);
+
+ buf[0] = ' ';
+ }
+
+ return(buf);
+ }
+ }
+ else
+ return(month_abbrev(month_num));
+#else /* DISABLE_LOCALE_DATES */
+ return(month_abbrev(month_num));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+char *
+month_name(int month_num)
+{
+ static char *months[] = {"January", "February", "March", "April",
+ "May", "June", "July", "August", "September", "October",
+ "November", "December", NULL};
+ if(month_num < 1 || month_num > 12)
+ return("");
+ return(months[month_num - 1]);
+}
+
+char *
+month_name_locale(int month_num)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(month_num < 1 || month_num > 12)
+ return("");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 107;
+ tm.tm_mon = month_num-1;
+ our_strftime(buf, sizeof(buf), "%B", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(month_name(month_num));
+#else /* DISABLE_LOCALE_DATES */
+ return(month_name(month_num));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+
+char *
+day_abbrev(int day_of_week)
+{
+ if(day_of_week < 0 || day_of_week > 6)
+ return("???");
+ return(xdays[day_of_week]);
+}
+
+char *
+day_abbrev_locale(int day_of_week)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(day_of_week < 0 || day_of_week > 6)
+ return("???");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_wday = day_of_week;
+ our_strftime(buf, sizeof(buf), "%a", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(day_abbrev(day_of_week));
+#else /* DISABLE_LOCALE_DATES */
+ return(day_abbrev(day_of_week));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+char *
+day_name(int day_of_week)
+{
+ static char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday", NULL};
+ if(day_of_week < 0 || day_of_week > 6)
+ return("");
+ return(days[day_of_week]);
+}
+
+char *
+day_name_locale(int day_of_week)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(day_of_week < 0 || day_of_week > 6)
+ return("");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_wday = day_of_week;
+ our_strftime(buf, sizeof(buf), "%A", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(day_name(day_of_week));
+#else /* DISABLE_LOCALE_DATES */
+ return(day_name(day_of_week));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+
+size_t
+our_strftime(char *dst, size_t dst_size, char *format, struct tm *tm)
+{
+#ifdef _WINDOWS
+ LPTSTR lptbuf, lptformat;
+ char *u;
+
+ lptbuf = (LPTSTR) fs_get(dst_size * sizeof(TCHAR));
+ lptbuf[0] = '\0';
+ lptformat = utf8_to_lptstr((LPSTR) format);
+
+ _tcsftime(lptbuf, dst_size, lptformat, tm);
+ u = lptstr_to_utf8(lptbuf);
+ if(u){
+ strncpy(dst, u, dst_size);
+ dst[dst_size-1] = '\0';
+ fs_give((void **) &u);
+ }
+
+ return(strlen(dst));
+#else
+ return(strftime(dst, dst_size, format, tm));
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Return month number of month named in string
+
+ Args: s -- string with 3 letter month abbreviation of form mmm-yyyy
+
+ Result: Returns month number with January, year 1900, 2000... being 0;
+ -1 if no month/year is matched
+ ----*/
+int
+month_num(char *s)
+{
+ int month = -1, year;
+ int i;
+
+ if(F_ON(F_PRUNE_USES_ISO,ps_global)){
+ char save, *p;
+ char digmon[3];
+
+ if(s && strlen(s) > 4 && s[4] == '-'){
+ save = s[4];
+ s[4] = '\0';
+ year = atoi(s);
+ s[4] = save;
+ if(year == 0)
+ return(-1);
+
+ p = s + 5;
+ for(i = 0; i < 12; i++){
+ digmon[0] = ((i+1) < 10) ? '0' : '1';
+ digmon[1] = '0' + (i+1) % 10;
+ digmon[2] = '\0';
+ if(strcmp(digmon, p) == 0)
+ break;
+ }
+
+ if(i == 12)
+ return(-1);
+
+ month = year * 12 + i;
+ }
+ }
+ else{
+ if(s && strlen(s) > 3 && s[3] == '-'){
+ for(i = 0; i < 12; i++){
+ if(struncmp(month_abbrev(i+1), s, 3) == 0)
+ break;
+ }
+
+ if(i == 12)
+ return(-1);
+
+ year = atoi(s + 4);
+ if(year == 0)
+ return(-1);
+
+ month = year * 12 + i;
+ }
+ }
+
+ return(month);
+}
+
+
+/*
+ * Structure containing all knowledge of symbolic time zones.
+ * To add support for a given time zone, add it here, but make sure
+ * the zone name is in upper case.
+ */
+static struct {
+ char *zone;
+ short len,
+ hour_offset,
+ min_offset;
+} known_zones[] = {
+ {"PST", 3, -8, 0}, /* Pacific Standard */
+ {"PDT", 3, -7, 0}, /* Pacific Daylight */
+ {"MST", 3, -7, 0}, /* Mountain Standard */
+ {"MDT", 3, -6, 0}, /* Mountain Daylight */
+ {"CST", 3, -6, 0}, /* Central Standard */
+ {"CDT", 3, -5, 0}, /* Central Daylight */
+ {"EST", 3, -5, 0}, /* Eastern Standard */
+ {"EDT", 3, -4, 0}, /* Eastern Daylight */
+ {"JST", 3, 9, 0}, /* Japan Standard */
+ {"GMT", 3, 0, 0}, /* Universal Time */
+ {"UT", 2, 0, 0}, /* Universal Time */
+#ifdef IST_MEANS_ISREAL
+ {"IST", 3, 2, 0}, /* Israel Standard */
+#else
+#ifdef IST_MEANS_INDIA
+ {"IST", 3, 5, 30}, /* India Standard */
+#endif
+#endif
+ {NULL, 0, 0},
+};
+
+/*----------------------------------------------------------------------
+ Parse date in or near RFC-822 format into the date structure
+
+Args: given_date -- The input string to parse
+ d -- Pointer to a struct date to place the result in
+
+Returns nothing
+
+The following date fomrats are accepted:
+ WKDAY DD MM YY HH:MM:SS ZZ
+ DD MM YY HH:MM:SS ZZ
+ WKDAY DD MM HH:MM:SS YY ZZ
+ DD MM HH:MM:SS YY ZZ
+ DD MM WKDAY HH:MM:SS YY ZZ
+ DD MM WKDAY YY MM HH:MM:SS ZZ
+
+All leading, intervening and trailing spaces tabs and commas are ignored.
+The prefered formats are the first or second ones. If a field is unparsable
+it's value is left as -1.
+
+ ----*/
+void
+parse_date(char *given_date, struct date *d)
+{
+ char *p, **i, *q;
+ int month, n;
+
+ d->sec = -1;
+ d->minute= -1;
+ d->hour = -1;
+ d->day = -1;
+ d->month = -1;
+ d->year = -1;
+ d->wkday = -1;
+ d->hours_off_gmt = -1;
+ d->min_off_gmt = -1;
+
+ if(given_date == NULL)
+ return;
+
+ p = given_date;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+
+ /* Start with weekday? */
+ if((q=strchr(p, ',')) != NULL){
+
+ if(q - p == 3){
+ *q = '\0';
+ for(i = xdays; *i != NULL; i++)
+ if(strucmp(p, *i) == 0) /* Match first 3 letters */
+ break;
+
+ *q = ',';
+
+ if(*i != NULL) {
+ /* Started with week day */
+ d->wkday = i - xdays;
+ }
+ }
+
+ p = q+1;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+ else if((q=strchr(p, ' ')) != NULL && q - p == 3){
+ *q = '\0';
+ for(i = xdays; *i != NULL; i++)
+ if(strucmp(p, *i) == 0) /* Match first 3 letters */
+ break;
+
+ *q = ' ';
+
+ if(*i != NULL) {
+ /* Started with week day */
+ d->wkday = i - xdays;
+ p = q+1;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+ }
+
+ if(isdigit((unsigned char)*p)) {
+ d->day = atoi(p);
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ }
+ for(month = 1; month <= 12; month++)
+ if(struncmp(p, month_abbrev(month), 3) == 0)
+ break;
+ if(month < 13) {
+ d->month = month;
+
+ }
+ /* Move over month, (or whatever is there) */
+ while(*p && !isspace((unsigned char)*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace((unsigned char)*p) || *p == ',' || *p == '-'))
+ p++;
+
+ /* Check again for day */
+ if(isdigit((unsigned char)*p) && d->day == -1) {
+ d->day = atoi(p);
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ }
+
+ /*-- Check for time --*/
+ for(q = p; *q && isdigit((unsigned char)*q); q++);
+ if(*q == ':') {
+ /* It's the time (out of place) */
+ d->hour = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->minute = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ d->sec = atoi(p);
+ while(*p && !isspace((unsigned char)*p))
+ p++;
+ }
+ }
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+
+
+ /* Get the year 0-49 is 2000-2049; 50-100 is 1950-1999 and
+ 101-9999 is 101-9999 */
+ if(isdigit((unsigned char)*p)) {
+ d->year = atoi(p);
+ if(d->year < 50)
+ d->year += 2000;
+ else if(d->year < 100)
+ d->year += 1900;
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ } else {
+ /* Something wierd, skip it and try to resynch */
+ while(*p && !isspace((unsigned char)*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace((unsigned char)*p) || *p == ',' || *p == '-'))
+ p++;
+ }
+
+ /*-- Now get hours minutes, seconds and ignore tenths --*/
+ for(q = p; *q && isdigit((unsigned char)*q); q++);
+ if(*q == ':' && d->hour == -1) {
+ d->hour = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->minute = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->sec = atoi(p);
+ while(*p && !isspace((unsigned char)*p))
+ p++;
+ }
+ }
+ }
+ while(*p && isspace((unsigned char)*p))
+ p++;
+
+
+ /*-- The time zone --*/
+ d->hours_off_gmt = 0;
+ d->min_off_gmt = 0;
+ if(*p) {
+ if((*p == '+' || *p == '-')
+ && isdigit((unsigned char)p[1])
+ && isdigit((unsigned char)p[2])
+ && isdigit((unsigned char)p[3])
+ && isdigit((unsigned char)p[4])
+ && !isdigit((unsigned char)p[5])) {
+ char tmp[3];
+ d->min_off_gmt = d->hours_off_gmt = (*p == '+' ? 1 : -1);
+ p++;
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ d->hours_off_gmt *= atoi(tmp);
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ d->min_off_gmt *= atoi(tmp);
+ } else {
+ for(n = 0; known_zones[n].zone; n++)
+ if(struncmp(p, known_zones[n].zone, known_zones[n].len) == 0){
+ d->hours_off_gmt = (int) known_zones[n].hour_offset;
+ d->min_off_gmt = (int) known_zones[n].min_offset;
+ break;
+ }
+ }
+ }
+
+ if(d->wkday == -1){
+ MESSAGECACHE elt;
+ struct tm *tm;
+ time_t t;
+
+ /*
+ * Not sure why we aren't just using this from the gitgo, but
+ * since not sure will just use it to repair wkday.
+ */
+ if(mail_parse_date(&elt, (unsigned char *) given_date)){
+ t = mail_longdate(&elt);
+ tm = localtime(&t);
+
+ if(tm)
+ d->wkday = tm->tm_wday;
+ }
+ }
+}
+
+
+char *
+convert_date_to_local(char *date)
+{
+ struct tm *tm;
+ time_t ltime;
+ static char datebuf[26];
+
+ ltime = date_to_local_time_t(date);
+ if(ltime == (time_t) -1)
+ return(date);
+
+ tm = localtime(&ltime);
+
+ if(tm == NULL)
+ return(date);
+
+ snprintf(datebuf, sizeof(datebuf), "%.3s, %d %.3s %d %02d:%02d:%02d",
+ day_abbrev(tm->tm_wday), tm->tm_mday, month_abbrev(tm->tm_mon+1),
+ tm->tm_year+1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ return(datebuf);
+}
+
+
+time_t
+date_to_local_time_t(char *date)
+{
+ time_t ourtime;
+ struct tm theirtime;
+ struct date d;
+ static int zone = 1000000; /* initialize timezone offset */
+ static int dst;
+
+ if(zone == 1000000){
+ int julian;
+ struct tm *tm;
+ time_t now;
+
+ zone = 0;
+ /* find difference between gmtime and localtime, from c-client do_date */
+ now = time((time_t *) 0);
+ if(now != (time_t) -1){
+ tm = gmtime(&now);
+ if(tm != NULL){
+ zone = tm->tm_hour * 60 + tm->tm_min; /* minutes */
+ julian = tm->tm_yday;
+
+ tm = localtime(&now);
+ dst = tm->tm_isdst; /* for converting back to our time */
+
+ zone = tm->tm_hour * 60 + tm->tm_min - zone;
+ if((julian = tm->tm_yday - julian) != 0)
+ zone += ((julian < 0) == (abs(julian) == 1)) ? -24*60 : 24*60;
+
+ zone *= 60; /* change to seconds */
+ }
+ }
+ }
+
+ parse_date(date, &d);
+
+ /* put d into struct tm so we can use mktime */
+ memset(&theirtime, 0, sizeof(theirtime));
+ theirtime.tm_year = d.year - 1900;
+ theirtime.tm_mon = d.month - 1;
+ theirtime.tm_mday = d.day;
+ theirtime.tm_hour = d.hour - d.hours_off_gmt;
+ theirtime.tm_min = d.minute - d.min_off_gmt;
+ theirtime.tm_sec = d.sec;
+
+ theirtime.tm_isdst = dst;
+
+ ourtime = mktime(&theirtime); /* still theirtime, actually */
+
+ /* convert to the time we want to show */
+ if(ourtime != (time_t) -1)
+ ourtime += zone;
+
+ return(ourtime);
+}
+
+
+/*----------------------------------------------------------------------
+ Create a little string of blanks of the specified length.
+ Max n is MAX_SCREEN_COLS. Can use up to e repeat_char results at once.
+ ----*/
+char *
+repeat_char(int n, int c)
+{
+ static char bb[3][MAX_SCREEN_COLS+1];
+ static int whichbb = 0;
+ char *b;
+
+ whichbb = (whichbb + 1) % 3;
+ b = bb[whichbb];
+
+ if(n > sizeof(bb[0]))
+ n = sizeof(bb[0]) - 1;
+
+ bb[whichbb][n--] = '\0';
+ while(n >= 0)
+ bb[whichbb][n--] = c;
+
+ return(bb[whichbb]);
+}
+
+
+/*----------------------------------------------------------------------
+ Format number as amount of bytes, appending Kb, Mb, Gb, bytes
+
+ Args: bytes -- number of bytes to format
+
+ Returns pointer to static string. The numbers are divided to produce a
+nice string with precision of about 2-4 digits
+ ----*/
+char *
+byte_string(long int bytes)
+{
+ char *a, aa[5];
+ char *abbrevs = "GMK";
+ long i, ones, tenths;
+ static char string[10];
+
+ ones = 0L;
+ tenths = 0L;
+
+ if(bytes == 0L){
+ strncpy(string, "0 bytes", sizeof(string));
+ string[sizeof(string)-1] = '\0';
+ }
+ else {
+ for(a = abbrevs, i = 1000000000; i >= 1; i /= 1000, a++) {
+ if(bytes > i) {
+ ones = bytes/i;
+ if(ones < 10L && i > 10L)
+ tenths = (bytes - (ones * i)) / (i / 10L);
+ break;
+ }
+ }
+
+ aa[0] = *a; aa[1] = '\0';
+
+ if(tenths == 0)
+ snprintf(string, sizeof(string), "%ld%s%s", ones, aa, *a ? "B" : "bytes");
+ else
+ snprintf(string, sizeof(string), "%ld.%ld%s%s", ones, tenths, aa, *a ? "B" : "bytes");
+ }
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Print a string corresponding to the number given:
+ 1st, 2nd, 3rd, 105th, 92342nd....
+ ----*/
+
+char *
+enth_string(int i)
+{
+ static char enth[10];
+
+ enth[0] = '\0';
+
+ switch (i % 10) {
+
+ case 1:
+ if( (i % 100 ) == 11)
+ snprintf(enth, sizeof(enth),"%dth", i);
+ else
+ snprintf(enth, sizeof(enth),"%dst", i);
+ break;
+
+ case 2:
+ if ((i % 100) == 12)
+ snprintf(enth, sizeof(enth), "%dth",i);
+ else
+ snprintf(enth, sizeof(enth), "%dnd",i);
+ break;
+
+ case 3:
+ if(( i % 100) == 13)
+ snprintf(enth, sizeof(enth), "%dth",i);
+ else
+ snprintf(enth, sizeof(enth), "%drd",i);
+ break;
+
+ default:
+ snprintf(enth, sizeof(enth),"%dth",i);
+ break;
+ }
+ return(enth);
+}
+
+
+/*
+ * Inserts newlines for folding at whitespace.
+ *
+ * Args src -- The source text.
+ * width -- Approximately where the fold should happen.
+ * maxwidth -- Maximum width we want to fold at.
+ * first_indent -- String to use as indent on first line.
+ * indent -- String to use as indent for subsequent folded lines.
+ * flags -- FLD_CRLF End of line is \r\n instead of \n.
+ * FLD_PWS PreserveWhiteSpace when folding. This is
+ * for vcard folding where CRLF SPACE is
+ * removed when unfolding, so we need to
+ * leave the space in. With rfc2822 unfolding
+ * only the CRLF is removed when unfolding.
+ *
+ * Returns An allocated string which caller should free.
+ */
+char *
+fold(char *src, int width, int maxwidth, char *first_indent, char *indent, unsigned int flags)
+{
+ char *next_piece, *res, *p;
+ int i, len = 0, starting_point, winner, eol, this_width;
+ int indent1 = 0, /* width of first_indent */
+ indent2 = 0, /* width of indent */
+ nbindent2 = 0, /* number of bytes in indent */
+ nb = 0; /* number of bytes needed */
+ int cr, preserve_ws;
+ char save_char;
+ char *endptr = NULL;
+ unsigned shorter, longer;
+ unsigned got_width;
+
+ cr = (flags & FLD_CRLF);
+ preserve_ws = (flags & FLD_PWS);
+
+ if(indent){
+ indent2 = (int) utf8_width(indent);
+ nbindent2 = strlen(indent);
+ }
+
+ if(first_indent){
+ indent1 = (int) utf8_width(first_indent);
+ nb = strlen(first_indent);
+ }
+
+ len = indent1;
+ next_piece = src;
+ eol = cr ? 2 : 1;
+ if(!src || !*src)
+ nb += eol;
+
+ /*
+ * We can't tell how much space is going to be needed without actually
+ * passing through the data to see.
+ */
+ while(next_piece && *next_piece){
+ if(next_piece != src && indent2){
+ len += indent2;
+ nb += nbindent2;
+ }
+
+ this_width = (int) utf8_width(next_piece);
+ if(this_width + len <= width){
+ nb += (strlen(next_piece) + eol);
+ break;
+ }
+ else{ /* fold it */
+ starting_point = width - len; /* space left on this line */
+ /* find a good folding spot */
+ winner = -1;
+ for(i = 0;
+ winner == -1 && (starting_point - i > 5 || i < maxwidth - width);
+ i++){
+
+ if((shorter=starting_point-i) > 5){
+ endptr = utf8_count_forw_width(next_piece, shorter, &got_width);
+ if(endptr && got_width == shorter && isspace((unsigned char) *endptr))
+ winner = (int) shorter;
+ }
+
+ if(winner == -1
+ && (longer=starting_point+i) && i < maxwidth - width){
+ endptr = utf8_count_forw_width(next_piece, longer, &got_width);
+ if(endptr && got_width == longer && isspace((unsigned char) *endptr))
+ winner = (int) longer;
+ }
+ }
+
+ if(winner == -1){ /* if no good folding spot, fold at width */
+ winner = starting_point;
+ endptr = NULL;
+ }
+
+ if(endptr == NULL){
+ endptr = utf8_count_forw_width(next_piece, (unsigned) winner, &got_width);
+ winner = (int) got_width;
+ }
+
+ nb += ((endptr - next_piece) + eol);
+ next_piece = endptr;
+ if(!preserve_ws && isspace((unsigned char) next_piece[0]))
+ next_piece++;
+ }
+
+ len = 0;
+ }
+
+ res = (char *) fs_get((nb+1) * sizeof(char));
+ p = res;
+ sstrncpy(&p, first_indent, nb+1-(p-res));
+ len = indent1;
+ next_piece = src;
+
+ while(next_piece && *next_piece){
+ if(next_piece != src && indent2){
+ sstrncpy(&p, indent, nb+1-(p-res));
+ len += indent2;
+ }
+
+ this_width = (int) utf8_width(next_piece);
+ if(this_width + len <= width){
+ sstrncpy(&p, next_piece, nb+1-(p-res));
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+
+ break;
+ }
+ else{ /* fold it */
+ starting_point = width - len; /* space left on this line */
+ /* find a good folding spot */
+ winner = -1;
+ for(i = 0;
+ winner == -1 && (starting_point - i > 5 || i < maxwidth - width);
+ i++){
+
+ if((shorter=starting_point-i) > 5){
+ endptr = utf8_count_forw_width(next_piece, shorter, &got_width);
+ if(endptr && got_width == shorter && isspace((unsigned char) *endptr))
+ winner = (int) shorter;
+ }
+
+ if(winner == -1
+ && (longer=starting_point+i) && i < maxwidth - width){
+ endptr = utf8_count_forw_width(next_piece, longer, &got_width);
+ if(endptr && got_width == longer && isspace((unsigned char) *endptr))
+ winner = (int) longer;
+ }
+ }
+
+ if(winner == -1){ /* if no good folding spot, fold at width */
+ winner = starting_point;
+ endptr = NULL;
+ }
+
+ if(endptr == NULL){
+ endptr = utf8_count_forw_width(next_piece, (unsigned) winner, &got_width);
+ winner = (int) got_width;
+ }
+
+ if(endptr){
+ save_char = *endptr;
+ *endptr = '\0';
+ sstrncpy(&p, next_piece, nb+1-(p-res));
+ *endptr = save_char;
+ next_piece = endptr;
+ }
+
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+
+ if(!preserve_ws && isspace((unsigned char) next_piece[0]))
+ next_piece++;
+ }
+
+ len = 0;
+ }
+
+ if(!src || !*src){
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+ }
+
+ if(p-res < nb+1)
+ *p = '\0';
+
+ res[nb] = '\0';
+
+ return(res);
+}
+
+
+/*
+ * strsquish - fancifies a string into the given buffer if it's too
+ * long to fit in the given width
+ */
+char *
+strsquish(char *buf, size_t buflen, char *src, int width)
+{
+ /*
+ * Replace strsquish() with calls to short_str().
+ */
+ if(width > 14)
+ return(short_str(src, buf, buflen, width, MidDots));
+ else
+ return(short_str(src, buf, buflen, width, FrontDots));
+}
+
+
+char *
+long2string(long int l)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%ld", l);
+ return(string);
+}
+
+
+char *
+ulong2string(unsigned long int l)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%lu", l);
+ return(string);
+}
+
+
+char *
+int2string(int i)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%d", i);
+ return(string);
+}
+
+
+/*
+ * strtoval - convert the given string to a positive integer.
+ */
+char *
+strtoval(char *s, int *val, int minmum, int maxmum, int otherok, char *errbuf,
+ size_t errbuflen, char *varname)
+{
+ int i = 0, neg = 1;
+ char *p = s, *errstr = NULL;
+
+ removing_leading_and_trailing_white_space(p);
+ for(; *p; p++)
+ if(isdigit((unsigned char) *p)){
+ i = (i * 10) + (*p - '0');
+ }
+ else if(*p == '-' && i == 0){
+ neg = -1;
+ }
+ else{
+ snprintf(errstr = errbuf, errbuflen,
+ "Non-numeric value ('%c' in \"%.8s\") in %s. Using \"%d\"",
+ *p, s, varname, *val);
+ return(errbuf);
+ }
+
+ i *= neg;
+
+ /* range describes acceptable values */
+ if(maxmum > minmum && (i < minmum || i > maxmum) && i != otherok)
+ snprintf(errstr = errbuf, errbuflen,
+ "%s of %d not supported (M%s %d). Using \"%d\"",
+ varname, i, (i > maxmum) ? "ax" : "in",
+ (i > maxmum) ? maxmum : minmum, *val);
+ /* range describes unacceptable values */
+ else if(minmum > maxmum && !(i < maxmum || i > minmum))
+ snprintf(errstr = errbuf, errbuflen, "%s of %d not supported. Using \"%d\"",
+ varname, i, *val);
+ else
+ *val = i;
+
+ return(errstr);
+}
+
+
+/*
+ * strtolval - convert the given string to a positive _long_ integer.
+ */
+char *
+strtolval(char *s, long int *val, long int minmum, long int maxmum, long int otherok,
+ char *errbuf, size_t errbuflen, char *varname)
+{
+ long i = 0, neg = 1L;
+ char *p = s, *errstr = NULL;
+
+ removing_leading_and_trailing_white_space(p);
+ for(; *p; p++)
+ if(isdigit((unsigned char) *p)){
+ i = (i * 10L) + (*p - '0');
+ }
+ else if(*p == '-' && i == 0L){
+ neg = -1L;
+ }
+ else{
+ snprintf(errstr = errbuf, errbuflen,
+ "Non-numeric value ('%c' in \"%.8s\") in %s. Using \"%ld\"",
+ *p, s, varname, *val);
+ return(errbuf);
+ }
+
+ i *= neg;
+
+ /* range describes acceptable values */
+ if(maxmum > minmum && (i < minmum || i > maxmum) && i != otherok)
+ snprintf(errstr = errbuf, errbuflen,
+ "%s of %ld not supported (M%s %ld). Using \"%ld\"",
+ varname, i, (i > maxmum) ? "ax" : "in",
+ (i > maxmum) ? maxmum : minmum, *val);
+ /* range describes unacceptable values */
+ else if(minmum > maxmum && !(i < maxmum || i > minmum))
+ snprintf(errstr = errbuf, errbuflen, "%s of %ld not supported. Using \"%ld\"",
+ varname, i, *val);
+ else
+ *val = i;
+
+ return(errstr);
+}
+
+
+/*
+ * Function to parse the given string into two space-delimited fields
+ * Quotes may be used to surround labels or values with spaces in them.
+ * Backslash negates the special meaning of a quote.
+ * Unescaping of backslashes only happens if the pair member is quoted,
+ * this provides for backwards compatibility.
+ *
+ * Args -- string -- the source string
+ * label -- the first half of the string, a return value
+ * value -- the last half of the string, a return value
+ * firstws -- if set, the halves are delimited by the first unquoted
+ * whitespace, else by the last unquoted whitespace
+ * strip_internal_label_quotes -- unescaped quotes in the middle of the label
+ * are removed. This is useful for vars
+ * like display-filters and url-viewers
+ * which may require quoting of an arg
+ * inside of a _TOKEN_.
+ */
+void
+get_pair(char *string, char **label, char **value, int firstws, int strip_internal_label_quotes)
+{
+ char *p, *q, *tmp, *token = NULL;
+ int quoted = 0;
+
+ *label = *value = NULL;
+
+ /*
+ * This for loop just finds the beginning of the value. If firstws
+ * is set, then it begins after the first whitespace. Otherwise, it begins
+ * after the last whitespace. Quoted whitespace doesn't count as
+ * whitespace. If there is no unquoted whitespace, then there is no
+ * label, there's just a value.
+ */
+ for(p = string; p && *p;){
+ if(*p == '"') /* quoted label? */
+ quoted = (quoted) ? 0 : 1;
+
+ if(*p == '\\' && *(p+1) == '"') /* escaped quote? */
+ p++; /* skip it... */
+
+ if(isspace((unsigned char)*p) && !quoted){ /* if space, */
+ while(*++p && isspace((unsigned char)*p)) /* move past it */
+ ;
+
+ if(!firstws || !token)
+ token = p; /* remember start of text */
+ }
+ else
+ p++;
+ }
+
+ if(token){ /* copy label */
+ *label = p = (char *)fs_get(((token - string) + 1) * sizeof(char));
+
+ /* make a copy of the string */
+ tmp = (char *)fs_get(((token - string) + 1) * sizeof(char));
+ strncpy(tmp, string, token - string);
+ tmp[token-string] = '\0';
+
+ removing_leading_and_trailing_white_space(tmp);
+ quoted = removing_double_quotes(tmp);
+
+ for(q = tmp; *q; q++){
+ if(quoted && *q == '\\' && (*(q+1) == '"' || *(q+1) == '\\'))
+ *p++ = *++q;
+ else if(!(strip_internal_label_quotes && *q == '"'))
+ *p++ = *q;
+ }
+
+ *p = '\0'; /* tie off label */
+ fs_give((void **)&tmp);
+ if(*label == '\0')
+ fs_give((void **)label);
+ }
+ else
+ token = string;
+
+ if(token){ /* copy value */
+ *value = p = (char *)fs_get((strlen(token) + 1) * sizeof(char));
+
+ tmp = cpystr(token);
+ removing_leading_and_trailing_white_space(tmp);
+ quoted = removing_double_quotes(tmp);
+
+ for(q = tmp; *q ; q++){
+ if(quoted && *q == '\\' && (*(q+1) == '"' || *(q+1) == '\\'))
+ *p++ = *++q;
+ else
+ *p++ = *q;
+ }
+
+ *p = '\0'; /* tie off value */
+ fs_give((void **)&tmp);
+ }
+}
+
+
+/*
+ * This is sort of the inverse of get_pair.
+ *
+ * Args -- label -- the first half of the string
+ * value -- the last half of the string
+ *
+ * Returns -- an allocated string which is "label" SPACE "value"
+ *
+ * Label and value are quoted separately. If quoting is needed (they contain
+ * whitespace) then backslash escaping is done inside the quotes for
+ * " and for \. If quoting is not needed, no escaping is done.
+ */
+char *
+put_pair(char *label, char *value)
+{
+ char *result, *lab = label, *val = value;
+ size_t l;
+
+ if(label && *label)
+ lab = quote_if_needed(label);
+
+ if(value && *value)
+ val = quote_if_needed(value);
+
+ l = strlen(lab) + strlen(val) +1;
+ result = (char *) fs_get((l+1) * sizeof(char));
+
+ snprintf(result, l+1, "%s%s%s",
+ lab ? lab : "",
+ (lab && lab[0] && val && val[0]) ? " " : "",
+ val ? val : "");
+
+ if(lab && lab != label)
+ fs_give((void **)&lab);
+ if(val && val != value)
+ fs_give((void **)&val);
+
+ return(result);
+}
+
+
+/*
+ * This is for put_pair type uses. It returns either an allocated
+ * string which is the quoted src string or it returns a pointer to
+ * the src string if no quoting is needed.
+ */
+char *
+quote_if_needed(char *src)
+{
+ char *result = src, *qsrc = NULL;
+
+ if(src && *src){
+ /* need quoting? */
+ if(strpbrk(src, " \t") != NULL)
+ qsrc = add_escapes(src, "\\\"", '\\', "", "");
+
+ if(qsrc && !*qsrc)
+ fs_give((void **)&qsrc);
+
+ if(qsrc){
+ size_t l;
+
+ l = strlen(qsrc)+2;
+ result = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(result, l+1, "\"%s\"", qsrc);
+ fs_give((void **)&qsrc);
+ }
+ }
+
+ return(result);
+}
+
+
+/*
+ * Convert a 1, 2, or 3-digit octal string into an 8-bit character.
+ * Only the first three characters of s will be used, and it is ok not
+ * to null-terminate it.
+ */
+int
+read_octal(char **s)
+{
+ register int i, j;
+
+ i = 0;
+ for(j = 0; j < 3 && **s >= '0' && **s < '8' ; (*s)++, j++)
+ i = (i * 8) + (int)(unsigned char)**s - '0';
+
+ return(i);
+}
+
+
+/*
+ * Convert two consecutive HEX digits to an integer. First two
+ * chars pointed to by "s" MUST already be tested for hexness.
+ */
+int
+read_hex(char *s)
+{
+ return(X2C(s));
+}
+
+
+/*
+ * Given a character c, put the 3-digit ascii octal value of that char
+ * in the 2nd argument, which must be at least 3 in length.
+ */
+void
+char_to_octal_triple(int c, char *octal)
+{
+ c &= 0xff;
+
+ octal[2] = (c % 8) + '0';
+ c /= 8;
+ octal[1] = (c % 8) + '0';
+ c /= 8;
+ octal[0] = c + '0';
+}
+
+
+/*
+ * Convert in memory string s to a C-style string, with backslash escapes
+ * like they're used in C character constants.
+ * Also convert leading spaces because read_pinerc deletes those
+ * if not quoted.
+ *
+ * Returns allocated C string version of s.
+ */
+char *
+string_to_cstring(char *s)
+{
+ char *b, *p;
+ int n, i, all_space_so_far = 1;
+
+ if(!s)
+ return(cpystr(""));
+
+ n = 20;
+ b = (char *)fs_get((n+1) * sizeof(char));
+ p = b;
+ *p = '\0';
+ i = 0;
+
+ while(*s){
+ if(*s != SPACE)
+ all_space_so_far = 0;
+
+ if(i + 4 > n){
+ /*
+ * The output string may overflow the output buffer.
+ * Make more room.
+ */
+ n += 20;
+ fs_resize((void **)&b, (n+1) * sizeof(char));
+ p = &b[i];
+ }
+ else{
+ switch(*s){
+ case '\n':
+ *p++ = '\\';
+ *p++ = 'n';
+ i += 2;
+ break;
+
+ case '\r':
+ *p++ = '\\';
+ *p++ = 'r';
+ i += 2;
+ break;
+
+ case '\t':
+ *p++ = '\\';
+ *p++ = 't';
+ i += 2;
+ break;
+
+ case '\b':
+ *p++ = '\\';
+ *p++ = 'b';
+ i += 2;
+ break;
+
+ case '\f':
+ *p++ = '\\';
+ *p++ = 'f';
+ i += 2;
+ break;
+
+ case '\\':
+ *p++ = '\\';
+ *p++ = '\\';
+ i += 2;
+ break;
+
+ case SPACE:
+ if(all_space_so_far){ /* use octal output */
+ *p++ = '\\';
+ char_to_octal_triple(*s, p);
+ p += 3;
+ i += 4;
+ break;
+ }
+ else{
+ /* fall through */
+ }
+
+
+ default:
+ if(*s >= SPACE && *s < '~' && *s != '\"' && *s != '$'){
+ *p++ = *s;
+ i++;
+ }
+ else{ /* use octal output */
+ *p++ = '\\';
+ char_to_octal_triple(*s, p);
+ p += 3;
+ i += 4;
+ }
+
+ break;
+ }
+
+ s++;
+ }
+ }
+
+ *p = '\0';
+ return(b);
+}
+
+
+/*
+ * Convert C-style string, with backslash escapes, into a hex string, two
+ * hex digits per character.
+ *
+ * Returns allocated hexstring version of s.
+ */
+char *
+cstring_to_hexstring(char *s)
+{
+ char *b, *p;
+ int n, i, c;
+
+ if(!s)
+ return(cpystr(""));
+
+ n = 20;
+ b = (char *)fs_get((n+1) * sizeof(char));
+ p = b;
+ *p = '\0';
+ i = 0;
+
+ while(*s){
+ if(i + 2 > n){
+ /*
+ * The output string may overflow the output buffer.
+ * Make more room.
+ */
+ n += 20;
+ fs_resize((void **)&b, (n+1) * sizeof(char));
+ p = &b[i];
+ }
+ else{
+ if(*s == '\\'){
+ s++;
+ switch(*s){
+ case 'n':
+ c = '\n';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'r':
+ c = '\r';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 't':
+ c = '\t';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'v':
+ c = '\v';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'b':
+ c = '\b';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'f':
+ c = '\f';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'a':
+ c = '\007';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\\':
+ c = '\\';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '?':
+ c = '?';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\'':
+ c = '\'';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\"':
+ c = '\"';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 0: /* reached end of s too early */
+ c = 0;
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ /* hex number */
+ case 'x':
+ s++;
+ if(isxpair(s)){
+ c = X2C(s);
+ s += 2;
+ }
+ else if(isxdigit((unsigned char)*s)){
+ c = XDIGIT2C(*s);
+ s++;
+ }
+ else
+ c = 0;
+
+ C2XPAIR(c, p);
+ i += 2;
+
+ break;
+
+ /* octal number */
+ default:
+ c = read_octal(&s);
+ C2XPAIR(c, p);
+ i += 2;
+
+ break;
+ }
+ }
+ else{
+ C2XPAIR(*s, p);
+ i += 2;
+ s++;
+ }
+ }
+ }
+
+ *p = '\0';
+ return(b);
+}
+
+
+/*
+ * Convert C-style string, with backslash escapes, into a regular string.
+ * Result goes in dst, which should be as big as src.
+ *
+ */
+void
+cstring_to_string(char *src, char *dst)
+{
+ char *p;
+ int c;
+
+ dst[0] = '\0';
+ if(!src)
+ return;
+
+ p = dst;
+
+ while(*src){
+ if(*src == '\\'){
+ src++;
+ switch(*src){
+ case 'n':
+ *p++ = '\n';
+ src++;
+ break;
+
+ case 'r':
+ *p++ = '\r';
+ src++;
+ break;
+
+ case 't':
+ *p++ = '\t';
+ src++;
+ break;
+
+ case 'v':
+ *p++ = '\v';
+ src++;
+ break;
+
+ case 'b':
+ *p++ = '\b';
+ src++;
+ break;
+
+ case 'f':
+ *p++ = '\f';
+ src++;
+ break;
+
+ case 'a':
+ *p++ = '\007';
+ src++;
+ break;
+
+ case '\\':
+ *p++ = '\\';
+ src++;
+ break;
+
+ case '?':
+ *p++ = '?';
+ src++;
+ break;
+
+ case '\'':
+ *p++ = '\'';
+ src++;
+ break;
+
+ case '\"':
+ *p++ = '\"';
+ src++;
+ break;
+
+ case 0: /* reached end of s too early */
+ src++;
+ break;
+
+ /* hex number */
+ case 'x':
+ src++;
+ if(isxpair(src)){
+ c = X2C(src);
+ src += 2;
+ }
+ else if(isxdigit((unsigned char)*src)){
+ c = XDIGIT2C(*src);
+ src++;
+ }
+ else
+ c = 0;
+
+ *p++ = c;
+
+ break;
+
+ /* octal number */
+ default:
+ c = read_octal(&src);
+ *p++ = c;
+ break;
+ }
+ }
+ else
+ *p++ = *src++;
+ }
+
+ *p = '\0';
+}
+
+
+/*
+ * Quotes /'s and \'s with \
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting added. Any / in the string is
+ * replaced with \/ and any \ is replaced with \\, and any
+ * " is replaced with \".
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_backslash_escapes(char *src)
+{
+ return(add_escapes(src, "/\\\"", '\\', "", ""));
+}
+
+
+/*
+ * Undoes backslash quoting of source string.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting removed or NULL. The string starts
+ * at src and goes until the end of src or until a / is reached. The
+ * / is not included in the string. /'s may be quoted by preceding
+ * them with a backslash (\) and \'s may also be quoted by
+ * preceding them with a \. In fact, \ quotes any character.
+ * Not quite, \nnn is octal escape, \xXX is hex escape.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+remove_backslash_escapes(char *src)
+{
+ char *ans = NULL, *q, *p;
+ int done = 0;
+
+ if(src){
+ p = q = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case '\\':
+ src++;
+ if(*src){
+ if(isdigit((unsigned char)*src))
+ *p++ = (char)read_octal(&src);
+ else if((*src == 'x' || *src == 'X') &&
+ *(src+1) && *(src+2) && isxpair(src+1)){
+ *p++ = (char)read_hex(src+1);
+ src += 3;
+ }
+ else
+ *p++ = *src++;
+ }
+
+ break;
+
+ case '\0':
+ case '/':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+
+ ans = cpystr(q);
+ fs_give((void **)&q);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Quote values for viewer-hdr-colors. We quote backslash, comma, and slash.
+ * Also replaces $ with $$.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting added.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_viewerhdr_escapes(char *src)
+{
+ char *tmp, *ans = NULL;
+
+ tmp = add_escapes(src, "/\\", '\\', ",", "");
+
+ if(tmp){
+ ans = dollar_escape_dollars(tmp);
+ fs_give((void **) &tmp);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Quote dollar sign by preceding it with another dollar sign. We use $$
+ * instead of \$ so that it will work for both PC-Pine and unix.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with $$ quoting added.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+dollar_escape_dollars(char *src)
+{
+ return(add_escapes(src, "$", '$', "", ""));
+}
+
+
+/*
+ * This adds the quoting for vcard backslash quoting.
+ * That is, commas are backslashed, backslashes are backslashed,
+ * semicolons are backslashed, and CRLFs are \n'd.
+ * This is thought to be correct for draft-ietf-asid-mime-vcard-06.txt, Apr 98.
+ */
+char *
+vcard_escape(char *src)
+{
+ char *p, *q;
+
+ q = add_escapes(src, ";,\\", '\\', "", "");
+ if(q){
+ /* now do CRLF -> \n in place */
+ for(p = q; *p != '\0'; p++)
+ if(*p == '\r' && *(p+1) == '\n'){
+ *p++ = '\\';
+ *p = 'n';
+ }
+ }
+
+ return(q);
+}
+
+
+/*
+ * This undoes the vcard backslash quoting.
+ *
+ * In particular, it turns \n into newline, \, into ',', \\ into \, \; -> ;.
+ * In fact, \<anything_else> is also turned into <anything_else>. The ID
+ * isn't clear on this.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+vcard_unescape(char *src)
+{
+ char *ans = NULL, *p;
+ int done = 0;
+
+ if(src){
+ p = ans = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case '\\':
+ src++;
+ if(*src == 'n' || *src == 'N'){
+ *p++ = '\n';
+ src++;
+ }
+ else if(*src)
+ *p++ = *src++;
+
+ break;
+
+ case '\0':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Turn folded lines into long lines in place.
+ *
+ * CRLF whitespace sequences are removed, the space is not preserved.
+ */
+void
+vcard_unfold(char *string)
+{
+ char *p = string;
+
+ while(*string) /* while something to copy */
+ if(*string == '\r' &&
+ *(string+1) == '\n' &&
+ (*(string+2) == SPACE || *(string+2) == TAB))
+ string += 3;
+ else
+ *p++ = *string++;
+
+ *p = '\0';
+}
+
+
+/*
+ * Quote specified chars with escape char.
+ *
+ * Args: src -- The source string.
+ * quote_these_chars -- Array of chars to quote
+ * quoting_char -- The quoting char to be used (e.g., \)
+ * hex_these_chars -- Array of chars to hex escape
+ * hex_these_quoted_chars -- Array of chars to hex escape if they are
+ * already quoted with quoting_char (that is,
+ * turn \, into hex comma)
+ *
+ * Returns: An allocated copy of string with quoting added.
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_escapes(char *src, char *quote_these_chars, int quoting_char,
+ char *hex_these_chars, char *hex_these_quoted_chars)
+{
+ char *ans = NULL;
+
+ if(!quote_these_chars)
+ panic("bad arg to add_escapes");
+
+ if(src){
+ char *q, *p, *qchar;
+
+ p = q = (char *)fs_get(2*strlen(src) + 1);
+
+ while(*src){
+ if(*src == quoting_char)
+ for(qchar = hex_these_quoted_chars; *qchar != '\0'; qchar++)
+ if(*(src+1) == *qchar)
+ break;
+
+ if(*src == quoting_char && *qchar){
+ src++; /* skip quoting_char */
+ *p++ = '\\';
+ *p++ = 'x';
+ C2XPAIR(*src, p);
+ src++; /* skip quoted char */
+ }
+ else{
+ for(qchar = quote_these_chars; *qchar != '\0'; qchar++)
+ if(*src == *qchar)
+ break;
+
+ if(*qchar){ /* *src is a char to be quoted */
+ *p++ = quoting_char;
+ *p++ = *src++;
+ }
+ else{
+ for(qchar = hex_these_chars; *qchar != '\0'; qchar++)
+ if(*src == *qchar)
+ break;
+
+ if(*qchar){ /* *src is a char to be escaped */
+ *p++ = '\\';
+ *p++ = 'x';
+ C2XPAIR(*src, p);
+ src++;
+ }
+ else /* a regular char */
+ *p++ = *src++;
+ }
+ }
+
+ }
+
+ *p = '\0';
+
+ ans = cpystr(q);
+ fs_give((void **)&q);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Copy a string enclosed in "" without fixing \" or \\. Skip past \"
+ * but copy it as is, removing only the enclosing quotes.
+ */
+char *
+copy_quoted_string_asis(char *src)
+{
+ char *q, *p;
+ int done = 0, quotes = 0;
+
+ if(src){
+ p = q = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case QUOTE:
+ if(++quotes == 2)
+ done++;
+ else
+ src++;
+
+ break;
+
+ case BSLASH: /* don't count \" as a quote, just copy */
+ if(*(src+1) == QUOTE){
+ if(quotes == 1){
+ *p++ = *src;
+ *p++ = *(src+1);
+ }
+
+ src += 2;
+ }
+ else{
+ if(quotes == 1)
+ *p++ = *src;
+
+ src++;
+ }
+
+ break;
+
+ case '\0':
+ fs_give((void **)&q);
+ return(NULL);
+
+ default:
+ if(quotes == 1)
+ *p++ = *src;
+
+ src++;
+
+ break;
+ }
+ }
+
+ *p = '\0';
+ }
+
+ return(q);
+}
+
+
+/*
+ * isxpair -- return true if the first two chars in string are
+ * hexidecimal characters
+ */
+int
+isxpair(char *s)
+{
+ return(isxdigit((unsigned char) *s) && isxdigit((unsigned char) *(s+1)));
+}
+
+
+
+
+
+/*
+ * * * * * * * something to help managing lists of strings * * * * * * * *
+ */
+
+
+STRLIST_S *
+new_strlist(char *name)
+{
+ STRLIST_S *sp = (STRLIST_S *) fs_get(sizeof(STRLIST_S));
+ memset(sp, 0, sizeof(STRLIST_S));
+ if(name)
+ sp->name = cpystr(name);
+
+ return(sp);
+}
+
+
+STRLIST_S *
+copy_strlist(STRLIST_S *src)
+{
+ STRLIST_S *ret = NULL, *sl, *ss, *new_sl;
+
+ if(src){
+ ss = NULL;
+ for(sl = src; sl; sl = sl->next){
+ new_sl = (STRLIST_S *) fs_get(sizeof(*new_sl));
+ memset((void *) new_sl, 0, sizeof(*new_sl));
+ if(sl->name)
+ new_sl->name = cpystr(sl->name);
+
+ if(ss){
+ ss->next = new_sl;
+ ss = ss->next;
+ }
+ else{
+ ret = new_sl;
+ ss = ret;
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Add the second list to the end of the first.
+ */
+void
+combine_strlists(STRLIST_S **first, STRLIST_S *second)
+{
+ STRLIST_S *sl;
+
+ if(!second)
+ return;
+
+ if(first){
+ if(*first){
+ for(sl = *first; sl->next; sl = sl->next)
+ ;
+
+ sl->next = second;
+ }
+ else
+ *first = second;
+ }
+}
+
+
+void
+free_strlist(STRLIST_S **strp)
+{
+ if(strp && *strp){
+ if((*strp)->next)
+ free_strlist(&(*strp)->next);
+
+ if((*strp)->name)
+ fs_give((void **) &(*strp)->name);
+
+ fs_give((void **) strp);
+ }
+}
diff --git a/pith/string.h b/pith/string.h
new file mode 100644
index 00000000..11c4d45f
--- /dev/null
+++ b/pith/string.h
@@ -0,0 +1,151 @@
+/*
+ * $Id: string.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STRING_INCLUDED
+#define PITH_STRING_INCLUDED
+
+
+/*
+ * Hex conversion aids
+ */
+#define HEX_ARRAY "0123456789ABCDEF"
+#define HEX_CHAR1(C) HEX_ARRAY[((C) & 0xf0) >> 4]
+#define HEX_CHAR2(C) HEX_ARRAY[(C) & 0xf]
+
+#define XDIGIT2C(C) ((C) - (isdigit((unsigned char) (C)) \
+ ? '0' : (isupper((unsigned char)(C))? '7' : 'W')))
+
+#define X2C(S) ((XDIGIT2C(*(S)) << 4) | XDIGIT2C(*((S)+1)))
+
+#define C2XPAIR(C, S) { \
+ *(S)++ = HEX_CHAR1(C); \
+ *(S)++ = HEX_CHAR2(C); \
+ }
+
+
+/* for flags to fold() routine */
+#define FLD_NONE 0x00
+#define FLD_CRLF 0x01 /* use CRLF end of line instead of LF */
+#define FLD_PWS 0x02 /* preserve whitespace when folding */
+
+
+typedef enum {FrontDots, MidDots, EndDots} WhereDots;
+
+
+/*
+ * Macro to help determine when we need to filter out chars
+ * from message index or headers...
+ */
+#define FILTER_THIS(c) (((((unsigned char) (c) < 0x20 \
+ || (unsigned char) (c) == 0x7f) \
+ && !ps_global->pass_ctrl_chars) \
+ || (((unsigned char) (c) >= 0x80 \
+ && (unsigned char) (c) < 0xA0) \
+ && !ps_global->pass_ctrl_chars \
+ && !ps_global->pass_c1_ctrl_chars)) \
+ && !((c) == SPACE \
+ || (c) == TAB \
+ || (c) == '\016' \
+ || (c) == '\017'))
+
+
+/*
+ * Keeps track of selected folders between instances of
+ * the folder list screen.
+ */
+typedef struct name_list {
+ char *name;
+ struct name_list *next;
+} STRLIST_S;
+
+
+struct date {
+ int sec, minute, hour, day, month,
+ year, hours_off_gmt, min_off_gmt, wkday;
+};
+
+
+/* just a convenient place to put these so everything can access */
+#define BUILDER_SCREEN_MANGLED 0x1
+#define BUILDER_MESSAGE_DISPLAYED 0x2
+#define BUILDER_FOOTER_MANGLED 0x4
+
+
+/* exported protoypes */
+char *rplstr(char *, size_t, int, char *);
+void sqzspaces(char *);
+void sqznewlines(char *);
+void removing_leading_white_space(char *);
+void removing_trailing_white_space(char *);
+void removing_leading_and_trailing_white_space(char *);
+int removing_double_quotes(char *);
+char *skip_white_space(char *);
+char *skip_to_white_space(char *);
+char *removing_quotes(char *);
+char *strclean(char *);
+char *short_str(char *, char *, size_t, int, WhereDots);
+char *srchstr(char *, char *);
+char *srchrstr(char *, char *);
+char *strindex(char *, int);
+char *strrindex(char *, int);
+char *iutf8ncpy(char *, char *, int);
+char *istrncpy(char *, char *, int);
+char *month_abbrev(int);
+char *month_abbrev_locale(int);
+char *month_name(int);
+char *month_name_locale(int);
+char *day_abbrev(int);
+char *day_abbrev_locale(int);
+char *day_name(int);
+char *day_name_locale(int);
+size_t our_strftime(char *, size_t, char *, struct tm *);
+int month_num(char *);
+void parse_date(char *, struct date *);
+char *convert_date_to_local(char *);
+char *repeat_char(int, int);
+char *byte_string(long);
+char *enth_string(int);
+char *fold(char *, int, int, char *, char *, unsigned);
+char *strsquish(char *, size_t, char *, int);
+char *long2string(long);
+char *ulong2string(unsigned long);
+char *int2string(int);
+char *strtoval(char *, int *, int, int, int, char *, size_t, char *);
+char *strtolval(char *, long *, long, long, long, char *, size_t, char *);
+void get_pair(char *, char **, char **, int, int);
+char *put_pair(char *, char *);
+char *quote_if_needed(char *);
+int read_hex(char *);
+char *string_to_cstring(char *);
+char *cstring_to_hexstring(char *);
+void cstring_to_string(char *, char *);
+char *add_backslash_escapes(char *);
+char *remove_backslash_escapes(char *);
+char *add_viewerhdr_escapes(char *);
+char *vcard_escape(char *);
+char *vcard_unescape(char *);
+void vcard_unfold(char *);
+char *add_escapes(char *, char *, int, char *, char *);
+char *copy_quoted_string_asis(char *);
+int isxpair(char *);
+STRLIST_S *new_strlist(char *);
+STRLIST_S *copy_strlist(STRLIST_S *);
+void combine_strlists(STRLIST_S **, STRLIST_S *);
+void free_strlist(STRLIST_S **);
+int read_octal(char **);
+
+
+#endif /* PITH_STRING_INCLUDED */
diff --git a/pith/strlst.c b/pith/strlst.c
new file mode 100644
index 00000000..52b28bca
--- /dev/null
+++ b/pith/strlst.c
@@ -0,0 +1,51 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: strlst.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ strlst.c
+ Implements STRINGLIST creation destruction routines
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/strlst.h"
+
+
+STRINGLIST *
+new_strlst(char **l)
+{
+ STRINGLIST *sl = mail_newstringlist();
+
+ sl->text.data = (unsigned char *) (*l);
+ sl->text.size = strlen(*l);
+ sl->next = (*++l) ? new_strlst(l) : NULL;
+ return(sl);
+}
+
+
+void
+free_strlst(struct string_list **sl)
+{
+ if(*sl){
+ if((*sl)->next)
+ free_strlst(&(*sl)->next);
+
+ fs_give((void **) sl);
+ }
+}
diff --git a/pith/strlst.h b/pith/strlst.h
new file mode 100644
index 00000000..df1bf856
--- /dev/null
+++ b/pith/strlst.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: strlst.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_STRLST_INCLUDED
+#define PITH_STRLST_INCLUDED
+
+
+/* exported protoypes */
+STRINGLIST *new_strlst(char **);
+void free_strlst(STRINGLIST **);
+
+
+#endif /* PITH_STRLST_INCLUDED */
diff --git a/pith/takeaddr.c b/pith/takeaddr.c
new file mode 100644
index 00000000..3dec5147
--- /dev/null
+++ b/pith/takeaddr.c
@@ -0,0 +1,2228 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: takeaddr.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ takeaddr.c
+ Mostly support for Take Address command.
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/takeaddr.h"
+#include "../pith/conf.h"
+#include "../pith/bldaddr.h"
+#include "../pith/adrbklib.h"
+#include "../pith/copyaddr.h"
+#include "../pith/addrstring.h"
+#include "../pith/status.h"
+#include "../pith/mailview.h"
+#include "../pith/reply.h"
+#include "../pith/url.h"
+#include "../pith/mailpart.h"
+#include "../pith/sequence.h"
+#include "../pith/stream.h"
+#include "../pith/busy.h"
+#include "../pith/ablookup.h"
+#include "../pith/list.h"
+
+
+static char *fakedomain = "@";
+long msgno_for_pico_callback;
+BODY *body_for_pico_callback = NULL;
+ENVELOPE *env_for_pico_callback = NULL;
+
+
+/*
+ * Previous selectable TA line.
+ * skips over the elements with skip_it set
+ */
+TA_S *
+pre_sel_taline(TA_S *current)
+{
+ TA_S *p;
+
+ if(!current)
+ return NULL;
+
+ p = current->prev;
+ while(p && p->skip_it)
+ p = p->prev;
+
+ return(p);
+}
+
+
+/*
+ * Previous TA line, selectable or just printable.
+ * skips over the elements with skip_it set
+ */
+TA_S *
+pre_taline(TA_S *current)
+{
+ TA_S *p;
+
+ if(!current)
+ return NULL;
+
+ p = current->prev;
+ while(p && (p->skip_it && !p->print))
+ p = p->prev;
+
+ return(p);
+}
+
+
+/*
+ * Next selectable TA line.
+ * skips over the elements with skip_it set
+ */
+TA_S *
+next_sel_taline(TA_S *current)
+{
+ TA_S *p;
+
+ if(!current)
+ return NULL;
+
+ p = current->next;
+ while(p && p->skip_it)
+ p = p->next;
+
+ return(p);
+}
+
+
+/*
+ * Next TA line, including print only lines.
+ * skips over the elements with skip_it set unless they are print lines
+ */
+TA_S *
+next_taline(TA_S *current)
+{
+ TA_S *p;
+
+ if(!current)
+ return NULL;
+
+ p = current->next;
+ while(p && (p->skip_it && !p->print))
+ p = p->next;
+
+ return(p);
+}
+
+
+/*
+ * Mark all of the addresses with a check.
+ *
+ * Args: f_line -- the first ta line
+ *
+ * Returns the number of lines checked.
+ */
+int
+ta_mark_all(TA_S *f_line)
+{
+ TA_S *ctmp;
+ int how_many_selected = 0;
+
+ for(ctmp = f_line; ctmp; ctmp = next_sel_taline(ctmp)){
+ ctmp->checked = 1;
+ how_many_selected++;
+ }
+
+ return(how_many_selected);
+}
+
+
+/*
+ * Does the takeaddr list consist of a single address?
+ *
+ * Args: f_line -- the first ta line
+ *
+ * Returns 1 if only one address and 0 otherwise.
+ */
+int
+is_talist_of_one(TA_S *f_line)
+{
+ if(f_line == NULL)
+ return 0;
+
+ /* there is at least one, see if there are two */
+ if(next_sel_taline(f_line) != NULL)
+ return 0;
+
+ return 1;
+}
+
+
+/*
+ * Turn off all of the check marks.
+ *
+ * Args: f_line -- the first ta line
+ *
+ * Returns the number of lines checked (0).
+ */
+int
+ta_unmark_all(TA_S *f_line)
+{
+ TA_S *ctmp;
+
+ for(ctmp = f_line; ctmp; ctmp = ctmp->next)
+ ctmp->checked = 0;
+
+ return 0;
+}
+
+
+/*
+ * new_taline - create new TA_S, zero it out, and insert it after current.
+ * NOTE current gets set to the new TA_S, too!
+ */
+TA_S *
+new_taline(TA_S **current)
+{
+ TA_S *p;
+
+ p = (TA_S *)fs_get(sizeof(TA_S));
+ memset((void *)p, 0, sizeof(TA_S));
+ if(current){
+ if(*current){
+ p->next = (*current)->next;
+ (*current)->next = p;
+ p->prev = *current;
+ if(p->next)
+ p->next->prev = p;
+ }
+
+ *current = p;
+ }
+
+ return(p);
+}
+
+
+void
+free_taline(TA_S **p)
+{
+ if(p && *p){
+ if((*p)->addr)
+ mail_free_address(&(*p)->addr);
+
+ if((*p)->strvalue)
+ fs_give((void **)&(*p)->strvalue);
+
+ if((*p)->nickname)
+ fs_give((void **)&(*p)->nickname);
+
+ if((*p)->fullname)
+ fs_give((void **)&(*p)->fullname);
+
+ if((*p)->fcc)
+ fs_give((void **)&(*p)->fcc);
+
+ if((*p)->comment)
+ fs_give((void **)&(*p)->comment);
+
+ if((*p)->prev)
+ (*p)->prev->next = (*p)->next;
+
+ if((*p)->next)
+ (*p)->next->prev = (*p)->prev;
+
+ fs_give((void **)p);
+ }
+}
+
+
+void
+free_talines(TA_S **ta_list)
+{
+ TA_S *current, *ctmp;
+
+ if(ta_list && (current = (*ta_list))){
+ while(current->prev)
+ current = current->prev;
+
+ while(current){
+ ctmp = current->next;
+ free_taline(&current);
+ current = ctmp;
+ }
+
+ *ta_list = NULL;
+ }
+}
+
+
+void
+free_ltline(LINES_TO_TAKE **p)
+{
+ if(p && *p){
+ if((*p)->printval)
+ fs_give((void **)&(*p)->printval);
+
+ if((*p)->exportval)
+ fs_give((void **)&(*p)->exportval);
+
+ if((*p)->prev)
+ (*p)->prev->next = (*p)->next;
+
+ if((*p)->next)
+ (*p)->next->prev = (*p)->prev;
+
+ fs_give((void **)p);
+ }
+}
+
+
+void
+free_ltlines(LINES_TO_TAKE **lt_list)
+{
+ LINES_TO_TAKE *current, *ctmp;
+
+ if(lt_list && (current = (*lt_list))){
+ while(current->prev)
+ current = current->prev;
+
+ while(current){
+ ctmp = current->next;
+ free_ltline(&current);
+ current = ctmp;
+ }
+
+ *lt_list = NULL;
+ }
+}
+
+
+/*
+ * Return the first selectable TakeAddr line.
+ *
+ * Args: q -- any line in the list
+ */
+TA_S *
+first_sel_taline(TA_S *q)
+{
+ TA_S *first;
+
+ if(q == NULL)
+ return NULL;
+
+ first = NULL;
+ /* back up to the head of the list */
+ while(q){
+ first = q;
+ q = pre_sel_taline(q);
+ }
+
+ /*
+ * If first->skip_it, that means we were already past the first
+ * legitimate line, so we have to look in the other direction.
+ */
+ if(first->skip_it)
+ first = next_sel_taline(first);
+
+ return(first);
+}
+
+
+/*
+ * Return the last selectable TakeAddr line.
+ *
+ * Args: q -- any line in the list
+ */
+TA_S *
+last_sel_taline(TA_S *q)
+{
+ TA_S *last;
+
+ if(q == NULL)
+ return NULL;
+
+ last = NULL;
+ /* go to the end of the list */
+ while(q){
+ last = q;
+ q = next_sel_taline(q);
+ }
+
+ /*
+ * If last->skip_it, that means we were already past the last
+ * legitimate line, so we have to look in the other direction.
+ */
+ if(last->skip_it)
+ last = pre_sel_taline(last);
+
+ return(last);
+}
+
+
+/*
+ * Return the first TakeAddr line, selectable or just printable.
+ *
+ * Args: q -- any line in the list
+ */
+TA_S *
+first_taline(TA_S *q)
+{
+ TA_S *first;
+
+ if(q == NULL)
+ return NULL;
+
+ first = NULL;
+ /* back up to the head of the list */
+ while(q){
+ first = q;
+ q = pre_taline(q);
+ }
+
+ /*
+ * If first->skip_it, that means we were already past the first
+ * legitimate line, so we have to look in the other direction.
+ */
+ if(first->skip_it && !first->print)
+ first = next_taline(first);
+
+ return(first);
+}
+
+
+/*
+ * Find the first TakeAddr line which is checked, beginning with the
+ * passed in line.
+ *
+ * Args: head -- A passed in TakeAddr line, usually will be the first
+ *
+ * Result: returns a pointer to the first checked line.
+ * NULL if no such line
+ */
+TA_S *
+first_checked(TA_S *head)
+{
+ TA_S *p;
+
+ p = head;
+
+ for(p = head; p; p = next_sel_taline(p))
+ if(p->checked && !p->skip_it)
+ break;
+
+ return(p);
+}
+
+
+/*
+ * Form a list of strings which are addresses to go in a list.
+ * These are entries in a list, so can be full rfc822 addresses.
+ * The strings are allocated here.
+ *
+ * Args: head -- A passed in TakeAddr line, usually will be the first
+ *
+ * Result: returns an allocated array of pointers to allocated strings
+ */
+char **
+list_of_checked(TA_S *head)
+{
+ TA_S *p;
+ int count;
+ char **list, **cur;
+ ADDRESS *a;
+
+ /* first count them */
+ for(p = head, count = 0; p; p = next_sel_taline(p)){
+ if(p->checked && !p->skip_it){
+ if(p->frwrded){
+ /*
+ * Remove fullname, fcc, comment, and nickname since not
+ * appropriate for list values.
+ */
+ if(p->fullname)
+ fs_give((void **)&p->fullname);
+ if(p->fcc)
+ fs_give((void **)&p->fcc);
+ if(p->comment)
+ fs_give((void **)&p->comment);
+ if(p->nickname)
+ fs_give((void **)&p->nickname);
+
+ for(a = p->addr; a; a = a->next)
+ count++;
+ }
+ else{
+ /*
+ * Don't even attempt to include bogus addresses in
+ * the list. If the user wants to get at those, they
+ * have to try taking only that single address.
+ */
+ if(p->addr && p->addr->host && p->addr->host[0] == '.')
+ p->skip_it = 1;
+ else
+ count++;
+ }
+ }
+ }
+
+ /* allocate pointers */
+ list = (char **)fs_get((count + 1) * sizeof(char *));
+ memset((void *)list, 0, (count + 1) * sizeof(char *));
+
+ cur = list;
+
+ /* allocate and point to address strings */
+ for(p = head; p; p = next_sel_taline(p)){
+ if(p->checked && !p->skip_it){
+ if(p->frwrded)
+ for(a = p->addr; a; a = a->next){
+ ADDRESS *next_addr;
+ char *bufp;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ *cur++ = cpystr(addr_string(a, bufp, len));
+ a->next = next_addr;
+ fs_give((void **)&bufp);
+ }
+ else
+ *cur++ = cpystr(p->strvalue);
+ }
+ }
+
+ return(list);
+}
+
+/* jpf: This used to be the guts of cmd_take_addr, but I made this
+ * function so I could generalize with WP. It still has some display
+ * stuff that we need make sure not to do when in WP mode.
+ *
+ * Set things up for when we take addresses
+ *
+ * Args: ps -- pine state
+ * msgmap -- the MessageMap
+ * ta_ret -- the take addr lines
+ * selected_num -- the number of selectable addresses
+ * flags -- takeaddr flags, like whether or not
+ * we're doing aggs, and whether to do prompts
+ *
+ * Returns: -1 on "failure"
+ */
+int
+set_up_takeaddr(int cmd, struct pine *ps, MSGNO_S *msgmap, TA_S **ta_ret,
+ int *selected_num, int flags, int (*att_addr_f)(TA_S *, int))
+{
+ long i;
+ ENVELOPE *env;
+ int how_many_selected = 0,
+ added, rtype = 0,
+ we_cancel = 0,
+ special_processing = 0,
+ select_froms = 0;
+ TA_S *current = NULL,
+ *prev_comment_line,
+ *ta;
+ BODY **body_h,
+ *special_body = NULL,
+ *body = NULL;
+
+ dprint((2, "\n - taking address into address book - \n"));
+
+ if(!(cmd == 'a' || cmd == 'e'))
+ return -1;
+
+ if((flags & TA_AGG) && !pseudo_selected(ps_global->mail_stream, msgmap))
+ return -1;
+
+ if(mn_get_total(msgmap) > 0 && mn_total_cur(msgmap) == 1)
+ special_processing = 1;
+
+ if(flags & TA_AGG)
+ select_froms = 1;
+
+ /* this is a non-selectable label */
+ current = fill_in_ta(&current, (ADDRESS *) NULL, 0,
+ /* TRANSLATORS: This is a heading describing some addresses the
+ user will have the chance to choose from. */
+ _(" These entries are taken from the attachments "));
+ prev_comment_line = current;
+
+ /*
+ * Add addresses from special attachments for each message.
+ */
+ added = 0;
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
+ env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i), &body);
+ if(!env){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't take address into address book. Error accessing folder"));
+ rtype = -1;
+ goto doneskee;
+ }
+
+ added += process_vcard_atts(ps->mail_stream, mn_m2raw(msgmap, i),
+ body, body, NULL, &current);
+ }
+
+ if(!(flags & TA_AGG)
+ && added > 1
+ && att_addr_f
+ && (*att_addr_f)(current, added) <= 0)
+ goto doneskee;
+
+ if(!(flags & TA_NOPROMPT))
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ /*
+ * add a comment line to separate attachment address lines
+ * from header address lines
+ */
+ if(added > 0){
+ current = fill_in_ta(&current, (ADDRESS *) NULL, 0,
+ /* TRANSLATORS: msg is message */
+ _(" These entries are taken from the msg headers "));
+ prev_comment_line = current;
+ how_many_selected += added;
+ select_froms = 0;
+ }
+ else{ /* turn off header comment, and no separator comment */
+ prev_comment_line->print = 0;
+ prev_comment_line = NULL;
+ }
+
+ /*
+ * Add addresses from headers of messages.
+ */
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
+
+ if(special_processing)
+ body_h = &special_body;
+ else
+ body_h = NULL;
+
+ env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i),
+ body_h);
+ if(!env){
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't take address into address book. Error accessing folder"));
+ rtype = -1;
+ goto doneskee;
+ }
+
+ added = add_addresses_to_talist(ps, i, "from", &current,
+ env->from, select_froms);
+ if(select_froms)
+ how_many_selected += added;
+
+ if(!address_is_same(env->from, env->reply_to))
+ (void)add_addresses_to_talist(ps, i, "reply-to", &current,
+ env->reply_to, 0);
+
+ if(!address_is_same(env->from, env->sender))
+ (void)add_addresses_to_talist(ps, i, "sender", &current,
+ env->sender, 0);
+
+ (void)add_addresses_to_talist(ps, i, "to", &current, env->to, 0);
+ (void)add_addresses_to_talist(ps, i, "cc", &current, env->cc, 0);
+ (void)add_addresses_to_talist(ps, i, "bcc", &current, env->bcc, 0);
+ }
+
+ /*
+ * Check to see if we added an explanatory line about the
+ * header addresses but no header addresses. If so, remove the
+ * explanatory line.
+ */
+ if(prev_comment_line){
+ how_many_selected -=
+ eliminate_dups_and_us(first_sel_taline(current));
+ for(ta = prev_comment_line->next; ta; ta = ta->next)
+ if(!ta->skip_it)
+ break;
+
+ /* all entries were skip_it entries, turn off print */
+ if(!ta){
+ prev_comment_line->print = 0;
+ prev_comment_line = NULL;
+ }
+ }
+
+ /*
+ * If there's only one message we let the user view it using ^T
+ * from the header editor.
+ */
+ if(!(flags & TA_NOPROMPT) && special_processing && env && special_body){
+ msgno_for_pico_callback = mn_m2raw(msgmap, mn_first_cur(msgmap));
+ body_for_pico_callback = special_body;
+ env_for_pico_callback = env;
+ }
+ else{
+ env_for_pico_callback = NULL;
+ body_for_pico_callback = NULL;
+ }
+
+ current = fill_in_ta(&current, (ADDRESS *)NULL, 0,
+ _(" Below this line are some possibilities taken from the text of the msg "));
+ prev_comment_line = current;
+
+ /*
+ * Add addresses from the text of the body.
+ */
+ added = 0;
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
+
+ body = NULL;
+ env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i),
+ &body);
+ if(env && body)
+ added += grab_addrs_from_body(ps->mail_stream,
+ mn_m2raw(msgmap, i),
+ body, &current);
+ }
+
+ if(added == 0){
+ prev_comment_line->print = 0;
+ prev_comment_line = NULL;
+ }
+
+ /*
+ * Check to see if all we added are dups.
+ * If so, remove the comment line.
+ */
+ if(prev_comment_line){
+ how_many_selected -= eliminate_dups_and_us(first_sel_taline(current));
+ for(ta = prev_comment_line->next; ta; ta = ta->next)
+ if(!ta->skip_it)
+ break;
+
+ /* all entries were skip_it entries, turn off print */
+ if(!ta)
+ prev_comment_line->print = 0;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+doneskee:
+ env_for_pico_callback = NULL;
+ body_for_pico_callback = NULL;
+
+ ps->mangled_screen = 1;
+
+ if(flags & TA_AGG)
+ restore_selected(msgmap);
+
+ *ta_ret = current;
+
+ if(selected_num)
+ *selected_num = how_many_selected;
+
+ return(rtype);
+}
+
+
+int
+convert_ta_to_lines(TA_S *ta_list, LINES_TO_TAKE **old_current)
+{
+ ADDRESS *a;
+ TA_S *ta;
+ LINES_TO_TAKE *new_current;
+ char *exportval, *printval;
+ int ret = 0;
+
+ for(ta = first_sel_taline(ta_list);
+ ta;
+ ta = next_sel_taline(ta)){
+ if(ta->skip_it)
+ continue;
+
+ if(ta->frwrded){
+ if(ta->fullname)
+ fs_give((void **)&ta->fullname);
+ if(ta->fcc)
+ fs_give((void **)&ta->fcc);
+ if(ta->comment)
+ fs_give((void **)&ta->comment);
+ if(ta->nickname)
+ fs_give((void **)&ta->nickname);
+ }
+ else if(ta->addr && ta->addr->host && ta->addr->host[0] == '.')
+ ta->skip_it = 1;
+
+ if(ta->frwrded){
+ for(a = ta->addr; a; a = a->next){
+ ADDRESS *next_addr;
+
+ if(a->host && a->host[0] == '.')
+ continue;
+
+ next_addr = a->next;
+ a->next = NULL;
+
+ exportval = cpystr(simple_addr_string(a, tmp_20k_buf,
+ SIZEOF_20KBUF));
+ if(!exportval || !exportval[0]){
+ if(exportval)
+ fs_give((void **)&exportval);
+
+ a->next = next_addr;
+ continue;
+ }
+
+ printval = addr_list_string(a, NULL, 0);
+ if(!printval || !printval[0]){
+ if(printval)
+ fs_give((void **)&printval);
+
+ printval = cpystr(exportval);
+ }
+
+ new_current = new_ltline(old_current);
+ new_current->flags = LT_NONE;
+
+ new_current->printval = printval;
+ new_current->exportval = exportval;
+ a->next = next_addr;
+ ret++;
+ }
+ }
+ else{
+ if(ta->addr){
+ exportval = cpystr(simple_addr_string(ta->addr, tmp_20k_buf,
+ SIZEOF_20KBUF));
+ if(exportval && exportval[0]){
+ new_current = new_ltline(old_current);
+ new_current->flags = LT_NONE;
+
+ new_current->exportval = exportval;
+
+ if(ta->strvalue && ta->strvalue[0])
+ new_current->printval = cpystr(ta->strvalue);
+ else
+ new_current->printval = cpystr(new_current->exportval);
+
+ ret++;
+ }
+ else if(exportval)
+ fs_give((void **)&exportval);
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * new_ltline - create new LINES_TO_TAKE, zero it out,
+ * and insert it after current.
+ * NOTE current gets set to the new current.
+ */
+LINES_TO_TAKE *
+new_ltline(LINES_TO_TAKE **current)
+{
+ LINES_TO_TAKE *p;
+
+ p = (LINES_TO_TAKE *)fs_get(sizeof(LINES_TO_TAKE));
+ memset((void *)p, 0, sizeof(LINES_TO_TAKE));
+ if(current){
+ if(*current){
+ p->next = (*current)->next;
+ (*current)->next = p;
+ p->prev = *current;
+ if(p->next)
+ p->next->prev = p;
+ }
+
+ *current = p;
+ }
+
+ return(p);
+}
+
+
+int
+add_addresses_to_talist(struct pine *ps, long int msgno, char *field,
+ TA_S **old_current, struct mail_address *adrlist, int checked)
+{
+ ADDRESS *addr;
+ int added = 0;
+
+ for(addr = adrlist; addr; addr = addr->next){
+ if(addr->host && addr->host[0] == '.'){
+ char *h, *fields[2];
+
+ fields[0] = field;
+ fields[1] = NULL;
+ if((h = pine_fetchheader_lines(ps->mail_stream, msgno,NULL,fields)) != NULL){
+ char *p, fname[32];
+ int q;
+
+ q = strlen(h);
+
+ snprintf(fname, sizeof(fname), "%s:", field);
+ for(p = h; (p = strstr(p, fname)) != NULL; )
+ rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
+
+ sqznewlines(h); /* blat out CR's & LF's */
+ for(p = h; (p = strchr(p, TAB)) != NULL; )
+ *p++ = ' '; /* turn TABs to space */
+
+ if(*h){
+ unsigned long l;
+ ADDRESS *new_addr;
+ size_t ll;
+
+ new_addr = (ADDRESS *)fs_get(sizeof(ADDRESS));
+ memset(new_addr, 0, sizeof(ADDRESS));
+
+ /*
+ * Base64 armor plate the gunk to protect against
+ * c-client quoting in output.
+ */
+ p = (char *)rfc822_binary((void *)h,
+ (unsigned long)strlen(h), &l);
+ sqznewlines(p);
+ ll = strlen(p) + 3;
+ new_addr->mailbox = (char *) fs_get((ll+1) * sizeof(char));
+ snprintf(new_addr->mailbox, ll+1, "&%s", p);
+ fs_give((void **)&p);
+ new_addr->host = cpystr(RAWFIELD);
+ fill_in_ta(old_current, new_addr, 0, (char *)NULL);
+ mail_free_address(&new_addr);
+ }
+
+ fs_give((void **)&h);
+ }
+
+ return(0);
+ }
+ }
+
+ /* no syntax errors in list, add them all */
+ for(addr = adrlist; addr; addr = addr->next){
+ fill_in_ta(old_current, addr, checked, (char *)NULL);
+ added++;
+ }
+
+ return(added);
+}
+
+
+/*
+ * Look for Text/directory attachments and process them.
+ * Return number of lines added to the ta_list.
+ */
+int
+process_vcard_atts(MAILSTREAM *stream, long int msgno,
+ struct mail_bodystruct *root, struct mail_bodystruct *body,
+ char *partnum, TA_S **ta_list)
+{
+ char *addrs, /* comma-separated list of addresses */
+ *value,
+ *encoded,
+ *escval,
+ *nickname,
+ *fullname,
+ *struct_name,
+ *fcc,
+ *tag,
+ *decoded,
+ *num = NULL,
+ *defaulttype = NULL,
+ *charset = NULL,
+ *altcharset,
+ *comma,
+ *p,
+ *comment,
+ *title,
+ **lines = NULL,
+ **ll;
+ size_t space;
+ int used = 0,
+ is_encoded = 0,
+ vcard_nesting = 0,
+ selected = 0;
+ PART *part;
+ PARAMETER *parm;
+ static char a_comma = ',';
+
+ /*
+ * Look through all the subparts searching for our special type.
+ */
+ if(body && body->type == TYPEMULTIPART){
+ for(part = body->nested.part; part; part = part->next)
+ selected += process_vcard_atts(stream, msgno, root, &part->body,
+ partnum, ta_list);
+ }
+ /* only look in rfc822 subtype of type message */
+ else if(body && body->type == TYPEMESSAGE
+ && body->subtype && !strucmp(body->subtype, "rfc822")){
+ selected += process_vcard_atts(stream, msgno, root,
+ body->nested.msg->body,
+ partnum, ta_list);
+ }
+ /* found one (TYPEAPPLICATION for back compat.) */
+ else if(body && MIME_VCARD(body->type,body->subtype)){
+
+ dprint((4, "\n - found abook attachment to process - \n"));
+
+ /*
+ * The Text/directory type has a possible parameter called
+ * "defaulttype" that we need to look for (this is from an old draft
+ * and is supported only for backwards compatibility). There is
+ * also a possible default "charset". (Default charset is also from
+ * an old draft and is no longer allowed.) We don't care about any of
+ * the other parameters.
+ */
+ for(parm = body->parameter; parm; parm = parm->next)
+ if(!strucmp("defaulttype", parm->attribute))
+ break;
+
+ if(parm)
+ defaulttype = parm->value;
+
+ /* ...and look for possible charset parameter */
+ for(parm = body->parameter; parm; parm = parm->next)
+ if(!strucmp("charset", parm->attribute))
+ break;
+
+ if(parm)
+ charset = parm->value;
+
+ num = partnum ? cpystr(partnum) : partno(root, body);
+ lines = detach_vcard_att(stream, msgno, body, num);
+ if(num)
+ fs_give((void **)&num);
+
+ nickname = fullname = comment = title = fcc = struct_name = NULL;
+#define CHUNK (size_t)500
+ space = CHUNK;
+ /* make comma-separated list of email addresses in addrs */
+ addrs = (char *)fs_get((space+1) * sizeof(char));
+ *addrs = '\0';
+ for(ll = lines; ll && *ll; ll++){
+ altcharset = NULL;
+ decoded = NULL;
+ escval = NULL;
+ is_encoded = 0;
+ value = getaltcharset(*ll, &tag, &altcharset, &is_encoded);
+
+ if(!value || !tag){
+ if(tag)
+ fs_give((void **)&tag);
+
+ if(altcharset)
+ fs_give((void **)&altcharset);
+
+ continue;
+ }
+
+ if(is_encoded){
+ unsigned long length;
+
+ decoded = (char *)rfc822_base64((unsigned char *)value,
+ (unsigned long)strlen(value),
+ &length);
+ if(decoded){
+ decoded[length] = '\0';
+ value = decoded;
+ }
+ else
+ value = "<malformed B64 data>";
+ }
+
+ /* support default tag (back compat.) */
+ if(*tag == '\0' && defaulttype){
+ fs_give((void **)&tag);
+ tag = cpystr(defaulttype);
+ }
+
+ if(!strucmp(tag, "begin")){ /* vCard BEGIN */
+ vcard_nesting++;
+ }
+ else if(!strucmp(tag, "end")){ /* vCard END */
+ if(--vcard_nesting == 0){
+ if(title){
+ if(!comment)
+ comment = title;
+ else
+ fs_give((void **)&title);
+ }
+
+ /* add this entry */
+ selected += vcard_to_ta(addrs, fullname, struct_name,
+ nickname, comment, fcc, ta_list);
+ if(addrs)
+ *addrs = '\0';
+
+ used = 0;
+ nickname = fullname = comment = title = fcc = NULL;
+ struct_name = NULL;
+ }
+ }
+ /* add another address to addrs */
+ else if(!strucmp(tag, "email")){
+ if(*value){
+ escval = vcard_unescape(value);
+ encoded = encode_fullname_of_addrstring(escval,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ if(encoded){
+ /* allocate more space */
+ if((used + strlen(encoded) + 1) > space){
+ space += CHUNK;
+ fs_resize((void **)&addrs, (space+1)*sizeof(char));
+ }
+
+ if(*addrs){
+ strncat(addrs, ",", space+1-1-strlen(addrs));
+ addrs[space-1] = '\0';
+ }
+
+ strncat(addrs, encoded, space+1-1-strlen(addrs));
+ addrs[space-1] = '\0';
+ used += (strlen(encoded) + 1);
+ fs_give((void **)&encoded);
+ }
+ }
+ }
+ else if(!strucmp(tag, "note") || !strucmp(tag, "misc")){
+ if(*value){
+ escval = vcard_unescape(value);
+ encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *)escval,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ if(encoded){
+ /* Last one wins */
+ if(comment)
+ fs_give((void **)&comment);
+
+ comment = cpystr(encoded);
+ }
+ }
+ }
+ else if(!strucmp(tag, "title")){
+ if(*value){
+ escval = vcard_unescape(value);
+ encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *)escval,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ if(encoded){
+ /* Last one wins */
+ if(title)
+ fs_give((void **)&title);
+
+ title = cpystr(encoded);
+ }
+ }
+ }
+ else if(!strucmp(tag, "x-fcc")){
+ if(*value){
+ escval = vcard_unescape(value);
+ encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *)escval,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ if(encoded){
+ /* Last one wins */
+ if(fcc)
+ fs_give((void **)&fcc);
+
+ fcc = cpystr(encoded);
+ }
+ }
+ }
+ else if(!strucmp(tag, "fn") || !strucmp(tag, "cn")){
+ if(*value){
+ escval = vcard_unescape(value);
+ encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF,
+ (unsigned char *)escval,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ if(encoded){
+ /* Last one wins */
+ if(fullname)
+ fs_give((void **)&fullname);
+
+ fullname = cpystr(encoded);
+ }
+ }
+ }
+ else if(!strucmp(tag, "n")){
+ /*
+ * This is a structured name field. It has up to five
+ * fields separated by ';'. The fields are Family Name (which
+ * we'll take to be Last name for our purposes), Given Name
+ * (First name for us), additional names, prefixes, and
+ * suffixes. We'll ignore prefixes and suffixes.
+ *
+ * If we find one of these records we'll use it in preference
+ * to the formatted name field (fn).
+ */
+ if(*value && *value != ';'){
+ char *last, *first, *middle = NULL, *rest = NULL;
+ char *esclast, *escfirst, *escmiddle;
+ static char a_semi = ';';
+
+ last = value;
+
+ first = NULL;
+ p = last;
+ while(p && (first = strindex(p, a_semi)) != NULL){
+ if(first > last && first[-1] != '\\')
+ break;
+ else
+ p = first + 1;
+ }
+
+ if(first)
+ *first = '\0';
+
+ if(first && *(first+1) && *(first+1) != a_semi){
+ first++;
+
+ middle = NULL;
+ p = first;
+ while(p && (middle = strindex(p, a_semi)) != NULL){
+ if(middle > first && middle[-1] != '\\')
+ break;
+ else
+ p = middle + 1;
+ }
+
+ if(middle)
+ *middle = '\0';
+
+ if(middle && *(middle+1) && *(middle+1) != a_semi){
+ middle++;
+
+ rest = NULL;
+ p = middle;
+ while(p && (rest = strindex(p, a_semi)) != NULL){
+ if(rest > middle && rest[-1] != '\\')
+ break;
+ else
+ p = rest + 1;
+ }
+
+ /* we don't care about the rest */
+ if(rest)
+ *rest = '\0';
+ }
+ else
+ middle = NULL;
+ }
+ else
+ first = NULL;
+
+ /*
+ * Structured name pieces can have multiple values.
+ * We're just going to take the first value in each part.
+ * Skip excaped commas, though.
+ */
+ p = last;
+ while(p && (comma = strindex(p, a_comma)) != NULL){
+ if(comma > last && comma[-1] != '\\'){
+ *comma = '\0';
+ break;
+ }
+ else
+ p = comma + 1;
+ }
+
+ p = first;
+ while(p && (comma = strindex(p, a_comma)) != NULL){
+ if(comma > first && comma[-1] != '\\'){
+ *comma = '\0';
+ break;
+ }
+ else
+ p = comma + 1;
+ }
+
+ p = middle;
+ while(p && (comma = strindex(p, a_comma)) != NULL){
+ if(comma > middle && comma[-1] != '\\'){
+ *comma = '\0';
+ break;
+ }
+ else
+ p = comma + 1;
+ }
+
+ esclast = vcard_unescape(last);
+ escfirst = vcard_unescape(first);
+ escmiddle = vcard_unescape(middle);
+ snprintf(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, "%s%s%s%s%s",
+ esclast ? esclast : "",
+ escfirst ? ", " : "",
+ escfirst ? escfirst : "",
+ escmiddle ? " " : "",
+ escmiddle ? escmiddle : "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF-10000,
+ (unsigned char *)tmp_20k_buf+10000,
+ (altcharset && *altcharset) ? altcharset
+ : (charset && *charset)
+ ? charset
+ : ps_global->posting_charmap);
+ tmp_20k_buf[SIZEOF_20KBUF-10000-1] = '\0';
+
+ if(esclast && *esclast && escfirst && *escfirst){
+ if(struct_name)
+ fs_give((void **)&struct_name);
+
+ struct_name = cpystr(encoded);
+ }
+ else{
+ /* in case we don't get a fullname better than this */
+ if(!fullname)
+ fullname = cpystr(encoded);
+ }
+
+ if(esclast)
+ fs_give((void **)&esclast);
+ if(escfirst)
+ fs_give((void **)&escfirst);
+ if(escmiddle)
+ fs_give((void **)&escmiddle);
+ }
+ }
+ /* suggested nickname */
+ else if(!strucmp(tag, "nickname") || !strucmp(tag, "x-nickname")){
+ if(*value){
+ /* Last one wins */
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ /*
+ * Nickname can have multiple values. We're just
+ * going to take the first. Skip escaped commas, though.
+ */
+ p = value;
+ while(p && (comma = strindex(p, a_comma)) != NULL){
+ if(comma > value && comma[-1] != '\\'){
+ *comma = '\0';
+ break;
+ }
+ else
+ p = comma + 1;
+ }
+
+ nickname = vcard_unescape(value);
+ }
+ }
+
+ if(tag)
+ fs_give((void **)&tag);
+
+ if(altcharset)
+ fs_give((void **)&altcharset);
+
+ if(decoded)
+ fs_give((void **)&decoded);
+
+ if(escval)
+ fs_give((void **)&escval);
+ }
+
+ if(title){
+ if(!comment)
+ comment = title;
+ else
+ fs_give((void **)&title);
+ }
+
+ if(fullname || struct_name || nickname || fcc
+ || comment || (addrs && *addrs))
+ selected += vcard_to_ta(addrs, fullname, struct_name, nickname,
+ comment, fcc, ta_list);
+
+ if(addrs)
+ fs_give((void **)&addrs);
+
+ free_list_array(&lines);
+ }
+
+ return(selected);
+}
+
+
+int
+cmp_swoop_list(const qsort_t *a1, const qsort_t *a2)
+{
+ SWOOP_S *x = (SWOOP_S *)a1;
+ SWOOP_S *y = (SWOOP_S *)a2;
+
+ if(x->dup){
+ if(y->dup)
+ return((x->dst_enum > y->dst_enum) ? -1
+ : (x->dst_enum == y->dst_enum) ? 0 : 1);
+ else
+ return(-1);
+ }
+ else if(y->dup)
+ return(1);
+ else
+ return((x > y) ? -1 : (x == y) ? 0 : 1); /* doesn't matter */
+}
+
+
+
+/*
+ * Take the split out contents of a vCard entry and turn them into a TA.
+ *
+ * Always returns 1, the number of TAs added to ta_list.
+ */
+int
+vcard_to_ta(char *addrs, char *fullname, char *better_fullname, char *nickname,
+ char *comment, char *fcc, TA_S **ta_list)
+{
+ ADDRESS *addrlist = NULL;
+
+ /*
+ * Parse it into an addrlist, which is what fill_in_ta wants
+ * to see.
+ */
+ rfc822_parse_adrlist(&addrlist, addrs, fakedomain);
+ if(!addrlist)
+ addrlist = mail_newaddr(); /* empty addr, to make right thing happen */
+
+ *ta_list = fill_in_ta(ta_list, addrlist, 1,
+ fullname ? fullname
+ : better_fullname ? better_fullname
+ : "Forwarded Entry");
+ (*ta_list)->nickname = nickname ? nickname : cpystr("");
+ (*ta_list)->comment = comment;
+ (*ta_list)->fcc = fcc;
+
+ /*
+ * We are tempted to want to call switch_to_last_comma_first() here when
+ * we don't have a better_fullname (which is already last, first).
+ * But, since this is the way it was sent to us we should probably leave
+ * it alone. That means that if we use a fullname culled from the
+ * body of a message, or from a header, or from an "N" vcard record,
+ * we will make it be Last, First; but if we use one from an "FN" record
+ * we won't.
+ */
+ (*ta_list)->fullname = better_fullname ? better_fullname
+ : fullname;
+
+ if((*ta_list)->nickname)
+ convert_possibly_encoded_str_to_utf8(&(*ta_list)->nickname);
+
+ if((*ta_list)->comment)
+ convert_possibly_encoded_str_to_utf8(&(*ta_list)->comment);
+
+ if((*ta_list)->fcc)
+ convert_possibly_encoded_str_to_utf8(&(*ta_list)->fcc);
+
+ if((*ta_list)->fullname)
+ convert_possibly_encoded_str_to_utf8(&(*ta_list)->fullname);
+
+ /*
+ * The TA list will free its members but we have to take care of this
+ * extra one that isn't assigned to a TA member.
+ */
+ if(better_fullname && fullname)
+ fs_give((void **)&fullname);
+
+ if(addrlist)
+ mail_free_address(&addrlist);
+
+ return(1);
+}
+
+
+/*
+ * Look through line which is supposed to look like
+ *
+ * type;charset=iso-8859-1;encoding=b: value
+ *
+ * Type might be email, or nickname, ... It is optional because of the
+ * defaulttype parameter (there isn't such a thing as defaulttype parameters
+ * as of Nov. 96 draft). The semicolon and colon are special chars. Each
+ * parameter in this line is a semicolon followed by the parameter type "="
+ * the parameter value. Parameters are optional, too. There is always a colon,
+ * followed by value. Whitespace can be everywhere up to where value starts.
+ * There is also an optional <group> "." preceding the type, which we will
+ * ignore. It's for grouping related lines.
+ * (As of July, 97 draft, it is no longer permissible to include a charset
+ * parameter in each line. Only one is permitted in the content-type mime hdr.)
+ * (Also as of July, 97 draft, all white space is supposed to be significant.
+ * We will continue to skip white space surrounding '=' and so forth for
+ * backwards compatibility and because it does no harm in almost all cases.)
+ * (As of Nov, 97 draft, some white space is not significant. That is, it is
+ * allowed in some places.)
+ *
+ *
+ * Args: line -- the line to look at
+ * type -- this is a return value, and is an allocated copy of
+ * the type, freed by the caller. For example, "email".
+ * alt -- this is a return value, and is an allocated copy of
+ * the value of the alternate charset, to be freed by
+ * the caller. For example, this might be "iso-8859-2".
+ * encoded -- this is a return value, and is set to 1 if the value
+ * is b-encoded
+ *
+ * Return value: a pointer to the start of value, or NULL if the syntax
+ * isn't right. It's possible for value to be equal to "".
+ */
+char *
+getaltcharset(char *line, char **type, char **alt, int *encoded)
+{
+ char *p, *q, *left_semi, *group_dot, *colon;
+ char *start_of_enc, *start_of_cset, tmpsave;
+ static char *cset = "charset";
+ static char *enc = "encoding";
+
+ if(type)
+ *type = NULL;
+
+ if(alt)
+ *alt = NULL;
+
+ if(encoded)
+ *encoded = 0;
+
+ colon = strindex(line, ':');
+ if(!colon)
+ return NULL;
+
+ left_semi = strindex(line, ';');
+ if(left_semi && left_semi > colon)
+ left_semi = NULL;
+
+ group_dot = strindex(line, '.');
+ if(group_dot && (group_dot > colon || (left_semi && group_dot > left_semi)))
+ group_dot = NULL;
+
+ /*
+ * Type is everything up to the semicolon, or the colon if no semicolon.
+ * However, we want to skip optional <group> ".".
+ */
+ if(type){
+ q = left_semi ? left_semi : colon;
+ tmpsave = *q;
+ *q = '\0';
+ *type = cpystr(group_dot ? group_dot+1 : line);
+ *q = tmpsave;
+ sqzspaces(*type);
+ }
+
+ if(left_semi && alt
+ && (p = srchstr(left_semi+1, cset))
+ && p < colon){
+ p += strlen(cset);
+ p = skip_white_space(p);
+ if(*p++ == '='){
+ p = skip_white_space(p);
+ if(p < colon){
+ start_of_cset = p;
+ p++;
+ while(p < colon && !isspace((unsigned char)*p) && *p != ';')
+ p++;
+
+ tmpsave = *p;
+ *p = '\0';
+ *alt = cpystr(start_of_cset);
+ *p = tmpsave;
+ }
+ }
+ }
+
+ if(encoded && left_semi
+ && (p = srchstr(left_semi+1, enc))
+ && p < colon){
+ p += strlen(enc);
+ p = skip_white_space(p);
+ if(*p++ == '='){
+ p = skip_white_space(p);
+ if(p < colon){
+ start_of_enc = p;
+ p++;
+ while(p < colon && !isspace((unsigned char)*p) && *p != ';')
+ p++;
+
+ if(*start_of_enc == 'b' && start_of_enc + 1 == p)
+ *encoded = 1;
+ }
+ }
+ }
+
+ p = colon + 1;
+
+ return(skip_white_space(p));
+}
+
+
+/*
+ * Return incoming_fullname in Last, First form.
+ * The passed in fullname should already be in UTF-8.
+ * If there is already a comma, we add quotes around the fullname so we can
+ * tell it isn't a Last, First comma but a real comma in the Fullname.
+ *
+ * Args: incoming_fullname --
+ * new_full -- passed in pointer to place to put new fullname
+ * nbuf -- size of new_full
+ * Returns: new_full
+ */
+void
+switch_to_last_comma_first(char *incoming_fullname, char *new_full, size_t nbuf)
+{
+ char save_value;
+ char *save_end, *nf, *inc, *p = NULL;
+
+ if(nbuf < 1)
+ return;
+
+ if(incoming_fullname == NULL){
+ new_full[0] = '\0';
+ return;
+ }
+
+ /* get rid of leading white space */
+ for(inc = incoming_fullname; *inc && isspace((unsigned char)*inc); inc++)
+ ;/* do nothing */
+
+ /* get rid of trailing white space */
+ for(p = inc + strlen(inc) - 1; *p && p >= inc &&
+ isspace((unsigned char)*p); p--)
+ ;/* do nothing */
+
+ save_end = ++p;
+ if(save_end == inc){
+ new_full[0] = '\0';
+ return;
+ }
+
+ save_value = *save_end; /* so we don't alter incoming_fullname */
+ *save_end = '\0';
+ nf = new_full;
+ memset(new_full, 0, nbuf); /* need this the way it is done below */
+
+ if(strindex(inc, ',') != NULL){
+ int add_quotes = 0;
+
+ /*
+ * Quote already existing comma.
+ *
+ * We'll get this wrong if it is already quoted but the quote
+ * doesn't start right at the beginning.
+ */
+ if(inc[0] != '"'){
+ add_quotes++;
+ if(nf-new_full < nbuf-1)
+ *nf++ = '"';
+ }
+
+ strncpy(nf, inc, MIN(nbuf - (add_quotes ? 3 : 1), nbuf-(nf-new_full)-1));
+ new_full[nbuf-1] = '\0';
+ if(add_quotes){
+ strncat(nf, "\"", nbuf-(nf-new_full)-1);
+ new_full[nbuf-1] = '\0';
+ }
+ }
+ else if(strindex(inc, SPACE)){
+ char *last, *end;
+
+ end = new_full + nbuf - 1;
+
+ /*
+ * Switch First Middle Last to Last, First Middle
+ */
+
+ /* last points to last word, which does exist */
+ last = skip_white_space(strrindex(inc, SPACE));
+
+ /* copy Last */
+ for(p = last; *p && nf < end-2 && nf-new_full < nbuf; *nf++ = *p++)
+ ;/* do nothing */
+
+ if(nf-new_full < nbuf-1)
+ *nf++ = ',';
+
+ if(nf-new_full < nbuf-1)
+ *nf++ = SPACE;
+
+ /* copy First Middle */
+ for(p = inc; p < last && nf < end && nf-new_full < nbuf-1; *nf++ = *p++)
+ ;/* do nothing */
+
+ new_full[nbuf-1] = '\0';
+
+ removing_trailing_white_space(new_full);
+ }
+ else
+ strncpy(new_full, inc, nbuf-1);
+
+ new_full[nbuf-1] = '\0';
+
+ *save_end = save_value;
+}
+
+
+/*
+ * Fetch this body part, content-decode it if necessary, split it into
+ * lines, and return the lines in an allocated array, which is freed
+ * by the caller. Folded lines are replaced by single long lines.
+ */
+char **
+detach_vcard_att(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *body, char *partnum)
+{
+ unsigned long length;
+ char *text = NULL, /* raw text */
+ *dtext = NULL, /* content-decoded text */
+ **res = NULL, /* array of decoded lines */
+ *lptr, /* line pointer */
+ *next_line;
+ int i, count;
+
+ /* make our own copy of text so we can change it */
+ text = cpystr(mail_fetchbody_full(stream, msgno, partnum, &length,FT_PEEK));
+ text[length] = '\0';
+
+ /* decode the text */
+ switch(body->encoding){
+ default:
+ case ENC7BIT:
+ case ENC8BIT:
+ case ENCBINARY:
+ dtext = text;
+ break;
+
+ case ENCBASE64:
+ dtext = (char *)rfc822_base64((unsigned char *)text,
+ (unsigned long)strlen(text),
+ &length);
+ if(dtext)
+ dtext[length] = '\0';
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Malformed B64 data in address book attachment.");
+
+ if(text)
+ fs_give((void **)&text);
+
+ break;
+
+ case ENCQUOTEDPRINTABLE:
+ dtext = (char *)rfc822_qprint((unsigned char *)text,
+ (unsigned long)strlen(text),
+ &length);
+ if(dtext)
+ dtext[length] = '\0';
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Malformed QP data in address book attachment.");
+
+ if(text)
+ fs_give((void **)&text);
+
+ break;
+ }
+
+ /* count the lines */
+ next_line = dtext;
+ count = 0;
+ for(lptr = next_line; lptr && *lptr; lptr = next_line){
+ for(next_line = lptr; *next_line; next_line++){
+ /*
+ * Find end of current line.
+ */
+ if(*next_line == '\r' && *(next_line+1) == '\n'){
+ next_line += 2;
+ /* not a folded line, count it */
+ if(!*next_line || (*next_line != SPACE && *next_line != TAB))
+ break;
+ }
+ }
+
+ count++;
+ }
+
+ /* allocate space for resulting array of lines */
+ res = (char **)fs_get((count + 1) * sizeof(char *));
+ memset((void *)res, 0, (count + 1) * sizeof(char *));
+ next_line = dtext;
+ for(i=0, lptr=next_line; lptr && *lptr && i < count; lptr=next_line, i++){
+ /*
+ * Move next_line to start of next line and null terminate
+ * current line.
+ */
+ for(next_line = lptr; *next_line; next_line++){
+ /*
+ * Find end of current line.
+ */
+ if(*next_line == '\r' && *(next_line+1) == '\n'){
+ next_line += 2;
+ /* not a folded line, terminate it */
+ if(!*next_line || (*next_line != SPACE && *next_line != TAB)){
+ *(next_line-2) = '\0';
+ break;
+ }
+ }
+ }
+
+ /* turn folded lines into long lines in place */
+ vcard_unfold(lptr);
+ res[i] = cpystr(lptr);
+ }
+
+ if(dtext)
+ fs_give((void **)&dtext);
+
+ res[count] = '\0';
+ return(res);
+}
+
+
+/*
+ * Look for possible addresses in the first text part of a message for
+ * use by TakeAddr command.
+ * Returns the number of TA lines added.
+ */
+int
+grab_addrs_from_body(MAILSTREAM *stream, long int msgno,
+ struct mail_bodystruct *body, TA_S **ta_list)
+{
+#define MAXLINESZ 2000
+ char line[MAXLINESZ + 1];
+ STORE_S *so;
+ gf_io_t pc;
+ SourceType src = CharStar;
+ int added = 0, failure;
+
+ dprint((9, "\n - grab_addrs_from_body - \n"));
+
+ /*
+ * If it is text/plain or it is multipart with a first part of text/plain,
+ * we want to continue, else forget it.
+ */
+ if(!((body->type == TYPETEXT && body->subtype
+ && ALLOWED_SUBTYPE(body->subtype))
+ ||
+ (body->type == TYPEMULTIPART && body->nested.part
+ && body->nested.part->body.type == TYPETEXT
+ && ALLOWED_SUBTYPE(body->nested.part->body.subtype))))
+ return 0;
+
+#ifdef DOS
+ src = TmpFileStar;
+#endif
+
+ if((so = so_get(src, NULL, EDIT_ACCESS)) == NULL)
+ return 0;
+
+ gf_set_so_writec(&pc, so);
+
+ failure = !get_body_part_text(stream, body, msgno, "1", 0L, pc,
+ NULL, NULL, GBPT_NONE);
+
+ gf_clear_so_writec(so);
+
+ if(failure){
+ so_give(&so);
+ return 0;
+ }
+
+ so_seek(so, 0L, 0);
+
+ while(get_line_of_message(so, line, sizeof(line))){
+ ADDRESS *addr;
+ char save_end, *start, *tmp_a_string, *tmp_personal, *p;
+ int n;
+
+ /* process each @ in the line */
+ for(p = (char *) line; (start = mail_addr_scan(p, &n)); p = start + n){
+
+ tmp_personal = NULL;
+
+ if(start > line && *(start-1) == '<' && *(start+n) == '>'){
+ int words, in_quote;
+ char *fn_start;
+
+ /*
+ * Take a shot at looking for full name
+ * If we can find a colon maybe we've got a header line
+ * embedded in the body.
+ * This is real ad hoc.
+ */
+
+ /*
+ * Go back until we run into a character that probably
+ * isn't a fullname character.
+ */
+ fn_start = start-1;
+ in_quote = words = 0;
+ while(fn_start > p && (in_quote || !(*(fn_start-1) == ':'
+ || *(fn_start-1) == ';' || *(fn_start-1) == ','))){
+ fn_start--;
+ if(!in_quote && isspace((unsigned char)*fn_start))
+ words++;
+ else if(*fn_start == '"' &&
+ (fn_start == p || *(fn_start-1) != '\\')){
+ if(in_quote){
+ in_quote = 0;
+ break;
+ }
+ else
+ in_quote = 1;
+ }
+
+ if(words == 5)
+ break;
+ }
+
+ /* wasn't a real quote, forget about fullname */
+ if(in_quote)
+ fn_start = start-1;
+
+ /* Skip forward over the white space. */
+ while(isspace((unsigned char)*fn_start) || *fn_start == '(')
+ fn_start++;
+
+ /*
+ * Make sure the first word is capitalized.
+ * (just so it is more likely it is a name)
+ */
+ while(fn_start < start-1 &&
+ !(isupper(*fn_start) || *fn_start == '"')){
+ if(*fn_start == '('){
+ fn_start++;
+ continue;
+ }
+
+ /* skip forward over this word */
+ while(fn_start < start-1 &&
+ !isspace((unsigned char)*fn_start))
+ fn_start++;
+
+ while(fn_start < start-1 &&
+ isspace((unsigned char)*fn_start))
+ fn_start++;
+ }
+
+ if(fn_start < start-1){
+ char *fn_end, save_fn_end;
+
+ /* remove white space between fullname and start */
+ fn_end = start-1;
+ while(fn_end > fn_start
+ && isspace((unsigned char)*(fn_end - 1)))
+ fn_end--;
+
+ save_fn_end = *fn_end;
+ *fn_end = '\0';
+
+ /* remove matching quotes */
+ if(*fn_start == '"' && *(fn_end-1) == '"'){
+ fn_start++;
+ *fn_end = save_fn_end;
+ fn_end--;
+ save_fn_end = *fn_end;
+ *fn_end = '\0';
+ }
+
+ /* copy fullname */
+ if(*fn_start)
+ tmp_personal = cpystr(fn_start);
+
+ *fn_end = save_fn_end;
+ }
+ }
+
+
+ save_end = *(start+n);
+ *(start+n) = '\0';
+ /* rfc822_parse_adrlist feels free to destroy input so send copy */
+ tmp_a_string = cpystr(start);
+ *(start+n) = save_end;
+ addr = NULL;
+ ps_global->c_client_error[0] = '\0';
+ rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **)&tmp_a_string);
+
+ if(tmp_personal){
+ if(addr){
+ if(addr->personal)
+ fs_give((void **)&addr->personal);
+
+ addr->personal = tmp_personal;
+ }
+ else
+ fs_give((void **)&tmp_personal);
+ }
+
+ if((addr && addr->host && addr->host[0] == '@') ||
+ ps_global->c_client_error[0]){
+ mail_free_address(&addr);
+ continue;
+ }
+
+ if(addr && addr->mailbox && addr->host){
+ added++;
+ *ta_list = fill_in_ta(ta_list, addr, 0, (char *)NULL);
+ }
+
+ if(addr)
+ mail_free_address(&addr);
+ }
+ }
+
+ so_give(&so);
+ return(added);
+}
+
+
+/*
+ * Get the next line of the object pointed to by source.
+ * Skips empty lines.
+ * Linebuf is a passed in buffer to put the line in.
+ * Linebuf is null terminated and \r and \n chars are removed.
+ * 0 is returned when there is no more input.
+ */
+int
+get_line_of_message(STORE_S *source, char *linebuf, int linebuflen)
+{
+ int pos = 0;
+ unsigned char c;
+
+ if(linebuflen < 2)
+ return 0;
+
+ while(so_readc(&c, source)){
+ if(c == '\n' || c == '\r'){
+ if(pos > 0)
+ break;
+ }
+ else{
+ linebuf[pos++] = c;
+ if(pos >= linebuflen - 2)
+ break;
+ }
+ }
+
+ linebuf[pos] = '\0';
+
+ return(pos);
+}
+
+
+/*
+ * Inserts a new entry based on addr in the TA list.
+ * Makes sure everything is UTF-8. That is,
+ * Values used for strvalue will be converted to UTF-8 first.
+ * Personal name fields of addr args will be converted to UTF-8.
+ *
+ * Args: old_current -- the TA list
+ * addr -- the address for this line
+ * checked -- start this line out checked
+ * print_string -- if non-null, this line is informational and is just
+ * printed out, it can't be selected
+ */
+TA_S *
+fill_in_ta(TA_S **old_current, struct mail_address *addr, int checked, char *print_string)
+{
+ TA_S *new_current;
+
+ /* c-client convention for group syntax, which we don't want to deal with */
+ if(!print_string && (!addr || !addr->mailbox || !addr->host))
+ new_current = *old_current;
+ else{
+
+ new_current = new_taline(old_current);
+ if(print_string && addr){ /* implied List when here */
+ new_current->frwrded = 1;
+ new_current->skip_it = 0;
+ new_current->print = 0;
+ new_current->checked = checked != 0;
+ new_current->addr = copyaddrlist(addr);
+ decode_addr_names_to_utf8(new_current->addr);
+ new_current->strvalue = cpystr(print_string);
+ convert_possibly_encoded_str_to_utf8(&new_current->strvalue);
+ }
+ else if(print_string){
+ new_current->frwrded = 0;
+ new_current->skip_it = 1;
+ new_current->print = 1;
+ new_current->checked = 0;
+ new_current->addr = 0;
+ new_current->strvalue = cpystr(print_string);
+ convert_possibly_encoded_str_to_utf8(&new_current->strvalue);
+ }
+ else{
+ new_current->frwrded = 0;
+ new_current->skip_it = 0;
+ new_current->print = 0;
+ new_current->checked = checked != 0;
+ new_current->addr = copyaddr(addr);
+ decode_addr_names_to_utf8(new_current->addr);
+ if(addr->host[0] == '.')
+ new_current->strvalue = cpystr("Error in address (ok to try Take anyway)");
+ else{
+ char *bufp;
+ size_t len;
+
+ len = est_size(new_current->addr);
+ bufp = (char *) fs_get(len * sizeof(char));
+ new_current->strvalue = cpystr(addr_string(new_current->addr, bufp, len));
+ fs_give((void **) &bufp);
+ }
+
+ convert_possibly_encoded_str_to_utf8(&new_current->strvalue);
+ }
+ }
+
+ return(new_current);
+}
+
+
+int
+eliminate_dups_and_us(TA_S *list)
+{
+ return(eliminate_dups_and_maybe_us(list, 1));
+}
+
+
+int
+eliminate_dups_but_not_us(TA_S *list)
+{
+ return(eliminate_dups_and_maybe_us(list, 0));
+}
+
+
+/*
+ * Look for dups in list and mark them so we'll skip them.
+ *
+ * We also throw out any addresses that are us (if us_too is set), since
+ * we won't usually want to take ourselves to the addrbook.
+ * On the otherhand, if there is nothing but us, we leave it.
+ *
+ * Don't toss out forwarded entries.
+ *
+ * Returns the number of dups eliminated that were also selected.
+ */
+int
+eliminate_dups_and_maybe_us(TA_S *list, int us_too)
+{
+ ADDRESS *a, *b;
+ TA_S *ta, *tb;
+ int eliminated = 0;
+
+ /* toss dupes */
+ for(ta = list; ta; ta = ta->next){
+
+ if(ta->skip_it || ta->frwrded) /* already tossed or forwarded */
+ continue;
+
+ a = ta->addr;
+
+ /* Check addr "a" versus tail of the list */
+ for(tb = ta->next; tb; tb = tb->next){
+ if(tb->skip_it || tb->frwrded) /* already tossed or forwarded */
+ continue;
+
+ b = tb->addr;
+ if(dup_addrs(a, b)){
+ if(ta->checked || !(tb->checked)){
+ /* throw out b */
+ if(tb->checked)
+ eliminated++;
+
+ tb->skip_it = 1;
+ }
+ else{ /* tb->checked */
+ /* throw out a */
+ ta->skip_it = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if(us_too){
+ /* check whether all remaining addrs are us */
+ for(ta = list; ta; ta = ta->next){
+
+ if(ta->skip_it) /* already tossed */
+ continue;
+
+ if(ta->frwrded) /* forwarded entry, so not us */
+ break;
+
+ a = ta->addr;
+
+ if(!address_is_us(a, ps_global))
+ break;
+ }
+
+ /*
+ * if at least one address that isn't us, remove all of us from
+ * the list
+ */
+ if(ta){
+ for(ta = list; ta; ta = ta->next){
+
+ if(ta->skip_it || ta->frwrded) /* already tossed or forwarded */
+ continue;
+
+ a = ta->addr;
+
+ if(address_is_us(a, ps_global)){
+ if(ta->checked)
+ eliminated++;
+
+ /* throw out a */
+ ta->skip_it = 1;
+ }
+ }
+ }
+ }
+
+ return(eliminated);
+}
+
+
+/*
+ * Returns 1 if x and y match, 0 if not.
+ */
+int
+dup_addrs(struct mail_address *x, struct mail_address *y)
+{
+ return(x && y && strucmp(x->mailbox, y->mailbox) == 0
+ && strucmp(x->host, y->host) == 0);
+}
diff --git a/pith/takeaddr.h b/pith/takeaddr.h
new file mode 100644
index 00000000..2476d386
--- /dev/null
+++ b/pith/takeaddr.h
@@ -0,0 +1,114 @@
+/*
+ * $Id: takeaddr.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_TAKEADDR_INCLUDED
+#define PITH_TAKEADDR_INCLUDED
+
+
+#include "../pith/adrbklib.h"
+#include "../pith/msgno.h"
+#include "../pith/state.h"
+#include "../pith/store.h"
+
+
+/*
+ * Information used to paint and maintain a line on the TakeAddr screen
+ */
+typedef struct takeaddr_line {
+ unsigned int checked:1;/* addr is selected */
+ unsigned int skip_it:1;/* skip this one */
+ unsigned int print:1; /* for printing only */
+ unsigned int frwrded:1;/* forwarded from another pine */
+ char *strvalue; /* alloc'd value string */
+ ADDRESS *addr; /* original ADDRESS this line came from */
+ char *nickname; /* The first TA may carry this extra */
+ char *fullname; /* information, or, */
+ char *fcc; /* all vcard ta's have it. */
+ char *comment;
+ struct takeaddr_line *next, *prev;
+} TA_S;
+
+typedef struct a_list {
+ int dup;
+ adrbk_cntr_t dst_enum;
+ TA_S *ta;
+} SWOOP_S;
+
+typedef struct lines_to_take {
+ char *printval;
+ char *exportval;
+ int flags;
+ struct lines_to_take *next, *prev;
+} LINES_TO_TAKE;
+
+#define LT_NONE 0x00
+#define LT_NOSELECT 0x01
+
+
+/*
+ * Flag definitions to control takeaddr behavior
+ */
+#define TA_NONE 0
+#define TA_AGG 1 /* if set, use the aggregate operation */
+#define TA_NOPROMPT 2 /* if set, we aren't in interactive mode */
+
+
+/* these should really be in ../pine */
+#define RS_NONE 0x00 /* rule_setup_type flags */
+#define RS_RULES 0x01 /* include Rules as option */
+#define RS_INCADDR 0x02 /* include Addrbook as option */
+#define RS_INCEXP 0x04 /* include Export as option */
+#define RS_INCFILTNOW 0x08 /* filter now */
+
+
+/* exported prototypes */
+int set_up_takeaddr(int, struct pine *, MSGNO_S *, TA_S **,
+ int *, int, int (*)(TA_S *, int));
+TA_S *pre_sel_taline(TA_S *);
+TA_S *pre_taline(TA_S *);
+TA_S *next_sel_taline(TA_S *);
+TA_S *next_taline(TA_S *);
+int ta_mark_all(TA_S *);
+int is_talist_of_one(TA_S *);
+int ta_unmark_all(TA_S *);
+TA_S *new_taline(TA_S **);
+void free_taline(TA_S **);
+void free_talines(TA_S **);
+void free_ltline(LINES_TO_TAKE **);
+void free_ltlines(LINES_TO_TAKE **);
+TA_S *first_sel_taline(TA_S *);
+TA_S *last_sel_taline(TA_S *);
+TA_S *first_taline(TA_S *);
+TA_S *first_checked(TA_S *);
+char **list_of_checked(TA_S *);
+int convert_ta_to_lines(TA_S *, LINES_TO_TAKE **);
+LINES_TO_TAKE *new_ltline(LINES_TO_TAKE **);
+int add_addresses_to_talist(struct pine *, long, char *, TA_S **, ADDRESS *, int);
+int process_vcard_atts(MAILSTREAM *, long, BODY *, BODY *, char *, TA_S **);
+int cmp_swoop_list(const qsort_t *, const qsort_t *);
+int vcard_to_ta(char *, char *, char *, char *, char *, char *, TA_S **);
+char *getaltcharset(char *, char **, char **, int *);
+void switch_to_last_comma_first(char *, char *, size_t);
+char **detach_vcard_att(MAILSTREAM *, long, BODY *, char *);
+int grab_addrs_from_body(MAILSTREAM *,long,BODY *,TA_S **);
+int get_line_of_message(STORE_S *, char *, int);
+TA_S *fill_in_ta(TA_S **, ADDRESS *, int, char *);
+int eliminate_dups_and_us(TA_S *);
+int eliminate_dups_but_not_us(TA_S *);
+int eliminate_dups_and_maybe_us(TA_S *, int);
+int dup_addrs(ADDRESS *, ADDRESS *);
+
+
+#endif /* PITH_TAKEADDR_INCLUDED */
diff --git a/pith/tempfile.c b/pith/tempfile.c
new file mode 100644
index 00000000..efe7c586
--- /dev/null
+++ b/pith/tempfile.c
@@ -0,0 +1,87 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: tempfile.c 770 2007-10-24 00:23:09Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/tempfile.h"
+
+
+/*
+ * Return the name of a file in the same directory as filename.
+ * Same as temp_nam except it figures out a name in the same directory.
+ * It also returns the name of the directory in ret_dir if ret_dir is
+ * not NULL. That has to be freed by caller. If return is not NULL the
+ * empty file has been created.
+ */
+char *
+tempfile_in_same_dir(char *filename, char *prefix, char **ret_dir)
+{
+#ifndef MAXPATH
+#define MAXPATH 1000 /* Longest file path we can deal with */
+#endif
+ char dir[MAXPATH+1];
+ char *dirp = NULL;
+ char *ret_file = NULL;
+
+ if(filename){
+ char *lc;
+
+ if((lc = last_cmpnt(filename)) != NULL){
+ int to_copy;
+
+ to_copy = (lc - filename > 1) ? (lc - filename - 1) : 1;
+ strncpy(dir, filename, MIN(to_copy, sizeof(dir)-1));
+ dir[MIN(to_copy, sizeof(dir)-1)] = '\0';
+ }
+ else{
+ dir[0] = '.';
+ dir[1] = '\0';
+ }
+
+ dirp = dir;
+ }
+
+
+ /* temp_nam creates ret_file */
+ ret_file = temp_nam(dirp, prefix);
+
+ /*
+ * If temp_nam can't write in dirp it puts the file in a temp directory
+ * anyway. We don't want that to happen to us.
+ */
+ if(dirp && ret_file && !in_dir(dirp, ret_file)){
+ our_unlink(ret_file);
+ fs_give((void **)&ret_file); /* sets it to NULL */
+ }
+
+ if(ret_file && ret_dir && dirp)
+ *ret_dir = cpystr(dirp);
+
+
+ return(ret_file);
+}
+
+
+/*
+ * Returns non-zero if dir is a prefix of path.
+ * zero if dir is not a prefix of path, or if dir is empty.
+ */
+int
+in_dir(char *dir, char *path)
+{
+ return(*dir ? !strncmp(dir, path, strlen(dir)) : 0);
+}
diff --git a/pith/tempfile.h b/pith/tempfile.h
new file mode 100644
index 00000000..c3255f75
--- /dev/null
+++ b/pith/tempfile.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: tempfile.h 770 2007-10-24 00:23:09Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_TEMPFILE_INCLUDED
+#define PITH_TEMPFILE_INCLUDED
+
+
+/* exported protoypes */
+char *tempfile_in_same_dir(char *, char *, char **);
+int in_dir(char *, char *);
+
+
+#endif /* PITH_TEMPFILE_INCLUDED */
diff --git a/pith/text.c b/pith/text.c
new file mode 100644
index 00000000..5de53e51
--- /dev/null
+++ b/pith/text.c
@@ -0,0 +1,964 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: text.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ text.c
+ Implements message text fetching and decoding
+
+ ====*/
+
+
+#include "../pith/headers.h"
+#include "../pith/text.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/handle.h"
+#include "../pith/margin.h"
+#include "../pith/editorial.h"
+#include "../pith/color.h"
+#include "../pith/filter.h"
+#include "../pith/charset.h"
+#include "../pith/status.h"
+#include "../pith/mailview.h"
+#include "../pith/mimedesc.h"
+#include "../pith/detach.h"
+
+
+/* internal prototypes */
+int charset_editorial(char *, long, HANDLE_S **, int, int, int, gf_io_t);
+int replace_quotes(long, char *, LT_INS_S **, void *);
+void mark_handles_in_line(char *, HANDLE_S **, int);
+void clear_html_risk(void);
+void set_html_risk(void);
+int get_html_risk(void);
+
+
+#define CHARSET_DISCLAIMER_1 \
+ "The following text is in the \"%.40s\" character set.\015\012Your "
+#define CHARSET_DISCLAIMER_2 "display is set"
+#define CHARSET_DISCLAIMER_3 \
+ " for the \"%.40s\" character set. \015\012Some %.40scharacters may be displayed incorrectly."
+#define ENCODING_DISCLAIMER \
+ "The following text contains the unknown encoding type \"%.20s\". \015\012Some or all of the text may be displayed incorrectly."
+#define HTML_WARNING \
+ "The following HTML text may contain deceptive links. Carefully note the destination URL before visiting any links."
+
+
+/*
+ * HTML risk state holder.
+ */
+static int _html_risk;
+
+
+
+/*----------------------------------------------------------------------
+ Handle fetching and filtering a text message segment to be displayed
+ by scrolltool or printed or exported or piped.
+
+Args: att -- segment to fetch
+ msgno -- message number segment is a part of
+ pc -- function to write characters from segment with
+ style -- Indicates special handling for error messages
+ flags -- Indicates special necessary handling
+
+Returns: 1 if errors encountered, 0 if everything went A-OK
+
+ ----*/
+int
+decode_text(ATTACH_S *att,
+ long int msgno,
+ gf_io_t pc,
+ HANDLE_S **handlesp,
+ DetachErrStyle style,
+ int flags)
+{
+ FILTLIST_S filters[14];
+ char *err, *charset;
+ int filtcnt = 0, error_found = 0, column, wrapit;
+ int is_in_sig = OUT_SIG_BLOCK;
+ int is_flowed_msg = 0;
+ int is_delsp_yes = 0;
+ int filt_only_c0 = 0;
+ char *parmval;
+ char *free_this = NULL;
+ STORE_S *warn_so = NULL;
+ DELQ_S dq;
+ URL_HILITE_S uh;
+
+ column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
+
+ if(!(flags & FM_DISPLAY))
+ flags |= FM_NOINDENT;
+
+ wrapit = column;
+
+ memset(filters, 0, sizeof(filters));
+
+ /* charset the body part is in */
+ charset = parameter_val(att->body->parameter, "charset");
+
+ /* determined if it's flowed, affects wrapping and quote coloring */
+ if(att->body->type == TYPETEXT
+ && !strucmp(att->body->subtype, "plain")
+ && (parmval = parameter_val(att->body->parameter, "format"))){
+ if(!strucmp(parmval, "flowed"))
+ is_flowed_msg = 1;
+ fs_give((void **) &parmval);
+
+ if(is_flowed_msg){
+ if((parmval = parameter_val(att->body->parameter, "delsp")) != NULL){
+ if(!strucmp(parmval, "yes"))
+ is_delsp_yes = 1;
+
+ fs_give((void **) &parmval);
+ }
+ }
+ }
+
+ if(!ps_global->pass_ctrl_chars){
+ filters[filtcnt++].filter = gf_escape_filter;
+ filters[filtcnt].filter = gf_control_filter;
+
+ filt_only_c0 = 1;
+ filters[filtcnt++].data = gf_control_filter_opt(&filt_only_c0);
+ }
+
+ if(flags & FM_DISPLAY)
+ filters[filtcnt++].filter = gf_tag_filter;
+
+ /*
+ * if it's just plain old text, look for url's
+ */
+ if(!(att->body->subtype && strucmp(att->body->subtype, "plain"))){
+ struct variable *vars = ps_global->vars;
+
+ if((F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ && handlesp){
+
+ /*
+ * The url_hilite filter really ought to come
+ * after flowing, because flowing with the DelSp=yes parameter
+ * can reassemble broken urls back into identifiable urls.
+ * We add the preflow filter to do only the reassembly part
+ * of the flowing so that we can spot the urls.
+ * At this time (2005-03-29) we know that Apple Mail does
+ * send mail like this sometimes. This filter removes the
+ * sequence SP CRLF if that seems safe.
+ */
+ if(ps_global->full_header != 2 && is_delsp_yes)
+ filters[filtcnt++].filter = gf_preflow;
+
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(url_hilite,
+ gf_url_hilite_opt(&uh,handlesp,0));
+ }
+
+ /*
+ * First, paint the signature.
+ * Disclaimers noted below for coloring quotes apply here as well.
+ */
+ if((flags & FM_DISPLAY)
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor()
+ && VAR_SIGNATURE_FORE_COLOR
+ && VAR_SIGNATURE_BACK_COLOR){
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(color_signature,
+ &is_in_sig);
+ }
+
+ /*
+ * Gotta be careful with this. The color_a_quote filter adds color
+ * to the beginning and end of the line. This will break some
+ * line_test-style filters which come after it. For example, if they
+ * are looking for something at the start of a line (like color_a_quote
+ * itself). I guess we could fix that by ignoring tags at the
+ * beginning of the line when doing the search.
+ */
+ if((flags & FM_DISPLAY)
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor()
+ && VAR_QUOTE1_FORE_COLOR
+ && VAR_QUOTE1_BACK_COLOR){
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(color_a_quote,
+ &is_flowed_msg);
+ }
+ }
+ else if(!strucmp(att->body->subtype, "richtext")){
+ int plain_opt;
+
+ plain_opt = !(flags&FM_DISPLAY);
+
+ /* maybe strip everything! */
+ filters[filtcnt].filter = gf_rich2plain;
+ filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
+ /* width to use for file or printer */
+ if(wrapit - 5 > 0)
+ wrapit -= 5;
+ }
+ else if(!strucmp(att->body->subtype, "enriched")){
+ int plain_opt;
+
+ plain_opt = !(flags&FM_DISPLAY);
+
+ filters[filtcnt].filter = gf_enriched2plain;
+ filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
+ /* width to use for file or printer */
+ if(wrapit - 5 > 0)
+ wrapit -= 5;
+ }
+ else if(!strucmp(att->body->subtype, "html") && ps_global->full_header < 2){
+/*BUG: sniff the params for "version=2.0" ala draft-ietf-html-spec-01 */
+ int opts = 0;
+
+ clear_html_risk();
+
+ if(flags & FM_DISPLAY){
+ gf_io_t warn_pc;
+
+ if(handlesp){ /* pass on handles awareness */
+ opts |= GFHP_HANDLES;
+
+ if(F_OFF(F_QUELL_HOST_AFTER_URL, ps_global) && !(flags & FM_HIDESERVER))
+ opts |= GFHP_SHOW_SERVER;
+ }
+
+ if(!(flags & FM_NOEDITORIAL)){
+ warn_so = so_get(CharStar, NULL, EDIT_ACCESS);
+ gf_set_so_writec(&warn_pc, warn_so);
+ format_editorial(HTML_WARNING, column, flags, handlesp, warn_pc);
+ gf_clear_so_writec(warn_so);
+ so_puts(warn_so, "\015\012");
+ so_writec('\0', warn_so);
+ }
+ }
+ else
+ opts |= GFHP_STRIPPED; /* don't embed anything! */
+
+ if(flags & FM_NOHTMLREL)
+ opts |= GFHP_NO_RELATIVE;
+
+ if(flags & FM_HTMLRELATED)
+ opts |= GFHP_RELATED_CONTENT;
+
+ if(flags & FM_HTML)
+ opts |= GFHP_HTML;
+
+ if(flags & FM_HTMLIMAGES)
+ opts |= GFHP_HTML_IMAGES;
+
+ wrapit = 0; /* wrap already handled! */
+ filters[filtcnt].filter = gf_html2plain;
+ filters[filtcnt++].data = gf_html2plain_opt(NULL, column,
+ (flags & (FM_NOINDENT | FM_HTML))
+ ? 0
+ : format_view_margin(),
+ handlesp, set_html_risk, opts);
+
+ if(warn_so){
+ filters[filtcnt].filter = gf_prepend_editorial;
+ filters[filtcnt++].data = gf_prepend_editorial_opt(get_html_risk,
+ (char *) so_text(warn_so));
+ }
+ }
+
+ /*
+ * If the message is not flowed, we do the quote suppression before
+ * the wrapping, because the wrapping does not preserve the quote
+ * characters at the beginnings of the lines in that case.
+ * Otherwise, we defer until after the wrapping.
+ *
+ * Also, this is a good place to do quote-replacement on nonflowed
+ * messages because no other filters depend on the "> ".
+ * Quote-replacement is easier in the flowed case and occurs
+ * automatically in the flowed wrapping filter.
+ */
+ if(!is_flowed_msg
+ && ps_global->full_header == 0
+ && !(att->body->subtype && strucmp(att->body->subtype, "plain"))
+ && (flags & FM_DISPLAY)){
+ if(ps_global->quote_suppression_threshold != 0){
+ memset(&dq, 0, sizeof(dq));
+ dq.lines = ps_global->quote_suppression_threshold;
+ dq.is_flowed = is_flowed_msg;
+ dq.indent_length = 0; /* indent didn't happen yet */
+ dq.saved_line = &free_this;
+ dq.handlesp = handlesp;
+ dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor());
+
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
+ }
+ if(ps_global->VAR_QUOTE_REPLACE_STRING
+ && F_ON(F_QUOTE_REPLACE_NOFLOW, ps_global)){
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(replace_quotes, NULL);
+ }
+ }
+
+ if(wrapit && !(flags & FM_NOWRAP)){
+ int wrapflags = (flags & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN)
+ : GFW_NONE;
+
+ if(flags & FM_DISPLAY
+ && !(flags & FM_NOCOLOR)
+ && pico_usingcolor())
+ wrapflags |= GFW_USECOLOR;
+
+ /* text/flowed (RFC 2646 + draft)? */
+ if(ps_global->full_header != 2 && is_flowed_msg){
+ wrapflags |= GFW_FLOWED;
+
+ if(is_delsp_yes)
+ wrapflags |= GFW_DELSP;
+ }
+
+ filters[filtcnt].filter = gf_wrap;
+ filters[filtcnt++].data = gf_wrap_filter_opt(wrapit, column,
+ (flags & FM_NOINDENT)
+ ? 0
+ : format_view_margin(),
+ 0, wrapflags);
+ }
+
+ /*
+ * This has to come after wrapping has happened because the user tells
+ * us how many quoted lines to display, and we want that number to be
+ * the wrapped lines, not the pre-wrapped lines. We do it before the
+ * wrapping if the message is not flowed, because the wrapping does not
+ * preserve the quote characters at the beginnings of the lines in that
+ * case.
+ */
+ if(is_flowed_msg
+ && ps_global->full_header == 0
+ && !(att->body->subtype && strucmp(att->body->subtype, "plain"))
+ && (flags & FM_DISPLAY)
+ && ps_global->quote_suppression_threshold != 0){
+ memset(&dq, 0, sizeof(dq));
+ dq.lines = ps_global->quote_suppression_threshold;
+ dq.is_flowed = is_flowed_msg;
+ dq.indent_length = (wrapit && !(flags & FM_NOWRAP)
+ && !(flags & FM_NOINDENT))
+ ? format_view_margin()[0]
+ : 0;
+ dq.saved_line = &free_this;
+ dq.handlesp = handlesp;
+ dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor());
+
+ filters[filtcnt].filter = gf_line_test;
+ filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
+ }
+
+ if(charset){
+ if(F_OFF(F_QUELL_CHARSET_WARNING, ps_global)
+ && !(flags & FM_NOEDITORIAL)){
+ int rv = TRUE;
+ CONV_TABLE *ct = NULL;
+
+ /*
+ * Need editorial if message charset is not ascii
+ * and the display charset is not either the same
+ * as the message charset or UTF-8.
+ */
+ if(strucmp(charset, "us-ascii")
+ && !((ps_global->display_charmap
+ && !strucmp(charset, ps_global->display_charmap))
+ || !strucmp("UTF-8", ps_global->display_charmap))){
+
+ /*
+ * This is probably overkill. We're just using this
+ * conversion_table routine to get at the quality.
+ */
+ if(ps_global->display_charmap)
+ ct = conversion_table(charset, ps_global->display_charmap);
+
+ rv = charset_editorial(charset, msgno, handlesp, flags,
+ ct ? ct->quality : CV_NO_TRANSLATE_POSSIBLE,
+ column, pc);
+ }
+ if(!rv)
+ goto write_error;
+ }
+
+ fs_give((void **) &charset);
+ }
+
+ /* Format editorial comment about unknown encoding */
+ if(att->body->encoding > ENCQUOTEDPRINTABLE){
+ char buf[2048];
+
+ snprintf(buf, sizeof(buf), ENCODING_DISCLAIMER, body_encodings[att->body->encoding]);
+
+ if(!(format_editorial(buf, column, flags, handlesp, pc) == NULL
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ goto write_error;
+ }
+
+ err = detach(ps_global->mail_stream, msgno, att->number, 0L,
+ NULL, pc, filtcnt ? filters : NULL, 0);
+
+ delete_unused_handles(handlesp);
+
+ if(free_this)
+ fs_give((void **) &free_this);
+
+ if(warn_so)
+ so_give(&warn_so);
+
+ if(err) {
+ error_found++;
+ if(style == QStatus) {
+ q_status_message1(SM_ORDER, 3, 4, "%.200s", err);
+ } else if(style == InLine) {
+ char buftmp[MAILTMPLEN];
+
+ snprintf(buftmp, sizeof(buftmp), "%s",
+ att->body->description ? att->body->description : "");
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s [Error: %s] %c%s%c%s%s",
+ NEWLINE, err,
+ att->body->description ? '\"' : ' ',
+ att->body->description ? buftmp : "",
+ att->body->description ? '\"' : ' ',
+ NEWLINE, NEWLINE);
+ if(!gf_puts(tmp_20k_buf, pc))
+ goto write_error;
+ }
+ }
+
+ if(att->body->subtype
+ && (!strucmp(att->body->subtype, "richtext")
+ || !strucmp(att->body->subtype, "enriched"))
+ && !(flags & FM_DISPLAY)){
+ if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
+ goto write_error;
+ }
+
+ return(error_found);
+
+ write_error:
+ if(style == QStatus)
+ q_status_message1(SM_ORDER, 3, 4, "Error writing message: %.200s",
+ error_description(errno));
+
+ return(1);
+}
+
+
+
+/*
+ * Format editorial comment about charset mismatch
+ */
+int
+charset_editorial(char *charset, long int msgno, HANDLE_S **handlesp, int flags,
+ int quality, int width, gf_io_t pc)
+{
+ char *p, color[64], buf[2048];
+ int i, n;
+ HANDLE_S *h = NULL;
+
+ snprintf(buf, sizeof(buf), CHARSET_DISCLAIMER_1, charset ? charset : "US-ASCII");
+ p = &buf[strlen(buf)];
+
+ if(!(ps_global->display_charmap
+ && strucmp(ps_global->display_charmap, "US-ASCII"))
+ && handlesp
+ && (flags & FM_DISPLAY) == FM_DISPLAY){
+ h = new_handle(handlesp);
+ h->type = URL;
+ h->h.url.path = cpystr("x-alpine-help:h_config_char_set");
+
+ /*
+ * Because this filter comes after the delete_quotes filter, we need
+ * to tell delete_quotes that this handle is used, so it won't
+ * delete it. This is a dangerous thing.
+ */
+ h->is_used = 1;
+
+ if(!(flags & FM_NOCOLOR)
+ && scroll_handle_start_color(color, sizeof(color), &n)){
+ for(i = 0; i < n; i++)
+ if(p-buf < sizeof(buf))
+ *p++ = color[i];
+ }
+ else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ if(p-buf+1 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_BOLDON;
+ }
+ }
+
+ if(p-buf+1 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_HANDLE;
+ }
+
+ snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
+ if(p-buf < sizeof(buf))
+ *p = strlen(p + 1);
+
+ p += (*p + 1);
+ }
+
+ sstrncpy(&p, CHARSET_DISCLAIMER_2, sizeof(buf)-(p-buf));
+
+ if(h){
+ /* in case it was the current selection */
+ if(p-buf+1 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_INVOFF;
+ }
+
+ if(scroll_handle_end_color(color, sizeof(color), &n, 0)){
+ for(i = 0; i < n; i++)
+ if(p-buf < sizeof(buf))
+ *p++ = color[i];
+ }
+ else{
+ if(p-buf+1 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_BOLDOFF;
+ }
+ }
+
+ if(p-buf < sizeof(buf))
+ *p = '\0';
+ }
+
+ snprintf(p, sizeof(buf)-(p-buf), CHARSET_DISCLAIMER_3,
+ ps_global->display_charmap ? ps_global->display_charmap : "US-ASCII",
+ (quality == CV_LOSES_SPECIAL_CHARS) ? "special " : "");
+
+ buf[sizeof(buf)-1] = '\0';
+
+ return(format_editorial(buf, width, flags, handlesp, pc) == NULL
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc));
+}
+
+
+
+
+/*
+ * This one is a little more complicated because it comes late in the
+ * filtering sequence after colors have been added and url's hilited and
+ * so on. So if it wants to look at the beginning of the line it has to
+ * wade through some stuff done by the other filters first. This one is
+ * skipping the leading indent added by the viewer-margin stuff and leading
+ * tags.
+ */
+int
+delete_quotes(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ DELQ_S *dq;
+ char *lp;
+ int i, lines, not_a_quote = 0;
+ size_t len;
+
+ dq = (DELQ_S *) local;
+ if(!dq)
+ return(0);
+
+ if(dq->lines > 0 || dq->lines == Q_DEL_ALL){
+
+ lines = (dq->lines == Q_DEL_ALL) ? 0 : dq->lines;
+
+ /*
+ * First determine if this line is part of a quote.
+ */
+
+ /* skip over indent added by viewer-margin-left */
+ lp = line;
+ for(i = dq->indent_length; i > 0 && !not_a_quote && *lp; i--)
+ if(*lp++ != SPACE)
+ not_a_quote++;
+
+ /* skip over leading tags */
+ while(!not_a_quote
+ && ((unsigned char) (*lp) == (unsigned char) TAG_EMBED)){
+ switch(*++lp){
+ case '\0':
+ not_a_quote++;
+ break;
+
+ default:
+ ++lp;
+ break;
+
+ case TAG_FGCOLOR:
+ case TAG_BGCOLOR:
+ case TAG_HANDLE:
+ switch(*lp){
+ case TAG_FGCOLOR:
+ case TAG_BGCOLOR:
+ len = RGBLEN + 1;
+ break;
+
+ case TAG_HANDLE:
+ len = *++lp + 1;
+ break;
+ }
+
+ if(strlen(lp) < len)
+ not_a_quote++;
+ else
+ lp += len;
+
+ break;
+
+ case TAG_EMBED:
+ break;
+ }
+ }
+
+ /* skip over whitespace */
+ if(!dq->is_flowed)
+ while(isspace((unsigned char) *lp))
+ lp++;
+
+ /* check first character to see if it is a quote */
+ if(!not_a_quote && *lp != '>')
+ not_a_quote++;
+
+ if(not_a_quote){
+ if(dq->in_quote > lines+1 && !dq->delete_all){
+ char tmp[500];
+ COLOR_PAIR *col = NULL;
+ char cestart[2 * RGBLEN + 5];
+ char ceend[2 * RGBLEN + 5];
+
+ /*
+ * Display how many lines were hidden.
+ */
+
+ cestart[0] = ceend[0] = '\0';
+ if(dq->do_color
+ && ps_global->VAR_METAMSG_FORE_COLOR
+ && ps_global->VAR_METAMSG_BACK_COLOR
+ && (col = new_color_pair(ps_global->VAR_METAMSG_FORE_COLOR,
+ ps_global->VAR_METAMSG_BACK_COLOR)))
+ if(!pico_is_good_colorpair(col))
+ free_color_pair(&col);
+
+ if(col){
+ strncpy(cestart, color_embed(col->fg, col->bg), sizeof(cestart));
+ cestart[sizeof(cestart)-1] = '\0';
+ strncpy(ceend, color_embed(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR), sizeof(ceend));
+ ceend[sizeof(ceend)-1] = '\0';
+ free_color_pair(&col);
+ }
+
+ snprintf(tmp, sizeof(tmp),
+ "%s[ %s%d lines of quoted text hidden from view%s ]\r\n",
+ repeat_char(dq->indent_length, SPACE),
+ cestart, dq->in_quote - lines, ceend);
+ if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
+
+ snprintf(tmp, sizeof(tmp), "%s[ %s%d lines of quoted text hidden%s ]\r\n",
+ repeat_char(dq->indent_length, SPACE),
+ cestart, dq->in_quote - lines, ceend);
+
+ if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
+ snprintf(tmp, sizeof(tmp), "%s[ %s%d lines hidden%s ]\r\n",
+ repeat_char(dq->indent_length, SPACE),
+ cestart, dq->in_quote - lines, ceend);
+
+ if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
+ snprintf(tmp, sizeof(tmp), "%s[ %s%d hidden%s ]\r\n",
+ repeat_char(dq->indent_length, SPACE),
+ cestart, dq->in_quote - lines, ceend);
+
+ if(strlen(tmp)-strlen(cestart)-strlen(ceend)-2 > ps_global->ttyo->screen_cols){
+ snprintf(tmp, sizeof(tmp), "%s[...]\r\n",
+ repeat_char(dq->indent_length, SPACE));
+
+ if(strlen(tmp)-2 > ps_global->ttyo->screen_cols){
+ snprintf(tmp, sizeof(tmp), "%s...\r\n",
+ repeat_char(dq->indent_length, SPACE));
+
+ if(strlen(tmp)-2 > ps_global->ttyo->screen_cols){
+ snprintf(tmp, sizeof(tmp), "%s\r\n",
+ repeat_char(MIN(ps_global->ttyo->screen_cols,3),
+ '.'));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ins = gf_line_test_new_ins(ins, line, tmp, strlen(tmp));
+ mark_handles_in_line(line, dq->handlesp, 1);
+ ps_global->some_quoting_was_suppressed = 1;
+ }
+ else if(dq->in_quote == lines+1
+ && dq->saved_line && *dq->saved_line){
+ /*
+ * We would have only had to delete a single line. Instead
+ * of deleting it, just show it.
+ */
+ ins = gf_line_test_new_ins(ins, line,
+ *dq->saved_line,
+ strlen(*dq->saved_line));
+ mark_handles_in_line(*dq->saved_line, dq->handlesp, 1);
+ }
+ else
+ mark_handles_in_line(line, dq->handlesp, 1);
+
+ if(dq->saved_line && *dq->saved_line)
+ fs_give((void **) dq->saved_line);
+
+ dq->in_quote = 0;
+ }
+ else{
+ dq->in_quote++; /* count it */
+ if(dq->in_quote > lines){
+ /*
+ * If the hidden part is a single line we'll show it instead.
+ */
+ if(dq->in_quote == lines+1 && !dq->delete_all){
+ if(dq->saved_line && *dq->saved_line)
+ fs_give((void **) dq->saved_line);
+
+ *dq->saved_line = fs_get(strlen(line) + 3);
+ snprintf(*dq->saved_line, strlen(line)+3, "%s\r\n", line);
+ }
+
+ mark_handles_in_line(line, dq->handlesp, 0);
+ /* skip it, at least for now */
+ return(2);
+ }
+
+ mark_handles_in_line(line, dq->handlesp, 1);
+ }
+ }
+
+ return(0);
+}
+
+
+/*
+ * This is a line-at-a-time filter that replaces incoming UTF-8 text with
+ * ISO-2022-JP text.
+ */
+int
+translate_utf8_to_2022_jp(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ LOC_2022_JP *lpj;
+ int ret = 0;
+
+ lpj = (LOC_2022_JP *) local;
+
+ if(line && line[0]){
+ char *converted;
+ size_t len;
+
+ converted = utf8_to_charset(line, "ISO-2022-JP", lpj ? lpj->report_err : 0);
+
+ if(!converted)
+ ret = -1;
+
+ if(converted && converted != line){
+ /* delete the old line and replace it with the translation */
+ len = strlen(line);
+ ins = gf_line_test_new_ins(ins, line, "", -((int) len));
+ ins = gf_line_test_new_ins(ins, line+len, converted, strlen(converted));
+ fs_give((void **) &converted);
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ * Replace quotes of nonflowed messages. This needs to happen
+ * towards the end of filtering because a lot of prior filters
+ * depend on "> ", such as quote coloring and suppression.
+ * Quotes are already colored here, so we have to account for
+ * that formatting.
+ */
+int
+replace_quotes(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char *lp = line, *prefix = NULL, *last_prefix = NULL;
+ int no_more_quotes = 0, len, saw_quote = 0;
+
+ if(ps_global->VAR_QUOTE_REPLACE_STRING){
+ get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0);
+ if(!prefix && last_prefix){
+ prefix = last_prefix;
+ last_prefix = NULL;
+ }
+ }
+ else
+ return(0);
+
+ while(isspace((unsigned char)(*lp)))
+ lp++;
+ while(*lp && !no_more_quotes){
+ switch(*lp++){
+ case '>':
+ if(*lp == ' '){
+ ins = gf_line_test_new_ins(ins, lp - 1, "", -2);
+ lp++;
+ }
+ else
+ ins = gf_line_test_new_ins(ins, lp - 1, "", -1);
+ if(strlen(prefix))
+ ins = gf_line_test_new_ins(ins, lp, prefix, strlen(prefix));
+ saw_quote = 1;
+ break;
+ case TAG_EMBED:
+ switch(*lp++){
+ case TAG_FGCOLOR:
+ case TAG_BGCOLOR:
+ len = RGBLEN;
+ break;
+ case TAG_HANDLE:
+ len = *lp++ + 1;
+ break;
+ }
+ if(strlen(lp) < len)
+ no_more_quotes = 1;
+ else
+ lp += len;
+ break;
+ case ' ':
+ case '\t':
+ break;
+ default:
+ if(saw_quote && last_prefix)
+ ins = gf_line_test_new_ins(ins, lp - 1, last_prefix, strlen(last_prefix));
+ no_more_quotes = 1;
+ break;
+ }
+ }
+ if(prefix)
+ fs_give((void **)&prefix);
+ if(last_prefix)
+ fs_give((void **)&last_prefix);
+ return(0);
+}
+
+
+/*
+ * We need to delete handles that we are removing from the displayed
+ * text. But there is a complication. Some handles appear at more than
+ * one location in the text. So we keep track of which handles we're
+ * actually using as we go, then at the end we delete the ones we
+ * didn't use.
+ *
+ * Args line -- the text line, which includes tags
+ * handlesp -- handles pointer
+ * used -- If 1, set handles in this line to used
+ * if 0, leave handles in this line set as they already are
+ */
+void
+mark_handles_in_line(char *line, HANDLE_S **handlesp, int used)
+{
+ unsigned char c;
+ size_t length;
+ int key, n;
+ HANDLE_S *h;
+
+ if(!(line && handlesp && *handlesp))
+ return;
+
+ length = strlen(line);
+
+ /* mimic code in PutLine0n8b */
+ while(length--){
+
+ c = (unsigned char) *line++;
+
+ if(c == (unsigned char) TAG_EMBED && length){
+
+ length--;
+
+ switch(*line++){
+ case TAG_HANDLE :
+ length -= *line + 1; /* key length plus length tag */
+
+ for(key = 0, n = *line++; n; n--)
+ key = (key * 10) + (*line++ - '0');
+
+ h = get_handle(*handlesp, key);
+ if(h){
+ h->using_is_used = 1;
+ /*
+ * If it's already marked is_used, leave it marked
+ * that way. We only call this function with used = 0
+ * in order to set using_is_used.
+ */
+ h->is_used = (h->is_used || used) ? 1 : 0;
+ }
+
+ break;
+
+ case TAG_FGCOLOR :
+ if(length < RGBLEN){
+ length = 0;
+ break;
+ }
+
+ length -= RGBLEN;
+ line += RGBLEN;
+ break;
+
+ case TAG_BGCOLOR :
+ if(length < RGBLEN){
+ length = 0;
+ break;
+ }
+
+ length -= RGBLEN;
+ line += RGBLEN;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+ * parsed html risk state functions
+ */
+void
+clear_html_risk(void)
+{
+ _html_risk = 0;
+}
+
+void
+set_html_risk(void)
+{
+ _html_risk = 1;
+}
+
+int
+get_html_risk(void)
+{
+ return(_html_risk);
+}
diff --git a/pith/text.h b/pith/text.h
new file mode 100644
index 00000000..1813e3f0
--- /dev/null
+++ b/pith/text.h
@@ -0,0 +1,58 @@
+/*
+ * $Id: text.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_TEXT_INCLUDED
+#define PITH_TEXT_INCLUDED
+
+
+#include "../pith/atttype.h"
+#include "../pith/filter.h"
+#include "../pith/handle.h"
+
+
+typedef enum {InLine, QStatus} DetachErrStyle;
+
+
+typedef struct local_for_2022_jp_trans {
+ int report_err;
+} LOC_2022_JP;
+
+
+/*
+ * If the number of lines in the quote is equal to lines or less, then
+ * we show the quote. If the number of lines in the quote is more than lines,
+ * then we show lines-1 of the quote followed by one line of editorial
+ * comment.
+ */
+typedef struct del_q_s {
+ int lines; /* show this many lines (counting editorial) */
+ int indent_length; /* skip over this indent */
+ int is_flowed; /* message is labelled flowed */
+ int do_color;
+ int delete_all; /* delete quotes completely, no comments */
+ HANDLE_S **handlesp;
+ int in_quote; /* dynamic data */
+ char **saved_line; /* " */
+} DELQ_S;
+
+
+/* exported protoypes */
+int decode_text(ATTACH_S *, long, gf_io_t, HANDLE_S **, DetachErrStyle, int);
+int translate_utf8_to_2022_jp(long, char *, LT_INS_S **, void *);
+int delete_quotes(long, char *, LT_INS_S **, void *);
+
+
+#endif /* PITH_TEXT_INCLUDED */
diff --git a/pith/thread.c b/pith/thread.c
new file mode 100644
index 00000000..ff9bff54
--- /dev/null
+++ b/pith/thread.c
@@ -0,0 +1,1561 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: thread.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/thread.h"
+#include "../pith/flag.h"
+#include "../pith/icache.h"
+#include "../pith/mailindx.h"
+#include "../pith/msgno.h"
+#include "../pith/sort.h"
+#include "../pith/pineelt.h"
+#include "../pith/status.h"
+#include "../pith/news.h"
+#include "../pith/search.h"
+#include "../pith/mailcmd.h"
+#include "../pith/ablookup.h"
+
+
+/*
+ * Internal prototypes
+ */
+long *sort_thread_flatten(THREADNODE *, MAILSTREAM *, long *,
+ char *, long, PINETHRD_S *, unsigned);
+void make_thrdflags_consistent(MAILSTREAM *, MSGNO_S *, PINETHRD_S *, int);
+THREADNODE *collapse_threadnode_tree(THREADNODE *);
+THREADNODE *collapse_threadnode_tree_sorted(THREADNODE *);
+THREADNODE *sort_threads_and_collapse(THREADNODE *);
+THREADNODE *insert_tree_in_place(THREADNODE *, THREADNODE *);
+unsigned long branch_greatest_num(THREADNODE *, int);
+long calculate_visible_threads(MAILSTREAM *);
+
+
+PINETHRD_S *
+fetch_thread(MAILSTREAM *stream, long unsigned int rawno)
+{
+ MESSAGECACHE *mc;
+ PINELT_S *pelt;
+ PINETHRD_S *thrd = NULL;
+
+ if(stream && rawno > 0L && rawno <= stream->nmsgs
+ && !sp_need_to_rethread(stream)){
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && (pelt = (PINELT_S *) mc->sparep))
+ thrd = pelt->pthrd;
+ }
+
+ return(thrd);
+}
+
+
+PINETHRD_S *
+fetch_head_thread(MAILSTREAM *stream)
+{
+ unsigned long rawno;
+ PINETHRD_S *thrd = NULL;
+
+ if(stream){
+ /* first find any thread */
+ for(rawno = 1L; !thrd && rawno <= stream->nmsgs; rawno++)
+ thrd = fetch_thread(stream, rawno);
+
+ if(thrd && thrd->head)
+ thrd = fetch_thread(stream, thrd->head);
+ }
+
+ return(thrd);
+}
+
+
+/*
+ * Set flag f to v for all messages in thrd.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately.
+ * Ok to call it on top-level thread which has no branch already.
+ */
+void
+set_flags_for_thread(MAILSTREAM *stream, MSGNO_S *msgmap, int f, PINETHRD_S *thrd, int v)
+{
+ PINETHRD_S *nthrd, *bthrd;
+
+ if(!(stream && thrd && msgmap))
+ return;
+
+ set_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), f, v);
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ set_flags_for_thread(stream, msgmap, f, nthrd, v);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ set_flags_for_thread(stream, msgmap, f, bthrd, v);
+ }
+}
+
+
+void
+erase_threading_info(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ unsigned long n;
+ MESSAGECACHE *mc;
+ PINELT_S *peltp;
+
+ if(!(stream && stream->spare))
+ return;
+
+ ps_global->view_skipped_index = 0;
+ sp_set_viewing_a_thread(stream, 0);
+
+ if(THRD_INDX())
+ setup_for_thread_index_screen();
+ else
+ setup_for_index_index_screen();
+
+ stream->spare = 0;
+
+ for(n = 1L; n <= stream->nmsgs; n++){
+ set_lflag(stream, msgmap, mn_raw2m(msgmap, n),
+ MN_COLL | MN_CHID | MN_CHID2, 0);
+ mc = mail_elt(stream, n);
+ if(mc && mc->sparep){
+ peltp = (PINELT_S *) mc->sparep;
+ if(peltp->pthrd)
+ fs_give((void **) &peltp->pthrd);
+ }
+ }
+}
+
+
+void
+sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree)
+{
+ THREADNODE *collapsed_tree = NULL;
+ PINETHRD_S *thrd = NULL;
+ unsigned long msgno, rawno;
+ int un_view_thread = 0;
+ long raw_current;
+ char *dup_chk = NULL;
+
+
+ dprint((2, "sort_thread_callback\n"));
+
+ g_sort.msgmap->max_thrdno = 0L;
+
+ /*
+ * Eliminate dummy nodes from tree and collapse the tree in a logical
+ * way. If the dummy node is at the top-level, then its children are
+ * promoted to the top-level as separate threads.
+ */
+ if(F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global))
+ collapsed_tree = collapse_threadnode_tree_sorted(tree);
+ else
+ collapsed_tree = collapse_threadnode_tree(tree);
+
+ /* dup_chk is like sort with an origin of 1 */
+ dup_chk = (char *) fs_get((mn_get_nmsgs(g_sort.msgmap)+1) * sizeof(char));
+ memset(dup_chk, 0, (mn_get_nmsgs(g_sort.msgmap)+1) * sizeof(char));
+
+ memset(&g_sort.msgmap->sort[1], 0, mn_get_total(g_sort.msgmap) * sizeof(long));
+
+ (void) sort_thread_flatten(collapsed_tree, stream,
+ &g_sort.msgmap->sort[1],
+ dup_chk, mn_get_nmsgs(g_sort.msgmap),
+ NULL, THD_TOP);
+
+ /* reset the inverse array */
+ msgno_reset_isort(g_sort.msgmap);
+
+ if(dup_chk)
+ fs_give((void **) &dup_chk);
+
+ if(collapsed_tree)
+ mail_free_threadnode(&collapsed_tree);
+
+ if(any_lflagged(g_sort.msgmap, MN_HIDE))
+ g_sort.msgmap->visible_threads = calculate_visible_threads(stream);
+ else
+ g_sort.msgmap->visible_threads = g_sort.msgmap->max_thrdno;
+
+ raw_current = mn_m2raw(g_sort.msgmap, mn_get_cur(g_sort.msgmap));
+
+ sp_set_need_to_rethread(stream, 0);
+
+ /*
+ * Set appropriate bits to start out collapsed if desired. We use the
+ * stream spare bit to tell us if we've done this before for this
+ * stream.
+ */
+ if(!stream->spare
+ && (COLL_THRDS() || SEP_THRDINDX())
+ && mn_get_total(g_sort.msgmap) > 1L){
+
+ collapse_threads(stream, g_sort.msgmap, NULL);
+ }
+ else if(stream->spare){
+
+ /*
+ * If we're doing auto collapse then new threads need to have
+ * their collapse bit set. This happens below if we're in the
+ * thread index, but if we're in the regular index with auto
+ * collapse we have to look for these.
+ */
+ if(any_lflagged(g_sort.msgmap, MN_USOR)){
+ if(COLL_THRDS()){
+ for(msgno = 1L; msgno <= mn_get_total(g_sort.msgmap); msgno++){
+ rawno = mn_m2raw(g_sort.msgmap, msgno);
+ if(get_lflag(stream, NULL, rawno, MN_USOR)){
+ thrd = fetch_thread(stream, rawno);
+
+ /*
+ * Node is new, unsorted, top-level thread,
+ * and we're using auto collapse.
+ */
+ if(thrd && !thrd->parent)
+ set_lflag(stream, g_sort.msgmap, msgno, MN_COLL, 1);
+
+ /*
+ * If a parent is collapsed, clear that parent's
+ * index cache entry. This is only necessary if
+ * the parent's index display can depend on its
+ * children, of course.
+ */
+ if(thrd && thrd->parent){
+ thrd = fetch_thread(stream, thrd->parent);
+ while(thrd){
+ long t;
+
+ if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
+ && (t = mn_raw2m(g_sort.msgmap,
+ (long) thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+
+ if(thrd->parent)
+ thrd = fetch_thread(stream, thrd->parent);
+ else
+ thrd = NULL;
+ }
+ }
+
+ }
+ }
+ }
+
+ set_lflags(stream, g_sort.msgmap, MN_USOR, 0);
+ }
+
+ if(sp_viewing_a_thread(stream)){
+ if(any_lflagged(g_sort.msgmap, MN_CHID2)){
+ /* current should be part of viewed thread */
+ if(get_lflag(stream, NULL, raw_current, MN_CHID2)){
+ thrd = fetch_thread(stream, raw_current);
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+
+ if(thrd){
+ /*
+ * For messages that are part of thread set MN_CHID2
+ * and for messages that aren't part of the thread
+ * clear MN_CHID2. Easiest is to just do it instead
+ * of checking if it is true first.
+ */
+ set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
+ set_thread_lflags(stream, thrd, g_sort.msgmap,
+ MN_CHID2, 1);
+
+ /*
+ * Outside of the viewed thread everything else
+ * should be collapsed at the top-levels.
+ */
+ collapse_threads(stream, g_sort.msgmap, thrd);
+
+ /*
+ * Inside of the thread, the top of the thread
+ * can't be hidden, the rest are hidden if a
+ * parent somewhere above them is collapsed.
+ * There can be collapse points that are hidden
+ * inside of the tree. They remain collapsed even
+ * if the parent above them uncollapses.
+ */
+ msgno = mn_raw2m(g_sort.msgmap, (long) thrd->rawno);
+ if(msgno)
+ set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
+
+ if(thrd->next){
+ PINETHRD_S *nthrd;
+
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ make_thrdflags_consistent(stream, g_sort.msgmap,
+ nthrd,
+ get_lflag(stream, NULL,
+ thrd->rawno,
+ MN_COLL));
+ }
+ }
+ else
+ un_view_thread++;
+ }
+ else
+ un_view_thread++;
+ }
+ else
+ un_view_thread++;
+
+ if(un_view_thread){
+ set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
+ unview_thread(ps_global, stream, g_sort.msgmap);
+ }
+ else{
+ mn_reset_cur(g_sort.msgmap,
+ mn_raw2m(g_sort.msgmap, raw_current));
+ view_thread(ps_global, stream, g_sort.msgmap, 0);
+ }
+ }
+ else if(SEP_THRDINDX()){
+ set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
+ collapse_threads(stream, g_sort.msgmap, NULL);
+ }
+ else{
+ thrd = fetch_head_thread(stream);
+ while(thrd){
+ /*
+ * The top-level threads aren't hidden by collapse.
+ */
+ msgno = mn_raw2m(g_sort.msgmap, thrd->rawno);
+ if(msgno)
+ set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
+
+ if(thrd->next){
+ PINETHRD_S *nthrd;
+
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ make_thrdflags_consistent(stream, g_sort.msgmap,
+ nthrd,
+ get_lflag(stream, NULL,
+ thrd->rawno,
+ MN_COLL));
+ }
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+
+ stream->spare = 1;
+
+ dprint((2, "sort_thread_callback done\n"));
+}
+
+
+void
+collapse_threads(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *not_this_thread)
+{
+ PINETHRD_S *thrd = NULL, *nthrd;
+ unsigned long msgno;
+
+ dprint((9, "collapse_threads\n"));
+
+ thrd = fetch_head_thread(stream);
+ while(thrd){
+ if(thrd != not_this_thread){
+ msgno = mn_raw2m(g_sort.msgmap, thrd->rawno);
+
+ /* set collapsed bit */
+ if(msgno){
+ set_lflag(stream, g_sort.msgmap, msgno, MN_COLL, 1);
+ set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
+ }
+
+ /* hide its children */
+ if(thrd->next && (nthrd = fetch_thread(stream, thrd->next)))
+ set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID);
+ }
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+
+ dprint((9, "collapse_threads done\n"));
+}
+
+
+void
+make_thrdflags_consistent(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *thrd,
+ int a_parent_is_collapsed)
+{
+ PINETHRD_S *nthrd, *bthrd;
+ unsigned long msgno;
+
+ if(!thrd)
+ return;
+
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+
+ if(a_parent_is_collapsed){
+ /* if some parent is collapsed, we should be hidden */
+ if(msgno)
+ set_lflag(stream, msgmap, msgno, MN_CHID, 1);
+ }
+ else{
+ /* no parent is collapsed so we are not hidden */
+ if(msgno)
+ set_lflag(stream, msgmap, msgno, MN_CHID, 0);
+ }
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ make_thrdflags_consistent(stream, msgmap, nthrd,
+ a_parent_is_collapsed
+ ? a_parent_is_collapsed
+ : get_lflag(stream, NULL, thrd->rawno,
+ MN_COLL));
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ make_thrdflags_consistent(stream, msgmap, bthrd,
+ a_parent_is_collapsed);
+ }
+}
+
+
+long
+calculate_visible_threads(MAILSTREAM *stream)
+{
+ PINETHRD_S *thrd = NULL;
+ long vis = 0L;
+
+ thrd = fetch_head_thread(stream);
+ while(thrd){
+ vis += (thread_has_some_visible(stream, thrd) ? 1 : 0);
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+
+ return(vis);
+}
+
+
+/*
+ * This routine does a couple things. The input is the THREADNODE node
+ * that we get from c-client because of the THREAD command. The rest of
+ * the arguments are used to help guide this function through its
+ * recursive steps. One thing it does is to place the sort order in
+ * the array initially pointed to by the entry argument. All it is doing
+ * is walking the tree in the next then branch order you see below and
+ * incrementing the entry number one for each node. The other thing it
+ * is doing at the same time is to create a PINETHRD_S tree from the
+ * THREADNODE tree. The two trees are completely equivalent but the
+ * PINETHRD_S version has additional back pointers and parent pointers
+ * and so on to make it easier for alpine to deal with it. Each node
+ * of that tree is tied to the data associated with a particular message
+ * by the msgno_thread_info() call, so that we can go from a message
+ * number to the place in the thread tree that message sits.
+ */
+long *
+sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream,
+ long *entry, char *dup_chk, long maxno,
+ PINETHRD_S *thrd, unsigned int flags)
+{
+ PINETHRD_S *newthrd = NULL;
+
+ if(node){
+ if(node->num > 0L && node->num <= maxno){ /* holes happen */
+ if(!dup_chk[node->num]){ /* not a duplicate */
+ *entry = node->num;
+ dup_chk[node->num] = 1;
+
+ /*
+ * Build a richer threading structure that will help us paint
+ * and operate on threads and subthreads.
+ */
+ newthrd = msgno_thread_info(stream, node->num, thrd, flags);
+ if(newthrd){
+ entry++;
+
+ if(node->next)
+ entry = sort_thread_flatten(node->next, stream,
+ entry, dup_chk, maxno,
+ newthrd, THD_NEXT);
+
+ if(node->branch)
+ entry = sort_thread_flatten(node->branch, stream,
+ entry, dup_chk, maxno,
+ newthrd,
+ (flags == THD_TOP) ? THD_TOP
+ : THD_BRANCH);
+ }
+ }
+ }
+ }
+
+ return(entry);
+}
+
+
+/*
+ * Make a copy of c-client's THREAD tree while eliminating dummy nodes.
+ */
+THREADNODE *
+collapse_threadnode_tree(THREADNODE *tree)
+{
+ THREADNODE *newtree = NULL;
+
+ if(tree){
+ if(tree->num){
+ newtree = mail_newthreadnode(NULL);
+ newtree->num = tree->num;
+ if(tree->next)
+ newtree->next = collapse_threadnode_tree(tree->next);
+
+ if(tree->branch)
+ newtree->branch = collapse_threadnode_tree(tree->branch);
+ }
+ else{
+ if(tree->next)
+ newtree = collapse_threadnode_tree(tree->next);
+
+ if(tree->branch){
+ if(newtree){
+ THREADNODE *last_branch = NULL;
+
+ /*
+ * Next moved up to replace "tree" in the tree.
+ * If next has no branches, then we want to branch off
+ * of next. If next has branches, we want to branch off
+ * of the last of those branches instead.
+ */
+ last_branch = newtree;
+ while(last_branch->branch)
+ last_branch = last_branch->branch;
+
+ last_branch->branch = collapse_threadnode_tree(tree->branch);
+ }
+ else
+ newtree = collapse_threadnode_tree(tree->branch);
+ }
+ }
+ }
+
+ return(newtree);
+}
+
+
+/*
+ * Like collapse_threadnode_tree, we collapse the dummy nodes.
+ * In addition we rearrange the threads by order of arrival of
+ * the last message in the thread, rather than the first message
+ * in the thread.
+ */
+THREADNODE *
+collapse_threadnode_tree_sorted(THREADNODE *tree)
+{
+ THREADNODE *sorted_tree = NULL;
+
+ sorted_tree = sort_threads_and_collapse(tree);
+
+ /*
+ * We used to eliminate top-level dummy nodes here so that
+ * orphans would still get sorted together, but we changed
+ * to sort the orphans themselves as top-level threads.
+ *
+ * It might be a matter of choice how they get sorted, but
+ * we'll try doing it this way and not add another feature.
+ */
+
+ return(sorted_tree);
+}
+
+/*
+ * Recurse through the tree, sorting each top-level branch by the
+ * greatest num in the thread.
+ */
+THREADNODE *
+sort_threads_and_collapse(THREADNODE *tree)
+{
+ THREADNODE *newtree = NULL, *newbranchtree = NULL, *newtreefree = NULL;
+
+ if(tree){
+ newtree = mail_newthreadnode(NULL);
+ newtree->num = tree->num;
+
+ /*
+ * Only sort at the top level. Individual threads can
+ * rely on collapse_threadnode_tree
+ */
+ if(tree->next)
+ newtree->next = collapse_threadnode_tree(tree->next);
+
+ if(tree->branch){
+ /*
+ * This recursive call returns an already re-sorted tree.
+ * With that, we can loop through and inject ourselves
+ * where we fit in with that sort, and pass back to the
+ * caller to inject themselves.
+ */
+ newbranchtree = sort_threads_and_collapse(tree->branch);
+ }
+
+ if(newtree->num)
+ newtree = insert_tree_in_place(newtree, newbranchtree);
+ else{
+ /*
+ * If top node is a dummy, here is where we collapse it.
+ */
+ newtreefree = newtree;
+ newtree = insert_tree_in_place(newtree->next, newbranchtree);
+ newtreefree->next = NULL;
+ mail_free_threadnode(&newtreefree);
+ }
+ }
+
+ return(newtree);
+}
+
+/*
+ * Recursively insert each of the top-level nodes in newtree in their place
+ * in tree according to which tree has the most recent arrival
+ */
+THREADNODE *
+insert_tree_in_place(THREADNODE *newtree, THREADNODE *tree)
+{
+ THREADNODE *node = NULL;
+ unsigned long newtree_greatest_num = 0;
+ if(newtree->branch){
+ node = newtree->branch;
+ newtree->branch = NULL;
+ tree = insert_tree_in_place(node, tree);
+ }
+
+ newtree_greatest_num = branch_greatest_num(newtree, 0);
+
+ if(tree){
+ /*
+ * Since tree is already sorted, we can insert when we find something
+ * newtree is less than
+ */
+ if(newtree_greatest_num < branch_greatest_num(tree, 0))
+ newtree->branch = tree;
+ else {
+ for(node = tree; node->branch; node = node->branch){
+ if(newtree_greatest_num < branch_greatest_num(node->branch, 0)){
+ newtree->branch = node->branch;
+ node->branch = newtree;
+ break;
+ }
+ }
+ if(!node->branch)
+ node->branch = newtree;
+
+ newtree = tree;
+ }
+ }
+
+ return(newtree);
+}
+
+/*
+ * Given a thread, return the greatest num in the tree.
+ * is_subthread tells us not to recurse through branches, so
+ * we can split the top level into threads.
+ */
+unsigned long
+branch_greatest_num(THREADNODE *tree, int is_subthread)
+{
+ unsigned long ret, branch_ret;
+
+ ret = tree->num;
+
+ if(tree->next && (branch_ret = branch_greatest_num(tree->next, 1)) > ret)
+ ret = branch_ret;
+ if(is_subthread && tree->branch &&
+ (branch_ret = branch_greatest_num(tree->branch, 1)) > ret)
+ ret = branch_ret;
+
+ return ret;
+}
+
+
+/*
+ * Args stream -- the usual
+ * rawno -- the raw msg num associated with this new node
+ * attached_to_thrd -- the PINETHRD_S node that this is either a next or branch
+ * off of
+ * flags --
+ */
+PINETHRD_S *
+msgno_thread_info(MAILSTREAM *stream, long unsigned int rawno,
+ PINETHRD_S *attached_to_thrd, unsigned int flags)
+{
+ PINELT_S **peltp;
+ MESSAGECACHE *mc;
+
+ if(!stream || rawno < 1L || rawno > stream->nmsgs)
+ return NULL;
+
+ /*
+ * any private elt data yet?
+ */
+ if((mc = mail_elt(stream, rawno))
+ && (*(peltp = (PINELT_S **) &mc->sparep) == NULL)){
+ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
+ memset(*peltp, 0, sizeof(PINELT_S));
+ }
+
+ if((*peltp)->pthrd == NULL)
+ (*peltp)->pthrd = (PINETHRD_S *) fs_get(sizeof(PINETHRD_S));
+
+ memset((*peltp)->pthrd, 0, sizeof(PINETHRD_S));
+
+ (*peltp)->pthrd->rawno = rawno;
+
+ if(attached_to_thrd)
+ (*peltp)->pthrd->head = attached_to_thrd->head;
+ else
+ (*peltp)->pthrd->head = (*peltp)->pthrd->rawno; /* it's me */
+
+ if(flags == THD_TOP){
+ /*
+ * We can tell this thread is a top-level thread because it doesn't
+ * have a parent.
+ */
+ (*peltp)->pthrd->top = (*peltp)->pthrd->rawno; /* I am a top */
+ if(attached_to_thrd){
+ attached_to_thrd->nextthd = (*peltp)->pthrd->rawno;
+ (*peltp)->pthrd->prevthd = attached_to_thrd->rawno;
+ (*peltp)->pthrd->thrdno = attached_to_thrd->thrdno + 1L;
+ }
+ else
+ (*peltp)->pthrd->thrdno = 1L; /* 1st thread */
+
+ g_sort.msgmap->max_thrdno = (*peltp)->pthrd->thrdno;
+ }
+ else if(flags == THD_NEXT){
+ if(attached_to_thrd){
+ attached_to_thrd->next = (*peltp)->pthrd->rawno;
+ (*peltp)->pthrd->parent = attached_to_thrd->rawno;
+ (*peltp)->pthrd->top = attached_to_thrd->top;
+ }
+ }
+ else if(flags == THD_BRANCH){
+ if(attached_to_thrd){
+ attached_to_thrd->branch = (*peltp)->pthrd->rawno;
+ (*peltp)->pthrd->parent = attached_to_thrd->parent;
+ (*peltp)->pthrd->top = attached_to_thrd->top;
+ }
+ }
+
+ return((*peltp)->pthrd);
+}
+
+
+/*
+ * Collapse or expand a threading subtree. Not called from separate thread
+ * index.
+ */
+void
+collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
+ long unsigned int msgno)
+{
+ int collapsed, adjust_current = 0;
+ PINETHRD_S *thrd = NULL, *nthrd;
+ unsigned long rawno;
+
+ if(!stream)
+ return;
+
+ /*
+ * If msgno is a good msgno, then we collapse or expand the subthread
+ * which begins at msgno. If msgno is 0, we collapse or expand the
+ * entire current thread.
+ */
+
+ if(msgno > 0L && msgno <= mn_get_total(msgmap)){
+ rawno = mn_m2raw(msgmap, msgno);
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+ }
+ else if(msgno == 0L){
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(thrd && thrd->top != thrd->rawno){
+ adjust_current++;
+ thrd = fetch_thread(stream, thrd->top);
+
+ /*
+ * Special case. If the user is collapsing the entire thread
+ * (msgno == 0), and we are in a Zoomed view, and the top of
+ * the entire thread is not part of the Zoomed view, then watch
+ * out. If we were to collapse the entire thread it would just
+ * disappear, because the top is not in the Zoom. Therefore,
+ * don't allow it. Do what the user probably wants, which is to
+ * collapse the thread at that point instead of the entire thread,
+ * leaving behind the top of the subthread to expand if needed.
+ * In other words, treat it as if they didn't have the
+ * F_SLASH_COLL_ENTIRE feature set.
+ */
+ collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL)
+ && thrd->next;
+
+ if(!collapsed && get_lflag(stream, NULL, thrd->rawno, MN_HIDE))
+ thrd = fetch_thread(stream, rawno);
+ }
+ }
+
+
+ if(!thrd)
+ return;
+
+ collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL) && thrd->next;
+
+ if(collapsed){
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+ if(msgno > 0L && msgno <= mn_get_total(msgmap)){
+ set_lflag(stream, msgmap, msgno, MN_COLL, 0);
+ if(thrd->next){
+ if((nthrd = fetch_thread(stream, thrd->next)) != NULL)
+ set_thread_subtree(stream, nthrd, msgmap, 0, MN_CHID);
+
+ clear_index_cache_ent(stream, msgno, 0);
+ }
+ }
+ }
+ else if(thrd && thrd->next){
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+ if(msgno > 0L && msgno <= mn_get_total(msgmap)){
+ set_lflag(stream, msgmap, msgno, MN_COLL, 1);
+ if((nthrd = fetch_thread(stream, thrd->next)) != NULL)
+ set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID);
+
+ clear_index_cache_ent(stream, msgno, 0);
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 1,
+ _("No thread to collapse or expand on this line"));
+
+ /* if current is hidden, adjust */
+ if(adjust_current)
+ adjust_cur_to_visible(stream, msgmap);
+}
+
+
+/*
+ * Select the messages in a subthread. If all of the messages are already
+ * selected, unselect them. This routine is a bit strange because it
+ * doesn't set the MN_SLCT bit. Instead, it sets MN_STMP in apply_command
+ * and then thread_command copies the MN_STMP messages back to MN_SLCT.
+ */
+void
+select_thread_stmp(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ PINETHRD_S *thrd;
+ unsigned long rawno, in_thread, set_in_thread, save_branch;
+
+ /* ugly bit means the same thing as return of 1 from individual_select */
+ state->ugly_consider_advancing_bit = 0;
+
+ if(!(stream && msgmap))
+ return;
+
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(!thrd)
+ return;
+
+ /* run through thrd to see if it is all selected */
+ save_branch = thrd->branch;
+ thrd->branch = 0L;
+ if((set_in_thread = count_lflags_in_thread(stream, thrd, msgmap, MN_STMP))
+ == (in_thread = count_lflags_in_thread(stream, thrd, msgmap, MN_NONE))){
+ /*
+ * If everything is selected, the first unselect should cause
+ * an autozoom. In order to trigger the right code in
+ * thread_command()
+ * copy_lflags()
+ * we set the MN_HIDE bit on the current message here.
+ */
+ if(F_ON(F_AUTO_ZOOM, state) && !any_lflagged(msgmap, MN_HIDE)
+ && any_lflagged(msgmap, MN_STMP) == mn_get_total(msgmap))
+ set_lflag(stream, msgmap, mn_get_cur(msgmap), MN_HIDE, 1);
+ set_thread_lflags(stream, thrd, msgmap, MN_STMP, 0);
+ }
+ else{
+ set_thread_lflags(stream, thrd, msgmap, MN_STMP, 1);
+ state->ugly_consider_advancing_bit = 1;
+ }
+
+ thrd->branch = save_branch;
+
+ if(set_in_thread == in_thread)
+ q_status_message1(SM_ORDER, 0, 3, _("Unselected %s messages in thread"),
+ comatose((long) in_thread));
+ else if(set_in_thread == 0)
+ q_status_message1(SM_ORDER, 0, 3, _("Selected %s messages in thread"),
+ comatose((long) in_thread));
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Selected %s more messages in thread"),
+ comatose((long) (in_thread-set_in_thread)));
+}
+
+
+/*
+ * Count how many of this system flag in this thread subtree.
+ * If flags == 0 count the messages in the thread.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately.
+ * Ok to call it on top-level thread which has no branch already.
+ */
+unsigned long
+count_flags_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, long int flags)
+{
+ unsigned long count = 0;
+ PINETHRD_S *nthrd, *bthrd;
+ MESSAGECACHE *mc;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return count;
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ count += count_flags_in_thread(stream, nthrd, flags);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ count += count_flags_in_thread(stream, bthrd, flags);
+ }
+
+ mc = (thrd && thrd->rawno > 0L && stream && thrd->rawno <= stream->nmsgs)
+ ? mail_elt(stream, thrd->rawno) : NULL;
+ if(mc && mc->valid && FLAG_MATCH(flags, mc, stream))
+ count++;
+
+ return count;
+}
+
+
+/*
+ * Count how many of this local flag in this thread subtree.
+ * If flags == MN_NONE then we just count the messages instead of whether
+ * the messages have a flag set.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately.
+ * Ok to call it on top-level thread which has no branch already.
+ */
+unsigned long
+count_lflags_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int flags)
+{
+ unsigned long count = 0;
+ PINETHRD_S *nthrd, *bthrd;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return count;
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ count += count_lflags_in_thread(stream, nthrd, msgmap, flags);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ count += count_lflags_in_thread(stream, bthrd, msgmap,flags);
+ }
+
+ if(flags == MN_NONE)
+ count++;
+ else
+ count += get_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), flags);
+
+ return count;
+}
+
+
+/*
+ * Special-purpose for performance improvement.
+ */
+int
+thread_has_some_visible(MAILSTREAM *stream, PINETHRD_S *thrd)
+{
+ PINETHRD_S *nthrd, *bthrd;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return 0;
+
+ if(get_lflag(stream, NULL, thrd->rawno, MN_HIDE) == 0)
+ return 1;
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd && thread_has_some_visible(stream, nthrd))
+ return 1;
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd && thread_has_some_visible(stream, bthrd))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int
+mark_msgs_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap)
+{
+ int count = 0;
+ PINETHRD_S *nthrd, *bthrd;
+ MESSAGECACHE *mc;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return count;
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ count += mark_msgs_in_thread(stream, nthrd, msgmap);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ count += mark_msgs_in_thread(stream, bthrd, msgmap);
+ }
+
+ if(stream && thrd->rawno >= 1L && thrd->rawno <= stream->nmsgs &&
+ (mc = mail_elt(stream,thrd->rawno))
+ && !mc->sequence
+ && !mc->private.msg.env){
+ mc->sequence = 1;
+ count++;
+ }
+
+ return count;
+}
+
+
+/*
+ * This sets or clears flags for the messages at this node and below in
+ * a tree.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately.
+ * Ok to call it on top-level thread which has no branch already.
+ */
+void
+set_thread_lflags(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int flags, int v)
+
+
+
+ /* flags to set or clear */
+ /* set or clear? */
+{
+ unsigned long msgno;
+ PINETHRD_S *nthrd, *bthrd;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return;
+
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+
+ set_lflag(stream, msgmap, msgno, flags, v);
+
+ /*
+ * Careful, performance hack. This should logically be a separate
+ * operation on the thread but it is convenient to stick it in here.
+ *
+ * When we back out of viewing a thread to the separate-thread-index
+ * we may leave behind some cached hlines that aren't quite right
+ * because they were collapsed. In particular, the plus_col character
+ * may be wrong. Instead of trying to figure out what it should be just
+ * clear the cache entries for the this thread when we come back in
+ * to view it again.
+ */
+ if(msgno > 0L && flags == MN_CHID2 && v == 1)
+ clear_index_cache_ent(stream, msgno, 0);
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ set_thread_lflags(stream, nthrd, msgmap, flags, v);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ set_thread_lflags(stream, bthrd, msgmap, flags, v);
+ }
+}
+
+
+char
+status_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, IndexColType type)
+{
+ char status = ' ';
+ unsigned long save_branch, cnt, tot_in_thrd;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return status;
+
+ save_branch = thrd->branch;
+ thrd->branch = 0L; /* branch is a sibling, not part of thread */
+
+ /*
+ * This is D if all of thread is deleted,
+ * else A if all of thread is answered,
+ * else F if all of thread is forwarded,
+ * else N if any unseen and not deleted,
+ * else blank.
+ */
+ if(type == iStatus){
+ tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
+ /* all deleted */
+ if(count_flags_in_thread(stream, thrd, F_DEL) == tot_in_thrd)
+ status = 'D';
+ /* all answered */
+ else if(count_flags_in_thread(stream, thrd, F_ANS) == tot_in_thrd)
+ status = 'A';
+ /* all forwarded */
+ else if(count_flags_in_thread(stream, thrd, F_FWD) == tot_in_thrd)
+ status = 'F';
+ /* or any new and not deleted */
+ else if((!IS_NEWS(stream)
+ || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
+ && count_flags_in_thread(stream, thrd, F_UNDEL|F_UNSEEN))
+ status = 'N';
+ }
+ else if(type == iFStatus){
+ if(!IS_NEWS(stream) || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
+ tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
+ cnt = count_flags_in_thread(stream, thrd, F_UNSEEN);
+ if(cnt)
+ status = (cnt == tot_in_thrd) ? 'N' : 'n';
+ }
+ }
+ else if(type == iIStatus || type == iSIStatus){
+ tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
+
+ /* unseen and recent */
+ cnt = count_flags_in_thread(stream, thrd, F_RECENT|F_UNSEEN);
+ if(cnt)
+ status = (cnt == tot_in_thrd) ? 'N' : 'n';
+ else{
+ /* unseen and !recent */
+ cnt = count_flags_in_thread(stream, thrd, F_UNSEEN);
+ if(cnt)
+ status = (cnt == tot_in_thrd) ? 'U' : 'u';
+ else{
+ /* seen and recent */
+ cnt = count_flags_in_thread(stream, thrd, F_RECENT|F_SEEN);
+ if(cnt)
+ status = (cnt == tot_in_thrd) ? 'R' : 'r';
+ }
+ }
+ }
+
+ thrd->branch = save_branch;
+
+ return status;
+}
+
+
+/*
+ * Symbol is * if some message in thread is important,
+ * + if some message is to us,
+ * - if mark-for-cc and some message is cc to us, else blank.
+ */
+char
+to_us_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, int consider_flagged)
+{
+ char to_us = ' ';
+ char branch_to_us = ' ';
+ PINETHRD_S *nthrd, *bthrd;
+ MESSAGECACHE *mc;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return to_us;
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ to_us = to_us_symbol_for_thread(stream, nthrd, consider_flagged);
+ }
+
+ if(((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+'))
+ && thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ branch_to_us = to_us_symbol_for_thread(stream, bthrd, consider_flagged);
+
+ /* use branch to_us symbol if it has higher priority than what we have so far */
+ if(to_us == ' '){
+ if(branch_to_us == '-' || branch_to_us == '+' || branch_to_us == '*')
+ to_us = branch_to_us;
+ }
+ else if(to_us == '-'){
+ if(branch_to_us == '+' || branch_to_us == '*')
+ to_us = branch_to_us;
+ }
+ else if(to_us == '+'){
+ if(branch_to_us == '*')
+ to_us = branch_to_us;
+ }
+ }
+
+ if((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+')){
+ if(consider_flagged && thrd && thrd->rawno > 0L
+ && stream && thrd->rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, thrd->rawno))
+ && FLAG_MATCH(F_FLAG, mc, stream))
+ to_us = '*';
+ else if(to_us != '+' && !IS_NEWS(stream)){
+ INDEXDATA_S idata;
+ MESSAGECACHE *mc;
+ ADDRESS *addr;
+
+ memset(&idata, 0, sizeof(INDEXDATA_S));
+ idata.stream = stream;
+ idata.rawno = thrd->rawno;
+ idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
+ if(idata.rawno > 0L && stream && idata.rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, idata.rawno))){
+ idata.size = mc->rfc822_size;
+ index_data_env(&idata,
+ pine_mail_fetchenvelope(stream, idata.rawno));
+ }
+ else
+ idata.bogus = 2;
+
+ for(addr = fetch_to(&idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ to_us = '+';
+ break;
+ }
+
+ if(to_us != '+' && resent_to_us(&idata))
+ to_us = '+';
+
+ if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
+ for(addr = fetch_cc(&idata); addr; addr = addr->next)
+ if(address_is_us(addr, ps_global)){
+ to_us = '-';
+ break;
+ }
+ }
+ }
+
+ return to_us;
+}
+
+
+/*
+ * This sets or clears flags for the messages at this node and below in
+ * a tree. It doesn't just blindly do it, perhaps it should. Instead,
+ * when un-hiding a subtree it leaves the sub-subtree hidden if a node
+ * is collapsed.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately.
+ * Ok to call it on top-level thread which has no branch already.
+ */
+void
+set_thread_subtree(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int v, int flags)
+
+
+
+ /* set or clear? */
+ /* flags to set or clear */
+{
+ int hiding;
+ unsigned long msgno;
+ PINETHRD_S *nthrd, *bthrd;
+
+ hiding = (flags == MN_CHID) && v;
+
+ if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
+ return;
+
+ msgno = mn_raw2m(msgmap, thrd->rawno);
+
+ set_lflag(stream, msgmap, msgno, flags, v);
+
+ if(thrd->next && (hiding || !get_lflag(stream,NULL,thrd->rawno,MN_COLL))){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ set_thread_subtree(stream, nthrd, msgmap, v, flags);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ set_thread_subtree(stream, bthrd, msgmap, v, flags);
+ }
+}
+
+
+/*
+ * View a thread. Move from the thread index screen to a message index
+ * screen for the current thread.
+ *
+ * set_lflags - Set the local flags appropriately to start viewing
+ * the thread. We would not want to set this if we are
+ * already viewing the thread (and expunge or new mail
+ * happened) and we want to preserve the collapsed state
+ * of the subthreads.
+ */
+int
+view_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int set_lflags)
+{
+ PINETHRD_S *thrd = NULL;
+ unsigned long rawno, cur;
+
+ if(!any_messages(msgmap, NULL, "to View"))
+ return 0;
+
+ if(!(stream && msgmap))
+ return 0;
+
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+
+ if(!thrd)
+ return 0;
+
+ /*
+ * Clear hidden and collapsed flag for this thread.
+ * And set CHID2.
+ * Don't have to worry about there being a branch because
+ * this is a toplevel thread.
+ */
+ if(set_lflags){
+ set_thread_lflags(stream, thrd, msgmap, MN_COLL | MN_CHID, 0);
+ set_thread_lflags(stream, thrd, msgmap, MN_CHID2, 1);
+ }
+
+ /*
+ * If this is one of those wacky users who like to sort backwards
+ * they would probably prefer that the current message be the last
+ * one in the thread (the one highest up the screen).
+ */
+ if(mn_get_revsort(msgmap)){
+ cur = mn_get_cur(msgmap);
+ while(cur > 1L && get_lflag(stream, msgmap, cur-1L, MN_CHID2))
+ cur--;
+
+ if(cur != mn_get_cur(msgmap))
+ mn_set_cur(msgmap, cur);
+ }
+
+ /* first message in thread might be hidden if zoomed */
+ if(any_lflagged(msgmap, MN_HIDE)){
+ cur = mn_get_cur(msgmap);
+ while(get_lflag(stream, msgmap, cur, MN_HIDE))
+ cur++;
+
+ if(cur != mn_get_cur(msgmap))
+ mn_set_cur(msgmap, cur);
+ }
+
+ msgmap->top = mn_get_cur(msgmap);
+ sp_set_viewing_a_thread(stream, 1);
+
+ state->mangled_screen = 1;
+ setup_for_index_index_screen();
+
+ return 1;
+}
+
+
+int
+unview_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ PINETHRD_S *thrd = NULL, *topthrd = NULL;
+ unsigned long rawno;
+
+ if(!(stream && msgmap))
+ return 0;
+
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ if(!topthrd)
+ return 0;
+
+ /* hide this thread */
+ set_thread_lflags(stream, topthrd, msgmap, MN_CHID, 1);
+
+ /* clear special CHID2 flags for this thread */
+ set_thread_lflags(stream, topthrd, msgmap, MN_CHID2, 0);
+
+ /* clear CHID for top-level message and set COLL */
+ set_lflag(stream, msgmap, mn_raw2m(msgmap, topthrd->rawno), MN_CHID, 0);
+ set_lflag(stream, msgmap, mn_raw2m(msgmap, topthrd->rawno), MN_COLL, 1);
+
+ mn_set_cur(msgmap, mn_raw2m(msgmap, topthrd->rawno));
+ sp_set_viewing_a_thread(stream, 0);
+ setup_for_thread_index_screen();
+
+ return 1;
+}
+
+
+PINETHRD_S *
+find_thread_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, long int target, PINETHRD_S *startthrd)
+{
+ PINETHRD_S *thrd = NULL;
+
+ if(!(stream && msgmap))
+ return(thrd);
+
+ thrd = startthrd;
+
+ if(!thrd || !(thrd->prevthd || thrd->nextthd))
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+
+ if(thrd && !(thrd->prevthd || thrd->nextthd) && thrd->head)
+ thrd = fetch_thread(stream, thrd->head);
+
+ if(thrd){
+ /* go forward from here */
+ if(thrd->thrdno < target){
+ while(thrd){
+ if(thrd->thrdno == target)
+ break;
+
+ if(mn_get_revsort(msgmap) && thrd->prevthd)
+ thrd = fetch_thread(stream, thrd->prevthd);
+ else if(!mn_get_revsort(msgmap) && thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+ }
+ /* back up from here */
+ else if(thrd->thrdno > target
+ && (mn_get_revsort(msgmap)
+ || (thrd->thrdno - target) < (target - 1L))){
+ while(thrd){
+ if(thrd->thrdno == target)
+ break;
+
+ if(mn_get_revsort(msgmap) && thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else if(!mn_get_revsort(msgmap) && thrd->prevthd)
+ thrd = fetch_thread(stream, thrd->prevthd);
+ else
+ thrd = NULL;
+ }
+ }
+ /* go forward from head */
+ else if(thrd->thrdno > target){
+ if(thrd->head){
+ thrd = fetch_thread(stream, thrd->head);
+ while(thrd){
+ if(thrd->thrdno == target)
+ break;
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ }
+
+ return(thrd);
+}
+
+
+/*
+ * Set search bit for every message in a thread.
+ *
+ * Watch out when calling this. The thrd->branch is not part of thrd.
+ * Branch is a sibling to thrd, not a child. Zero out branch before calling
+ * or call on thrd->next and worry about thrd separately. Top-level threads
+ * already have a branch equal to zero.
+ *
+ * If msgset is non-NULL, then only set the search bit for a message if that
+ * message is included in the msgset.
+ */
+void
+set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgset)
+{
+ PINETHRD_S *nthrd, *bthrd;
+
+ if(!(stream && thrd))
+ return;
+
+ if(thrd->rawno > 0L && thrd->rawno <= stream->nmsgs
+ && (!(msgset && *msgset) || in_searchset(*msgset, thrd->rawno)))
+ mm_searched(stream, thrd->rawno);
+
+ if(thrd->next){
+ nthrd = fetch_thread(stream, thrd->next);
+ if(nthrd)
+ set_search_bit_for_thread(stream, nthrd, msgset);
+ }
+
+ if(thrd->branch){
+ bthrd = fetch_thread(stream, thrd->branch);
+ if(bthrd)
+ set_search_bit_for_thread(stream, bthrd, msgset);
+ }
+}
diff --git a/pith/thread.h b/pith/thread.h
new file mode 100644
index 00000000..40b09bb0
--- /dev/null
+++ b/pith/thread.h
@@ -0,0 +1,111 @@
+/*
+ * $Id: thread.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_THREAD_INCLUDED
+#define PITH_THREAD_INCLUDED
+
+
+#include "../pith/msgno.h"
+#include "../pith/indxtype.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+
+
+#define THD_TOP 0x0000 /* start of an individual thread */
+#define THD_NEXT 0x0001
+#define THD_BRANCH 0x0004
+
+typedef struct pine_thrd {
+ unsigned long rawno; /* raw msgno of this message */
+ unsigned long thrdno; /* thread number */
+ unsigned long flags;
+ unsigned long next; /* msgno of first reply to us */
+ unsigned long branch; /* like THREADNODE branch, next replier */
+ unsigned long parent; /* message that this is a reply to */
+ unsigned long nextthd; /* next thread, only tops have this */
+ unsigned long prevthd; /* previous thread, only tops have this */
+ unsigned long top; /* top of this thread */
+ unsigned long head; /* head of the whole thread list */
+} PINETHRD_S;
+
+
+/*
+ * Some macros useful for threading
+ */
+
+/* Sort is a threaded sort */
+#define SORT_IS_THREADED(msgmap) \
+ (mn_get_sort(msgmap) == SortThread \
+ || mn_get_sort(msgmap) == SortSubject2)
+
+#define SEP_THRDINDX() \
+ (ps_global->thread_index_style == THRDINDX_SEP \
+ || ps_global->thread_index_style == THRDINDX_SEP_AUTO)
+
+#define COLL_THRDS() \
+ (ps_global->thread_index_style == THRDINDX_COLL)
+
+#define THRD_AUTO_VIEW() \
+ (ps_global->thread_index_style == THRDINDX_SEP_AUTO)
+
+/* We are threading now, pay attention to all the other variables */
+#define THREADING() \
+ (!ps_global->turn_off_threading_temporarily \
+ && SORT_IS_THREADED(ps_global->msgmap) \
+ && (SEP_THRDINDX() \
+ || ps_global->thread_disp_style != THREAD_NONE))
+
+/* If we were to view the folder, we would get a thread index */
+#define THRD_INDX_ENABLED() \
+ (SEP_THRDINDX() \
+ && THREADING())
+
+/* We are in the thread index (or would be if we weren't in an index menu) */
+#define THRD_INDX() \
+ (THRD_INDX_ENABLED() \
+ && !sp_viewing_a_thread(ps_global->mail_stream))
+
+/* The thread command ought to work now */
+#define THRD_COLLAPSE_ENABLE() \
+ (THREADING() \
+ && !THRD_INDX() \
+ && ps_global->thread_disp_style != THREAD_NONE)
+
+
+/* exported protoypes */
+PINETHRD_S *fetch_thread(MAILSTREAM *, unsigned long);
+PINETHRD_S *fetch_head_thread(MAILSTREAM *);
+void set_flags_for_thread(MAILSTREAM *, MSGNO_S *, int, PINETHRD_S *, int);
+void erase_threading_info(MAILSTREAM *, MSGNO_S *);
+void sort_thread_callback(MAILSTREAM *, THREADNODE *);
+void collapse_threads(MAILSTREAM *, MSGNO_S *, PINETHRD_S *);
+PINETHRD_S *msgno_thread_info(MAILSTREAM *, unsigned long, PINETHRD_S *, unsigned);
+void collapse_or_expand(struct pine *, MAILSTREAM *, MSGNO_S *, unsigned long);
+void select_thread_stmp(struct pine *, MAILSTREAM *, MSGNO_S *);
+unsigned long count_flags_in_thread(MAILSTREAM *, PINETHRD_S *, long);
+unsigned long count_lflags_in_thread(MAILSTREAM *, PINETHRD_S *, MSGNO_S *, int);
+int thread_has_some_visible(MAILSTREAM *, PINETHRD_S *);
+int mark_msgs_in_thread(MAILSTREAM *, PINETHRD_S *, MSGNO_S *);
+void set_thread_lflags(MAILSTREAM *, PINETHRD_S *, MSGNO_S *, int, int);
+char status_symbol_for_thread(MAILSTREAM *, PINETHRD_S *, IndexColType);
+char to_us_symbol_for_thread(MAILSTREAM *, PINETHRD_S *, int);
+void set_thread_subtree(MAILSTREAM *, PINETHRD_S *, MSGNO_S *, int, int);
+int view_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int);
+int unview_thread(struct pine *, MAILSTREAM *, MSGNO_S *);
+PINETHRD_S *find_thread_by_number(MAILSTREAM *, MSGNO_S *, long, PINETHRD_S *);
+void set_search_bit_for_thread(MAILSTREAM *, PINETHRD_S *, SEARCHSET **);
+
+
+#endif /* PITH_THREAD_INCLUDED */
diff --git a/pith/url.c b/pith/url.c
new file mode 100644
index 00000000..173cb879
--- /dev/null
+++ b/pith/url.c
@@ -0,0 +1,542 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: url.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/url.h"
+#include "../pith/mailview.h"
+#include "../pith/string.h"
+
+/*
+ * Internal prototypes
+ */
+char *rfc1738_scheme_part(char *);
+int rfc1738uchar(char *);
+int rfc1738xchar(char *);
+
+
+/*
+ * * * * * * * * * RFC 1738 support routines * * * * * * * *
+ */
+
+
+/*
+ * Various helpful definitions
+ */
+#define RFC1738_SAFE "$-_.+" /* "safe" */
+#define RFC1738_EXTRA "!*'()," /* "extra" */
+#define RFC1738_RSVP ";/?:@&=" /* "reserved" */
+#define RFC1738_NEWS "-.+_" /* valid for "news:" URL */
+#define RFC1738_FUDGE "#{}|\\^~[]" /* Unsafe, but popular */
+#define RFC1738_ESC(S) (*(S) == '%' && isxpair((S) + 1))
+
+
+/*
+ * rfc1738_scan -- Scan the given line for possible URLs as defined
+ * in RFC1738
+ */
+char *
+rfc1738_scan(char *line, int *len)
+{
+ char *colon, *start, *end;
+ int n;
+
+ /* process each : in the line */
+ for(; (colon = strindex(line, ':')) != NULL; line = end){
+ end = colon + 1;
+ if(colon == line) /* zero length scheme? */
+ continue;
+
+ /*
+ * Valid URL (ala RFC1738 BNF)? First, first look to the
+ * left to make sure there are valid "scheme" chars...
+ */
+ start = colon - 1;
+ while(1)
+ if(!(isdigit((unsigned char) *start)
+ || isalpha((unsigned char) *start)
+ || strchr("+-.", *start))){
+ start++; /* advance over bogus char */
+ break;
+ }
+ else if(start > line)
+ start--;
+ else
+ break;
+
+ /*
+ * Make sure everyhing up to the colon is a known scheme...
+ */
+ if(start && (n = colon - start) && !isdigit((unsigned char) *start)
+ && (((n == 3
+ && (*start == 'F' || *start == 'f')
+ && !struncmp(start+1, "tp", 2))
+ || (n == 4
+ && (((*start == 'H' || *start == 'h')
+ && !struncmp(start + 1, "ttp", 3))
+ || ((*start == 'N' || *start == 'n')
+ && !struncmp(start + 1, "ews", 3))
+ || ((*start == 'N' || *start == 'n')
+ && !struncmp(start + 1, "ntp", 3))
+ || ((*start == 'W' || *start == 'w')
+ && !struncmp(start + 1, "ais", 3))
+#ifdef ENABLE_LDAP
+ || ((*start == 'L' || *start == 'l')
+ && !struncmp(start + 1, "dap", 3))
+#endif
+ || ((*start == 'I' || *start == 'i')
+ && !struncmp(start + 1, "map", 3))
+ || ((*start == 'F' || *start == 'f')
+ && !struncmp(start + 1, "ile", 3))))
+ || (n == 5
+ && (*start == 'H' || *start == 'h')
+ && !struncmp(start+1, "ttps", 4))
+ || (n == 6
+ && (((*start == 'G' || *start == 'g')
+ && !struncmp(start+1, "opher", 5))
+ || ((*start == 'M' || *start == 'm')
+ && !struncmp(start + 1, "ailto", 5))
+ || ((*start == 'T' || *start == 't')
+ && !struncmp(start + 1, "elnet", 5))))
+ || (n == 8
+ && (*start == 'P' || *start == 'p')
+ && !struncmp(start + 1, "rospero", 7))
+ || (n == 11
+ && (*start == 'x' || *start == 'X')
+ && !struncmp(start + 1, "-pine-help", 10))
+ || (n == 13
+ && (*start == 'x' || *start == 'X')
+ && !struncmp(start + 1, "-alpine-help", 12)))
+ || url_external_specific_handler(start, n))){
+ /*
+ * Second, make sure that everything to the right of the
+ * colon is valid for a "schemepart"...
+ */
+
+ if((end = rfc1738_scheme_part(colon + 1)) - colon > 1){
+ int i, j;
+
+ /* make sure something useful follows colon */
+ for(i = 0, j = end - colon; i < j; i++)
+ if(!strchr(RFC1738_RSVP, colon[i]))
+ break;
+
+ if(i != j){
+ *len = end - start;
+
+ /*
+ * Special case handling for comma.
+ * See the problem is comma's valid, but if it's the
+ * last character in the url, it's likely intended
+ * as a delimiter in the text rather part of the URL.
+ * In most cases any way, that's why we have the
+ * exception.
+ */
+ if(*(end - 1) == ','
+ || (*(end - 1) == '.' && (!*end || *end == ' ')))
+ (*len)--;
+
+ if(*len - (colon - start) > 0)
+ return(start);
+ }
+ }
+ }
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * rfc1738_scheme_part - make sure what's to the right of the
+ * colon is valid
+ *
+ * NOTE: we have a problem matching closing parens when users
+ * bracket the url in parens. So, lets try terminating our
+ * match on any closing paren that doesn't have a coresponding
+ * open-paren.
+ */
+char *
+rfc1738_scheme_part(char *s)
+{
+ int n, paren = 0, bracket = 0;
+
+ while(1)
+ switch(*s){
+ default :
+ if((n = rfc1738xchar(s)) != 0){
+ s += n;
+ break;
+ }
+
+ case '\0' :
+ return(s);
+
+ case '[' :
+ bracket++;
+ s++;
+ break;
+
+ case ']' :
+ if(bracket--){
+ s++;
+ break;
+ }
+
+ return(s);
+
+ case '(' :
+ paren++;
+ s++;
+ break;
+
+ case ')' :
+ if(paren--){
+ s++;
+ break;
+ }
+
+ return(s);
+ }
+}
+
+
+
+/*
+ * rfc1738_str - convert rfc1738 escaped octets in place
+ */
+char *
+rfc1738_str(char *s)
+{
+ register char *p = s, *q = s;
+
+ while(1)
+ switch(*q = *p++){
+ case '%' :
+ if(isxpair(p)){
+ *q = X2C(p);
+ p += 2;
+ }
+
+ default :
+ q++;
+ break;
+
+ case '\0':
+ return(s);
+ }
+}
+
+
+/*
+ * rfc1738uchar - returns TRUE if the given char fits RFC 1738 "uchar" BNF
+ */
+int
+rfc1738uchar(char *s)
+{
+ return((RFC1738_ESC(s)) /* "escape" */
+ ? 2
+ : (isalnum((unsigned char) *s) /* alphanumeric */
+ || strchr(RFC1738_SAFE, *s) /* other special stuff */
+ || strchr(RFC1738_EXTRA, *s)));
+}
+
+
+/*
+ * rfc1738xchar - returns TRUE if the given char fits RFC 1738 "xchar" BNF
+ */
+int
+rfc1738xchar(char *s)
+{
+ int n;
+
+ return((n = rfc1738uchar(s))
+ ? n
+ : (strchr(RFC1738_RSVP, *s) != NULL
+ || strchr(RFC1738_FUDGE, *s)));
+}
+
+
+/*
+ * rfc1738_num - return long value of a string of digits, possibly escaped
+ */
+unsigned long
+rfc1738_num(char **s)
+{
+ register char *p = *s;
+ unsigned long n = 0L;
+
+ for(; *p; p++)
+ if(*p == '%' && isxpair(p+1)){
+ int c = X2C(p+1);
+ if(isdigit((unsigned char) c)){
+ n = (c - '0') + (n * 10);
+ p += 2;
+ }
+ else
+ break;
+ }
+ else if(isdigit((unsigned char) *p))
+ n = (*p - '0') + (n * 10);
+ else
+ break;
+
+ *s = p;
+ return(n);
+}
+
+
+int
+rfc1738_group(char *s)
+{
+ return(isalnum((unsigned char) *s)
+ || RFC1738_ESC(s)
+ || strchr(RFC1738_NEWS, *s));
+}
+
+
+/*
+ * Encode (hexify) a mailto url.
+ *
+ * Args s -- src url
+ *
+ * Returns An allocated string which is suitably encoded.
+ * Result should be freed by caller.
+ *
+ * Since we don't know here which characters are reserved characters (? and &)
+ * for use in delimiting the pieces of the url and which are just those
+ * characters contained in the data that should be encoded, we always encode
+ * them. That's because we know we don't use those as reserved characters.
+ * If you do use those as reserved characters you have to encode each part
+ * separately.
+ */
+char *
+rfc1738_encode_mailto(char *s)
+{
+ char *d, *ret = NULL;
+
+ if(s){
+ /* Worst case, encode every character */
+ ret = d = (char *)fs_get((3*strlen(s) + 1) * sizeof(char));
+ while(*s){
+ if(isalnum((unsigned char)*s)
+ || strchr(RFC1738_SAFE, *s)
+ || strchr(RFC1738_EXTRA, *s))
+ *d++ = *s++;
+ else{
+ *d++ = '%';
+ C2XPAIR(*s, d);
+ s++;
+ }
+ }
+
+ *d = '\0';
+ }
+
+ return(ret);
+}
+
+
+/*
+ * * * * * * * * * RFC 1808 support routines * * * * * * * *
+ */
+
+
+int
+rfc1808_tokens(char *url, char **scheme, char **net_loc, char **path,
+ char **parms, char **query, char **frag)
+{
+ char *p, *q, *start, *tmp = cpystr(url);
+
+ start = tmp;
+ if((p = strchr(start, '#')) != NULL){ /* fragment spec? */
+ *p++ = '\0';
+ if(*p)
+ *frag = cpystr(p);
+ }
+
+ if((p = strchr(start, ':')) && p != start){ /* scheme part? */
+ for(q = start; q < p; q++)
+ if(!(isdigit((unsigned char) *q)
+ || isalpha((unsigned char) *q)
+ || strchr("+-.", *q)))
+ break;
+
+ if(p == q){
+ *p++ = '\0';
+ *scheme = cpystr(start);
+ start = p;
+ }
+ }
+
+ if(*start == '/' && *(start+1) == '/'){ /* net_loc */
+ if((p = strchr(start+2, '/')) != NULL)
+ *p++ = '\0';
+
+ *net_loc = cpystr(start+2);
+ if(p)
+ start = p;
+ else *start = '\0'; /* End of parse */
+ }
+
+ if((p = strchr(start, '?')) != NULL){
+ *p++ = '\0';
+ *query = cpystr(p);
+ }
+
+ if((p = strchr(start, ';')) != NULL){
+ *p++ = '\0';
+ *parms = cpystr(p);
+ }
+
+ if(*start)
+ *path = cpystr(start);
+
+ fs_give((void **) &tmp);
+
+ return(1);
+}
+
+
+
+/*
+ * web_host_scan -- Scan the given line for possible web host names
+ *
+ * NOTE: scan below is limited to DNS names ala RFC1034
+ */
+char *
+web_host_scan(char *line, int *len)
+{
+ char *end, last = '\0';
+
+ for(; *line; last = *line++)
+ if((*line == 'w' || *line == 'W')
+ && (!last || !(isalnum((unsigned char) last)
+ || last == '.' || last == '-' || last == '/'))
+ && (((*(line + 1) == 'w' || *(line + 1) == 'W') /* "www." */
+ && (*(line + 2) == 'w' || *(line + 2) == 'W'))
+ || ((*(line + 1) == 'e' || *(line + 1) == 'E') /* "web." */
+ && (*(line + 2) == 'b' || *(line + 2) == 'B')))
+ && (*(line + 3) == '.')){
+ end = rfc1738_scheme_part(line + 3);
+ if((*len = end - line) > ((*(line+3) == '.') ? 4 : 3)){
+ /* Dread comma exception, see note in rfc1738_scan */
+ if(strchr(",:", *(line + (*len) - 1))
+ || (*(line + (*len) - 1) == '.'
+ && (!*(line + (*len)) || *(line + (*len)) == ' ')))
+ (*len)--;
+
+ return(line);
+ }
+ else
+ line += 3;
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * mail_addr_scan -- Scan the given line for possible RFC822 addr-spec's
+ *
+ * NOTE: Well, OK, not strictly addr-specs since there's alot of junk
+ * we're tying to sift thru and we'd like to minimize false-pos
+ * matches.
+ */
+char *
+mail_addr_scan(char *line, int *len)
+{
+ char *amp, *start, *end;
+/*
+ * This list is not the whole standards-based list, this is just a list
+ * of likely email address characters. We don't want to include everything
+ * because punctuation in the text might get mixed in with the address.
+ */
+#define NONALPHANUMOK ".-_+%/="
+
+ /* process each : in the line */
+ for(; (amp = strindex(line, '@')) != NULL; line = end){
+ end = amp + 1;
+ /* zero length addr? */
+ if(amp == line || !(isalnum((unsigned char) *(start = amp - 1))
+ || strchr(NONALPHANUMOK, *start)))
+ continue;
+
+ /*
+ * Valid address (ala RFC822 BNF)? First, first look to the
+ * left to make sure there are valid "scheme" chars...
+ */
+ while(1)
+ /* NOTE: we're not doing quoted-strings */
+ if(!(isalnum((unsigned char) *start) || strchr(NONALPHANUMOK, *start))){
+ /* advance over bogus char, and erase leading punctuation */
+ for(start++; *start && strchr(NONALPHANUMOK, *start); start++)
+ ;
+
+ break;
+ }
+ else if(start > line)
+ start--;
+ else
+ break;
+
+ /*
+ * Make sure everyhing up to the colon is a known scheme...
+ */
+ if(start && (amp - start) > 0){
+ /*
+ * Second, make sure that everything to the right of
+ * amp is valid for a "domain"...
+ */
+ if(*(end = amp + 1) == '['){ /* domain literal */
+ int dots = 3;
+
+ for(++end; *end ; end++)
+ if(*end == ']'){
+ if(!dots){
+ *len = end - start + 1;
+ return(start);
+ }
+ else
+ break; /* bogus */
+ }
+ else if(*end == '.'){
+ if(--dots < 0)
+ break; /* bogus */
+ }
+ else if(!isdigit((unsigned char) *end))
+ break; /* bogus */
+ }
+ else if(isalnum((unsigned char) *end)){ /* domain name? */
+ for(++end; ; end++)
+ if(!(*end && (isalnum((unsigned char) *end)
+ || *end == '-'
+ || *end == '.'
+ || *end == '_'))){
+ /* can't end with dash, dot or underscore */
+ while(!isalnum((unsigned char) *(end - 1)))
+ end--;
+
+ *len = end - start;
+ return(start);
+ }
+ }
+ }
+ }
+
+ return(NULL);
+}
diff --git a/pith/url.h b/pith/url.h
new file mode 100644
index 00000000..a1936c89
--- /dev/null
+++ b/pith/url.h
@@ -0,0 +1,31 @@
+/*
+ * $Id: url.h 764 2007-10-23 23:44:49Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_URL_INCLUDED
+#define PITH_URL_INCLUDED
+
+
+/* exported protoypes */
+char *rfc1738_scan(char *, int *);
+char *rfc1738_str(char *);
+unsigned long rfc1738_num(char **);
+int rfc1738_group(char *);
+char *rfc1738_encode_mailto(char *);
+int rfc1808_tokens(char *, char **, char **, char **, char **, char **, char **);
+char *web_host_scan(char *, int *);
+char *mail_addr_scan(char *, int *);
+
+
+#endif /* PITH_URL_INCLUDED */
diff --git a/pith/user.h b/pith/user.h
new file mode 100644
index 00000000..7372996c
--- /dev/null
+++ b/pith/user.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: user.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_USER_INCLUDED
+#define PITH_USER_INCLUDED
+
+
+/*
+ * used to store system derived user info
+ */
+typedef struct user_info {
+ char *homedir;
+ char *login;
+ char *fullname;
+} USER_S;
+
+
+#endif /* PITH_USER_INCLUDED */
diff --git a/pith/util.c b/pith/util.c
new file mode 100644
index 00000000..56f586da
--- /dev/null
+++ b/pith/util.c
@@ -0,0 +1,40 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: util.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/util.h"
+
+
+/*
+ * Internal prototypes
+ */
+
+
+/*
+ * Allocate space for an int and copy val into it.
+ */
+int *
+cpyint(int val)
+{
+ int *ip;
+
+ ip = (int *)fs_get(sizeof(int));
+
+ *ip = val;
+
+ return(ip);
+}
diff --git a/pith/util.h b/pith/util.h
new file mode 100644
index 00000000..defbca6c
--- /dev/null
+++ b/pith/util.h
@@ -0,0 +1,68 @@
+/*
+ * $Id: util.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_UTIL_INCLUDED
+#define PITH_UTIL_INCLUDED
+
+
+#include "../pith/news.h"
+
+
+#define plural(n) ((n) == 1 ? "" : "s")
+
+
+#define READONLY_FOLDER(S) ((S) && (S)->rdonly && !IS_NEWS(S))
+
+#define STREAMNAME(S) (((S) && sp_fldr(S) && sp_fldr(S)[0]) \
+ ? sp_fldr(S) \
+ : ((S) && (S)->mailbox && (S)->mailbox[0]) \
+ ? (S)->mailbox \
+ : "?")
+
+
+/*
+ * Simple, handy macro to determine if folder name is remote
+ * (on an imap server)
+ */
+#define IS_REMOTE(X) (*(X) == '{' && *((X) + 1) && *((X) + 1) != '}' \
+ && strchr(((X) + 2), '}'))
+
+
+/* (0,0) is upper left */
+typedef struct screen_position {
+ int row;
+ int col;
+} Pos;
+
+
+#define SCREEN_FUN_NULL ((void (*)(struct pine *)) NULL)
+
+
+/* exported protoypes */
+int *cpyint(int);
+
+/* currently mandatory to implement stubs */
+
+/* called when we detect a serious program error */
+void panic(char *);
+
+/* called when testing to see if panic state is in effect */
+int panicking(void);
+
+/* logs or prints a message then exits */
+void exceptional_exit(char *, int);
+
+
+#endif /* PITH_UTIL_INCLUDED */