From 61ba4eb0c552cf94553f8c585aa3fae8f6e72ade03aa4ae9095dcb8d9c437fe5 Mon Sep 17 00:00:00 2001 From: OBS User buildservice-autocommit Date: Wed, 12 Apr 2017 15:37:46 +0000 Subject: [PATCH] Updating link to change in openSUSE:Factory/alpine revision 41.0 OBS-URL: https://build.opensuse.org/package/show/server:mail/alpine?expand=0&rev=292ceea80481d5f0ef48accc96aeae6a --- alpine-2.20.tar.xz | 3 - alpine-2.21.tar.xz | 3 + alpine-no-add-needed.patch | 33 - alpine-timestamp.patch | 21 - alpine.changes | 30 + alpine.spec | 24 +- chappa-WrtAcc.patch | 703 ++-- chappa-colortext.patch | 1059 ++--- chappa-fancy.patch | 7218 ++++++++++++++++++--------------- chappa-fillpara.patch | 4114 +++++++++++++++++++ chappa-fromheader.patch | 159 + chappa-ignoresize.patch | 112 - chappa-insertpat.patch | 83 +- chappa-maildir.patch | 7527 ++++++++++++++++++----------------- chappa-rules.patch | 6242 +++++++++++++++++++++++++++++ chappa-unixnullbug.patch | 35 - lint.diff | 69 - pico-stripwhitespace.diff | 33 - pine-nonvoid-function.patch | 16 - 19 files changed, 19354 insertions(+), 8130 deletions(-) delete mode 100644 alpine-2.20.tar.xz create mode 100644 alpine-2.21.tar.xz delete mode 100644 alpine-no-add-needed.patch create mode 100644 chappa-fillpara.patch create mode 100644 chappa-fromheader.patch delete mode 100644 chappa-ignoresize.patch create mode 100644 chappa-rules.patch delete mode 100644 chappa-unixnullbug.patch delete mode 100644 lint.diff delete mode 100644 pico-stripwhitespace.diff delete mode 100644 pine-nonvoid-function.patch diff --git a/alpine-2.20.tar.xz b/alpine-2.20.tar.xz deleted file mode 100644 index 84eb67d..0000000 --- a/alpine-2.20.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed639b6e5bb97e6b0645c85262ca6a784316195d461ce8d8411999bf80449227 -size 4841816 diff --git a/alpine-2.21.tar.xz b/alpine-2.21.tar.xz new file mode 100644 index 0000000..098ecee --- /dev/null +++ b/alpine-2.21.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6030b6881b8168546756ab3a5e43628d8d564539b0476578e287775573a77438 +size 4720856 diff --git a/alpine-no-add-needed.patch b/alpine-no-add-needed.patch deleted file mode 100644 index 7188b53..0000000 --- a/alpine-no-add-needed.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- - alpine/Makefile.am | 2 +- - configure.ac | 3 ++- - 2 files changed, 3 insertions(+), 2 deletions(-) - -Index: alpine-2.11/alpine/Makefile.am -=================================================================== ---- alpine-2.11.orig/alpine/Makefile.am -+++ alpine-2.11/alpine/Makefile.am -@@ -35,7 +35,7 @@ BUILT_SOURCES = date.c - LDADD = ../pico/libpico.a ../pico/osdep/libpicoosd.a \ - ../pith/libpith.a ../pith/osdep/libpithosd.a \ - ../pith/charconv/libpithcc.a \ -- osdep/libpineosd.a ../c-client/c-client.a -+ osdep/libpineosd.a ../c-client/c-client.a -lkrb5 -lcrypto - - AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include -DLOCALEDIR=\"$(localedir)\" - -Index: alpine-2.11/configure.ac -=================================================================== ---- alpine-2.11.orig/configure.ac -+++ alpine-2.11/configure.ac -@@ -38,8 +38,9 @@ WEB_BUILD=web/src/alpined.d - - dnl CHECK PROGRAMS - --AC_PROG_CC - AC_PROG_CC_STDC -+AC_USE_SYSTEM_EXTENSIONS -+AC_SYS_LARGEFILE - AC_PROG_INSTALL - AC_PROG_MAKE_SET - AC_PROG_LN_S diff --git a/alpine-timestamp.patch b/alpine-timestamp.patch index a1886f9..98c3756 100644 --- a/alpine-timestamp.patch +++ b/alpine-timestamp.patch @@ -1,24 +1,3 @@ ---- - alpine/Makefile | 4 ++-- - alpine/Makefile.am | 4 ++-- - alpine/Makefile.in | 4 ++-- - 3 files changed, 6 insertions(+), 6 deletions(-) - -Index: alpine-2.20/alpine/Makefile -=================================================================== ---- alpine-2.20.orig/alpine/Makefile -+++ alpine-2.20/alpine/Makefile -@@ -888,8 +888,8 @@ uninstall-am: uninstall-binPROGRAMS - - - date.c: -- echo "char datestamp[]="\"`date`\"";" > date.c -- echo "char hoststamp[]="\"`hostname`\"";" >> date.c -+ echo 'char datestamp[]="today";' > date.c -+ echo 'char hoststamp[]="sand";' >> date.c - - # 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. Index: alpine-2.20/alpine/Makefile.am =================================================================== --- alpine-2.20.orig/alpine/Makefile.am diff --git a/alpine.changes b/alpine.changes index cb0d639..2105b48 100644 --- a/alpine.changes +++ b/alpine.changes @@ -1,3 +1,33 @@ +------------------------------------------------------------------- +Tue Mar 21 21:21:06 UTC 2017 - opensuse@dstoecker.de + +- add chappa patch to add own rules (chappa-rules.patch) +- add chappa patch to change from header (chappa-fromheader.patch) +- add chappa patch to better justify paragraphs (chappa-fillpara.patch) +- drop empty lint.diff + +------------------------------------------------------------------- +Tue Mar 21 15:31:10 UTC 2017 - max@suse.com + +- New version 2.21: + * New SHORTSUBJECT, SHORTSUBJKEY and SHORTSUBJKEYINIT token for + index format + * New SMARTTIME24 token for index screen. + * Added support for RFC 2971 - IMAP ID extension. + * SMIME: Upgrade the default signature digest from SHA-1 to + SHA-256. + * SMIME: Turn off automatic signing and encrypting of a message + when bouncing. + * The complete change list can be viewed by pressing "R" + in the Alpine main menu. +- Updated "Chappa" patches to the latest version. +- Obsolete patches: + * alpine-no-add-needed.patch + * chappa-ignoresize.patch + * chappa-unixnullbug.patch + * pico-stripwhitespace.diff + * pine-nonvoid-function.patch + ------------------------------------------------------------------- Mon Apr 4 14:18:00 CEST 2016 - kukuk@suse.de diff --git a/alpine.spec b/alpine.spec index bf286d3..0725448 100644 --- a/alpine.spec +++ b/alpine.spec @@ -1,7 +1,7 @@ # # spec file for package alpine # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -22,7 +22,7 @@ Name: alpine Summary: Mail User Agent License: Apache-2.0 Group: Productivity/Networking/Email/Clients -Version: 2.20 +Version: 2.21 Release: 0 Url: http://patches.freeiz.com/alpine/ @@ -31,7 +31,6 @@ Url: http://patches.freeiz.com/alpine/ Source: %name-%version.tar.xz Source1: %name.png Source2: %name.desktop -Patch1: pine-nonvoid-function.patch Patch2: make-use-of-strncat-safer.diff Patch3: operation-may-be-undefined-warning.diff Patch4: fix-implicit.patch @@ -39,21 +38,19 @@ Patch5: alpine-gcc44.diff Patch6: alpine-timestamp.patch Patch10: pico-fix-spurious-undef-warnings.diff Patch20: pine-expression-warnings.diff -Patch40: pico-stripwhitespace.diff Patch60: signal-and-panic-improvements.diff -Patch61: lint.diff # # Eduardo Chappa's patches. # http://patches.freeiz.com/alpine/ # Patch600: chappa-colortext.patch Patch601: chappa-fancy.patch -Patch602: chappa-ignoresize.patch Patch603: chappa-insertpat.patch Patch604: chappa-maildir.patch Patch605: chappa-WrtAcc.patch -Patch606: chappa-unixnullbug.patch -Patch613: alpine-no-add-needed.patch +Patch614: chappa-fillpara.patch +Patch615: chappa-fromheader.patch +Patch616: chappa-rules.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: autoconf >= 2.69 BuildRequires: imap-devel @@ -121,16 +118,15 @@ fi # %patch600 -p1 %patch601 -p1 -%patch602 -p1 %patch603 -p1 %patch604 -p1 %patch605 -p1 -%patch606 -p1 -%patch613 -p1 +%patch614 -p1 +%patch615 -p1 +%patch616 -p1 # # SuSE patches - warning fixes, etc: # -%patch1 -p1 %patch2 -p1 %patch3 -p1 %patch4 -p1 @@ -138,9 +134,7 @@ fi %patch6 -p1 %patch10 -p1 %patch20 -p1 -%patch40 -p1 %patch60 -p1 -%patch61 -p1 %endif # End of "if !{build_vanilla}" %build @@ -214,7 +208,7 @@ install -D -m644 %{SOURCE2} $RPM_BUILD_ROOT/usr/share/applications/%name.desktop ln -sf alpine $RPM_BUILD_ROOT%{_bindir}/pine : install -m755 pico/{pico,pilot} $RPM_BUILD_ROOT%{_bindir} -install -m644 doc/{pico.1,pilot.1} $RPM_BUILD_ROOT%{_mandir}/man1/ +install -m644 doc/man1/{pico.1,pilot.1} $RPM_BUILD_ROOT%{_mandir}/man1/ %check #since where are no logs in the package at the moment, there are no checks, diff --git a/chappa-WrtAcc.patch b/chappa-WrtAcc.patch index 2cfb520..3eda2b8 100644 --- a/chappa-WrtAcc.patch +++ b/chappa-WrtAcc.patch @@ -1,351 +1,354 @@ ---- - pico/basic.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - pico/composer.c | 9 ++ - pico/display.c | 7 +- - pico/ebind.h | 8 +- - pico/efunc.h | 3 - pico/main.c | 6 + - pico/search.c | 6 - - 7 files changed, 214 insertions(+), 8 deletions(-) - -Index: alpine-2.20/pico/basic.c -=================================================================== ---- alpine-2.20.orig/pico/basic.c -+++ alpine-2.20/pico/basic.c -@@ -342,6 +342,189 @@ gotobop(int f, int n) - return(TRUE); - } - -+unsigned char GetAccent() -+{ -+ UCS c,d; -+ c = GetKey(); -+ if ((c == '?') || (c == '!')) { -+ d = c; -+ c = '\\'; -+ } -+ else -+ if ((c == 's') || (c == 'S')){ -+ c = d = 's'; -+ } -+ else -+ if ((c == 'l') || (c == 'L')){ -+ c = d = 'l'; -+ } -+ else -+ d = GetKey(); -+ return accent(c,d); -+} -+ -+int pineaccent(f,n) -+ int f,n; -+{ unsigned char e; -+ -+ if (e = GetAccent()) -+ execute(e, 0, 1); -+ return 1; -+} -+ -+unsigned char accent(f,n) -+UCS f,n; -+{ UCS c,d; -+ -+ c = f; -+ d = n; -+ switch(c){ -+ case '~' : -+ switch(d){ -+ case 'a' : return '\343'; -+ case 'n' : return '\361'; -+ case 'o' : return '\365'; -+ case 'A' : return '\303'; -+ case 'N' : return '\321'; -+ case 'O' : return '\325'; -+ } -+ break; -+ case '\047' : -+ switch(d){ -+ case 'a' : return '\341'; -+ case 'e' : return '\351'; -+ case 'i' : return '\355'; -+ case 'o' : return '\363'; -+ case 'u' : return '\372'; -+ case 'y' : return '\375'; -+ case 'A' : return '\301'; -+ case 'E' : return '\311'; -+ case 'I' : return '\315'; -+ case 'O' : return '\323'; -+ case 'U' : return '\332'; -+ case 'Y' : return '\335'; -+ } -+ break; -+ case '"' : -+ switch(d){ -+ case 'a' : return '\344'; -+ case 'e' : return '\353'; -+ case 'i' : return '\357'; -+ case 'o' : return '\366'; -+ case 'u' : return '\374'; -+ case 'y' : return '\377'; -+ case 'A' : return '\304'; -+ case 'E' : return '\313'; -+ case 'I' : return '\317'; -+ case 'O' : return '\326'; -+ case 'U' : return '\334'; -+ } -+ break; -+ case '^' : -+ switch(d){ -+ case 'a' : return '\342'; -+ case 'e' : return '\352'; -+ case 'i' : return '\356'; -+ case 'o' : return '\364'; -+ case 'u' : return '\373'; -+ case 'A' : return '\302'; -+ case 'E' : return '\312'; -+ case 'I' : return '\316'; -+ case 'O' : return '\324'; -+ case 'U' : return '\333'; -+ case '0' : return '\260'; -+ case '1' : return '\271'; -+ case '2' : return '\262'; -+ case '3' : return '\263'; -+ } -+ break; -+ case '`' : -+ switch(d){ -+ case 'a' : return '\340'; -+ case 'e' : return '\350'; -+ case 'i' : return '\354'; -+ case 'o' : return '\362'; -+ case 'u' : return '\371'; -+ case 'A' : return '\300'; -+ case 'E' : return '\310'; -+ case 'I' : return '\314'; -+ case 'O' : return '\322'; -+ case 'U' : return '\331'; -+ } -+ break; -+ case 'o' : -+ switch(d){ -+ case 'a' : return '\345'; -+ case 'A' : return '\305'; -+ case '/' : return '\370'; -+ case 'r' : return '\256'; -+ case 'R' : return '\256'; -+ case 'c' : return '\251'; -+ case 'C' : return '\251'; -+ } -+ break; -+ case '-' : -+ switch(d){ -+ case 'o' : return '\272'; -+ case 'O' : return '\272'; -+ case '0' : return '\272'; -+ case 'a' : return '\252'; -+ case 'A' : return '\252'; -+ case 'l' : return '\243'; -+ case 'L' : return '\243'; -+ } -+ break; -+ case 'O' : -+ switch(d){ -+ case '/' : return '\330'; -+ case 'r' : return '\256'; -+ case 'R' : return '\256'; -+ case 'c' : return '\251'; -+ case 'C' : return '\251'; -+ } -+ case '/' : -+ switch(d){ -+ case 'o' : return '\370'; -+ case 'O' : return '\330'; -+ } -+ break; -+ case 'a' : -+ switch(d){ -+ case 'e' : return '\346'; -+ case 'E' : return '\346'; -+ } -+ break; -+ case 'A' : -+ switch(d){ -+ case 'E' : return '\306'; -+ case 'e' : return '\306'; -+ } -+ break; -+ case ',' : -+ switch(d){ -+ case 'c' : return '\347'; -+ case 'C' : return '\307'; -+ } -+ break; -+ case '\\' : -+ switch(d){ -+ case '?' : return '\277'; -+ case '!' : return '\241'; -+ } -+ break; -+ case 's' : -+ switch(d){ -+ case 's' : return '\337'; -+ } -+ break; -+ case 'l' : -+ switch(d){ -+ case 'l' : return '\243'; -+ } -+ break; +diff -rc alpine-2.21/pico/basic.c alpine-2.21.WrtAcc/pico/basic.c +*** alpine-2.21/pico/basic.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/basic.c Sun Feb 5 16:15:24 2017 +*************** +*** 342,347 **** +--- 342,530 ---- + return(TRUE); + } + ++ unsigned char GetAccent() ++ { ++ UCS c,d; ++ c = GetKey(); ++ if ((c == '?') || (c == '!')) { ++ d = c; ++ c = '\\'; ++ } ++ else ++ if ((c == 's') || (c == 'S')){ ++ c = d = 's'; + } -+ return '\0'; -+} - - /* - * go forword to the end of the current paragraph -Index: alpine-2.20/pico/composer.c -=================================================================== ---- alpine-2.20.orig/pico/composer.c -+++ alpine-2.20/pico/composer.c -@@ -2015,7 +2015,7 @@ LineEdit(int allowedit, UCS *lastch) - tbufp = &strng[ods.p_len]; - - if(VALID_KEY(ch)){ /* char input */ -- /* -+insert_char:/* - * if we are allowing editing, insert the new char - * end up leaving tbufp pointing to newly - * inserted character in string, and offset to the -@@ -2095,6 +2095,13 @@ LineEdit(int allowedit, UCS *lastch) - } - else { /* interpret ch as a command */ - switch (ch = normalize_cmd(ch, ckm, 2)) { -+ case (CTRL|'\\') : -+ if (ch = GetAccent()) -+ goto insert_char; -+ else -+ clearcursor(); -+ break; -+ - case (CTRL|KEY_LEFT): /* word skip left */ - if(ods.p_ind > 0) /* Scoot one char left if possible */ - ods.p_ind--; -Index: alpine-2.20/pico/display.c -=================================================================== ---- alpine-2.20.orig/pico/display.c -+++ alpine-2.20/pico/display.c -@@ -1751,6 +1751,11 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf - b = &buf[ucs4_strlen(buf)]; - continue; - -+ case (CTRL|'\\'): -+ if (c = GetAccent()) -+ goto text; -+ continue; -+ - case (CTRL|'F') : /* CTRL-F forward a char*/ - case KEY_RIGHT : - if(*b == '\0') -@@ -1881,7 +1886,7 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf - #endif - - default : -- -+text: - /* look for match in extra_v */ - for(i = 0; i < 12; i++) - if(c && c == extra_v[i]){ -Index: alpine-2.20/pico/ebind.h -=================================================================== ---- alpine-2.20.orig/pico/ebind.h -+++ alpine-2.20/pico/ebind.h -@@ -61,7 +61,7 @@ KEYTAB keytab[NBINDS] = { - #ifdef MOUSE - {KEY_MOUSE, mousepress}, - #ifndef _WINDOWS -- {CTRL|'\\', toggle_xterm_mouse}, -+ {CTRL|'|', toggle_xterm_mouse}, - #endif - #endif - {CTRL|'A', gotobol}, -@@ -100,7 +100,9 @@ KEYTAB keytab[NBINDS] = { - {CTRL|KEY_HOME, gotobob}, - {CTRL|KEY_END, gotoeob}, - {0x7F, backdel}, -- {0, NULL} -+ {CTRL|'\\', pineaccent}, -+ {0, -+NULL} - }; - - -@@ -123,7 +125,7 @@ KEYTAB pkeytab[NBINDS] = { - #ifdef MOUSE - {KEY_MOUSE, mousepress}, - #ifndef _WINDOWS -- {CTRL|'\\', toggle_xterm_mouse}, -+ {CTRL|'|', toggle_xterm_mouse}, - #endif - #endif - {CTRL|'A', gotobol}, -Index: alpine-2.20/pico/efunc.h -=================================================================== ---- alpine-2.20.orig/pico/efunc.h -+++ alpine-2.20/pico/efunc.h -@@ -54,6 +54,9 @@ extern int forwline(int, int); - extern int backline(int, int); - extern int gotobop(int, int); - extern int gotoeop(int, int); -+extern int pineaccent(int, int); -+extern unsigned char accent(UCS, UCS); -+extern unsigned char GetAccent(void); - extern int forwpage(int, int); - extern int backpage(int, int); - extern int scrollupline(int, int); -Index: alpine-2.20/pico/main.c -=================================================================== ---- alpine-2.20.orig/pico/main.c -+++ alpine-2.20/pico/main.c -@@ -416,6 +416,12 @@ main(int argc, char *argv[]) - emlwrite(_("You may possibly have new mail."), NULL); - } - -+ if (c == (CTRL|'\\')){ -+ c = GetAccent(); -+ if (!c) -+ c = NODATA; -+ } -+ - if(km_popped) - switch(c){ - case NODATA: -Index: alpine-2.20/pico/search.c -=================================================================== ---- alpine-2.20.orig/pico/search.c -+++ alpine-2.20/pico/search.c -@@ -291,7 +291,7 @@ forwsearch(int f, int n) - } - - if(status + curwp->w_doto >= llength(curwp->w_dotp) || -- !eq(defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c)) -+ !eq((unsigned char) defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c)) - break; - status++; - } -@@ -1096,7 +1096,7 @@ forscan(int *wrapt, /* boolean indicatin - c = lgetc(curline, curoff++).c; /* get the char */ - - /* test it against first char in pattern */ -- if (eq(c, patrn[0]) != FALSE) { /* if we find it..*/ -+ if (eq(c, (unsigned char)patrn[0]) != FALSE) { /* if we find it..*/ - /* setup match pointers */ - matchline = curline; - matchoff = curoff; -@@ -1117,7 +1117,7 @@ forscan(int *wrapt, /* boolean indicatin - return(FALSE); - - /* and test it against the pattern */ -- if (eq(*patptr, c) == FALSE) -+ if (eq((unsigned char) *patptr, c) == FALSE) - goto fail; - } - ++ else ++ if ((c == 'l') || (c == 'L')){ ++ c = d = 'l'; ++ } ++ else ++ d = GetKey(); ++ return accent(c,d); ++ } ++ ++ int pineaccent(f,n) ++ int f,n; ++ { unsigned char e; ++ ++ if (e = GetAccent()) ++ execute(e, 0, 1); ++ return 1; ++ } ++ ++ unsigned char accent(f,n) ++ UCS f,n; ++ { UCS c,d; ++ ++ c = f; ++ d = n; ++ switch(c){ ++ case '~' : ++ switch(d){ ++ case 'a' : return '\343'; ++ case 'n' : return '\361'; ++ case 'o' : return '\365'; ++ case 'A' : return '\303'; ++ case 'N' : return '\321'; ++ case 'O' : return '\325'; ++ } ++ break; ++ case '\047' : ++ switch(d){ ++ case 'a' : return '\341'; ++ case 'e' : return '\351'; ++ case 'i' : return '\355'; ++ case 'o' : return '\363'; ++ case 'u' : return '\372'; ++ case 'y' : return '\375'; ++ case 'A' : return '\301'; ++ case 'E' : return '\311'; ++ case 'I' : return '\315'; ++ case 'O' : return '\323'; ++ case 'U' : return '\332'; ++ case 'Y' : return '\335'; ++ } ++ break; ++ case '"' : ++ switch(d){ ++ case 'a' : return '\344'; ++ case 'e' : return '\353'; ++ case 'i' : return '\357'; ++ case 'o' : return '\366'; ++ case 'u' : return '\374'; ++ case 'y' : return '\377'; ++ case 'A' : return '\304'; ++ case 'E' : return '\313'; ++ case 'I' : return '\317'; ++ case 'O' : return '\326'; ++ case 'U' : return '\334'; ++ } ++ break; ++ case '^' : ++ switch(d){ ++ case 'a' : return '\342'; ++ case 'e' : return '\352'; ++ case 'i' : return '\356'; ++ case 'o' : return '\364'; ++ case 'u' : return '\373'; ++ case 'A' : return '\302'; ++ case 'E' : return '\312'; ++ case 'I' : return '\316'; ++ case 'O' : return '\324'; ++ case 'U' : return '\333'; ++ case '0' : return '\260'; ++ case '1' : return '\271'; ++ case '2' : return '\262'; ++ case '3' : return '\263'; ++ } ++ break; ++ case '`' : ++ switch(d){ ++ case 'a' : return '\340'; ++ case 'e' : return '\350'; ++ case 'i' : return '\354'; ++ case 'o' : return '\362'; ++ case 'u' : return '\371'; ++ case 'A' : return '\300'; ++ case 'E' : return '\310'; ++ case 'I' : return '\314'; ++ case 'O' : return '\322'; ++ case 'U' : return '\331'; ++ } ++ break; ++ case 'o' : ++ switch(d){ ++ case 'a' : return '\345'; ++ case 'A' : return '\305'; ++ case '/' : return '\370'; ++ case 'r' : return '\256'; ++ case 'R' : return '\256'; ++ case 'c' : return '\251'; ++ case 'C' : return '\251'; ++ } ++ break; ++ case '-' : ++ switch(d){ ++ case 'o' : return '\272'; ++ case 'O' : return '\272'; ++ case '0' : return '\272'; ++ case 'a' : return '\252'; ++ case 'A' : return '\252'; ++ case 'l' : return '\243'; ++ case 'L' : return '\243'; ++ } ++ break; ++ case 'O' : ++ switch(d){ ++ case '/' : return '\330'; ++ case 'r' : return '\256'; ++ case 'R' : return '\256'; ++ case 'c' : return '\251'; ++ case 'C' : return '\251'; ++ } ++ case '/' : ++ switch(d){ ++ case 'o' : return '\370'; ++ case 'O' : return '\330'; ++ } ++ break; ++ case 'a' : ++ switch(d){ ++ case 'e' : return '\346'; ++ case 'E' : return '\346'; ++ } ++ break; ++ case 'A' : ++ switch(d){ ++ case 'E' : return '\306'; ++ case 'e' : return '\306'; ++ } ++ break; ++ case ',' : ++ switch(d){ ++ case 'c' : return '\347'; ++ case 'C' : return '\307'; ++ } ++ break; ++ case '\\' : ++ switch(d){ ++ case '?' : return '\277'; ++ case '!' : return '\241'; ++ } ++ break; ++ case 's' : ++ switch(d){ ++ case 's' : return '\337'; ++ } ++ break; ++ case 'l' : ++ switch(d){ ++ case 'l' : return '\243'; ++ } ++ break; ++ } ++ return '\0'; ++ } + + /* + * go forword to the end of the current paragraph +diff -rc alpine-2.21/pico/composer.c alpine-2.21.WrtAcc/pico/composer.c +*** alpine-2.21/pico/composer.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/composer.c Sun Feb 5 16:15:24 2017 +*************** +*** 2015,2021 **** + tbufp = &strng[ods.p_len]; + + if(VALID_KEY(ch)){ /* char input */ +! /* + * if we are allowing editing, insert the new char + * end up leaving tbufp pointing to newly + * inserted character in string, and offset to the +--- 2015,2021 ---- + tbufp = &strng[ods.p_len]; + + if(VALID_KEY(ch)){ /* char input */ +! insert_char:/* + * if we are allowing editing, insert the new char + * end up leaving tbufp pointing to newly + * inserted character in string, and offset to the +*************** +*** 2095,2100 **** +--- 2095,2107 ---- + } + else { /* interpret ch as a command */ + switch (ch = normalize_cmd(ch, ckm, 2)) { ++ case (CTRL|'\\') : ++ if (ch = GetAccent()) ++ goto insert_char; ++ else ++ clearcursor(); ++ break; ++ + case (CTRL|KEY_LEFT): /* word skip left */ + if(ods.p_ind > 0) /* Scoot one char left if possible */ + ods.p_ind--; +diff -rc alpine-2.21/pico/display.c alpine-2.21.WrtAcc/pico/display.c +*** alpine-2.21/pico/display.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/display.c Sun Feb 5 16:15:24 2017 +*************** +*** 2196,2201 **** +--- 2196,2206 ---- + b = &buf[ucs4_strlen(buf)]; + continue; + ++ case (CTRL|'\\'): ++ if (c = GetAccent()) ++ goto text; ++ continue; ++ + case (CTRL|'F') : /* CTRL-F forward a char*/ + case KEY_RIGHT : + if(*b == '\0') +*************** +*** 2314,2320 **** + #endif + + default : +! + /* look for match in extra_v */ + for(i = 0; i < 12; i++) + if(c && c == extra_v[i]){ +--- 2319,2325 ---- + #endif + + default : +! text: + /* look for match in extra_v */ + for(i = 0; i < 12; i++) + if(c && c == extra_v[i]){ +diff -rc alpine-2.21/pico/ebind.h alpine-2.21.WrtAcc/pico/ebind.h +*** alpine-2.21/pico/ebind.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/ebind.h Sun Feb 5 16:15:24 2017 +*************** +*** 61,67 **** + #ifdef MOUSE + {KEY_MOUSE, mousepress}, + #ifndef _WINDOWS +! {CTRL|'\\', toggle_xterm_mouse}, + #endif + #endif + {CTRL|'A', gotobol}, +--- 61,67 ---- + #ifdef MOUSE + {KEY_MOUSE, mousepress}, + #ifndef _WINDOWS +! {CTRL|'|', toggle_xterm_mouse}, + #endif + #endif + {CTRL|'A', gotobol}, +*************** +*** 100,106 **** + {CTRL|KEY_HOME, gotobob}, + {CTRL|KEY_END, gotoeob}, + {0x7F, backdel}, +! {0, NULL} + }; + + +--- 100,108 ---- + {CTRL|KEY_HOME, gotobob}, + {CTRL|KEY_END, gotoeob}, + {0x7F, backdel}, +! {CTRL|'\\', pineaccent}, +! {0, +! NULL} + }; + + +*************** +*** 123,129 **** + #ifdef MOUSE + {KEY_MOUSE, mousepress}, + #ifndef _WINDOWS +! {CTRL|'\\', toggle_xterm_mouse}, + #endif + #endif + {CTRL|'A', gotobol}, +--- 125,131 ---- + #ifdef MOUSE + {KEY_MOUSE, mousepress}, + #ifndef _WINDOWS +! {CTRL|'|', toggle_xterm_mouse}, + #endif + #endif + {CTRL|'A', gotobol}, +diff -rc alpine-2.21/pico/efunc.h alpine-2.21.WrtAcc/pico/efunc.h +*** alpine-2.21/pico/efunc.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/efunc.h Sun Feb 5 16:15:24 2017 +*************** +*** 54,59 **** +--- 54,62 ---- + extern int backline(int, int); + extern int gotobop(int, int); + extern int gotoeop(int, int); ++ extern int pineaccent(int, int); ++ extern unsigned char accent(UCS, UCS); ++ extern unsigned char GetAccent(void); + extern int forwpage(int, int); + extern int backpage(int, int); + extern int scrollupline(int, int); +diff -rc alpine-2.21/pico/main.c alpine-2.21.WrtAcc/pico/main.c +*** alpine-2.21/pico/main.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.WrtAcc/pico/main.c Sun Feb 5 16:15:24 2017 +*************** +*** 451,456 **** +--- 451,462 ---- + emlwrite(_("You may possibly have new mail."), NULL); + } + ++ if (c == (CTRL|'\\')){ ++ c = GetAccent(); ++ if (!c) ++ c = NODATA; ++ } ++ + if(km_popped) + switch(c){ + case NODATA: diff --git a/chappa-colortext.patch b/chappa-colortext.patch index 9124002..3e4adec 100644 --- a/chappa-colortext.patch +++ b/chappa-colortext.patch @@ -1,522 +1,545 @@ ---- - alpine/confscroll.c | 3 - pith/conf.c | 14 +++ - pith/conf.h | 4 + - pith/conftype.h | 3 - pith/mailview.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - pith/mailview.h | 15 ++++ - pith/pine.hlp | 63 +++++++++++++++++ - pith/state.c | 3 - pith/state.h | 2 - pith/text.c | 9 ++ - 10 files changed, 308 insertions(+) - -Index: alpine-2.20/alpine/confscroll.c -=================================================================== ---- alpine-2.20.orig/alpine/confscroll.c -+++ alpine-2.20/alpine/confscroll.c -@@ -5196,6 +5196,9 @@ fix_side_effects(struct pine *ps, struct - - clear_index_cache(ps->mail_stream, 0); - } -+ else if(var == &ps->vars[V_SPECIAL_TEXT]){ -+ regex_pattern(ps->VAR_SPECIAL_TEXT); -+ } - else if(var == &ps->vars[V_INIT_CMD_LIST]){ - if(!revert) - q_status_message(SM_ASYNC, 0, 3, -Index: alpine-2.20/pith/conf.c -=================================================================== ---- alpine-2.20.orig/pith/conf.c -+++ alpine-2.20/pith/conf.c -@@ -228,6 +228,8 @@ CONF_TXT_T cf_text_deadlets[] = "Specif - - CONF_TXT_T cf_text_fillcol[] = "Specifies the column of the screen where the composer should wrap."; - -+CONF_TXT_T cf_special_text_color[] = "Specifies a comma separated list of text and regular expresions that Pine\n# will highlight"; -+ - 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."; -@@ -562,6 +564,8 @@ static struct variable variables[] = { - NULL, cf_text_speller}, - {"composer-wrap-column", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, - NULL, cf_text_fillcol}, -+{"special-text-color", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, -+ NULL, cf_special_text_color}, - {"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, -@@ -821,6 +825,8 @@ static struct variable variables[] = { - {"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}, -+{"special-text-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, -+{"special-text-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}, -@@ -1990,6 +1996,8 @@ init_vars(struct pine *ps, void (*cmds_f - 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_SPECIAL_TEXT], TRUE, TRUE); -+ regex_pattern(VAR_SPECIAL_TEXT); - 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); -@@ -6549,6 +6557,7 @@ set_current_color_vals(struct pine *ps) - 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_color_val(&vars[V_SPECIAL_TEXT_FORE_COLOR], 0); - - set_current_val(&ps->vars[V_INDEX_TOKEN_COLORS], TRUE, TRUE); - set_current_val(&ps->vars[V_VIEW_HDR_COLORS], TRUE, TRUE); -@@ -7732,6 +7741,8 @@ config_help(int var, int feature) - return(h_config_scroll_margin); - case V_DEADLETS : - return(h_config_deadlets); -+ case V_SPECIAL_TEXT : -+ return(h_config_special_text_to_color); - case V_FILLCOL : - return(h_config_composer_wrap_column); - case V_TCPOPENTIMEO : -@@ -7897,6 +7908,9 @@ config_help(int var, int feature) - case V_SIGNATURE_FORE_COLOR : - case V_SIGNATURE_BACK_COLOR : - return(h_config_signature_color); -+ case V_SPECIAL_TEXT_FORE_COLOR : -+ case V_SPECIAL_TEXT_BACK_COLOR : -+ return(h_config_special_text_color); - case V_PROMPT_FORE_COLOR : - case V_PROMPT_BACK_COLOR : - return(h_config_prompt_color); -Index: alpine-2.20/pith/conf.h -=================================================================== ---- alpine-2.20.orig/pith/conf.h -+++ alpine-2.20/pith/conf.h -@@ -161,6 +161,8 @@ - #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_SPECIAL_TEXT vars[V_SPECIAL_TEXT].current_val.l -+#define GLO_SPECIAL_TEXT vars[V_SPECIAL_TEXT].global_val.l - #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 -@@ -458,6 +460,8 @@ - #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_SPECIAL_TEXT_FORE_COLOR vars[V_SPECIAL_TEXT_FORE_COLOR].current_val.p -+#define VAR_SPECIAL_TEXT_BACK_COLOR vars[V_SPECIAL_TEXT_BACK_COLOR].current_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 -Index: alpine-2.20/pith/conftype.h -=================================================================== ---- alpine-2.20.orig/pith/conftype.h -+++ alpine-2.20/pith/conftype.h -@@ -80,6 +80,7 @@ typedef enum { V_PERSONAL_NAME = 0 - , V_EDITOR - , V_SPELLER - , V_FILLCOL -+ , V_SPECIAL_TEXT - , V_REPLY_STRING - , V_REPLY_INTRO - , V_QUOTE_REPLACE_STRING -@@ -231,6 +232,8 @@ typedef enum { V_PERSONAL_NAME = 0 - , V_INCUNSEEN_BACK_COLOR - , V_SIGNATURE_FORE_COLOR - , V_SIGNATURE_BACK_COLOR -+ , V_SPECIAL_TEXT_FORE_COLOR -+ , V_SPECIAL_TEXT_BACK_COLOR - , V_PROMPT_FORE_COLOR - , V_PROMPT_BACK_COLOR - , V_HEADER_GENERAL_FORE_COLOR -Index: alpine-2.20/pith/mailview.c -=================================================================== ---- alpine-2.20.orig/pith/mailview.c -+++ alpine-2.20/pith/mailview.c -@@ -282,6 +282,14 @@ format_body(long int msgno, BODY *body, - if((flgs & FM_DISPLAY) - && !(flgs & FM_NOCOLOR) - && pico_usingcolor() -+ && ps_global->VAR_SPECIAL_TEXT_FORE_COLOR -+ && ps_global->VAR_SPECIAL_TEXT_BACK_COLOR){ -+ gf_link_filter(gf_line_test, gf_line_test_opt(color_this_text, NULL)); -+ } -+ -+ 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)); -@@ -2503,6 +2511,190 @@ hdr_color(char *fieldname, char *value, - return(color_pair); - } - -+void -+interval_free(IVAL_S **ival) -+{ -+ if (!(*ival)) -+ return; -+ -+ if ((*ival)->next) -+ interval_free(&((*ival)->next)); -+ -+ fs_give((void **)(ival)); -+} -+ -+IVAL_S * -+compute_interval (char *string, int endm) -+{ -+ IVAL_S *ival = NULL; -+ regmatch_t pmatch; -+ -+ if(ps_global->paterror == 0 && -+ regexec(&ps_global->colorpat, string + endm, 1, &pmatch, 0) == 0){ -+ ival = (IVAL_S *) fs_get(sizeof(IVAL_S)); -+ ival->start = endm + pmatch.rm_so; -+ ival->end = endm + pmatch.rm_eo; -+ ival->next = compute_interval(string, ival->end); -+ } -+ return ival; -+} -+ -+void -+regex_pattern(char **plist) -+{ -+ int i = 0, j = 0, len = 0; -+ char *pattern = NULL; -+ regex_t preg; -+ -+ if(ps_global->paterror == 0) -+ regfree(&ps_global->colorpat); -+ -+ if(plist && *plist && *plist){ -+ for (i = 0; plist[i] && plist[i][0]; i++) -+ len += strlen(plist[i]) + 1; -+ pattern = (char *) fs_get(len * sizeof(char)); -+ *pattern = '\0'; -+ for (j = 0; j < i; j++){ -+ strcat(pattern, plist[j]); -+ strcat(pattern, (j < i - 1) ? "|" : ""); -+ } -+ if ((ps_global->paterror = regcomp(&preg, pattern, REG_EXTENDED)) != 0) -+ regfree(&preg); -+ else -+ ps_global->colorpat = preg; -+ } -+ if(pattern) -+ fs_give((void **)&pattern); -+} -+ -+LT_INS_S ** -+insert_color_special_text(LT_INS_S **ins, char **p, IVAL_S *ival, int last_end, -+ COLOR_PAIR *col) -+{ -+ struct variable *vars = ps_global->vars; -+ -+ if (ival){ -+ *p += ival->start - last_end; -+ ins = gf_line_test_new_ins(ins, *p, color_embed(col->fg, col->bg), -+ (2 * RGBLEN) + 4); -+ *p += ival->end - ival->start; -+ ins = gf_line_test_new_ins(ins, *p, color_embed(VAR_NORM_FORE_COLOR, -+ VAR_NORM_BACK_COLOR), (2 * RGBLEN) + 4); -+ ins = insert_color_special_text(ins, p, ival->next, ival->end, col); +diff -rc alpine-2.21/alpine/confscroll.c alpine-2.21.colortext/alpine/confscroll.c +*** alpine-2.21/alpine/confscroll.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/alpine/confscroll.c Sun Feb 5 16:15:26 2017 +*************** +*** 5222,5227 **** +--- 5222,5230 ---- + + clear_index_cache(ps->mail_stream, 0); + } ++ else if(var == &ps->vars[V_SPECIAL_TEXT]){ ++ regex_pattern(ps->VAR_SPECIAL_TEXT); ++ } + else if(var == &ps->vars[V_INIT_CMD_LIST]){ + if(!revert) + q_status_message(SM_ASYNC, 0, 3, +diff -rc alpine-2.21/pith/conf.c alpine-2.21.colortext/pith/conf.c +*** alpine-2.21/pith/conf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/conf.c Sun Feb 5 16:15:26 2017 +*************** +*** 232,237 **** +--- 232,239 ---- + + CONF_TXT_T cf_text_fillcol[] = "Specifies the column of the screen where the composer should wrap."; + ++ CONF_TXT_T cf_special_text_color[] = "Specifies a comma separated list of text and regular expresions that Pine\n# will highlight"; ++ + 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."; +*************** +*** 572,577 **** +--- 574,581 ---- + #endif /* _WINDOWS */ + {"composer-wrap-column", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_fillcol}, ++ {"special-text-color", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ NULL, cf_special_text_color}, + {"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, +*************** +*** 833,838 **** +--- 837,844 ---- + {"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}, ++ {"special-text-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, ++ {"special-text-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}, +*************** +*** 2021,2026 **** +--- 2027,2034 ---- + set_current_val(&vars[V_DICTIONARY], TRUE, TRUE); + #endif /* _WINDOWS */ + set_current_val(&vars[V_IMAGE_VIEWER], TRUE, TRUE); ++ set_current_val(&vars[V_SPECIAL_TEXT], TRUE, TRUE); ++ regex_pattern(VAR_SPECIAL_TEXT); + set_current_val(&vars[V_BROWSER], TRUE, TRUE); + set_current_val(&vars[V_HISTORY], TRUE, TRUE); + set_current_val(&vars[V_SMTP_SERVER], TRUE, TRUE); +*************** +*** 6567,6572 **** +--- 6575,6581 ---- + 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_color_val(&vars[V_SPECIAL_TEXT_FORE_COLOR], 0); + + set_current_val(&ps->vars[V_INDEX_TOKEN_COLORS], TRUE, TRUE); + set_current_val(&ps->vars[V_VIEW_HDR_COLORS], TRUE, TRUE); +*************** +*** 7755,7760 **** +--- 7764,7771 ---- + return(h_config_scroll_margin); + case V_DEADLETS : + return(h_config_deadlets); ++ case V_SPECIAL_TEXT : ++ return(h_config_special_text_to_color); + case V_FILLCOL : + return(h_config_composer_wrap_column); + case V_TCPOPENTIMEO : +*************** +*** 7922,7927 **** +--- 7933,7941 ---- + case V_SIGNATURE_FORE_COLOR : + case V_SIGNATURE_BACK_COLOR : + return(h_config_signature_color); ++ case V_SPECIAL_TEXT_FORE_COLOR : ++ case V_SPECIAL_TEXT_BACK_COLOR : ++ return(h_config_special_text_color); + case V_PROMPT_FORE_COLOR : + case V_PROMPT_BACK_COLOR : + return(h_config_prompt_color); +diff -rc alpine-2.21/pith/conf.h alpine-2.21.colortext/pith/conf.h +*** alpine-2.21/pith/conf.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/conf.h Sun Feb 5 16:15:26 2017 +*************** +*** 165,170 **** +--- 165,172 ---- + #define VAR_DICTIONARY vars[V_DICTIONARY].current_val.l + #define GLO_DICTIONARY vars[V_DICTIONARY].global_val.l + #endif /* _WINDOWS */ ++ #define VAR_SPECIAL_TEXT vars[V_SPECIAL_TEXT].current_val.l ++ #define GLO_SPECIAL_TEXT vars[V_SPECIAL_TEXT].global_val.l + #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 +*************** +*** 463,468 **** +--- 465,472 ---- + #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_SPECIAL_TEXT_FORE_COLOR vars[V_SPECIAL_TEXT_FORE_COLOR].current_val.p ++ #define VAR_SPECIAL_TEXT_BACK_COLOR vars[V_SPECIAL_TEXT_BACK_COLOR].current_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 +diff -rc alpine-2.21/pith/conftype.h alpine-2.21.colortext/pith/conftype.h +*** alpine-2.21/pith/conftype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/conftype.h Sun Feb 5 16:15:26 2017 +*************** +*** 83,88 **** +--- 83,89 ---- + , V_DICTIONARY + #endif /* _WINDOWS */ + , V_FILLCOL ++ , V_SPECIAL_TEXT + , V_REPLY_STRING + , V_REPLY_INTRO + , V_QUOTE_REPLACE_STRING +*************** +*** 235,240 **** +--- 236,243 ---- + , V_INCUNSEEN_BACK_COLOR + , V_SIGNATURE_FORE_COLOR + , V_SIGNATURE_BACK_COLOR ++ , V_SPECIAL_TEXT_FORE_COLOR ++ , V_SPECIAL_TEXT_BACK_COLOR + , V_PROMPT_FORE_COLOR + , V_PROMPT_BACK_COLOR + , V_HEADER_GENERAL_FORE_COLOR +diff -rc alpine-2.21/pith/mailview.c alpine-2.21.colortext/pith/mailview.c +*** alpine-2.21/pith/mailview.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/mailview.c Sun Feb 5 16:15:26 2017 +*************** +*** 282,287 **** +--- 282,295 ---- + if((flgs & FM_DISPLAY) + && !(flgs & FM_NOCOLOR) + && pico_usingcolor() ++ && ps_global->VAR_SPECIAL_TEXT_FORE_COLOR ++ && ps_global->VAR_SPECIAL_TEXT_BACK_COLOR){ ++ gf_link_filter(gf_line_test, gf_line_test_opt(color_this_text, NULL)); ++ } ++ ++ 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)); +*************** +*** 2503,2508 **** +--- 2511,2700 ---- + return(color_pair); + } + ++ void ++ interval_free(IVAL_S **ival) ++ { ++ if (!(*ival)) ++ return; ++ ++ if ((*ival)->next) ++ interval_free(&((*ival)->next)); ++ ++ fs_give((void **)(ival)); ++ } ++ ++ IVAL_S * ++ compute_interval (char *string, int endm) ++ { ++ IVAL_S *ival = NULL; ++ regmatch_t pmatch; ++ ++ if(ps_global->paterror == 0 && ++ regexec(&ps_global->colorpat, string + endm, 1, &pmatch, 0) == 0){ ++ ival = (IVAL_S *) fs_get(sizeof(IVAL_S)); ++ ival->start = endm + pmatch.rm_so; ++ ival->end = endm + pmatch.rm_eo; ++ ival->next = compute_interval(string, ival->end); + } -+ return ins; -+} -+ -+int -+length_color(char *p, int begin_color) -+{ -+ int len = 0, done = begin_color ? 0 : -1; -+ char *orig = p; -+ -+ while (*p && done <= 0){ -+ switch(*p++){ -+ case TAG_HANDLE : -+ p += *p + 1; -+ done++; -+ break; -+ -+ case TAG_FGCOLOR : -+ case TAG_BGCOLOR : -+ p += RGBLEN; -+ if (!begin_color) -+ done++; -+ break; -+ -+ default : -+ break; -+ } ++ return ival; ++ } ++ ++ void ++ regex_pattern(char **plist) ++ { ++ int i = 0, j = 0, len = 0; ++ char *pattern = NULL; ++ regex_t preg; ++ ++ if(ps_global->paterror == 0) ++ regfree(&ps_global->colorpat); ++ ++ if(plist && *plist && *plist){ ++ for (i = 0; plist[i] && plist[i][0]; i++) ++ len += strlen(plist[i]) + 1; ++ pattern = (char *) fs_get(len * sizeof(char)); ++ *pattern = '\0'; ++ for (j = 0; j < i; j++){ ++ strcat(pattern, plist[j]); ++ strcat(pattern, (j < i - 1) ? "|" : ""); ++ } ++ if ((ps_global->paterror = regcomp(&preg, pattern, REG_EXTENDED)) != 0) ++ regfree(&preg); ++ else ++ ps_global->colorpat = preg; + } -+ len = p - orig; -+ return len; -+} -+ -+int -+any_color_in_string(char *p) -+{ -+ int rv = 0; -+ char *orig = p; -+ while (*p && !rv) -+ if (*p++ == TAG_EMBED) -+ rv = p - orig; -+ return rv; -+} -+ -+void -+remove_spaces_ival(IVAL_S **ivalp, char *p) -+{ -+ IVAL_S *ival; -+ int i; -+ if (!ivalp || !*ivalp) -+ return; -+ ival = *ivalp; -+ for (i = 0; isspace((unsigned char) p[ival->start + i]); i++); -+ if (ival->start + i < ival->end) /* do not do this if match only spaces */ -+ ival->start += i; -+ else -+ return; -+ for (i = 0; isspace((unsigned char) p[ival->end - i - 1]); i++); -+ ival->end -= i; -+ if (ival->next) -+ remove_spaces_ival(&(ival->next), p); -+} -+ -+int -+color_this_text(long linenum, char *line, LT_INS_S **ins, void *local) -+{ ++ if(pattern) ++ fs_give((void **)&pattern); ++ } ++ ++ LT_INS_S ** ++ insert_color_special_text(LT_INS_S **ins, char **p, IVAL_S *ival, int last_end, ++ COLOR_PAIR *col) ++ { + struct variable *vars = ps_global->vars; -+ COLOR_PAIR *col = NULL; -+ char *p; -+ int i = 0; -+ static char *pattern = NULL; -+ -+/* select_quote(linenum, line, ins, (void *) &i); -+ for (i = 0; tmp_20k_buf[i] != '\0'; i++); */ -+ p = line + i; -+ -+ if(VAR_SPECIAL_TEXT_FORE_COLOR && VAR_SPECIAL_TEXT_BACK_COLOR -+ && (col = new_color_pair(VAR_SPECIAL_TEXT_FORE_COLOR, -+ VAR_SPECIAL_TEXT_BACK_COLOR)) -+ && !pico_is_good_colorpair(col)) -+ free_color_pair(&col); -+ -+ if(ps_global->VAR_SPECIAL_TEXT && *ps_global->VAR_SPECIAL_TEXT -+ && **ps_global->VAR_SPECIAL_TEXT && col){ -+ IVAL_S *ival; -+ int done = 0, begin_color = 0; -+ -+ while (!done){ -+ if (i = any_color_in_string(p)){ -+ begin_color = (begin_color + 1) % 2; -+ if (begin_color){ -+ p[i - 1] = '\0'; -+ ival = compute_interval(p, 0); -+ remove_spaces_ival(&ival, p); -+ p[i - 1] = TAG_EMBED; -+ ins = insert_color_special_text(ins, &p, ival, 0, col); -+ } -+ for (;*p++ != TAG_EMBED; ); -+ p += length_color(p, begin_color); -+ } -+ else{ -+ ival = compute_interval(p, 0); -+ remove_spaces_ival(&ival, p); -+ ins = insert_color_special_text(ins, &p, ival, 0, col); -+ done++; -+ } -+ interval_free(&ival); -+ if (!*p) -+ done++; -+ } -+ free_color_pair(&col); ++ ++ if (ival){ ++ *p += ival->start - last_end; ++ ins = gf_line_test_new_ins(ins, *p, color_embed(col->fg, col->bg), ++ (2 * RGBLEN) + 4); ++ *p += ival->end - ival->start; ++ ins = gf_line_test_new_ins(ins, *p, color_embed(VAR_NORM_FORE_COLOR, ++ VAR_NORM_BACK_COLOR), (2 * RGBLEN) + 4); ++ ins = insert_color_special_text(ins, p, ival->next, ival->end, col); + } -+ -+ return 0; -+} - - /* - * The argument fieldname is something like "Subject:..." or "Subject". -Index: alpine-2.20/pith/mailview.h -=================================================================== ---- alpine-2.20.orig/pith/mailview.h -+++ alpine-2.20/pith/mailview.h -@@ -30,6 +30,12 @@ - #include "../pith/color.h" - - -+typedef struct IVAL { -+ int start; -+ int end; -+ struct IVAL *next; -+} IVAL_S; -+ - /* 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 */ -@@ -126,6 +132,15 @@ char *format_body(long int, BODY *, HAND - 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 *); -+IVAL_S *compute_interval(char *, int); -+void remove_spaces_ival(IVAL_S **, char *); -+void interval_free(IVAL_S **); -+void regex_pattern(char **); -+LT_INS_S **insert_color_special_text(LT_INS_S **, char **, IVAL_S *, -+ int, COLOR_PAIR *); -+int any_color_in_string(char *); -+int length_color(char *, int); -+int color_this_text(long, char *, LT_INS_S **, void *); - - /* - * BUG: BELOW IS UNIX/PC ONLY since config'd browser means nothing to webpine -Index: alpine-2.20/pith/pine.hlp -=================================================================== ---- alpine-2.20.orig/pith/pine.hlp -+++ alpine-2.20/pith/pine.hlp -@@ -3833,6 +3833,7 @@ There are also additional details on -
  • OPTION: -
  • OPTION: -
  • OPTION: -+
  • OPTION: -
  • OPTION: -
  • OPTION: Print-Font-Char-Set -
  • OPTION: Print-Font-Name -@@ -3861,6 +3862,7 @@ There are also additional details on -
  • OPTION: -
  • OPTION: -
  • OPTION: Signature Color -+
  • OPTION: Special Text Color -
  • OPTION: -
  • OPTION: -
  • OPTION: -@@ -23234,6 +23236,43 @@ That won't work because spell works in a - <End of help on this topic> - - -+====== h_config_special_text_to_color ===== -+ -+ -+OPTION: <!--#echo var="VAR_special-text-color"--> -+ -+ -+

    OPTION:

    -+ -+Use this option to enter patterns (text or regular expressions) that -+Alpine will highlight in the body of the text that is not part of a handle -+(an internal or external link that Alpine paints in a different color). -+ -+

    -+Enter each pattern in a different line. Pine will internally merge these -+patterns (by adding a "|" character), or you can add them all in one line -+by separating them by a "|" character. There is only a set of regular expressions that are matched. -+ -+

    -+Pine will use the colors defined in the -+Special Text Color variable. -+to paint any match. -+ -+

    -+If the Special Text Color is not set, setting this variable will not -+cause that special text to be indicated in any special way. It will look -+like any normal text. You must set those colors in order to make Pine -+paint the screen differently when it finds the patterns specified in this -+variable. -+ -+

    -+

    -+<End of help on this topic> -+ -+ - ====== h_config_display_filters ===== - - -@@ -31851,6 +31890,30 @@ the Quote3 Color is black characters on -

    - Descriptions of the available commands -

    -+Look here -+to see the available Editing and Navigation commands. -+

    -+<End of help on this topic> -+ -+ -+====== h_config_special_text_color ===== -+ -+ -+OPTION: Special Text Color -+ -+ -+

    OPTION: Special Text Color

    -+ -+Sets the color Pine uses for coloring any text in the body of the message -+that is not part of a handle (and internal or external link that Pine -+paints in a different color). By default, this variable is not defined, -+which means that text that matches the pattern is not painted in any -+particular way. This variable must be set in a special form if you -+want text to be painted. -+ -+

    -+Descriptions of the available commands -+

    - Look here - to see the available Editing and Navigation commands. -

    -Index: alpine-2.20/pith/state.c -=================================================================== ---- alpine-2.20.orig/pith/state.c -+++ alpine-2.20/pith/state.c -@@ -131,6 +131,9 @@ free_pine_struct(struct pine **pps) - if((*pps)->folders_dir != NULL) - fs_give((void **)&(*pps)->folders_dir); - -+ if((*pps)->paterror == 0) -+ regfree(&(*pps)->colorpat); -+ - if((*pps)->ui.homedir) - fs_give((void **)&(*pps)->ui.homedir); - -Index: alpine-2.20/pith/state.h -=================================================================== ---- alpine-2.20.orig/pith/state.h -+++ alpine-2.20/pith/state.h -@@ -331,6 +331,8 @@ struct pine { - char *display_charmap; /* needs to be freed */ - char *keyboard_charmap; /* needs to be freed */ - void *input_cs; -+ regex_t colorpat; -+ int paterror; - - char *posting_charmap; /* needs to be freed */ - -Index: alpine-2.20/pith/text.c -=================================================================== ---- alpine-2.20.orig/pith/text.c -+++ alpine-2.20/pith/text.c -@@ -171,6 +171,15 @@ decode_text(ATTACH_S *att, - gf_url_hilite_opt(&uh,handlesp,0)); - } - -+ if((flags & FM_DISPLAY) -+ && !(flags & FM_NOCOLOR) -+ && pico_usingcolor() -+ && VAR_SPECIAL_TEXT_FORE_COLOR -+ && VAR_SPECIAL_TEXT_BACK_COLOR){ -+ filters[filtcnt].filter = gf_line_test; -+ filters[filtcnt++].data = gf_line_test_opt(color_this_text, NULL); -+ } -+ - /* - * First, paint the signature. - * Disclaimers noted below for coloring quotes apply here as well. ++ return ins; ++ } ++ ++ int ++ length_color(char *p, int begin_color) ++ { ++ int len = 0, done = begin_color ? 0 : -1; ++ char *orig = p; ++ ++ while (*p && done <= 0){ ++ switch(*p++){ ++ case TAG_HANDLE : ++ p += *p + 1; ++ done++; ++ break; ++ ++ case TAG_FGCOLOR : ++ case TAG_BGCOLOR : ++ p += RGBLEN; ++ if (!begin_color) ++ done++; ++ break; ++ ++ default : ++ break; ++ } ++ } ++ len = p - orig; ++ return len; ++ } ++ ++ int ++ any_color_in_string(char *p) ++ { ++ int rv = 0; ++ char *orig = p; ++ while (*p && !rv) ++ if (*p++ == TAG_EMBED) ++ rv = p - orig; ++ return rv; ++ } ++ ++ void ++ remove_spaces_ival(IVAL_S **ivalp, char *p) ++ { ++ IVAL_S *ival; ++ int i; ++ if (!ivalp || !*ivalp) ++ return; ++ ival = *ivalp; ++ for (i = 0; isspace((unsigned char) p[ival->start + i]); i++); ++ if (ival->start + i < ival->end) /* do not do this if match only spaces */ ++ ival->start += i; ++ else ++ return; ++ for (i = 0; isspace((unsigned char) p[ival->end - i - 1]); i++); ++ ival->end -= i; ++ if (ival->next) ++ remove_spaces_ival(&(ival->next), p); ++ } ++ ++ int ++ color_this_text(long linenum, char *line, LT_INS_S **ins, void *local) ++ { ++ struct variable *vars = ps_global->vars; ++ COLOR_PAIR *col = NULL; ++ char *p; ++ int i = 0; ++ static char *pattern = NULL; ++ ++ /* select_quote(linenum, line, ins, (void *) &i); ++ for (i = 0; tmp_20k_buf[i] != '\0'; i++); */ ++ p = line + i; ++ ++ if(VAR_SPECIAL_TEXT_FORE_COLOR && VAR_SPECIAL_TEXT_BACK_COLOR ++ && (col = new_color_pair(VAR_SPECIAL_TEXT_FORE_COLOR, ++ VAR_SPECIAL_TEXT_BACK_COLOR)) ++ && !pico_is_good_colorpair(col)) ++ free_color_pair(&col); ++ ++ if(ps_global->VAR_SPECIAL_TEXT && *ps_global->VAR_SPECIAL_TEXT ++ && **ps_global->VAR_SPECIAL_TEXT && col){ ++ IVAL_S *ival; ++ int done = 0, begin_color = 0; ++ ++ while (!done){ ++ if (i = any_color_in_string(p)){ ++ begin_color = (begin_color + 1) % 2; ++ if (begin_color){ ++ p[i - 1] = '\0'; ++ ival = compute_interval(p, 0); ++ remove_spaces_ival(&ival, p); ++ p[i - 1] = TAG_EMBED; ++ ins = insert_color_special_text(ins, &p, ival, 0, col); ++ } ++ for (;*p++ != TAG_EMBED; ); ++ p += length_color(p, begin_color); ++ } ++ else{ ++ ival = compute_interval(p, 0); ++ remove_spaces_ival(&ival, p); ++ ins = insert_color_special_text(ins, &p, ival, 0, col); ++ done++; ++ } ++ interval_free(&ival); ++ if (!*p) ++ done++; ++ } ++ free_color_pair(&col); ++ } ++ ++ return 0; ++ } + + /* + * The argument fieldname is something like "Subject:..." or "Subject". +diff -rc alpine-2.21/pith/mailview.h alpine-2.21.colortext/pith/mailview.h +*** alpine-2.21/pith/mailview.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/mailview.h Sun Feb 5 16:15:26 2017 +*************** +*** 30,35 **** +--- 30,41 ---- + #include "../pith/color.h" + + ++ typedef struct IVAL { ++ int start; ++ int end; ++ struct IVAL *next; ++ } IVAL_S; ++ + /* 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 */ +*************** +*** 126,131 **** +--- 132,146 ---- + 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 *); ++ IVAL_S *compute_interval(char *, int); ++ void remove_spaces_ival(IVAL_S **, char *); ++ void interval_free(IVAL_S **); ++ void regex_pattern(char **); ++ LT_INS_S **insert_color_special_text(LT_INS_S **, char **, IVAL_S *, ++ int, COLOR_PAIR *); ++ int any_color_in_string(char *); ++ int length_color(char *, int); ++ int color_this_text(long, char *, LT_INS_S **, void *); + + /* + * BUG: BELOW IS UNIX/PC ONLY since config'd browser means nothing to webpine +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.colortext/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/pine.hlp Sun Feb 5 16:15:26 2017 +*************** +*** 4227,4232 **** +--- 4227,4233 ---- +

  • OPTION: +
  • OPTION: +
  • OPTION: ++
  • OPTION: +
  • OPTION: +
  • OPTION: Print-Font-Char-Set +
  • OPTION: Print-Font-Name +*************** +*** 4255,4260 **** +--- 4256,4262 ---- +
  • OPTION: +
  • OPTION: +
  • OPTION: Signature Color ++
  • OPTION: Special Text Color +
  • OPTION: +
  • OPTION: +
  • OPTION: +*************** +*** 23729,23734 **** +--- 23731,23773 ---- + <End of help on this topic> + + ++ ====== h_config_special_text_to_color ===== ++ ++ ++ OPTION: <!--#echo var="VAR_special-text-color"--> ++ ++ ++

    OPTION:

    ++ ++ Use this option to enter patterns (text or regular expressions) that ++ Alpine will highlight in the body of the text that is not part of a handle ++ (an internal or external link that Alpine paints in a different color). ++ ++

    ++ Enter each pattern in a different line. Pine will internally merge these ++ patterns (by adding a "|" character), or you can add them all in one line ++ by separating them by a "|" character. There is only a set of regular expressions that are matched. ++ ++

    ++ Pine will use the colors defined in the ++ Special Text Color variable. ++ to paint any match. ++ ++

    ++ If the Special Text Color is not set, setting this variable will not ++ cause that special text to be indicated in any special way. It will look ++ like any normal text. You must set those colors in order to make Pine ++ paint the screen differently when it finds the patterns specified in this ++ variable. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_display_filters ===== + + +*************** +*** 32340,32345 **** +--- 32379,32408 ---- +

    + Descriptions of the available commands +

    ++ Look here ++ to see the available Editing and Navigation commands. ++

    ++ <End of help on this topic> ++ ++ ++ ====== h_config_special_text_color ===== ++ ++ ++ OPTION: Special Text Color ++ ++ ++

    OPTION: Special Text Color

    ++ ++ Sets the color Pine uses for coloring any text in the body of the message ++ that is not part of a handle (and internal or external link that Pine ++ paints in a different color). By default, this variable is not defined, ++ which means that text that matches the pattern is not painted in any ++ particular way. This variable must be set in a special form if you ++ want text to be painted. ++ ++

    ++ Descriptions of the available commands ++

    + Look here + to see the available Editing and Navigation commands. +

    +diff -rc alpine-2.21/pith/state.c alpine-2.21.colortext/pith/state.c +*** alpine-2.21/pith/state.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/state.c Sun Feb 5 16:15:26 2017 +*************** +*** 134,139 **** +--- 134,142 ---- + if((*pps)->folders_dir != NULL) + fs_give((void **)&(*pps)->folders_dir); + ++ if((*pps)->paterror == 0) ++ regfree(&(*pps)->colorpat); ++ + if((*pps)->ui.homedir) + fs_give((void **)&(*pps)->ui.homedir); + +diff -rc alpine-2.21/pith/state.h alpine-2.21.colortext/pith/state.h +*** alpine-2.21/pith/state.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/state.h Sun Feb 5 16:15:26 2017 +*************** +*** 338,343 **** +--- 338,345 ---- + char *display_charmap; /* needs to be freed */ + char *keyboard_charmap; /* needs to be freed */ + void *input_cs; ++ regex_t colorpat; ++ int paterror; + + char *posting_charmap; /* needs to be freed */ + +diff -rc alpine-2.21/pith/text.c alpine-2.21.colortext/pith/text.c +*** alpine-2.21/pith/text.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.colortext/pith/text.c Sun Feb 5 16:15:26 2017 +*************** +*** 171,176 **** +--- 171,185 ---- + gf_url_hilite_opt(&uh,handlesp,0)); + } + ++ if((flags & FM_DISPLAY) ++ && !(flags & FM_NOCOLOR) ++ && pico_usingcolor() ++ && VAR_SPECIAL_TEXT_FORE_COLOR ++ && VAR_SPECIAL_TEXT_BACK_COLOR){ ++ filters[filtcnt].filter = gf_line_test; ++ filters[filtcnt++].data = gf_line_test_opt(color_this_text, NULL); ++ } ++ + /* + * First, paint the signature. + * Disclaimers noted below for coloring quotes apply here as well. diff --git a/chappa-fancy.patch b/chappa-fancy.patch index bdb6c3c..73ebd77 100644 --- a/chappa-fancy.patch +++ b/chappa-fancy.patch @@ -1,3198 +1,4050 @@ ---- - alpine/arg.c | 14 - alpine/confscroll.c | 66 +++ - alpine/confscroll.h | 2 - alpine/keymenu.c | 47 ++ - alpine/keymenu.h | 13 - alpine/mailcmd.c | 464 +++++++++++++++++++++++++- - alpine/mailcmd.h | 13 - alpine/mailindx.c | 124 ++++++- - alpine/mailindx.h | 2 - alpine/mailview.c | 46 ++ - alpine/roleconf.c | 10 - alpine/setup.c | 60 +++ - pith/conf.c | 24 + - pith/conf.h | 3 - pith/conftype.h | 3 - pith/flag.c | 6 - pith/indxtype.h | 2 - pith/mailindx.c | 58 ++- - pith/pattern.c | 2 - pith/pine.hlp | 222 ++++++++++++ - pith/sort.c | 33 + - pith/sort.h | 6 - pith/state.c | 1 - pith/state.h | 5 - pith/thread.c | 772 ++++++++++++++++++++++++++++++++++++++++---- - pith/thread.h | 23 + - web/src/alpined.d/alpined.c | 4 - 27 files changed, 1870 insertions(+), 155 deletions(-) - -Index: alpine-2.20/alpine/arg.c -=================================================================== ---- alpine-2.20.orig/alpine/arg.c -+++ alpine-2.20/alpine/arg.c -@@ -64,6 +64,7 @@ static char args_err_non_abs_pwdcertdir[ - #endif /* SMIME inside PASSFILE */ - #endif - static char args_err_missing_sort[] = N_("missing argument for option \"-sort\""); -+static char args_err_missing_thread_sort[] = N_("missing argument for option \"-threadsort\""); - static char args_err_missing_flag_arg[] = N_("missing argument for flag \"%c\""); - static char args_err_missing_flag_num[] = N_("Non numeric argument for flag \"%c\""); - static char args_err_missing_debug_num[] = N_("Non numeric argument for \"%s\""); -@@ -107,6 +108,7 @@ N_(" -k \t\tKeys - Force use of function - N_(" -z \t\tSuspend - allow use of ^Z suspension"), - N_(" -r \t\tRestricted - can only send mail to oneself"), - N_(" -sort \tSort - Specify sort order of folder:"), -+N_(" -threadsort \tSort - Specify sort order of thread index screen:"), - N_("\t\t\tarrival, subject, threaded, orderedsubject, date,"), - N_("\t\t\tfrom, size, score, to, cc, /reverse"), - N_(" -i\t\tIndex - Go directly to index, bypassing main menu"), -@@ -200,6 +202,7 @@ pine_args(struct pine *pine_state, int a - char *cmd_list = NULL; - char *debug_str = NULL; - char *sort = NULL; -+ char *threadsort = NULL; - char *pinerc_file = NULL; - char *lc = NULL; - int do_help = 0; -@@ -396,6 +399,17 @@ Loop: while(--ac > 0) - - goto Loop; - } -+ else if(strcmp(*av, "threadsort") == 0){ -+ if(--ac){ -+ threadsort = *++av; -+ COM_THREAD_SORT_KEY = cpystr(threadsort); -+ } -+ else{ -+ display_args_err(_(args_err_missing_thread_sort), NULL, 1); -+ ++usage; -+ } -+ goto Loop; -+ } - else if(strcmp(*av, "url") == 0){ - if(args->action == aaFolder && !args->data.folder){ - args->action = aaURL; -Index: alpine-2.20/alpine/confscroll.c -=================================================================== ---- alpine-2.20.orig/alpine/confscroll.c -+++ alpine-2.20/alpine/confscroll.c -@@ -139,7 +139,7 @@ char *yesno_pretty_value(struct pine - char *radio_pretty_value(struct pine *, CONF_S *); - char *sigfile_pretty_value(struct pine *, CONF_S *); - char *color_pretty_value(struct pine *, CONF_S *); --char *sort_pretty_value(struct pine *, CONF_S *); -+char *sort_pretty_value(struct pine *, CONF_S *, int); - int longest_feature_name(void); - COLOR_PAIR *sample_color(struct pine *, struct variable *); - COLOR_PAIR *sampleexc_color(struct pine *, struct variable *); -@@ -287,7 +287,8 @@ set_radio_pretty_vals(struct pine *ps, C - CONF_S *ctmp; - - if(!(cl && *cl && -- ((*cl)->var == &ps->vars[V_SORT_KEY] || -+ (((*cl)->var == &ps->vars[V_SORT_KEY]) || -+ ((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]) || - standard_radio_var(ps, (*cl)->var) || - (*cl)->var == startup_ptr))) - return; -@@ -2927,7 +2928,7 @@ radiobutton_tool(struct pine *ps, int cm - } - - set_current_val((*cl)->var, TRUE, TRUE); -- if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev) != -1){ -+ if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev,0) != -1){ - ps->def_sort = def_sort; - ps->def_sort_rev = def_sort_rev; - } -@@ -2936,6 +2937,37 @@ radiobutton_tool(struct pine *ps, int cm - ps->mangled_body = 1; /* BUG: redraw it all for now? */ - rv = 1; - } -+ else if((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]){ -+ SortOrder thread_def_sort; -+ int thread_def_sort_rev; -+ -+ thread_def_sort_rev = (*cl)->varmem >= (short) EndofList; -+ thread_def_sort = (SortOrder) ((*cl)->varmem - (thread_def_sort_rev -+ * EndofList)); -+ sprintf(tmp_20k_buf, "%s%s", sort_name(thread_def_sort), -+ (thread_def_sort_rev) ? "/Reverse" : ""); -+ -+ if((*cl)->var->cmdline_val.p) -+ fs_give((void **)&(*cl)->var->cmdline_val.p); -+ -+ if(apval){ -+ if(*apval) -+ fs_give((void **)apval); -+ -+ *apval = cpystr(tmp_20k_buf); -+ } -+ -+ set_current_val((*cl)->var, TRUE, TRUE); -+ if(decode_sort(ps->VAR_THREAD_SORT_KEY, &thread_def_sort, -+ &thread_def_sort_rev, 1) != -1){ -+ ps->thread_def_sort = thread_def_sort; -+ ps->thread_def_sort_rev = thread_def_sort_rev; -+ } -+ -+ set_radio_pretty_vals(ps, cl); -+ ps->mangled_body = 1; /* BUG: redraw it all for now? */ -+ rv = 1; -+ } - else - q_status_message(SM_ORDER | SM_DING, 3, 6, - "Programmer botch! Unknown radiobutton type."); -@@ -3799,7 +3831,9 @@ pretty_value(struct pine *ps, CONF_S *cl - else if(standard_radio_var(ps, v) || v == startup_ptr) - return(radio_pretty_value(ps, cl)); - else if(v == &ps->vars[V_SORT_KEY]) -- return(sort_pretty_value(ps, cl)); -+ return(sort_pretty_value(ps, cl, 0)); -+ else if(v == &ps->vars[V_THREAD_SORT_KEY]) -+ return(sort_pretty_value(ps, cl, 1)); - else if(v == &ps->vars[V_SIGNATURE_FILE]) - return(sigfile_pretty_value(ps, cl)); - else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME]) -@@ -4330,14 +4364,14 @@ color_pretty_value(struct pine *ps, CONF - - - char * --sort_pretty_value(struct pine *ps, CONF_S *cl) -+sort_pretty_value(struct pine *ps, CONF_S *cl, int thread) - { -- return(generalized_sort_pretty_value(ps, cl, 1)); -+ return(generalized_sort_pretty_value(ps, cl, 1, thread)); - } - - - char * --generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) -+generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok, int thread) - { - char tmp[6*MAXPATH]; - char *pvalnorm, *pvalexc, *pval; -@@ -4387,7 +4421,7 @@ generalized_sort_pretty_value(struct pin - } - else if(fixed){ - pval = v->fixed_val.p; -- decode_sort(pval, &var_sort, &var_sort_rev); -+ decode_sort(pval, &var_sort, &var_sort_rev, thread); - is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); - - utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", -@@ -4398,9 +4432,9 @@ generalized_sort_pretty_value(struct pin - is_the_one ? " (value is fixed)" : ""); - } - else if(is_set_for_this_level){ -- decode_sort(pval, &var_sort, &var_sort_rev); -+ decode_sort(pval, &var_sort, &var_sort_rev, thread); - is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); -- decode_sort(pvalexc, &exc_sort, &exc_sort_rev); -+ decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); - the_exc_one = (editing_normal_which_isnt_except && pvalexc && - exc_sort_rev == line_sort_rev && exc_sort == line_sort); - utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", -@@ -4418,7 +4452,7 @@ generalized_sort_pretty_value(struct pin - } - else{ - if(pvalexc){ -- decode_sort(pvalexc, &exc_sort, &exc_sort_rev); -+ decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); - is_the_one = (exc_sort_rev == line_sort_rev && - exc_sort == line_sort); - utf8_snprintf(tmp, sizeof(tmp), "( ) %s%-*w%*s%s", -@@ -4429,7 +4463,7 @@ generalized_sort_pretty_value(struct pin - } - else{ - pval = v->current_val.p; -- decode_sort(pval, &var_sort, &var_sort_rev); -+ decode_sort(pval, &var_sort, &var_sort_rev, thread); - is_the_one = ((pval || default_ok) && - var_sort_rev == line_sort_rev && - var_sort == line_sort); -@@ -5571,9 +5605,15 @@ fix_side_effects(struct pine *ps, struct - else if(revert && var == &ps->vars[V_SORT_KEY]){ - int def_sort_rev; - -- decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev); -+ decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev, 0); - ps->def_sort_rev = def_sort_rev; - } -+ else if(revert && var == &ps->vars[V_THREAD_SORT_KEY]){ -+ int thread_def_sort_rev; -+ -+ decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, &thread_def_sort_rev, 1); -+ ps->thread_def_sort_rev = thread_def_sort_rev; -+ } - else if(var == &ps->vars[V_THREAD_MORE_CHAR] || - var == &ps->vars[V_THREAD_EXP_CHAR] || - var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){ -Index: alpine-2.20/alpine/confscroll.h -=================================================================== ---- alpine-2.20.orig/alpine/confscroll.h -+++ alpine-2.20/alpine/confscroll.h -@@ -95,7 +95,7 @@ int checkbox_tool(struct pine *, int, C - int radiobutton_tool(struct pine *, int, CONF_S **, unsigned); - int yesno_tool(struct pine *, int, CONF_S **, unsigned); - int text_toolit(struct pine *, int, CONF_S **, unsigned, int); --char *generalized_sort_pretty_value(struct pine *, CONF_S *, int); -+char *generalized_sort_pretty_value(struct pine *, CONF_S *, int, int); - int exclude_config_var(struct pine *, struct variable *, int); - int config_exit_cmd(unsigned); - int simple_exit_cmd(unsigned); -Index: alpine-2.20/alpine/keymenu.c -=================================================================== ---- alpine-2.20.orig/alpine/keymenu.c -+++ alpine-2.20/alpine/keymenu.c -@@ -650,10 +650,25 @@ struct key index_keys[] = - RCOMPOSE_MENU, - HOMEKEY_MENU, - ENDKEY_MENU, -- NULL_MENU, -+ {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, - /* TRANSLATORS: toggles a collapsed view or an expanded view - of a message thread on and off */ - {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, -+ /* TRANSLATORS: Collapse all threads */ -+ {"{",N_("Collapse All"),{MC_KOLAPSE,1,{'{'}},KS_NONE}, -+ /* TRANSLATORS: Expand all threads */ -+ {"}",N_("Expand All"), {MC_EXPTHREAD,1,{'}'}},KS_NONE}, -+ -+ HELP_MENU, -+ OTHER_MENU, -+ {")","Next Threa",{MC_NEXTHREAD,1,{')'}},KS_NONE}, -+ {"(","Prev Threa",{MC_PRETHREAD,1,{'('}},KS_NONE}, -+ {"^R","Remove Thr",{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, -+ {"^U","Undel Thre",{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, -+ {"^T","Select Thr",{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, -+ NULL_MENU, -+ {"[","Close Thre",{MC_CTHREAD,1,{'['}},KS_NONE}, -+ {"]","Open Threa",{MC_OTHREAD,1,{']'}},KS_NONE}, - {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, - NULL_MENU}; - INST_KEY_MENU(index_keymenu, index_keys); -@@ -728,9 +743,22 @@ struct key thread_keys[] = - RCOMPOSE_MENU, - HOMEKEY_MENU, - ENDKEY_MENU, -- NULL_MENU, -+ {"]",N_("Open Thread"),{MC_OTHREAD,1,{']'}},KS_NONE}, - {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, -+ {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, -+ {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, -+ -+ HELP_MENU, -+ OTHER_MENU, - {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, -+ NULL_MENU, -+ {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, -+ {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, -+ {"^T",N_("SelecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, -+ NULL_MENU, -+ NULL_MENU, -+ NULL_MENU, -+ {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, - NULL_MENU}; - INST_KEY_MENU(thread_keymenu, thread_keys); - -@@ -880,7 +908,20 @@ struct key view_keys[] = - NULL_MENU, - NULL_MENU, - NULL_MENU, -- NULL_MENU}; -+ NULL_MENU, -+ -+ HELP_MENU, -+ OTHER_MENU, -+ NULL_MENU, -+ NULL_MENU, -+ NULL_MENU, -+ NULL_MENU, -+ NULL_MENU, -+ {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, -+ {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, -+ {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, -+ {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, -+ {"^T",N_("selecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}}; - INST_KEY_MENU(view_keymenu, view_keys); - - -Index: alpine-2.20/alpine/keymenu.h -=================================================================== ---- alpine-2.20.orig/alpine/keymenu.h -+++ alpine-2.20/alpine/keymenu.h -@@ -215,6 +215,19 @@ struct key_menu { - #define MC_DECRYPT 802 - #define MC_QUOTA 803 - #define MC_ADDHEADER 804 -+#define MC_DELTHREAD 805 -+#define MC_UNDTHREAD 806 -+#define MC_SELTHREAD 807 -+#define MC_SSUTHREAD 808 -+#define MC_DSUTHREAD 809 -+#define MC_USUTHREAD 810 -+#define MC_SORTHREAD 811 -+#define MC_NEXTHREAD 812 -+#define MC_KOLAPSE 813 -+#define MC_EXPTHREAD 814 -+#define MC_PRETHREAD 815 -+#define MC_CTHREAD 816 -+#define MC_OTHREAD 817 - - - /* Commands for S/MIME screens */ -Index: alpine-2.20/alpine/mailcmd.c -=================================================================== ---- alpine-2.20.orig/alpine/mailcmd.c -+++ alpine-2.20/alpine/mailcmd.c -@@ -113,7 +113,7 @@ int select_by_thread(MAILSTREAM *, MSG - char *choose_a_rule(int); - int select_by_keyword(MAILSTREAM *, SEARCHSET **); - char *choose_a_keyword(void); --int select_sort(struct pine *, int, SortOrder *, int *); -+int select_sort(struct pine *, int, SortOrder *, int *, int); - int print_index(struct pine *, MSGNO_S *, int); - - /* -@@ -1371,7 +1371,7 @@ get_out: - if(any_messages(msgmap, NULL, NULL)){ - if(any_lflagged(msgmap, MN_SLCT) > 0L){ - if(apply_command(state, stream, msgmap, 0, -- AC_NONE, question_line)){ -+ AC_NONE, question_line, 1)){ - if(F_ON(F_AUTO_UNSELECT, state)){ - agg_select_all(stream, msgmap, NULL, 0); - unzoom_index(state, stream, msgmap); -@@ -1389,23 +1389,35 @@ get_out: - - /*-------- Sort command -------*/ - case MC_SORT : -+ case MC_SORTHREAD: - { - int were_threading = THREADING(); - SortOrder sort = mn_get_sort(msgmap); - int rev = mn_get_revsort(msgmap); -+ int thread = (command == MC_SORT) ? 0 : 1; - - dprint((1,"MAIL_CMD: sort\n")); -- if(select_sort(state, question_line, &sort, &rev)){ -+ if(sort == SortThread) -+ sort = ps_global->thread_cur_sort; -+ if(select_sort(state, question_line, &sort, &rev, thread)){ - /* $ command reinitializes threading collapsed/expanded info */ - if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) - erase_threading_info(stream, msgmap); - -+ if(command == MC_SORTHREAD){ -+ ps_global->thread_cur_sort = sort; -+ sort = SortThread; -+ } -+ else if(sort == SortThread) /* command = MC_SORT */ -+ ps_global->thread_cur_sort = F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) -+ ? SortArrival : ps_global->thread_def_sort; -+ - if(ps_global && ps_global->ttyo){ - blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); - ps_global->mangled_footer = 1; - } - -- sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN); -+ sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN, 1); - } - - state->mangled_footer = 1; -@@ -3286,6 +3298,10 @@ cmd_expunge(struct pine *state, MAILSTRE - if(SORT_IS_THREADED(msgmap)) - refresh_sort(stream, msgmap, SRT_NON); - -+ if (msgmap->nmsgs -+ && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) -+ kolapse_thread(state, stream, msgmap, '[', 0); -+ - state->mangled_body = 1; - state->mangled_header = 1; - q_status_message2(SM_ORDER, 0, 4, -@@ -3387,6 +3403,9 @@ cmd_expunge(struct pine *state, MAILSTRE - */ - if(SORT_IS_THREADED(msgmap)) - refresh_sort(stream, msgmap, SRT_NON); -+ if (msgmap->nmsgs -+ && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) -+ kolapse_thread(state, stream, msgmap, '[', 0); - } - else{ - if(del_count){ -@@ -7090,7 +7109,7 @@ select_by_current(struct pine *state, MS - * Maybe it makes sense to zoom after a select but not after a colon - * command even though they are very similar. - */ -- thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state)); -+ thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state), 1); - } - else{ - if((all_selected = -@@ -7146,7 +7165,7 @@ select_by_current(struct pine *state, MS - ----*/ - int - apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, -- UCS preloadkeystroke, int flags, int q_line) -+ UCS preloadkeystroke, int flags, int q_line, int display) - { - int i = 8, /* number of static entries in sel_opts3 */ - rv = 0, -@@ -7305,9 +7324,19 @@ apply_command(struct pine *state, MAILST - collapse_or_expand(state, stream, msgmap, - F_ON(F_SLASH_COLL_ENTIRE, ps_global) - ? 0L -- : mn_get_cur(msgmap)); -+ : mn_get_cur(msgmap), -+ display); - break; - -+ case '[' : -+ collapse_this_thread(state, stream, msgmap, display, 0); -+ break; -+ -+ case ']' : -+ expand_this_thread(state, stream, msgmap, display, 0); -+ break; -+ -+ - case ':' : - select_thread_stmp(state, stream, msgmap); - break; -@@ -9171,10 +9200,10 @@ Args: state -- pine state pointer - Returns 0 if it was cancelled, 1 otherwise. - ----*/ - int --select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) -+select_sort(struct pine *state, int ql, SortOrder *sort, int *rev, int thread) - { - char prompt[200], tmp[3], *p; -- int s, i; -+ int s, i, j; - int deefault = 'a', retval = 1; - HelpType help; - ESCKEY_S sorts[14]; -@@ -9207,17 +9236,26 @@ select_sort(struct pine *state, int ql, - strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "), - sizeof(prompt)); - -- for(i = 0; state->sort_types[i] != EndofList; i++) { -- sorts[i].rval = i; -- p = sorts[i].label = sort_name(state->sort_types[i]); -- while(*(p+1) && islower((unsigned char)*p)) -- p++; -- -- sorts[i].ch = tolower((unsigned char)(tmp[0] = *p)); -- sorts[i].name = cpystr(tmp); -- -- if(mn_get_sort(state->msgmap) == state->sort_types[i]) -- deefault = sorts[i].rval; -+ for(i = 0, j = 0; state->sort_types[i] != EndofList; i++) { -+ sorts[i].rval = i; -+ sorts[i].name = cpystr(""); -+ sorts[i].label = ""; -+ sorts[i].ch = -2; -+ if (!thread || allowed_thread_key(state->sort_types[i])){ -+ p = sorts[j].label = sort_name(state->sort_types[i]); -+ while(*(p+1) && islower((unsigned char)*p)) -+ p++; -+ sorts[j].ch = tolower((unsigned char)(tmp[0] = *p)); -+ sorts[j++].name = cpystr(tmp); -+ } -+ -+ if (thread){ -+ if (state->thread_def_sort == state->sort_types[i]) -+ deefault = sorts[j-1].rval; -+ } -+ else -+ if(mn_get_sort(state->msgmap) == state->sort_types[i]) -+ deefault = sorts[i].rval; - } - - sorts[i].ch = 'r'; -@@ -9241,8 +9279,17 @@ select_sort(struct pine *state, int ql, - state->mangled_body = 1; /* signal screen's changed */ - if(s == 'r') - *rev = !mn_get_revsort(state->msgmap); -- else -+ else{ -+ if(thread){ -+ for(i = 0; state->sort_types[i] != EndofList; i++){ -+ if(struncmp(sort_name(state->sort_types[i]), -+ sorts[s].label, strlen(sorts[s].label)) == 0) -+ break; -+ } -+ s = i; -+ } - *sort = state->sort_types[s]; -+ } - - if(F_ON(F_SHOW_SORT, ps_global)) - ps_global->mangled_header = 1; -@@ -9627,3 +9674,378 @@ flag_submenu(mc) - } - - #endif /* _WINDOWS */ -+ -+void -+cmd_delete_this_thread(state, stream, msgmap) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+{ -+ unsigned long rawno, top, save_kolapsed; -+ PINETHRD_S *thrd = NULL, *nxthrd; -+ -+ if(!stream) -+ return; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_this_thread(stream, msgmap, rawno); -+ top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return; -+ -+ save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, top); -+ collapse_this_thread(state, stream, msgmap, 0, 0); -+ thread_command(state, stream, msgmap, 'd', -FOOTER_ROWS(state), 1); -+ if (!save_kolapsed) -+ expand_this_thread(state, stream, msgmap, 0, 0); -+} -+ -+void -+cmd_delete_thread(state, stream, msgmap) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+{ -+ unsigned long rawno, top, orig_top, topnxt, save_kolapsed; -+ PINETHRD_S *thrd = NULL, *nxthrd; -+ int done = 0, count; -+ -+ if(!stream) -+ return; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_thread(stream, msgmap, rawno); -+ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return; -+ -+ while (!done){ -+ cmd_delete_this_thread(state, stream, msgmap); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); -+ cmd_delete(state, msgmap, MCMD_NONE, cmd_delete_index); -+ count = count_thread(state, stream, msgmap, rawno); -+ q_status_message2(SM_ORDER, 0, 1, "%s message%s marked deleted", -+ int2string(count), plural(count)); -+} -+ -+int -+collapse_this_thread(state, stream, msgmap, display, special) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+ int display; -+ int special; -+{ -+ int collapsed, rv = 1, done = 0; -+ PINETHRD_S *thrd = NULL, *nthrd; -+ unsigned long rawno, orig, msgno; -+ -+ if(!stream) -+ return 0; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return rv; -+ -+ collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); -+ -+ if (special && collapsed){ -+ expand_this_thread(state, stream, msgmap, 0, 0); -+ collapsed = 0; -+ } -+ -+ clear_index_cache_ent(stream, rawno, 0); -+ -+ if (!collapsed && thrd->next){ -+ if (thrd->rawno == top_thread(stream, thrd->rawno)) -+ collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); -+ else{ -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 1); -+ set_thread_subtree(stream, thrd, msgmap, 1, MN_CHID); -+ } -+ } -+ else{ -+ if (!collapsed && special -+ && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) -+ || F_ON(F_ENHANCED_THREAD, state))){ -+ if (thrd->toploose){ -+ if (thrd->rawno != thrd->toploose) -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, -+ 1); -+ else -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, -+ 1); -+ } -+ } -+ else{ -+ rv = 0; -+ if (display) -+ q_status_message(SM_ORDER, 0, 1, "Thread already collapsed"); -+ } -+ } -+ return rv; -+} -+ -+void -+collapse_thread(state, stream, msgmap, display) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+ int display; -+{ -+ int collapsed, rv = 1, done = 0; -+ PINETHRD_S *thrd = NULL; -+ unsigned long orig, orig_top, top; -+ -+ if(!stream) -+ return; -+ -+ expand_this_thread(state, stream, msgmap, display, 1); -+ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_thread(stream, msgmap,orig); -+ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return; -+ -+ while (!done){ -+ collapse_this_thread(state, stream, msgmap, display, 1); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); -+} -+ -+int -+expand_this_thread(state, stream, msgmap, display, special) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+ int display; -+ int special; -+{ -+ int collapsed, rv = 1, done = 0; -+ PINETHRD_S *thrd = NULL, *nthrd; -+ unsigned long rawno, orig, msgno; -+ -+ if(!stream) -+ return 0; -+ -+ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_this_thread(stream, msgmap,orig); -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return rv; -+ -+ collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); -+ -+ if (special && !collapsed){ -+ collapse_this_thread(state, stream, msgmap, 0, 0); -+ collapsed = 1; -+ } -+ -+ clear_index_cache_ent(stream, rawno, 0); -+ -+ if (collapsed && thrd->next){ -+ if (thrd->rawno == top_thread(stream, thrd->rawno)) -+ collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); -+ else{ -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 0); -+ set_thread_subtree(stream, thrd, msgmap, 0, MN_CHID); -+ } +diff -rc alpine-2.21/alpine/arg.c alpine-2.21.fancy/alpine/arg.c +*** alpine-2.21/alpine/arg.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/arg.c Sun Feb 5 16:15:20 2017 +*************** +*** 68,73 **** +--- 68,74 ---- + #endif /* SMIME inside PASSFILE */ + #endif + static char args_err_missing_sort[] = N_("missing argument for option \"-sort\""); ++ static char args_err_missing_thread_sort[] = N_("missing argument for option \"-threadsort\""); + static char args_err_missing_flag_arg[] = N_("missing argument for flag \"%c\""); + static char args_err_missing_flag_num[] = N_("Non numeric argument for flag \"%c\""); + static char args_err_missing_debug_num[] = N_("Non numeric argument for \"%s\""); +*************** +*** 111,116 **** +--- 112,118 ---- + N_(" -z \t\tSuspend - allow use of ^Z suspension"), + N_(" -r \t\tRestricted - can only send mail to oneself"), + N_(" -sort \tSort - Specify sort order of folder:"), ++ N_(" -threadsort \tSort - Specify sort order of thread index screen:"), + N_("\t\t\tarrival, subject, threaded, orderedsubject, date,"), + N_("\t\t\tfrom, size, score, to, cc, /reverse"), + N_(" -i\t\tIndex - Go directly to index, bypassing main menu"), +*************** +*** 208,213 **** +--- 210,216 ---- + char *cmd_list = NULL; + char *debug_str = NULL; + char *sort = NULL; ++ char *threadsort = NULL; + char *pinerc_file = NULL; + char *lc = NULL; + int do_help = 0; +*************** +*** 429,434 **** +--- 432,448 ---- + + goto Loop; + } ++ else if(strcmp(*av, "threadsort") == 0){ ++ if(--ac){ ++ threadsort = *++av; ++ COM_THREAD_SORT_KEY = cpystr(threadsort); ++ } ++ else{ ++ display_args_err(_(args_err_missing_thread_sort), NULL, 1); ++ ++usage; ++ } ++ goto Loop; ++ } + else if(strcmp(*av, "url") == 0){ + if(args->action == aaFolder && !args->data.folder){ + args->action = aaURL; +diff -rc alpine-2.21/alpine/confscroll.c alpine-2.21.fancy/alpine/confscroll.c +*** alpine-2.21/alpine/confscroll.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/confscroll.c Sun Feb 5 16:15:20 2017 +*************** +*** 139,145 **** + char *radio_pretty_value(struct pine *, CONF_S *); + char *sigfile_pretty_value(struct pine *, CONF_S *); + char *color_pretty_value(struct pine *, CONF_S *); +! char *sort_pretty_value(struct pine *, CONF_S *); + int longest_feature_name(void); + COLOR_PAIR *sample_color(struct pine *, struct variable *); + COLOR_PAIR *sampleexc_color(struct pine *, struct variable *); +--- 139,145 ---- + char *radio_pretty_value(struct pine *, CONF_S *); + char *sigfile_pretty_value(struct pine *, CONF_S *); + char *color_pretty_value(struct pine *, CONF_S *); +! char *sort_pretty_value(struct pine *, CONF_S *, int); + int longest_feature_name(void); + COLOR_PAIR *sample_color(struct pine *, struct variable *); + COLOR_PAIR *sampleexc_color(struct pine *, struct variable *); +*************** +*** 287,293 **** + CONF_S *ctmp; + + if(!(cl && *cl && +! ((*cl)->var == &ps->vars[V_SORT_KEY] || + standard_radio_var(ps, (*cl)->var) || + (*cl)->var == startup_ptr))) + return; +--- 287,294 ---- + CONF_S *ctmp; + + if(!(cl && *cl && +! (((*cl)->var == &ps->vars[V_SORT_KEY]) || +! ((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]) || + standard_radio_var(ps, (*cl)->var) || + (*cl)->var == startup_ptr))) + return; +*************** +*** 2932,2938 **** + } + + set_current_val((*cl)->var, TRUE, TRUE); +! if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev) != -1){ + ps->def_sort = def_sort; + ps->def_sort_rev = def_sort_rev; + } +--- 2933,2939 ---- + } + + set_current_val((*cl)->var, TRUE, TRUE); +! if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev,0) != -1){ + ps->def_sort = def_sort; + ps->def_sort_rev = def_sort_rev; + } +*************** +*** 2941,2946 **** +--- 2942,2978 ---- + ps->mangled_body = 1; /* BUG: redraw it all for now? */ + rv = 1; + } ++ else if((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]){ ++ SortOrder thread_def_sort; ++ int thread_def_sort_rev; ++ ++ thread_def_sort_rev = (*cl)->varmem >= (short) EndofList; ++ thread_def_sort = (SortOrder) ((*cl)->varmem - (thread_def_sort_rev ++ * EndofList)); ++ sprintf(tmp_20k_buf, "%s%s", sort_name(thread_def_sort), ++ (thread_def_sort_rev) ? "/Reverse" : ""); ++ ++ if((*cl)->var->cmdline_val.p) ++ fs_give((void **)&(*cl)->var->cmdline_val.p); ++ ++ if(apval){ ++ if(*apval) ++ fs_give((void **)apval); ++ ++ *apval = cpystr(tmp_20k_buf); ++ } ++ ++ set_current_val((*cl)->var, TRUE, TRUE); ++ if(decode_sort(ps->VAR_THREAD_SORT_KEY, &thread_def_sort, ++ &thread_def_sort_rev, 1) != -1){ ++ ps->thread_def_sort = thread_def_sort; ++ ps->thread_def_sort_rev = thread_def_sort_rev; ++ } ++ ++ set_radio_pretty_vals(ps, cl); ++ ps->mangled_body = 1; /* BUG: redraw it all for now? */ ++ rv = 1; ++ } + else + q_status_message(SM_ORDER | SM_DING, 3, 6, + "Programmer botch! Unknown radiobutton type."); +*************** +*** 3804,3810 **** + else if(standard_radio_var(ps, v) || v == startup_ptr) + return(radio_pretty_value(ps, cl)); + else if(v == &ps->vars[V_SORT_KEY]) +! return(sort_pretty_value(ps, cl)); + else if(v == &ps->vars[V_SIGNATURE_FILE]) + return(sigfile_pretty_value(ps, cl)); + else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME]) +--- 3836,3844 ---- + else if(standard_radio_var(ps, v) || v == startup_ptr) + return(radio_pretty_value(ps, cl)); + else if(v == &ps->vars[V_SORT_KEY]) +! return(sort_pretty_value(ps, cl, 0)); +! else if(v == &ps->vars[V_THREAD_SORT_KEY]) +! return(sort_pretty_value(ps, cl, 1)); + else if(v == &ps->vars[V_SIGNATURE_FILE]) + return(sigfile_pretty_value(ps, cl)); + else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME]) +*************** +*** 4335,4348 **** + + + char * +! sort_pretty_value(struct pine *ps, CONF_S *cl) + { +! return(generalized_sort_pretty_value(ps, cl, 1)); + } + + + char * +! generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) + { + char tmp[6*MAXPATH]; + char *pvalnorm, *pvalexc, *pval; +--- 4369,4382 ---- + + + char * +! sort_pretty_value(struct pine *ps, CONF_S *cl, int thread) + { +! return(generalized_sort_pretty_value(ps, cl, 1, thread)); + } + + + char * +! generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok, int thread) + { + char tmp[6*MAXPATH]; + char *pvalnorm, *pvalexc, *pval; +*************** +*** 4392,4398 **** + } + else if(fixed){ + pval = v->fixed_val.p; +! decode_sort(pval, &var_sort, &var_sort_rev); + is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); + + utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", +--- 4426,4432 ---- + } + else if(fixed){ + pval = v->fixed_val.p; +! decode_sort(pval, &var_sort, &var_sort_rev, thread); + is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); + + utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", +*************** +*** 4403,4411 **** + is_the_one ? " (value is fixed)" : ""); + } + else if(is_set_for_this_level){ +! decode_sort(pval, &var_sort, &var_sort_rev); + is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); +! decode_sort(pvalexc, &exc_sort, &exc_sort_rev); + the_exc_one = (editing_normal_which_isnt_except && pvalexc && + exc_sort_rev == line_sort_rev && exc_sort == line_sort); + utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", +--- 4437,4445 ---- + is_the_one ? " (value is fixed)" : ""); + } + else if(is_set_for_this_level){ +! decode_sort(pval, &var_sort, &var_sort_rev, thread); + is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); +! decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); + the_exc_one = (editing_normal_which_isnt_except && pvalexc && + exc_sort_rev == line_sort_rev && exc_sort == line_sort); + utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", +*************** +*** 4423,4429 **** + } + else{ + if(pvalexc){ +! decode_sort(pvalexc, &exc_sort, &exc_sort_rev); + is_the_one = (exc_sort_rev == line_sort_rev && + exc_sort == line_sort); + utf8_snprintf(tmp, sizeof(tmp), "( ) %s%-*w%*s%s", +--- 4457,4463 ---- + } + else{ + if(pvalexc){ +! decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); + is_the_one = (exc_sort_rev == line_sort_rev && + exc_sort == line_sort); + utf8_snprintf(tmp, sizeof(tmp), "( ) %s%-*w%*s%s", +*************** +*** 4434,4440 **** + } + else{ + pval = v->current_val.p; +! decode_sort(pval, &var_sort, &var_sort_rev); + is_the_one = ((pval || default_ok) && + var_sort_rev == line_sort_rev && + var_sort == line_sort); +--- 4468,4474 ---- + } + else{ + pval = v->current_val.p; +! decode_sort(pval, &var_sort, &var_sort_rev, thread); + is_the_one = ((pval || default_ok) && + var_sort_rev == line_sort_rev && + var_sort == line_sort); +*************** +*** 5594,5602 **** + else if(revert && var == &ps->vars[V_SORT_KEY]){ + int def_sort_rev; + +! decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev); + ps->def_sort_rev = def_sort_rev; + } + else if(var == &ps->vars[V_THREAD_MORE_CHAR] || + var == &ps->vars[V_THREAD_EXP_CHAR] || + var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){ +--- 5628,5642 ---- + else if(revert && var == &ps->vars[V_SORT_KEY]){ + int def_sort_rev; + +! decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev, 0); + ps->def_sort_rev = def_sort_rev; + } ++ else if(revert && var == &ps->vars[V_THREAD_SORT_KEY]){ ++ int thread_def_sort_rev; ++ ++ decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, &thread_def_sort_rev, 1); ++ ps->thread_def_sort_rev = thread_def_sort_rev; + } -+ else{ -+ if (collapsed && special -+ && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) -+ || F_ON(F_ENHANCED_THREAD, state))){ -+ if (thrd->toploose) -+ if (thrd->rawno != thrd->toploose) -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, 0); -+ else -+ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, 0); -+ } -+ else{ -+ rv = 0; -+ if (display) -+ q_status_message(SM_ORDER, 0, 1, "Thread already expanded"); -+ } -+ } -+ return rv; -+} -+ -+void -+expand_thread(state, stream, msgmap, display) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+ int display; -+{ -+ int collapsed, rv = 1, done = 0; -+ PINETHRD_S *thrd = NULL; -+ unsigned long orig, orig_top, top; -+ -+ if(!stream) -+ return; -+ -+ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return; -+ -+ while (!done){ -+ expand_this_thread(state, stream, msgmap, display, 1); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); -+} -+ -+ -+void -+cmd_undelete_this_thread(state, stream, msgmap) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+{ -+ unsigned long rawno; -+ int save_kolapsed; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); -+ collapse_this_thread(state, stream, msgmap, 0, 0); -+ thread_command(state, stream, msgmap, 'u', -FOOTER_ROWS(state), 1); -+ if (!save_kolapsed) -+ expand_this_thread(state, stream, msgmap, 0, 0); -+} -+ -+void -+cmd_undelete_thread(state, stream, msgmap) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+{ -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno, top, orig_top; -+ int done = 0, count; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_thread(stream, msgmap, rawno); -+ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return; -+ -+ while (!done){ -+ cmd_undelete_this_thread(state, stream, msgmap); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); -+ count = count_thread(state, stream, msgmap, rawno); -+ q_status_message2(SM_ORDER, 0, 1, "Deletion mark removed from %s message%s", -+ int2string(count), plural(count)); -+} -+ -+void -+kolapse_thread(state, stream, msgmap, ch, display) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+ char ch; -+ int display; -+{ -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno; -+ int rv = 1, done = 0; -+ -+ if(!stream) -+ return; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return; -+ -+ clear_index_cache(stream, 0); -+ mn_set_cur(msgmap,1); /* go to the first message */ -+ while (!done){ -+ if (ch == '[') -+ collapse_thread(state, stream, msgmap, display); -+ else -+ expand_thread(state, stream, msgmap, display); -+ if ((rv = move_next_thread(state, stream, msgmap, 0)) <= 0) -+ done++; -+ } -+ -+ if (rv < 0){ -+ if (display) -+ q_status_message(SM_ORDER, 0, 1, (ch == '[') -+ ? "Error while collapsing thread" -+ : "Error while expanding thread"); -+ } -+ else -+ if(display) -+ q_status_message(SM_ORDER, 0, 1, (ch == '[') -+ ? "All threads collapsed. Use \"}\" to expand them" -+ : "All threads expanded. Use \"{\" to collapse them"); -+ -+ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,rawno))); -+} -+ -+void -+cmd_select_thread(state, stream, msgmap) -+ struct pine *state; -+ MAILSTREAM *stream; -+ MSGNO_S *msgmap; -+{ -+ unsigned long rawno; -+ int save_kolapsed; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ save_kolapsed = thread_is_kolapsed(state, stream, msgmap, rawno); -+ collapse_thread(state, stream, msgmap, 0); -+ thread_command(state, stream, msgmap, ':', -FOOTER_ROWS(state), 1); -+ if (!save_kolapsed) -+ expand_thread(state, stream, msgmap, 0); -+} -+ -Index: alpine-2.20/alpine/mailcmd.h -=================================================================== ---- alpine-2.20.orig/alpine/mailcmd.h -+++ alpine-2.20/alpine/mailcmd.h -@@ -87,7 +87,7 @@ char *broach_folder(int, int, int *, - int ask_mailbox_reopen(struct pine *, int *); - void visit_folder(struct pine *, char *, CONTEXT_S *, MAILSTREAM *, unsigned long); - int select_by_current(struct pine *, MSGNO_S *, CmdWhere); --int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); -+int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int, int); - char **choose_list_of_keywords(void); - char *choose_a_charset(int); - char **choose_list_of_charsets(void); -@@ -105,6 +105,15 @@ int any_selected_callback(int, long) - int flag_callback(int, long); - MPopup *flag_submenu(MESSAGECACHE *); - #endif -- -+void cmd_delete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); -+void cmd_delete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); -+void cmd_undelete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); -+void cmd_undelete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); -+void cmd_select_thread(struct pine *, MAILSTREAM *, MSGNO_S *); -+void kolapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, char, int); -+void collapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+void expand_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+int collapse_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); -+int expand_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); - - #endif /* PINE_MAILCMD_INCLUDED */ -Index: alpine-2.20/alpine/mailindx.c -=================================================================== ---- alpine-2.20.orig/alpine/mailindx.c -+++ alpine-2.20/alpine/mailindx.c -@@ -561,6 +561,7 @@ index_lister(struct pine *state, CONTEXT - - /*---------- Scroll line up ----------*/ - case MC_CHARUP : -+previtem: - (void) process_cmd(state, stream, msgmap, MC_PREVITEM, - (style == MsgIndex - || style == MultiMsgIndex -@@ -578,6 +579,7 @@ index_lister(struct pine *state, CONTEXT - - /*---------- Scroll line down ----------*/ - case MC_CHARDOWN : -+nextitem: - /* - * Special Page framing handling here. If we - * did something that should scroll-by-a-line, frame -@@ -795,6 +797,7 @@ view_a_thread: - - - case MC_THRDINDX : -+mc_thrdindx: - msgmap->top = msgmap->top_after_thrd; - if(unview_thread(state, stream, msgmap)){ - state->next_screen = mail_index_screen; -@@ -845,7 +848,7 @@ view_a_thread: - && mp.col == id.plus_col - && style != ThreadIndex){ - collapse_or_expand(state, stream, msgmap, -- mn_get_cur(msgmap)); -+ mn_get_cur(msgmap), 1); - } - else if (mp.doubleclick){ - if(mp.button == M_BUTTON_LEFT){ -@@ -954,9 +957,105 @@ view_a_thread: - - - case MC_COLLAPSE : -- thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state)); -+ thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state), 1); - break; - -+ case MC_CTHREAD : -+ if (SEP_THRDINDX()) -+ goto mc_thrdindx; -+ else -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, -+ "to collapse a thread")) -+ collapse_thread(state, stream,msgmap, 1); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_OTHREAD : -+ if (SEP_THRDINDX()) -+ goto view_a_thread; -+ else -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to expand a thread")) -+ expand_thread(state, stream,msgmap, 1); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_NEXTHREAD: -+ case MC_PRETHREAD: -+ if (THRD_INDX()){ -+ if (cmd == MC_NEXTHREAD) -+ goto nextitem; -+ else -+ goto previtem; -+ } -+ else -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, -+ "to move to other thread")) -+ move_thread(state, stream, msgmap, -+ cmd == MC_NEXTHREAD ? 1 : -1); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_KOLAPSE: -+ case MC_EXPTHREAD: -+ if (SEP_THRDINDX()){ -+ q_status_message(SM_ORDER, 0, 1, -+ "Command not available in this screen"); -+ } -+ else{ -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, -+ cmd == MC_KOLAPSE ? "to collapse" : "to expand")) -+ kolapse_thread(state, stream, msgmap, -+ (cmd == MC_KOLAPSE) ? '[' : ']', 1); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ } -+ break; -+ -+ case MC_DELTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to delete")) -+ cmd_delete_thread(state, stream, msgmap); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_UNDTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to undelete")) -+ cmd_undelete_thread(state, stream, msgmap); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_SELTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to undelete")) -+ cmd_select_thread(state, stream, msgmap); -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ - case MC_DELETE : - case MC_UNDELETE : - case MC_REPLY : -@@ -977,13 +1076,12 @@ view_a_thread: - if(rawno) - thrd = fetch_thread(stream, rawno); - -- collapsed = thrd && thrd->next -- && get_lflag(stream, NULL, rawno, MN_COLL); -+ collapsed = thread_is_kolapsed(ps_global, stream, msgmap, rawno); - } - - if(collapsed){ - thread_command(state, stream, msgmap, -- ch, -FOOTER_ROWS(state)); -+ ch, -FOOTER_ROWS(state),1); - /* increment current */ - if(cmd == MC_DELETE){ - advance_cur_after_delete(state, stream, msgmap, -@@ -2673,6 +2771,7 @@ top_ent_calc(MAILSTREAM *stream, MSGNO_S - n = mn_raw2m(msgs, thrd->rawno); - - while(thrd){ -+ unsigned long branch; - if(!msgline_hidden(stream, msgs, n, 0) - && (++m % lines_per_page) == 1L) - t = n; -@@ -2741,11 +2840,12 @@ top_ent_calc(MAILSTREAM *stream, MSGNO_S - - /* n is the end of this thread */ - while(thrd){ -+ unsigned long next = 0L, branch = 0L; - n = mn_raw2m(msgs, thrd->rawno); -- if(thrd->branch) -- thrd = fetch_thread(stream, thrd->branch); -- else if(thrd->next) -- thrd = fetch_thread(stream, thrd->next); -+ if(branch = get_branch(stream,thrd)) -+ thrd = fetch_thread(stream, branch); -+ else if(next = get_next(stream,thrd)) -+ thrd = fetch_thread(stream, next); - else - thrd = NULL; - } -@@ -2853,7 +2953,7 @@ warn_other_cmds(void) - - void - thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, -- UCS preloadkeystroke, int q_line) -+ UCS preloadkeystroke, int q_line, int display) - { - PINETHRD_S *thrd = NULL; - unsigned long rawno, save_branch; -@@ -2902,7 +3002,7 @@ thread_command(struct pine *state, MAILS - cancel_busy_cue(0); - - (void ) apply_command(state, stream, msgmap, preloadkeystroke, flags, -- q_line); -+ q_line, display); - - /* restore the original flags */ - copy_lflags(stream, msgmap, MN_STMP, MN_SLCT); -@@ -3396,7 +3496,7 @@ index_sort_callback(set, order) - if(set){ - sort_folder(ps_global->mail_stream, ps_global->msgmap, - order & 0x000000ff, -- (order & 0x00000100) != 0, SRT_VRB); -+ (order & 0x00000100) != 0, SRT_VRB, 1); - mswin_beginupdate(); - update_titlebar_message(); - update_titlebar_status(); -Index: alpine-2.20/alpine/mailindx.h -=================================================================== ---- alpine-2.20.orig/alpine/mailindx.h -+++ alpine-2.20/alpine/mailindx.h -@@ -103,7 +103,7 @@ int truncate_subj_and_from_strings(voi - void paint_index_hline(MAILSTREAM *, long, ICE_S *); - void setup_index_state(int); - void warn_other_cmds(void); --void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int); -+void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); - COLOR_PAIR *apply_rev_color(COLOR_PAIR *, int); - #ifdef _WINDOWS - int index_sort_callback(int, long); -Index: alpine-2.20/alpine/mailview.c -=================================================================== ---- alpine-2.20.orig/alpine/mailview.c -+++ alpine-2.20/alpine/mailview.c -@@ -3369,6 +3369,52 @@ scrolltool(SCROLL_S *sparms) - print_to_printer(sparms); - break; - -+ case MC_NEXTHREAD: -+ case MC_PRETHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, -+ "to move to other thread")) -+ move_thread(ps_global, ps_global->mail_stream, ps_global->msgmap, -+ cmd == MC_NEXTHREAD ? 1 : -1); -+ done = 1; -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_DELTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to delete")) -+ cmd_delete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); -+ done = 1; -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_UNDTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to undelete")) -+ cmd_undelete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); -+ done = 1; -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; -+ -+ case MC_SELTHREAD: -+ if (THREADING()){ -+ if (any_messages(ps_global->msgmap, NULL, "to undelete")) -+ cmd_select_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); -+ done = 1; -+ } -+ else -+ q_status_message(SM_ORDER, 0, 1, -+ "Command available in threaded mode only"); -+ break; - - /* ------- First handle on Line ------ */ - case MC_GOTOBOL : -Index: alpine-2.20/alpine/roleconf.c -=================================================================== ---- alpine-2.20.orig/alpine/roleconf.c -+++ alpine-2.20/alpine/roleconf.c -@@ -4478,11 +4478,11 @@ role_config_edit_screen(struct pine *ps, - ctmp->tool = role_sort_tool; - ctmp->valoffset = rindent; - ctmp->flags |= CF_NOSELECT; -- ctmp->value = cpystr(set_choose); \ -+ ctmp->value = cpystr(set_choose); - - pval = PVAL(&sort_act_var, ew); - if(pval) -- decode_sort(pval, &def_sort, &def_sort_rev); -+ decode_sort(pval, &def_sort, &def_sort_rev, 0); - - /* allow user to set their default sort order */ - new_confline(&ctmp)->var = &sort_act_var; -@@ -4492,7 +4492,7 @@ role_config_edit_screen(struct pine *ps, - ctmp->tool = role_sort_tool; - ctmp->valoffset = rindent; - ctmp->varmem = -1; -- ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0); -+ ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0, 0); - - for(j = 0; j < 2; j++){ - for(i = 0; ps->sort_types[i] != EndofList; i++){ -@@ -4504,7 +4504,7 @@ role_config_edit_screen(struct pine *ps, - ctmp->valoffset = rindent; - ctmp->varmem = i + (j * EndofList); - ctmp->value = generalized_sort_pretty_value(ps, ctmp, -- 0); -+ 0, 0); - } - } - -@@ -5437,7 +5437,7 @@ role_config_edit_screen(struct pine *ps, - (*result)->patgrp->stat_boy = PAT_STAT_EITHER; - - if(sort_act){ -- decode_sort(sort_act, &def_sort, &def_sort_rev); -+ decode_sort(sort_act, &def_sort, &def_sort_rev, 0); - (*result)->action->sort_is_set = 1; - (*result)->action->sortorder = def_sort; - (*result)->action->revsort = (def_sort_rev ? 1 : 0); -Index: alpine-2.20/alpine/setup.c -=================================================================== ---- alpine-2.20.orig/alpine/setup.c -+++ alpine-2.20/alpine/setup.c -@@ -258,7 +258,7 @@ option_screen(struct pine *ps, int edit_ - ctmpa->flags |= CF_NOSELECT; - ctmpa->value = cpystr("--- ----------------------"); - -- decode_sort(pval, &def_sort, &def_sort_rev); -+ decode_sort(pval, &def_sort, &def_sort_rev, 0); - - for(j = 0; j < 2; j++){ - for(i = 0; ps->sort_types[i] != EndofList; i++){ -@@ -273,6 +273,55 @@ option_screen(struct pine *ps, int edit_ - } - } - } -+ else if(vtmp == &ps->vars[V_THREAD_SORT_KEY]){ /* radio case */ -+ SortOrder thread_def_sort; -+ int thread_def_sort_rev, lv; -+ -+ ctmpa->flags |= CF_NOSELECT; -+ ctmpa->keymenu = &config_radiobutton_keymenu; -+ ctmpa->tool = NULL; -+ -+ /* put a nice delimiter before list */ -+ new_confline(&ctmpa)->var = NULL; -+ ctmpa->varnamep = ctmpb; -+ ctmpa->keymenu = &config_radiobutton_keymenu; -+ ctmpa->help = NO_HELP; -+ ctmpa->tool = radiobutton_tool; -+ ctmpa->valoffset = 12; -+ ctmpa->flags |= CF_NOSELECT; -+ ctmpa->value = cpystr("Set Thread Sort Options"); -+ -+ new_confline(&ctmpa)->var = NULL; -+ ctmpa->varnamep = ctmpb; -+ ctmpa->keymenu = &config_radiobutton_keymenu; -+ ctmpa->help = NO_HELP; -+ ctmpa->tool = radiobutton_tool; -+ ctmpa->valoffset = 12; -+ ctmpa->flags |= CF_NOSELECT; -+ ctmpa->value = cpystr("--- ----------------------"); -+ -+ /* find longest value's name */ -+ for(lv = 0, i = 0; ps->sort_types[i] != EndofList; i++) -+ if(lv < (j = strlen(sort_name(ps->sort_types[i])))) -+ lv = j; -+ -+ decode_sort(pval, &thread_def_sort, &thread_def_sort_rev, 1); -+ -+ for(j = 0; j < 2; j++){ -+ for(i = 0; ps->sort_types[i] != EndofList; i++){ -+ if (allowed_thread_key(ps->sort_types[i])){ -+ new_confline(&ctmpa)->var = vtmp; -+ ctmpa->varnamep = ctmpb; -+ ctmpa->keymenu = &config_radiobutton_keymenu; -+ ctmpa->help = config_help(vtmp - ps->vars, 0); -+ ctmpa->tool = radiobutton_tool; -+ ctmpa->valoffset = 12; -+ ctmpa->varmem = i + (j * EndofList); -+ ctmpa->value = pretty_value(ps, ctmpa); -+ } -+ } -+ } -+ } - else if(vtmp == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){ /* yesno case */ - ctmpa->keymenu = &config_yesno_keymenu; - ctmpa->tool = yesno_tool; -@@ -465,6 +514,15 @@ option_screen(struct pine *ps, int edit_ - } - } - -+ pval = PVAL(&ps->vars[V_THREAD_SORT_KEY], ew); -+ if(vsave[V_THREAD_SORT_KEY].saved_user_val.p && pval -+ && strcmp(vsave[V_THREAD_SORT_KEY].saved_user_val.p, pval)){ -+ if(!mn_get_mansort(ps_global->msgmap)){ -+ clear_index_cache(ps_global->mail_stream, 0); -+ reset_sort_order(SRT_VRB); -+ } -+ } -+ - treat_color_vars_as_text = 0; - free_saved_config(ps, &vsave, expose_hidden_config); - #ifdef _WINDOWS -Index: alpine-2.20/pith/conf.c -=================================================================== ---- alpine-2.20.orig/pith/conf.c -+++ alpine-2.20/pith/conf.c -@@ -206,6 +206,8 @@ CONF_TXT_T cf_text_fcc_name_rule[] = "De - - 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_thread_sort_key[] = "#Sets presentation order of threads in thread index. Choices:\n#arrival, and thread."; -+ - 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\"."; -@@ -524,6 +526,8 @@ static struct variable variables[] = { - NULL, cf_text_fcc_name_rule}, - {"sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, - NULL, cf_text_sort_key}, -+{"thread-sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, -+ NULL, cf_text_thread_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, -@@ -1576,7 +1580,7 @@ init_vars(struct pine *ps, void (*cmds_f - 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; -+ obs_save_by_sender, i, def_sort_rev, thread_def_sort_rev; - long rvl; - PINERC_S *fixedprc = NULL; - FeatureLevel obs_feature_level; -@@ -1601,6 +1605,7 @@ init_vars(struct pine *ps, void (*cmds_f - GLO_FEATURE_LEVEL = cpystr("sappling"); - GLO_OLD_STYLE_REPLY = cpystr(DF_OLD_STYLE_REPLY); - GLO_SORT_KEY = cpystr(DF_SORT_KEY); -+ GLO_THREAD_SORT_KEY = cpystr(DF_THREAD_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); -@@ -2517,7 +2522,7 @@ init_vars(struct pine *ps, void (*cmds_f - 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){ -+ if(decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev,0) == -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; -@@ -2526,6 +2531,17 @@ init_vars(struct pine *ps, void (*cmds_f - else - ps->def_sort_rev = def_sort_rev; - -+ set_current_val(&vars[V_THREAD_SORT_KEY], TRUE, TRUE); -+ if(decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, -+ &thread_def_sort_rev, 1) == -1){ -+ sprintf(tmp_20k_buf, "Sort type \"%s\" is invalid", VAR_THREAD_SORT_KEY); -+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf); -+ ps->thread_def_sort = SortThread; -+ ps->thread_def_sort_rev = 0; -+ } -+ else -+ ps->thread_def_sort_rev = thread_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++) -@@ -2949,6 +2965,8 @@ feature_list(int index) - 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}, -+ {"enhanced-fancy-thread-support", "Enhanced Fancy Thread Support", -+ F_ENHANCED_THREAD, h_config_enhanced_thread, PREF_INDX, 0}, - - /* Viewer prefs */ - {"enable-msg-view-addresses", "Enable Message View Address Links", -@@ -7685,6 +7703,8 @@ config_help(int var, int feature) - return(h_config_fcc_rule); - case V_SORT_KEY : - return(h_config_sort_key); -+ case V_THREAD_SORT_KEY : -+ return(h_config_thread_sort_key); - case V_AB_SORT_RULE : - return(h_config_ab_sort_rule); - case V_FLD_SORT_RULE : -Index: alpine-2.20/pith/conf.h -=================================================================== ---- alpine-2.20.orig/pith/conf.h -+++ alpine-2.20/pith/conf.h -@@ -144,6 +144,9 @@ - #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_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].current_val.p -+#define GLO_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].global_val.p -+#define COM_THREAD_SORT_KEY vars[V_THREAD_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 -Index: alpine-2.20/pith/conftype.h -=================================================================== ---- alpine-2.20.orig/pith/conftype.h -+++ alpine-2.20/pith/conftype.h -@@ -59,6 +59,7 @@ typedef enum { V_PERSONAL_NAME = 0 - , V_SAVED_MSG_NAME_RULE - , V_FCC_RULE - , V_SORT_KEY -+ , V_THREAD_SORT_KEY - , V_AB_SORT_RULE - , V_FLD_SORT_RULE - , V_GOTO_DEFAULT_RULE -@@ -511,6 +512,7 @@ typedef enum { - F_QUELL_TIMEZONE, - F_QUELL_USERAGENT, - F_COLOR_LINE_IMPORTANT, -+ F_ENHANCED_THREAD, - F_SLASH_COLL_ENTIRE, - F_ENABLE_FULL_HDR_AND_TEXT, - F_QUELL_FULL_HDR_RESET, -@@ -766,5 +768,6 @@ typedef struct smime_stuff { - - /* exported protoypes */ - -+#define DF_THREAD_SORT_KEY "thread" - - #endif /* PITH_CONFTYPE_INCLUDED */ -Index: alpine-2.20/pith/flag.c -=================================================================== ---- alpine-2.20.orig/pith/flag.c -+++ alpine-2.20/pith/flag.c -@@ -594,14 +594,16 @@ set_lflag(MAILSTREAM *stream, MSGNO_S *m - - was_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0; - -+ thrd = fetch_thread(stream, rawno); -+ - 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) -+ if(top_thread(stream, thrd->top) == thrd->rawno) - topthrd = thrd; - else -- topthrd = fetch_thread(stream, thrd->top); -+ topthrd = fetch_thread(stream, top_thread(stream, thrd->top)); - } - - if(topthrd){ -Index: alpine-2.20/pith/indxtype.h -=================================================================== ---- alpine-2.20.orig/pith/indxtype.h -+++ alpine-2.20/pith/indxtype.h -@@ -76,7 +76,7 @@ typedef enum {iNothing, iStatus, iFStatu - iKey, iKeyInit, - iPrefDate, iPrefTime, iPrefDateTime, - iCurPrefDate, iCurPrefTime, iCurPrefDateTime, -- iSize, iSizeComma, iSizeNarrow, iDescripSize, -+ iSize, iSizeComma, iSizeNarrow, iDescripSize, iSizeThread, - iNewsAndTo, iToAndNews, iNewsAndRecips, iRecipsAndNews, - iFromTo, iFromToNotNews, iFrom, iTo, iSender, iCc, iNews, iRecips, - iCurNews, iArrow, -Index: alpine-2.20/pith/mailindx.c -=================================================================== ---- alpine-2.20.orig/pith/mailindx.c -+++ alpine-2.20/pith/mailindx.c -@@ -228,6 +228,7 @@ init_index_format(char *format, INDEX_CO - case iSTime: - case iKSize: - case iSize: -+ case iSizeThread: - case iPrioAlpha: - (*answer)[column].req_width = 7; - break; -@@ -452,6 +453,7 @@ static INDEX_PARSE_T itokens[] = { - {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX}, - {"SIZE", iSize, FOR_INDEX}, - {"SIZECOMMA", iSizeComma, FOR_INDEX}, -+ {"SIZETHREAD", iSizeThread, FOR_INDEX}, - {"SIZENARROW", iSizeNarrow, FOR_INDEX}, - {"KSIZE", iKSize, FOR_INDEX}, - {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, -@@ -943,7 +945,7 @@ static IndexColType fixed_ctypes[] = { - iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4, - iSDateTimeIso24, iSDateTimeIsoS24, - iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424, -- iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, -+ iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, iSizeThread, - iPrio, iPrioBang, iPrioAlpha, iInit, - iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit, - iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek -@@ -1136,6 +1138,7 @@ setup_index_header_widths(MAILSTREAM *st - case iTime12: - case iSize: - case iKSize: -+ case iSizeThread: - cdesc->actual_length = 7; - cdesc->adjustment = Right; - break; -@@ -1229,7 +1232,7 @@ setup_index_header_widths(MAILSTREAM *st - cdesc->ctype != iNothing; - cdesc++) - if(cdesc->ctype == iSize || cdesc->ctype == iKSize || -- cdesc->ctype == iSizeNarrow || -+ cdesc->ctype == iSizeNarrow || cdesc->ctype == iSizeThread || - cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){ - if(cdesc->actual_length == 0){ - if((fix=cdesc->width) > 0){ /* had this reserved */ -@@ -1612,10 +1615,12 @@ build_header_work(struct pine *state, MA - - /* find next thread which is visible */ - do{ -+ unsigned long branch; - 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); -+ /*branch = get_branch(stream,thrd)*/ -+ else if(!mn_get_revsort(msgmap) && thrd->branch) -+ thrd = fetch_thread(stream, thrd->branch); - else - thrd = NULL; - } while(thrd -@@ -2027,13 +2032,10 @@ format_index_index_line(INDEXDATA_S *ida - */ - ice = copy_ice(ice); - -+ thrd = fetch_thread(idata->stream, idata->rawno); - /* 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); -- } -+ if(!idata->bogus && THREADING()) -+ collapsed = thrd && thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno); - - /* calculate contents of the required fields */ - for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) -@@ -2531,7 +2533,30 @@ format_index_index_line(INDEXDATA_S *ida - - break; - -+ case iSizeThread: -+ if (!THREADING()){ -+ goto getsize; -+ } else if (collapsed){ -+ l = count_flags_in_thread(idata->stream, thrd, F_NONE); -+ snprintf(str, sizeof(str), "(%lu)", l); -+ } -+ else{ -+ thrd = fetch_thread(idata->stream, idata->rawno); -+ if(!thrd) -+ snprintf(str, sizeof(str), "%s", "Error"); -+ else{ -+ long lengthb; -+ lengthb = get_length_branch(idata->stream, idata->rawno); -+ if (lengthb > 0L) -+ snprintf(str, sizeof(str), "(%lu)", lengthb); -+ else -+ snprintf(str,sizeof(str), "%s", " "); -+ } -+ } -+ break; -+ - case iSize: -+getsize: - /* 0 ... 9999 */ - if((l = fetch_size(idata)) < 10*1000L) - snprintf(str, sizeof(str), "(%lu)", l); -@@ -5413,10 +5438,8 @@ subj_str(INDEXDATA_S *idata, char *str, - - 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)); -+ this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && -+ (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); - - /* - * width is < available strsize and -@@ -6044,11 +6067,8 @@ from_str(IndexColType ctype, INDEXDATA_S - 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)); -- -+ this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && -+ (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); - fptr = str; - - if(thd) -Index: alpine-2.20/pith/pattern.c -=================================================================== ---- alpine-2.20.orig/pith/pattern.c -+++ alpine-2.20/pith/pattern.c -@@ -1756,7 +1756,7 @@ parse_action_slash(char *str, ACTION_S * - SortOrder def_sort; - int def_sort_rev; - -- if(decode_sort(p, &def_sort, &def_sort_rev) != -1){ -+ if(decode_sort(p, &def_sort, &def_sort_rev, 0) != -1){ - action->sort_is_set = 1; - action->sortorder = def_sort; - action->revsort = (def_sort_rev ? 1 : 0); -Index: alpine-2.20/pith/pine.hlp -=================================================================== ---- alpine-2.20.orig/pith/pine.hlp -+++ alpine-2.20/pith/pine.hlp -@@ -3866,6 +3866,7 @@ There are also additional details on -

  • OPTION: -
  • OPTION: -
  • OPTION: -+
  • OPTION: -
  • OPTION: -
  • OPTION: -
  • OPTION: -@@ -5802,6 +5803,163 @@ the names of the carbon copy addresses o - <End of help on this topic> - - -+======= h_thread_index_sort_arrival ======= -+ -+ -+SORT OPTION: Arrival -+ -+ -+

    SORT OPTION: Arrival

    -+ -+The Arrival sort option arranges threads according to the last -+time that a message was added to it. In this order the last thread -+contains the most recent message in the folder. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_date ======= -+ -+ -+SORT OPTION: Date -+ -+ -+

    SORT OPTION: Date

    -+ -+The Date sort option in the THREAD INDEX screen sorts -+threads by the date in which messages were sent. The thread containing the -+last message in this order is displayed last. -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_subj ======= -+ -+ -+SORT OPTION: Subject -+ -+ -+

    SORT OPTION: Subject

    -+ -+The Subject sort option has not been defined yet. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_ordsubj ======= -+ -+ -+SORT OPTION: OrderedSubject -+ -+ -+

    SORT OPTION: OrderedSubject

    -+ -+The OrderedSubject sort option in the THREAD INDEX screen is -+the same as sorting by Subject. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_thread ======= -+ -+ -+SORT OPTION: Thread -+ -+ -+

    SORT OPTION: Thread

    -+ -+The Thread sort option in the THREAD INDEX screen sorts all -+messages by the proposed algorithm by Crispin and Murchison. In this -+method of sorting once threads have been isolated they are sorted by the -+date of their parents, or if that is missing, the first message in that -+thread. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_from ======= -+ -+ -+SORT OPTION: From -+ -+ -+

    SORT OPTION: From

    -+ -+The From sort option has not been defined yet. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_size ======= -+ -+ -+SORT OPTION: Size -+ -+ -+

    SORT OPTION: Size

    -+ -+The Size sort option sorts threads by their size (the number -+of messages in the thread). This could be used to find conversations -+where no reply has been sent by any of the participants in the thread -+(e.g. those whose length is equal to one). Longer threads appear -+below shorter ones. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_score ======= -+ -+ -+SORT OPTION: Score -+ -+ -+

    SORT OPTION: Score

    -+ -+The Score sort option means that threads are sorted according to -+the maximum score of a message in that thread. A thread all of whose -+messages contain a smaller score than a message in some other thread is -+placed in an earlier place in the list of messages for that folder; that -+is, threads with the highest scores appear at the bottom of the index -+list. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_to ======= -+ -+ -+SORT OPTION: To -+ -+ -+

    SORT OPTION: To

    -+ -+The To sort option has not been defined yet. -+ -+

    -+<End of help on this topic> -+ -+ -+======= h_thread_index_sort_cc ======= -+ -+ -+SORT OPTION: Cc -+ -+ -+

    SORT OPTION: Cc

    -+ -+The Cc sort option has not been defined yet. -+ -+

    -+<End of help on this topic> -+ -+ - ======= h_index_cmd_whereis ======= - - -@@ -19184,6 +19342,14 @@ The progression of sizes used looks like -

    - - -+

    SIZETHREAD
    -+
    -+This token represents the total size of the thread for a collapsed thread -+or the size of the branch for an expanded thread. The field is omitted for -+messages that are not top of threads nor branches and it defaults to -+the SIZE token when your folders is not sorted by thread. -+
    -+ -
    SIZENARROW
    -
    - This token represents the total size, in bytes, of the message. -@@ -22655,6 +22821,45 @@ command, then it will not be re-sorted u - <End of help on this topic> - - -+====== h_config_thread_sort_key ===== -+ -+ -+OPTION: <!--#echo var="VAR_thread-sort-key--> -+ -+ -+

    OPTION:

    -+ -+This option determines the order in which threads will be displayed. You -+can choose from the options listed below. Each folder is sorted in one of -+the sort orders displayed below first, then the thread containing the last -+message of that sorted list is put at the end of the index. All messages -+of that thread are "removed" from the sorted list and the -+process is repeated with the remaining messages in that list. -+ -+

    -+

    -+ -+

    Each type of sort may also be reversed. Normal default is by -+"Thread". -+ -+

    -+

    -+<End of help on this topic> -+ -+ - ====== h_config_other_startup ===== - - -@@ -30485,6 +30690,23 @@ Reply Use, Forward Use, and Compose Use. - <End of help on this topic> - - -+====== h_config_enhanced_thread ===== -+ -+ -+FEATURE: <!--#echo var="FEAT_enhanced-fancy-thread-support"--> -+ -+ -+

    FEATURE:

    -+ -+If this option is set certain commands in Pine will operate in loose -+threads too. For example, the command ^R marks a thread deleted, but if -+this feature is set, it will remove all threads that share the same missing -+parent with this thread. -+ -+

    -+<End of help on this topic> -+ -+ - ====== h_config_news_cross_deletes ===== - - -Index: alpine-2.20/pith/sort.c -=================================================================== ---- alpine-2.20.orig/pith/sort.c -+++ alpine-2.20/pith/sort.c -@@ -91,7 +91,7 @@ Args: msgmap -- - ----*/ - void - sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort, -- int new_rev, unsigned int flags) -+ int new_rev, unsigned int flags, int first) - { - long raw_current, i, j; - unsigned long *sort = NULL; -@@ -101,6 +101,15 @@ sort_folder(MAILSTREAM *stream, MSGNO_S - int current_rev; - MESSAGECACHE *mc; - -+ if (first){ -+ if (new_sort == SortThread) -+ find_msgmap(stream, msgmap, flags, -+ ps_global->thread_cur_sort, new_rev); -+ else -+ sort_folder(stream, msgmap, new_sort, new_rev, flags, 0); + else if(var == &ps->vars[V_THREAD_MORE_CHAR] || + var == &ps->vars[V_THREAD_EXP_CHAR] || + var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){ +diff -rc alpine-2.21/alpine/confscroll.h alpine-2.21.fancy/alpine/confscroll.h +*** alpine-2.21/alpine/confscroll.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/confscroll.h Sun Feb 5 16:15:20 2017 +*************** +*** 97,103 **** + int radiobutton_tool(struct pine *, int, CONF_S **, unsigned); + int yesno_tool(struct pine *, int, CONF_S **, unsigned); + int text_toolit(struct pine *, int, CONF_S **, unsigned, int); +! char *generalized_sort_pretty_value(struct pine *, CONF_S *, int); + int exclude_config_var(struct pine *, struct variable *, int); + int config_exit_cmd(unsigned); + int simple_exit_cmd(unsigned); +--- 97,103 ---- + int radiobutton_tool(struct pine *, int, CONF_S **, unsigned); + int yesno_tool(struct pine *, int, CONF_S **, unsigned); + int text_toolit(struct pine *, int, CONF_S **, unsigned, int); +! char *generalized_sort_pretty_value(struct pine *, CONF_S *, int, int); + int exclude_config_var(struct pine *, struct variable *, int); + int config_exit_cmd(unsigned); + int simple_exit_cmd(unsigned); +diff -rc alpine-2.21/alpine/keymenu.c alpine-2.21.fancy/alpine/keymenu.c +*** alpine-2.21/alpine/keymenu.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/keymenu.c Sun Feb 5 16:15:20 2017 +*************** +*** 650,659 **** + RCOMPOSE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU, +! NULL_MENU, + /* TRANSLATORS: toggles a collapsed view or an expanded view + of a message thread on and off */ + {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, + {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, + NULL_MENU}; + INST_KEY_MENU(index_keymenu, index_keys); +--- 650,674 ---- + RCOMPOSE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU, +! {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, + /* TRANSLATORS: toggles a collapsed view or an expanded view + of a message thread on and off */ + {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, ++ /* TRANSLATORS: Collapse all threads */ ++ {"{",N_("Collapse All"),{MC_KOLAPSE,1,{'{'}},KS_NONE}, ++ /* TRANSLATORS: Expand all threads */ ++ {"}",N_("Expand All"), {MC_EXPTHREAD,1,{'}'}},KS_NONE}, ++ ++ HELP_MENU, ++ OTHER_MENU, ++ {")","Next Threa",{MC_NEXTHREAD,1,{')'}},KS_NONE}, ++ {"(","Prev Threa",{MC_PRETHREAD,1,{'('}},KS_NONE}, ++ {"^R","Remove Thr",{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, ++ {"^U","Undel Thre",{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, ++ {"^T","Select Thr",{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, ++ NULL_MENU, ++ {"[","Close Thre",{MC_CTHREAD,1,{'['}},KS_NONE}, ++ {"]","Open Threa",{MC_OTHREAD,1,{']'}},KS_NONE}, + {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, + NULL_MENU}; + INST_KEY_MENU(index_keymenu, index_keys); +*************** +*** 728,736 **** + RCOMPOSE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU, +! NULL_MENU, + {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, + {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, + NULL_MENU}; + INST_KEY_MENU(thread_keymenu, thread_keys); + +--- 743,764 ---- + RCOMPOSE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU, +! {"]",N_("Open Thread"),{MC_OTHREAD,1,{']'}},KS_NONE}, + {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, ++ {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, ++ {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, ++ ++ HELP_MENU, ++ OTHER_MENU, + {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, ++ NULL_MENU, ++ {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, ++ {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, ++ {"^T",N_("SelecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, + NULL_MENU}; + INST_KEY_MENU(thread_keymenu, thread_keys); + +*************** +*** 898,904 **** + NULL_MENU, + NULL_MENU, + NULL_MENU, +! NULL_MENU}; + INST_KEY_MENU(view_keymenu, view_keys); + + +--- 926,945 ---- + NULL_MENU, + NULL_MENU, + NULL_MENU, +! NULL_MENU, +! +! HELP_MENU, +! OTHER_MENU, +! NULL_MENU, +! NULL_MENU, +! NULL_MENU, +! NULL_MENU, +! NULL_MENU, +! {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, +! {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, +! {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, +! {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, +! {"^T",N_("selecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}}; + INST_KEY_MENU(view_keymenu, view_keys); + + +diff -rc alpine-2.21/alpine/keymenu.h alpine-2.21.fancy/alpine/keymenu.h +*** alpine-2.21/alpine/keymenu.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/keymenu.h Sun Feb 5 16:15:20 2017 +*************** +*** 215,220 **** +--- 215,233 ---- + #define MC_DECRYPT 802 + #define MC_QUOTA 803 + #define MC_ADDHEADER 804 ++ #define MC_DELTHREAD 805 ++ #define MC_UNDTHREAD 806 ++ #define MC_SELTHREAD 807 ++ #define MC_SSUTHREAD 808 ++ #define MC_DSUTHREAD 809 ++ #define MC_USUTHREAD 810 ++ #define MC_SORTHREAD 811 ++ #define MC_NEXTHREAD 812 ++ #define MC_KOLAPSE 813 ++ #define MC_EXPTHREAD 814 ++ #define MC_PRETHREAD 815 ++ #define MC_CTHREAD 816 ++ #define MC_OTHREAD 817 + + + /* Commands for S/MIME screens */ +diff -rc alpine-2.21/alpine/mailcmd.c alpine-2.21.fancy/alpine/mailcmd.c +*** alpine-2.21/alpine/mailcmd.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/mailcmd.c Sun Feb 5 16:15:20 2017 +*************** +*** 113,119 **** + char *choose_a_rule(int); + int select_by_keyword(MAILSTREAM *, SEARCHSET **); + char *choose_a_keyword(void); +! int select_sort(struct pine *, int, SortOrder *, int *); + int print_index(struct pine *, MSGNO_S *, int); + + /* +--- 113,119 ---- + char *choose_a_rule(int); + int select_by_keyword(MAILSTREAM *, SEARCHSET **); + char *choose_a_keyword(void); +! int select_sort(struct pine *, int, SortOrder *, int *, int); + int print_index(struct pine *, MSGNO_S *, int); + + /* +*************** +*** 1383,1389 **** + if(any_messages(msgmap, NULL, NULL)){ + if(any_lflagged(msgmap, MN_SLCT) > 0L){ + if(apply_command(state, stream, msgmap, 0, +! AC_NONE, question_line)){ + if(F_ON(F_AUTO_UNSELECT, state)){ + agg_select_all(stream, msgmap, NULL, 0); + unzoom_index(state, stream, msgmap); +--- 1383,1389 ---- + if(any_messages(msgmap, NULL, NULL)){ + if(any_lflagged(msgmap, MN_SLCT) > 0L){ + if(apply_command(state, stream, msgmap, 0, +! AC_NONE, question_line, 1)){ + if(F_ON(F_AUTO_UNSELECT, state)){ + agg_select_all(stream, msgmap, NULL, 0); + unzoom_index(state, stream, msgmap); +*************** +*** 1401,1423 **** + + /*-------- Sort command -------*/ + case MC_SORT : + { + int were_threading = THREADING(); + SortOrder sort = mn_get_sort(msgmap); + int rev = mn_get_revsort(msgmap); + + dprint((1,"MAIL_CMD: sort\n")); +! if(select_sort(state, question_line, &sort, &rev)){ + /* $ command reinitializes threading collapsed/expanded info */ + if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) + erase_threading_info(stream, msgmap); + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + +! sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN); + } + + state->mangled_footer = 1; +--- 1401,1435 ---- + + /*-------- Sort command -------*/ + case MC_SORT : ++ case MC_SORTHREAD: + { + int were_threading = THREADING(); + SortOrder sort = mn_get_sort(msgmap); + int rev = mn_get_revsort(msgmap); ++ int thread = (command == MC_SORT) ? 0 : 1; + + dprint((1,"MAIL_CMD: sort\n")); +! if(sort == SortThread) +! sort = ps_global->thread_cur_sort; +! if(select_sort(state, question_line, &sort, &rev, thread)){ + /* $ command reinitializes threading collapsed/expanded info */ + if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) + erase_threading_info(stream, msgmap); + ++ if(command == MC_SORTHREAD){ ++ ps_global->thread_cur_sort = sort; ++ sort = SortThread; ++ } ++ else if(sort == SortThread) /* command = MC_SORT */ ++ ps_global->thread_cur_sort = F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) ++ ? SortArrival : ps_global->thread_def_sort; ++ + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + +! sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN, 1); + } + + state->mangled_footer = 1; +*************** +*** 3262,3267 **** +--- 3274,3283 ---- + if(SORT_IS_THREADED(msgmap)) + refresh_sort(stream, msgmap, SRT_NON); + ++ if (msgmap->nmsgs ++ && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) ++ kolapse_thread(state, stream, msgmap, '[', 0); ++ + state->mangled_body = 1; + state->mangled_header = 1; + q_status_message2(SM_ORDER, 0, 4, +*************** +*** 3363,3368 **** +--- 3379,3387 ---- + */ + if(SORT_IS_THREADED(msgmap)) + refresh_sort(stream, msgmap, SRT_NON); ++ if (msgmap->nmsgs ++ && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) ++ kolapse_thread(state, stream, msgmap, '[', 0); + } + else{ + if(del_count){ +*************** +*** 7222,7228 **** + * Maybe it makes sense to zoom after a select but not after a colon + * command even though they are very similar. + */ +! thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state)); + } + else{ + if((all_selected = +--- 7241,7247 ---- + * Maybe it makes sense to zoom after a select but not after a colon + * command even though they are very similar. + */ +! thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state), 1); + } + else{ + if((all_selected = +*************** +*** 7278,7284 **** + ----*/ + int + apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, +! UCS preloadkeystroke, int flags, int q_line) + { + int i = 8, /* number of static entries in sel_opts3 */ + rv = 0, +--- 7297,7303 ---- + ----*/ + int + apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, +! UCS preloadkeystroke, int flags, int q_line, int display) + { + int i = 8, /* number of static entries in sel_opts3 */ + rv = 0, +*************** +*** 7445,7453 **** + collapse_or_expand(state, stream, msgmap, + F_ON(F_SLASH_COLL_ENTIRE, ps_global) + ? 0L +! : mn_get_cur(msgmap)); + break; + + case ':' : + select_thread_stmp(state, stream, msgmap); + break; +--- 7464,7482 ---- + collapse_or_expand(state, stream, msgmap, + F_ON(F_SLASH_COLL_ENTIRE, ps_global) + ? 0L +! : mn_get_cur(msgmap), +! display); + break; + ++ case '[' : ++ collapse_this_thread(state, stream, msgmap, display, 0); ++ break; ++ ++ case ']' : ++ expand_this_thread(state, stream, msgmap, display, 0); ++ break; ++ ++ + case ':' : + select_thread_stmp(state, stream, msgmap); + break; +*************** +*** 9426,9435 **** + Returns 0 if it was cancelled, 1 otherwise. + ----*/ + int +! select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) + { + char prompt[200], tmp[3], *p; +! int s, i; + int deefault = 'a', retval = 1; + HelpType help; + ESCKEY_S sorts[14]; +--- 9455,9464 ---- + Returns 0 if it was cancelled, 1 otherwise. + ----*/ + int +! select_sort(struct pine *state, int ql, SortOrder *sort, int *rev, int thread) + { + char prompt[200], tmp[3], *p; +! int s, i, j; + int deefault = 'a', retval = 1; + HelpType help; + ESCKEY_S sorts[14]; +*************** +*** 9462,9478 **** + strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "), + sizeof(prompt)); + +! for(i = 0; state->sort_types[i] != EndofList; i++) { +! sorts[i].rval = i; +! p = sorts[i].label = sort_name(state->sort_types[i]); +! while(*(p+1) && islower((unsigned char)*p)) +! p++; +! +! sorts[i].ch = tolower((unsigned char)(tmp[0] = *p)); +! sorts[i].name = cpystr(tmp); +! +! if(mn_get_sort(state->msgmap) == state->sort_types[i]) +! deefault = sorts[i].rval; + } + + sorts[i].ch = 'r'; +--- 9491,9516 ---- + strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "), + sizeof(prompt)); + +! for(i = 0, j = 0; state->sort_types[i] != EndofList; i++) { +! sorts[i].rval = i; +! sorts[i].name = cpystr(""); +! sorts[i].label = ""; +! sorts[i].ch = -2; +! if (!thread || allowed_thread_key(state->sort_types[i])){ +! p = sorts[j].label = sort_name(state->sort_types[i]); +! while(*(p+1) && islower((unsigned char)*p)) +! p++; +! sorts[j].ch = tolower((unsigned char)(tmp[0] = *p)); +! sorts[j++].name = cpystr(tmp); +! } +! +! if (thread){ +! if (state->thread_def_sort == state->sort_types[i]) +! deefault = sorts[j-1].rval; +! } +! else +! if(mn_get_sort(state->msgmap) == state->sort_types[i]) +! deefault = sorts[i].rval; + } + + sorts[i].ch = 'r'; +*************** +*** 9496,9503 **** + state->mangled_body = 1; /* signal screen's changed */ + if(s == 'r') + *rev = !mn_get_revsort(state->msgmap); +! else + *sort = state->sort_types[s]; + + if(F_ON(F_SHOW_SORT, ps_global)) + ps_global->mangled_header = 1; +--- 9534,9550 ---- + state->mangled_body = 1; /* signal screen's changed */ + if(s == 'r') + *rev = !mn_get_revsort(state->msgmap); +! else{ +! if(thread){ +! for(i = 0; state->sort_types[i] != EndofList; i++){ +! if(struncmp(sort_name(state->sort_types[i]), +! sorts[s].label, strlen(sorts[s].label)) == 0) +! break; +! } +! s = i; +! } + *sort = state->sort_types[s]; ++ } + + if(F_ON(F_SHOW_SORT, ps_global)) + ps_global->mangled_header = 1; +*************** +*** 9882,9884 **** +--- 9929,10306 ---- + } + + #endif /* _WINDOWS */ ++ ++ void ++ cmd_delete_this_thread(state, stream, msgmap) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ { ++ unsigned long rawno, top, save_kolapsed; ++ PINETHRD_S *thrd = NULL, *nxthrd; ++ ++ if(!stream) + return; -+ } -+ - dprint((2, "Sorting by %s%s\n", - sort_name(new_sort), new_rev ? "/reverse" : "")); - -@@ -530,20 +539,20 @@ percent_sorted(void) - * argument also means arrival/reverse. - */ - int --decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev) -+decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev, int thread) - { - char *sep; - char *fix_this = NULL; -- int x, reverse; -+ int x = 0, reverse; - - if(!sort_spec || !*sort_spec){ -- *def_sort = SortArrival; -+ *def_sort = thread ? SortThread : SortArrival; - *def_sort_rev = 0; - return(0); - } - - if(struncmp(sort_spec, "reverse", strlen(sort_spec)) == 0){ -- *def_sort = SortArrival; -+ *def_sort = thread ? SortThread : SortArrival; - *def_sort_rev = 1; - return(0); - } -@@ -572,7 +581,7 @@ decode_sort(char *sort_spec, SortOrder * - if(ps_global->sort_types[x] == EndofList) - return(-1); - -- *def_sort = ps_global->sort_types[x]; -+ *def_sort = ps_global->sort_types[x]; - *def_sort_rev = reverse; - return(0); - } -@@ -689,7 +698,9 @@ reset_sort_order(unsigned int flags) - - /* set default order */ - the_sort_order = ps_global->def_sort; -- sort_is_rev = ps_global->def_sort_rev; -+ sort_is_rev = the_sort_order == SortThread -+ ? (ps_global->thread_def_sort_rev + ps_global->def_sort_rev) % 2 -+ : ps_global->def_sort_rev; - - if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ - for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ -@@ -702,9 +713,15 @@ reset_sort_order(unsigned int flags) - && pat->action->sort_is_set){ - the_sort_order = pat->action->sortorder; - sort_is_rev = pat->action->revsort; -+ sort_is_rev = the_sort_order == SortThread -+ ? (ps_global->thread_def_sort_rev + pat->action->revsort) % 2 -+ : pat->action->revsort; - } - } - -+ if(the_sort_order == SortThread && !(flags & SRT_MAN)) -+ ps_global->thread_cur_sort = ps_global->thread_def_sort; -+ - sort_folder(ps_global->mail_stream, ps_global->msgmap, -- the_sort_order, sort_is_rev, flags); -+ the_sort_order, sort_is_rev, flags, 1); - } -Index: alpine-2.20/pith/sort.h -=================================================================== ---- alpine-2.20.orig/pith/sort.h -+++ alpine-2.20/pith/sort.h -@@ -22,7 +22,7 @@ - - - #define refresh_sort(S,M,F) sort_folder((S), (M), mn_get_sort(M), \ -- mn_get_revsort(M), (F)) -+ mn_get_revsort(M), (F), 1) - - struct global_sort_data { - MSGNO_S *msgmap; -@@ -41,8 +41,8 @@ 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 sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned, int); -+int decode_sort(char *, SortOrder *, int *, int); - void reset_sort_order(unsigned); - - -Index: alpine-2.20/pith/state.c -=================================================================== ---- alpine-2.20.orig/pith/state.c -+++ alpine-2.20/pith/state.c -@@ -74,6 +74,7 @@ new_pine_struct(void) - - p = (struct pine *)fs_get(sizeof (struct pine)); - memset((void *) p, 0, sizeof(struct pine)); -+ p->thread_def_sort = SortDate; - p->def_sort = SortArrival; - p->sort_types[0] = SortSubject; - p->sort_types[1] = SortArrival; -Index: alpine-2.20/pith/state.h -=================================================================== ---- alpine-2.20.orig/pith/state.h -+++ alpine-2.20/pith/state.h -@@ -137,6 +137,8 @@ struct pine { - 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 thread_def_sort_rev:1; /* true if reverse sort is default in thread screen */ -+ unsigned msgmap_thread_def_sort_rev:1; /* true if reverse sort is being used in thread screen */ - unsigned restricted:1; - - unsigned tcptimeout:1; /* a tcp timeout is in progress */ -@@ -291,6 +293,9 @@ struct pine { - EditWhich ew_for_srch_take; - - SortOrder def_sort, /* Default sort type */ -+ thread_def_sort, /* Default Sort Type in Thread Screen */ -+ thread_cur_sort, /* current sort style for threads */ -+ msgmap_thread_sort, - sort_types[22]; - - int preserve; -Index: alpine-2.20/pith/thread.c -=================================================================== ---- alpine-2.20.orig/pith/thread.c -+++ alpine-2.20/pith/thread.c -@@ -30,12 +30,18 @@ static char rcsid[] = "$Id: thread.c 942 - #include "../pith/mailcmd.h" - #include "../pith/ablookup.h" - -+static int erase_thread_info = 1; -+ -+typedef struct sizethread_t { -+ int count; -+ long pos; -+} SIZETHREAD_T; - - /* - * Internal prototypes - */ - long *sort_thread_flatten(THREADNODE *, MAILSTREAM *, long *, -- char *, long, PINETHRD_S *, unsigned); -+ char *, long, PINETHRD_S *, unsigned, int, long, long); - void make_thrdflags_consistent(MAILSTREAM *, MSGNO_S *, PINETHRD_S *, int); - THREADNODE *collapse_threadnode_tree(THREADNODE *); - THREADNODE *collapse_threadnode_tree_sorted(THREADNODE *); -@@ -43,6 +49,7 @@ THREADNODE *sort_threads_and_collapse( - THREADNODE *insert_tree_in_place(THREADNODE *, THREADNODE *); - unsigned long branch_greatest_num(THREADNODE *, int); - long calculate_visible_threads(MAILSTREAM *); -+int pine_compare_size_thread(const qsort_t *, const qsort_t *); - - - PINETHRD_S * -@@ -95,20 +102,22 @@ void - set_flags_for_thread(MAILSTREAM *stream, MSGNO_S *msgmap, int f, PINETHRD_S *thrd, int v) - { - PINETHRD_S *nthrd, *bthrd; -+ unsigned long next = 0L, branch = 0L; - - 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(next = get_next(stream,thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - set_flags_for_thread(stream, msgmap, f, nthrd, v); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ -+ if(branch = get_branch(stream, thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - set_flags_for_thread(stream, msgmap, f, bthrd, v); - } -@@ -122,7 +131,7 @@ erase_threading_info(MAILSTREAM *stream, - MESSAGECACHE *mc; - PINELT_S *peltp; - -- if(!(stream && stream->spare)) -+ if(!(stream && stream->spare) || !erase_thread_info) - return; - - ps_global->view_skipped_index = 0; -@@ -155,7 +164,7 @@ sort_thread_callback(MAILSTREAM *stream, - PINETHRD_S *thrd = NULL; - unsigned long msgno, rawno; - int un_view_thread = 0; -- long raw_current; -+ long raw_current, branch; - char *dup_chk = NULL; - - -@@ -168,10 +177,11 @@ sort_thread_callback(MAILSTREAM *stream, - * 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); -+ collapsed_tree = F_ON(F_ENHANCED_THREAD, ps_global) -+ ? copy_tree(tree) -+ : (F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) -+ ? collapse_threadnode_tree_sorted(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)); -@@ -182,7 +192,7 @@ sort_thread_callback(MAILSTREAM *stream, - (void) sort_thread_flatten(collapsed_tree, stream, - &g_sort.msgmap->sort[1], - dup_chk, mn_get_nmsgs(g_sort.msgmap), -- NULL, THD_TOP); -+ NULL, THD_TOP, 0, 1L, 0L); - - /* reset the inverse array */ - msgno_reset_isort(g_sort.msgmap); -@@ -340,12 +350,14 @@ sort_thread_callback(MAILSTREAM *stream, - else{ - thrd = fetch_head_thread(stream); - while(thrd){ -+ unsigned long raw = thrd->rawno; -+ unsigned long top = top_thread(stream, raw); - /* - * 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(msgno && !get_lflag(stream, NULL,thrd->rawno, MN_COLL)) -+ set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0); - - if(thrd->next){ - PINETHRD_S *nthrd; -@@ -359,9 +371,10 @@ sort_thread_callback(MAILSTREAM *stream, - MN_COLL)); - } - -- if(thrd->nextthd) -- thrd = fetch_thread(stream, thrd->nextthd); -- else -+ while (thrd && top_thread(stream, thrd->rawno) == top -+ && thrd->nextthd) -+ thrd = fetch_thread(stream, thrd->nextthd); -+ if (!(thrd && thrd->nextthd)) - thrd = NULL; - } - } -@@ -412,7 +425,7 @@ make_thrdflags_consistent(MAILSTREAM *st - int a_parent_is_collapsed) - { - PINETHRD_S *nthrd, *bthrd; -- unsigned long msgno; -+ unsigned long msgno, next, branch; - - if(!thrd) - return; -@@ -430,8 +443,8 @@ make_thrdflags_consistent(MAILSTREAM *st - set_lflag(stream, msgmap, msgno, MN_CHID, 0); - } - -- if(thrd->next){ -- nthrd = fetch_thread(stream, thrd->next); -+ if(next = get_next(stream, thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - make_thrdflags_consistent(stream, msgmap, nthrd, - a_parent_is_collapsed -@@ -440,8 +453,8 @@ make_thrdflags_consistent(MAILSTREAM *st - MN_COLL)); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ if(branch = get_branch(stream, thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - make_thrdflags_consistent(stream, msgmap, bthrd, - a_parent_is_collapsed); -@@ -488,9 +501,10 @@ calculate_visible_threads(MAILSTREAM *st - long * - sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream, - long *entry, char *dup_chk, long maxno, -- PINETHRD_S *thrd, unsigned int flags) -+ PINETHRD_S *thrd, unsigned int flags, -+ int adopted, long top, long threadno) - { -- PINETHRD_S *newthrd = NULL; -+ PINETHRD_S *newthrd = NULL, *save_thread = NULL; - - if(node){ - if(node->num > 0L && node->num <= maxno){ /* holes happen */ -@@ -498,6 +512,9 @@ sort_thread_flatten(THREADNODE *node, MA - *entry = node->num; - dup_chk[node->num] = 1; - -+ if(adopted == 2) -+ top = node->num; -+ - /* - * Build a richer threading structure that will help us paint - * and operate on threads and subthreads. -@@ -506,20 +523,51 @@ sort_thread_flatten(THREADNODE *node, MA - if(newthrd){ - entry++; - -+ if(adopted == 2) -+ threadno = newthrd->thrdno; -+ if(adopted){ -+ newthrd->toploose = top; -+ newthrd->thrdno = threadno; -+ } -+ adopted = adopted ? 1 : 0; - if(node->next) - entry = sort_thread_flatten(node->next, stream, - entry, dup_chk, maxno, -- newthrd, THD_NEXT); -+ newthrd, THD_NEXT, adopted, top, threadno); - - if(node->branch) - entry = sort_thread_flatten(node->branch, stream, - entry, dup_chk, maxno, - newthrd, -- (flags == THD_TOP) ? THD_TOP -- : THD_BRANCH); -+ ((flags == THD_TOP) ? THD_TOP -+ : THD_BRANCH), -+ adopted, top, threadno); - } - } - } -+ else{ -+ adopted = 2; -+ if(node->next) -+ entry = sort_thread_flatten(node->next, stream, entry, dup_chk, -+ maxno, thrd, THD_TOP, adopted, top, threadno); -+ adopted = 0; -+ if(node->branch){ -+ if(entry){ -+ long *last_entry = entry; -+ -+ do{ -+ last_entry--; -+ save_thread = ((PINELT_S *)mail_elt(stream, *last_entry)->sparep)->pthrd; -+ } while (save_thread->parent != 0L); -+ entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, -+ maxno, save_thread, (flags == THD_TOP ? THD_TOP : THD_BRANCH), -+ adopted, top, threadno); -+ } -+ else -+ entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, -+ maxno, NULL, THD_TOP, adopted, top, threadno); -+ } -+ } - } - - return(entry); -@@ -788,7 +836,7 @@ msgno_thread_info(MAILSTREAM *stream, lo - */ - void - collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, -- long unsigned int msgno) -+ long unsigned int msgno, int display) - { - int collapsed, adjust_current = 0; - PINETHRD_S *thrd = NULL, *nthrd; -@@ -841,7 +889,7 @@ collapse_or_expand(struct pine *state, M - if(!thrd) - return; - -- collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL) && thrd->next; -+ collapsed = this_thread_is_kolapsed(ps_global, stream, msgmap, thrd->rawno); - - if(collapsed){ - msgno = mn_raw2m(msgmap, thrd->rawno); -@@ -859,13 +907,13 @@ collapse_or_expand(struct pine *state, M - 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) -+ if((thrd->next) && ((nthrd = fetch_thread(stream, thrd->next)) != NULL)) - set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID); - - clear_index_cache_ent(stream, msgno, 0); - } - } -- else -+ else if(display) - q_status_message(SM_ORDER, 0, 1, - _("No thread to collapse or expand on this line")); - -@@ -952,18 +1000,19 @@ count_flags_in_thread(MAILSTREAM *stream - unsigned long count = 0; - PINETHRD_S *nthrd, *bthrd; - MESSAGECACHE *mc; -+ unsigned long next = 0L, branch = 0L; - - if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) - return count; - -- if(thrd->next){ -- nthrd = fetch_thread(stream, thrd->next); -+ if(next = get_next(stream, thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - count += count_flags_in_thread(stream, nthrd, flags); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ if(branch = get_branch(stream, thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - count += count_flags_in_thread(stream, bthrd, flags); - } -@@ -1051,20 +1100,21 @@ int - mark_msgs_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap) - { - int count = 0; -+ long next, branch; - 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(next = get_next(stream, thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - count += mark_msgs_in_thread(stream, nthrd, msgmap); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ if(branch = get_branch(stream, thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - count += mark_msgs_in_thread(stream, bthrd, msgmap); - } -@@ -1098,7 +1148,7 @@ set_thread_lflags(MAILSTREAM *stream, PI - /* flags to set or clear */ - /* set or clear? */ - { -- unsigned long msgno; -+ unsigned long msgno, next, branch; - PINETHRD_S *nthrd, *bthrd; - - if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) -@@ -1122,14 +1172,14 @@ set_thread_lflags(MAILSTREAM *stream, PI - 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(next = get_next(stream, thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - set_thread_lflags(stream, nthrd, msgmap, flags, v); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ if(branch = get_branch(stream,thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - set_thread_lflags(stream, bthrd, msgmap, flags, v); - } -@@ -1218,19 +1268,20 @@ to_us_symbol_for_thread(MAILSTREAM *stre - char to_us = ' '; - char branch_to_us = ' '; - PINETHRD_S *nthrd, *bthrd; -+ unsigned long next = 0L, branch = 0L; - 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(next = get_next(stream,thrd)){ -+ nthrd = fetch_thread(stream, 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){ -+ && (branch = get_branch(stream, thrd))){ - bthrd = fetch_thread(stream, thrd->branch); - if(bthrd) - branch_to_us = to_us_symbol_for_thread(stream, bthrd, consider_flagged); -@@ -1280,7 +1331,7 @@ to_us_symbol_for_thread(MAILSTREAM *stre - break; - } - -- if(to_us != '+' && resent_to_us(&idata)) -+ if(to_us != '+' && !idata.bogus && resent_to_us(&idata)) - to_us = '+'; - - if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) -@@ -1328,7 +1379,8 @@ set_thread_subtree(MAILSTREAM *stream, P - - set_lflag(stream, msgmap, msgno, flags, v); - -- if(thrd->next && (hiding || !get_lflag(stream,NULL,thrd->rawno,MN_COLL))){ -+ 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); -@@ -1368,8 +1420,8 @@ view_thread(struct pine *state, MAILSTRE - if(rawno) - thrd = fetch_thread(stream, rawno); - -- if(thrd && thrd->top && thrd->top != thrd->rawno) -- thrd = fetch_thread(stream, thrd->top); -+ if(thrd && thrd->top && top_thread(stream,thrd->top) != thrd->rawno) -+ thrd = fetch_thread(stream, top_thread(stream,thrd->top)); - - if(!thrd) - return 0; -@@ -1433,7 +1485,7 @@ unview_thread(struct pine *state, MAILST - thrd = fetch_thread(stream, rawno); - - if(thrd && thrd->top) -- topthrd = fetch_thread(stream, thrd->top); -+ topthrd = fetch_thread(stream, top_thread(stream,thrd->top)); - - if(!topthrd) - return 0; -@@ -1539,6 +1591,7 @@ void - set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgset) - { - PINETHRD_S *nthrd, *bthrd; -+ unsigned long next, branch; - - if(!(stream && thrd)) - return; -@@ -1547,15 +1600,622 @@ set_search_bit_for_thread(MAILSTREAM *st - && (!(msgset && *msgset) || in_searchset(*msgset, thrd->rawno))) - mm_searched(stream, thrd->rawno); - -- if(thrd->next){ -- nthrd = fetch_thread(stream, thrd->next); -+ if(next= get_next(stream, thrd)){ -+ nthrd = fetch_thread(stream, next); - if(nthrd) - set_search_bit_for_thread(stream, nthrd, msgset); - } - -- if(thrd->branch){ -- bthrd = fetch_thread(stream, thrd->branch); -+ if(branch = get_branch(stream, thrd)){ -+ bthrd = fetch_thread(stream, branch); - if(bthrd) - set_search_bit_for_thread(stream, bthrd, msgset); - } - } -+ -+/* -+ * Make a copy of c-client's THREAD tree -+ */ -+THREADNODE * -+copy_tree(THREADNODE *tree) -+{ -+ THREADNODE *newtree = NULL; -+ -+ if(tree){ -+ newtree = mail_newthreadnode(NULL); -+ newtree->num = tree->num; -+ if(tree->next) -+ newtree->next = copy_tree(tree->next); -+ -+ if(tree->branch) -+ newtree->branch = copy_tree(tree->branch); -+ } -+ return(newtree); -+} -+ -+long -+top_thread(MAILSTREAM *stream, long rawmsgno) -+{ -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno; -+ -+ if(!stream) -+ return -1L; -+ -+ if(rawmsgno) -+ thrd = fetch_thread(stream, rawmsgno); -+ ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_this_thread(stream, msgmap, rawno); ++ top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(top) ++ thrd = fetch_thread(stream, top); ++ + if(!thrd) -+ return -1L; -+ -+ return F_ON(F_ENHANCED_THREAD, ps_global) -+ ? (thrd->toploose ? thrd->toploose : thrd->top) -+ : thrd->top; -+} -+ -+void -+move_top_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) -+{ -+ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream, rawmsgno))); -+} -+ -+long -+top_this_thread(MAILSTREAM *stream, long rawmsgno) -+{ -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno; -+ ++ return; ++ ++ save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, top); ++ collapse_this_thread(state, stream, msgmap, 0, 0); ++ thread_command(state, stream, msgmap, 'd', -FOOTER_ROWS(state), 1); ++ if (!save_kolapsed) ++ expand_this_thread(state, stream, msgmap, 0, 0); ++ } ++ ++ void ++ cmd_delete_thread(state, stream, msgmap) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ { ++ unsigned long rawno, top, orig_top, topnxt, save_kolapsed; ++ PINETHRD_S *thrd = NULL, *nxthrd; ++ int done = 0, count; ++ + if(!stream) -+ return -1L; -+ -+ if(rawmsgno) -+ thrd = fetch_thread(stream, rawmsgno); -+ ++ return; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_thread(stream, msgmap, rawno); ++ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(top) ++ thrd = fetch_thread(stream, top); ++ + if(!thrd) -+ return -1L; -+ -+ return thrd->top; -+} -+ -+void -+move_top_this_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) -+{ -+ mn_set_cur(msgmap,mn_raw2m(msgmap, top_this_thread(stream, rawmsgno))); -+} -+ -+int -+thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) -+{ -+ int collapsed; -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno, orig, orig_rawno; -+ -+ if(!stream) -+ return -1; -+ -+ orig = mn_get_cur(msgmap); -+ move_top_thread(stream, msgmap, rawmsgno); -+ rawno = orig_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return -1; -+ -+ while(collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno)) ++ return; ++ ++ while (!done){ ++ cmd_delete_this_thread(state, stream, msgmap); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_rawno != top_thread(stream, rawno))) -+ break; -+ -+ mn_set_cur(msgmap,orig); /* return home */ -+ -+ return collapsed; -+} -+ -+/* this function tells us if the thread (or branch in the case of loose threads) -+ * is collapsed -+ */ -+ -+int -+this_thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) -+{ -+ int collapsed; -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno, orig; -+ -+ if(!stream) -+ return -1; -+ -+ rawno = rawmsgno; -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return -1; -+ -+ collapsed = get_lflag(stream, NULL, rawno, MN_COLL | MN_CHID); -+ -+ if (!thrd->next){ -+ if (thrd->rawno != top_thread(stream, thrd->rawno)) -+ collapsed = get_lflag(stream, NULL, rawno, MN_CHID); -+ else -+ collapsed = get_lflag(stream, NULL, rawno, MN_COLL); -+ } -+ -+ return collapsed; -+} -+ -+/* -+ * This function assumes that it is called at a top of a thread in its -+ * first call -+ */ -+ -+int -+count_this_thread(MAILSTREAM *stream, unsigned long rawno) -+{ -+ unsigned long top, orig_top, topnxt; -+ PINETHRD_S *thrd = NULL; -+ int count = 1; -+ -+ if(!stream) -+ return 0; -+ -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return 0; -+ -+ if (thrd->next) -+ count += count_this_thread(stream, thrd->next); -+ -+ if (thrd->branch) -+ count += count_this_thread(stream, thrd->branch); -+ -+ return count; -+} -+ -+int -+count_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawno) -+{ -+ unsigned long top, orig, orig_top; -+ PINETHRD_S *thrd = NULL; -+ int done = 0, count = 0; -+ -+ if(!stream) -+ return 0; -+ -+ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_thread(stream, msgmap,rawno); -+ top = orig_top = top_thread(stream, rawno); -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return 0; -+ -+ while (!done){ -+ count += count_this_thread(stream, top); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ mn_set_cur(msgmap,mn_raw2m(msgmap, orig)); -+ return count; -+} -+ -+unsigned long -+get_branch(MAILSTREAM *stream, PINETHRD_S *thrd) -+{ -+ PINETHRD_S *nthrd = NULL; -+ unsigned long top; -+ -+ if (thrd->toploose && thrd->nextthd) -+ nthrd = fetch_thread(stream, thrd->nextthd); -+ if (!nthrd) -+ return thrd->branch; -+ top = top_thread(stream, thrd->rawno); -+ return thrd->branch -+ ? thrd->branch -+ : (F_ON(F_ENHANCED_THREAD, ps_global) -+ ? (top == top_thread(stream, nthrd->rawno) ? thrd->nextthd : 0L) -+ : 0L); -+} -+ -+unsigned long -+get_next(MAILSTREAM *stream, PINETHRD_S *thrd) -+{ -+ return thrd->next; -+} -+ -+long -+get_length_branch(MAILSTREAM *stream, long rawno) -+{ -+ int branchp = 0, done = 0; -+ long top, count = 1L, raw; -+ PINETHRD_S *thrd, *pthrd = NULL, *nthrd; -+ -+ thrd = fetch_thread(stream, rawno); -+ -+ if (!thrd) -+ return -1L; -+ -+ top = thrd->top; -+ -+ if (thrd->parent) -+ pthrd = fetch_thread(stream, thrd->parent); -+ -+ if (thrd->rawno == top) -+ branchp++; -+ -+ if (!branchp && !pthrd){ /* what!!?? */ -+ raw = top; -+ while (!done){ -+ pthrd = fetch_thread(stream, raw); -+ if ((pthrd->next == rawno) || (pthrd->branch == rawno)) -+ done++; -+ else{ -+ if (pthrd->next) -+ raw = pthrd->next; -+ else if (pthrd->branch) -+ raw = pthrd->branch; -+ } ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; + } -+ } -+ -+ if (pthrd && pthrd->next == thrd->rawno && thrd->branch) -+ branchp++; -+ -+ if (pthrd && pthrd->next && pthrd->next != thrd->rawno){ -+ nthrd = fetch_thread(stream, pthrd->next); -+ while (nthrd && nthrd->branch && nthrd->branch != thrd->rawno) -+ nthrd = fetch_thread(stream, nthrd->branch); -+ if(nthrd && nthrd->branch && nthrd->branch == thrd->rawno) -+ branchp++; -+ } -+ -+ if(branchp){ -+ int entry = 0; -+ while(thrd && thrd->next){ -+ entry = 1; -+ count++; -+ thrd = fetch_thread(stream, thrd->next); -+ if (thrd->branch) -+ break; ++ mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); ++ cmd_delete(state, msgmap, MCMD_NONE, cmd_delete_index); ++ count = count_thread(state, stream, msgmap, rawno); ++ q_status_message2(SM_ORDER, 0, 1, "%s message%s marked deleted", ++ int2string(count), plural(count)); ++ } ++ ++ int ++ collapse_this_thread(state, stream, msgmap, display, special) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ int display; ++ int special; ++ { ++ int collapsed, rv = 1, done = 0; ++ PINETHRD_S *thrd = NULL, *nthrd; ++ unsigned long rawno, orig, msgno; ++ ++ if(!stream) ++ return 0; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return rv; ++ ++ collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); ++ ++ if (special && collapsed){ ++ expand_this_thread(state, stream, msgmap, 0, 0); ++ collapsed = 0; ++ } ++ ++ clear_index_cache_ent(stream, rawno, 0); ++ ++ if (!collapsed && thrd->next){ ++ if (thrd->rawno == top_thread(stream, thrd->rawno)) ++ collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); ++ else{ ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 1); ++ set_thread_subtree(stream, thrd, msgmap, 1, MN_CHID); ++ } ++ } ++ else{ ++ if (!collapsed && special ++ && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) ++ || F_ON(F_ENHANCED_THREAD, state))){ ++ if (thrd->toploose){ ++ if (thrd->rawno != thrd->toploose) ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, ++ 1); ++ else ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, ++ 1); ++ } ++ } ++ else{ ++ rv = 0; ++ if (display) ++ q_status_message(SM_ORDER, 0, 1, "Thread already collapsed"); ++ } ++ } ++ return rv; ++ } ++ ++ void ++ collapse_thread(state, stream, msgmap, display) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ int display; ++ { ++ int collapsed, rv = 1, done = 0; ++ PINETHRD_S *thrd = NULL; ++ unsigned long orig, orig_top, top; ++ ++ if(!stream) ++ return; ++ ++ expand_this_thread(state, stream, msgmap, display, 1); ++ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_thread(stream, msgmap,orig); ++ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ if(top) ++ thrd = fetch_thread(stream, top); ++ ++ if(!thrd) ++ return; ++ ++ while (!done){ ++ collapse_this_thread(state, stream, msgmap, display, 1); ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; ++ } ++ mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); ++ } ++ ++ int ++ expand_this_thread(state, stream, msgmap, display, special) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ int display; ++ int special; ++ { ++ int collapsed, rv = 1, done = 0; ++ PINETHRD_S *thrd = NULL, *nthrd; ++ unsigned long rawno, orig, msgno; ++ ++ if(!stream) ++ return 0; ++ ++ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_this_thread(stream, msgmap,orig); ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return rv; ++ ++ collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); ++ ++ if (special && !collapsed){ ++ collapse_this_thread(state, stream, msgmap, 0, 0); ++ collapsed = 1; ++ } ++ ++ clear_index_cache_ent(stream, rawno, 0); ++ ++ if (collapsed && thrd->next){ ++ if (thrd->rawno == top_thread(stream, thrd->rawno)) ++ collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); ++ else{ ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 0); ++ set_thread_subtree(stream, thrd, msgmap, 0, MN_CHID); ++ } ++ } ++ else{ ++ if (collapsed && special ++ && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) ++ || F_ON(F_ENHANCED_THREAD, state))){ ++ if (thrd->toploose) ++ if (thrd->rawno != thrd->toploose) ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, 0); ++ else ++ set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, 0); ++ } ++ else{ ++ rv = 0; ++ if (display) ++ q_status_message(SM_ORDER, 0, 1, "Thread already expanded"); ++ } ++ } ++ return rv; ++ } ++ ++ void ++ expand_thread(state, stream, msgmap, display) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ int display; ++ { ++ int collapsed, rv = 1, done = 0; ++ PINETHRD_S *thrd = NULL; ++ unsigned long orig, orig_top, top; ++ ++ if(!stream) ++ return; ++ ++ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ if(top) ++ thrd = fetch_thread(stream, top); ++ ++ if(!thrd) ++ return; ++ ++ while (!done){ ++ expand_this_thread(state, stream, msgmap, display, 1); ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; ++ } ++ mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); ++ } ++ ++ ++ void ++ cmd_undelete_this_thread(state, stream, msgmap) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ { ++ unsigned long rawno; ++ int save_kolapsed; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); ++ collapse_this_thread(state, stream, msgmap, 0, 0); ++ thread_command(state, stream, msgmap, 'u', -FOOTER_ROWS(state), 1); ++ if (!save_kolapsed) ++ expand_this_thread(state, stream, msgmap, 0, 0); ++ } ++ ++ void ++ cmd_undelete_thread(state, stream, msgmap) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ { ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno, top, orig_top; ++ int done = 0, count; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_thread(stream, msgmap, rawno); ++ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(top) ++ thrd = fetch_thread(stream, top); ++ ++ if(!thrd) ++ return; ++ ++ while (!done){ ++ cmd_undelete_this_thread(state, stream, msgmap); ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; ++ } ++ mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); ++ count = count_thread(state, stream, msgmap, rawno); ++ q_status_message2(SM_ORDER, 0, 1, "Deletion mark removed from %s message%s", ++ int2string(count), plural(count)); ++ } ++ ++ void ++ kolapse_thread(state, stream, msgmap, ch, display) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ char ch; ++ int display; ++ { ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno; ++ int rv = 1, done = 0; ++ ++ if(!stream) ++ return; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return; ++ ++ clear_index_cache(stream, 0); ++ mn_set_cur(msgmap,1); /* go to the first message */ ++ while (!done){ ++ if (ch == '[') ++ collapse_thread(state, stream, msgmap, display); ++ else ++ expand_thread(state, stream, msgmap, display); ++ if ((rv = move_next_thread(state, stream, msgmap, 0)) <= 0) ++ done++; ++ } ++ ++ if (rv < 0){ ++ if (display) ++ q_status_message(SM_ORDER, 0, 1, (ch == '[') ++ ? "Error while collapsing thread" ++ : "Error while expanding thread"); ++ } ++ else ++ if(display) ++ q_status_message(SM_ORDER, 0, 1, (ch == '[') ++ ? "All threads collapsed. Use \"}\" to expand them" ++ : "All threads expanded. Use \"{\" to collapse them"); ++ ++ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,rawno))); ++ } ++ ++ void ++ cmd_select_thread(state, stream, msgmap) ++ struct pine *state; ++ MAILSTREAM *stream; ++ MSGNO_S *msgmap; ++ { ++ unsigned long rawno; ++ int save_kolapsed; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ save_kolapsed = thread_is_kolapsed(state, stream, msgmap, rawno); ++ collapse_thread(state, stream, msgmap, 0); ++ thread_command(state, stream, msgmap, ':', -FOOTER_ROWS(state), 1); ++ if (!save_kolapsed) ++ expand_thread(state, stream, msgmap, 0); ++ } ++ +diff -rc alpine-2.21/alpine/mailcmd.h alpine-2.21.fancy/alpine/mailcmd.h +*** alpine-2.21/alpine/mailcmd.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/mailcmd.h Sun Feb 5 16:15:20 2017 +*************** +*** 90,96 **** + int ask_mailbox_reopen(struct pine *, int *); + void visit_folder(struct pine *, char *, CONTEXT_S *, MAILSTREAM *, unsigned long); + int select_by_current(struct pine *, MSGNO_S *, CmdWhere); +! int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); + char **choose_list_of_keywords(void); + char *choose_a_charset(int); + char **choose_list_of_charsets(void); +--- 90,96 ---- + int ask_mailbox_reopen(struct pine *, int *); + void visit_folder(struct pine *, char *, CONTEXT_S *, MAILSTREAM *, unsigned long); + int select_by_current(struct pine *, MSGNO_S *, CmdWhere); +! int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int, int); + char **choose_list_of_keywords(void); + char *choose_a_charset(int); + char **choose_list_of_charsets(void); +*************** +*** 108,113 **** + int flag_callback(int, long); + MPopup *flag_submenu(MESSAGECACHE *); + #endif +! + + #endif /* PINE_MAILCMD_INCLUDED */ +--- 108,122 ---- + int flag_callback(int, long); + MPopup *flag_submenu(MESSAGECACHE *); + #endif +! void cmd_delete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +! void cmd_delete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +! void cmd_undelete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +! void cmd_undelete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +! void cmd_select_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +! void kolapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, char, int); +! void collapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! void expand_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! int collapse_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); +! int expand_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); + + #endif /* PINE_MAILCMD_INCLUDED */ +diff -rc alpine-2.21/alpine/mailindx.c alpine-2.21.fancy/alpine/mailindx.c +*** alpine-2.21/alpine/mailindx.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/mailindx.c Sun Feb 5 16:15:20 2017 +*************** +*** 564,569 **** +--- 564,570 ---- + + /*---------- Scroll line up ----------*/ + case MC_CHARUP : ++ previtem: + (void) process_cmd(state, stream, msgmap, MC_PREVITEM, + (style == MsgIndex + || style == MultiMsgIndex +*************** +*** 581,586 **** +--- 582,588 ---- + + /*---------- Scroll line down ----------*/ + case MC_CHARDOWN : ++ nextitem: + /* + * Special Page framing handling here. If we + * did something that should scroll-by-a-line, frame +*************** +*** 798,803 **** +--- 800,806 ---- + + + case MC_THRDINDX : ++ mc_thrdindx: + if(any_lflagged(msgmap, MN_SLCT)){ + PINETHRD_S *thrd, *topthrd; + for(i = 1L; i > 0L && i <= mn_get_total(msgmap);){ +*************** +*** 863,869 **** + && mp.col == id.plus_col + && style != ThreadIndex){ + collapse_or_expand(state, stream, msgmap, +! mn_get_cur(msgmap)); + } + else if (mp.doubleclick){ + if(mp.button == M_BUTTON_LEFT){ +--- 866,872 ---- + && mp.col == id.plus_col + && style != ThreadIndex){ + collapse_or_expand(state, stream, msgmap, +! mn_get_cur(msgmap), 1); + } + else if (mp.doubleclick){ + if(mp.button == M_BUTTON_LEFT){ +*************** +*** 972,980 **** + + + case MC_COLLAPSE : +! thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state)); + break; + + case MC_DELETE : + case MC_UNDELETE : + case MC_REPLY : +--- 975,1079 ---- + + + case MC_COLLAPSE : +! thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state), 1); + break; + ++ case MC_CTHREAD : ++ if (SEP_THRDINDX()) ++ goto mc_thrdindx; ++ else ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, ++ "to collapse a thread")) ++ collapse_thread(state, stream,msgmap, 1); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_OTHREAD : ++ if (SEP_THRDINDX()) ++ goto view_a_thread; ++ else ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to expand a thread")) ++ expand_thread(state, stream,msgmap, 1); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_NEXTHREAD: ++ case MC_PRETHREAD: ++ if (THRD_INDX()){ ++ if (cmd == MC_NEXTHREAD) ++ goto nextitem; ++ else ++ goto previtem; ++ } ++ else ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, ++ "to move to other thread")) ++ move_thread(state, stream, msgmap, ++ cmd == MC_NEXTHREAD ? 1 : -1); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_KOLAPSE: ++ case MC_EXPTHREAD: ++ if (SEP_THRDINDX()){ ++ q_status_message(SM_ORDER, 0, 1, ++ "Command not available in this screen"); ++ } ++ else{ ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, ++ cmd == MC_KOLAPSE ? "to collapse" : "to expand")) ++ kolapse_thread(state, stream, msgmap, ++ (cmd == MC_KOLAPSE) ? '[' : ']', 1); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ } ++ break; ++ ++ case MC_DELTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to delete")) ++ cmd_delete_thread(state, stream, msgmap); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_UNDTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to undelete")) ++ cmd_undelete_thread(state, stream, msgmap); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_SELTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to undelete")) ++ cmd_select_thread(state, stream, msgmap); ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ + case MC_DELETE : + case MC_UNDELETE : + case MC_REPLY : +*************** +*** 995,1007 **** + if(rawno) + thrd = fetch_thread(stream, rawno); + +! collapsed = thrd && thrd->next +! && get_lflag(stream, NULL, rawno, MN_COLL); + } + + if(collapsed){ + thread_command(state, stream, msgmap, +! ch, -FOOTER_ROWS(state)); + /* increment current */ + if(cmd == MC_DELETE){ + advance_cur_after_delete(state, stream, msgmap, +--- 1094,1105 ---- + if(rawno) + thrd = fetch_thread(stream, rawno); + +! collapsed = thread_is_kolapsed(ps_global, stream, msgmap, rawno); + } + + if(collapsed){ + thread_command(state, stream, msgmap, +! ch, -FOOTER_ROWS(state),1); + /* increment current */ + if(cmd == MC_DELETE){ + advance_cur_after_delete(state, stream, msgmap, +*************** +*** 2692,2697 **** +--- 2790,2796 ---- + n = mn_raw2m(msgs, thrd->rawno); + + while(thrd){ ++ unsigned long branch; + if(!msgline_hidden(stream, msgs, n, 0) + && (++m % lines_per_page) == 1L) + t = n; +*************** +*** 2760,2770 **** + + /* n is the end of this thread */ + while(thrd){ + n = mn_raw2m(msgs, thrd->rawno); +! if(thrd->branch) +! thrd = fetch_thread(stream, thrd->branch); +! else if(thrd->next) +! thrd = fetch_thread(stream, thrd->next); + else + thrd = NULL; + } +--- 2859,2870 ---- + + /* n is the end of this thread */ + while(thrd){ ++ unsigned long next = 0L, branch = 0L; + n = mn_raw2m(msgs, thrd->rawno); +! if(branch = get_branch(stream,thrd)) +! thrd = fetch_thread(stream, branch); +! else if(next = get_next(stream,thrd)) +! thrd = fetch_thread(stream, next); + else + thrd = NULL; + } +*************** +*** 2872,2878 **** + + void + thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, +! UCS preloadkeystroke, int q_line) + { + PINETHRD_S *thrd = NULL; + unsigned long rawno, save_branch; +--- 2972,2978 ---- + + void + thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, +! UCS preloadkeystroke, int q_line, int display) + { + PINETHRD_S *thrd = NULL; + unsigned long rawno, save_branch; +*************** +*** 2921,2927 **** + cancel_busy_cue(0); + + (void ) apply_command(state, stream, msgmap, preloadkeystroke, flags, +! q_line); + + /* restore the original flags */ + copy_lflags(stream, msgmap, MN_STMP, MN_SLCT); +--- 3021,3027 ---- + cancel_busy_cue(0); + + (void ) apply_command(state, stream, msgmap, preloadkeystroke, flags, +! q_line, display); + + /* restore the original flags */ + copy_lflags(stream, msgmap, MN_STMP, MN_SLCT); +*************** +*** 3415,3421 **** + if(set){ + sort_folder(ps_global->mail_stream, ps_global->msgmap, + order & 0x000000ff, +! (order & 0x00000100) != 0, SRT_VRB); + mswin_beginupdate(); + update_titlebar_message(); + update_titlebar_status(); +--- 3515,3521 ---- + if(set){ + sort_folder(ps_global->mail_stream, ps_global->msgmap, + order & 0x000000ff, +! (order & 0x00000100) != 0, SRT_VRB, 1); + mswin_beginupdate(); + update_titlebar_message(); + update_titlebar_status(); +diff -rc alpine-2.21/alpine/mailindx.h alpine-2.21.fancy/alpine/mailindx.h +*** alpine-2.21/alpine/mailindx.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/mailindx.h Sun Feb 5 16:15:20 2017 +*************** +*** 103,109 **** + void paint_index_hline(MAILSTREAM *, long, ICE_S *); + void setup_index_state(int); + void warn_other_cmds(void); +! void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int); + COLOR_PAIR *apply_rev_color(COLOR_PAIR *, int); + #ifdef _WINDOWS + int index_sort_callback(int, long); +--- 103,109 ---- + void paint_index_hline(MAILSTREAM *, long, ICE_S *); + void setup_index_state(int); + void warn_other_cmds(void); +! void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); + COLOR_PAIR *apply_rev_color(COLOR_PAIR *, int); + #ifdef _WINDOWS + int index_sort_callback(int, long); +diff -rc alpine-2.21/alpine/mailview.c alpine-2.21.fancy/alpine/mailview.c +*** alpine-2.21/alpine/mailview.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/mailview.c Sun Feb 5 16:15:20 2017 +*************** +*** 3369,3374 **** +--- 3369,3420 ---- + print_to_printer(sparms); + break; + ++ case MC_NEXTHREAD: ++ case MC_PRETHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, ++ "to move to other thread")) ++ move_thread(ps_global, ps_global->mail_stream, ps_global->msgmap, ++ cmd == MC_NEXTHREAD ? 1 : -1); ++ done = 1; ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_DELTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to delete")) ++ cmd_delete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); ++ done = 1; ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_UNDTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to undelete")) ++ cmd_undelete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); ++ done = 1; ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; ++ ++ case MC_SELTHREAD: ++ if (THREADING()){ ++ if (any_messages(ps_global->msgmap, NULL, "to undelete")) ++ cmd_select_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); ++ done = 1; ++ } ++ else ++ q_status_message(SM_ORDER, 0, 1, ++ "Command available in threaded mode only"); ++ break; + + /* ------- First handle on Line ------ */ + case MC_GOTOBOL : +diff -rc alpine-2.21/alpine/roleconf.c alpine-2.21.fancy/alpine/roleconf.c +*** alpine-2.21/alpine/roleconf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/roleconf.c Sun Feb 5 16:15:20 2017 +*************** +*** 4478,4488 **** + ctmp->tool = role_sort_tool; + ctmp->valoffset = rindent; + ctmp->flags |= CF_NOSELECT; +! ctmp->value = cpystr(set_choose); \ + + pval = PVAL(&sort_act_var, ew); + if(pval) +! decode_sort(pval, &def_sort, &def_sort_rev); + + /* allow user to set their default sort order */ + new_confline(&ctmp)->var = &sort_act_var; +--- 4478,4488 ---- + ctmp->tool = role_sort_tool; + ctmp->valoffset = rindent; + ctmp->flags |= CF_NOSELECT; +! ctmp->value = cpystr(set_choose); + + pval = PVAL(&sort_act_var, ew); + if(pval) +! decode_sort(pval, &def_sort, &def_sort_rev, 0); + + /* allow user to set their default sort order */ + new_confline(&ctmp)->var = &sort_act_var; +*************** +*** 4492,4498 **** + ctmp->tool = role_sort_tool; + ctmp->valoffset = rindent; + ctmp->varmem = -1; +! ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0); + + for(j = 0; j < 2; j++){ + for(i = 0; ps->sort_types[i] != EndofList; i++){ +--- 4492,4498 ---- + ctmp->tool = role_sort_tool; + ctmp->valoffset = rindent; + ctmp->varmem = -1; +! ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0, 0); + + for(j = 0; j < 2; j++){ + for(i = 0; ps->sort_types[i] != EndofList; i++){ +*************** +*** 4504,4510 **** + ctmp->valoffset = rindent; + ctmp->varmem = i + (j * EndofList); + ctmp->value = generalized_sort_pretty_value(ps, ctmp, +! 0); + } + } + +--- 4504,4510 ---- + ctmp->valoffset = rindent; + ctmp->varmem = i + (j * EndofList); + ctmp->value = generalized_sort_pretty_value(ps, ctmp, +! 0, 0); + } + } + +*************** +*** 5437,5443 **** + (*result)->patgrp->stat_boy = PAT_STAT_EITHER; + + if(sort_act){ +! decode_sort(sort_act, &def_sort, &def_sort_rev); + (*result)->action->sort_is_set = 1; + (*result)->action->sortorder = def_sort; + (*result)->action->revsort = (def_sort_rev ? 1 : 0); +--- 5437,5443 ---- + (*result)->patgrp->stat_boy = PAT_STAT_EITHER; + + if(sort_act){ +! decode_sort(sort_act, &def_sort, &def_sort_rev, 0); + (*result)->action->sort_is_set = 1; + (*result)->action->sortorder = def_sort; + (*result)->action->revsort = (def_sort_rev ? 1 : 0); +diff -rc alpine-2.21/alpine/setup.c alpine-2.21.fancy/alpine/setup.c +*** alpine-2.21/alpine/setup.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/alpine/setup.c Sun Feb 5 16:15:20 2017 +*************** +*** 262,268 **** + ctmpa->flags |= CF_NOSELECT; + ctmpa->value = cpystr("--- ----------------------"); + +! decode_sort(pval, &def_sort, &def_sort_rev); + + for(j = 0; j < 2; j++){ + for(i = 0; ps->sort_types[i] != EndofList; i++){ +--- 262,268 ---- + ctmpa->flags |= CF_NOSELECT; + ctmpa->value = cpystr("--- ----------------------"); + +! decode_sort(pval, &def_sort, &def_sort_rev, 0); + + for(j = 0; j < 2; j++){ + for(i = 0; ps->sort_types[i] != EndofList; i++){ +*************** +*** 277,282 **** +--- 277,331 ---- + } + } + } ++ else if(vtmp == &ps->vars[V_THREAD_SORT_KEY]){ /* radio case */ ++ SortOrder thread_def_sort; ++ int thread_def_sort_rev, lv; ++ ++ ctmpa->flags |= CF_NOSELECT; ++ ctmpa->keymenu = &config_radiobutton_keymenu; ++ ctmpa->tool = NULL; ++ ++ /* put a nice delimiter before list */ ++ new_confline(&ctmpa)->var = NULL; ++ ctmpa->varnamep = ctmpb; ++ ctmpa->keymenu = &config_radiobutton_keymenu; ++ ctmpa->help = NO_HELP; ++ ctmpa->tool = radiobutton_tool; ++ ctmpa->valoffset = 12; ++ ctmpa->flags |= CF_NOSELECT; ++ ctmpa->value = cpystr("Set Thread Sort Options"); ++ ++ new_confline(&ctmpa)->var = NULL; ++ ctmpa->varnamep = ctmpb; ++ ctmpa->keymenu = &config_radiobutton_keymenu; ++ ctmpa->help = NO_HELP; ++ ctmpa->tool = radiobutton_tool; ++ ctmpa->valoffset = 12; ++ ctmpa->flags |= CF_NOSELECT; ++ ctmpa->value = cpystr("--- ----------------------"); ++ ++ /* find longest value's name */ ++ for(lv = 0, i = 0; ps->sort_types[i] != EndofList; i++) ++ if(lv < (j = strlen(sort_name(ps->sort_types[i])))) ++ lv = j; ++ ++ decode_sort(pval, &thread_def_sort, &thread_def_sort_rev, 1); ++ ++ for(j = 0; j < 2; j++){ ++ for(i = 0; ps->sort_types[i] != EndofList; i++){ ++ if (allowed_thread_key(ps->sort_types[i])){ ++ new_confline(&ctmpa)->var = vtmp; ++ ctmpa->varnamep = ctmpb; ++ ctmpa->keymenu = &config_radiobutton_keymenu; ++ ctmpa->help = config_help(vtmp - ps->vars, 0); ++ ctmpa->tool = radiobutton_tool; ++ ctmpa->valoffset = 12; ++ ctmpa->varmem = i + (j * EndofList); ++ ctmpa->value = pretty_value(ps, ctmpa); ++ } ++ } ++ } ++ } + else if(vtmp == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){ /* yesno case */ + ctmpa->keymenu = &config_yesno_keymenu; + ctmpa->tool = yesno_tool; +*************** +*** 470,475 **** +--- 519,533 ---- + } + } + ++ pval = PVAL(&ps->vars[V_THREAD_SORT_KEY], ew); ++ if(vsave[V_THREAD_SORT_KEY].saved_user_val.p && pval ++ && strcmp(vsave[V_THREAD_SORT_KEY].saved_user_val.p, pval)){ ++ if(!mn_get_mansort(ps_global->msgmap)){ ++ clear_index_cache(ps_global->mail_stream, 0); ++ reset_sort_order(SRT_VRB); ++ } ++ } ++ + treat_color_vars_as_text = 0; + free_saved_config(ps, &vsave, expose_hidden_config); + #ifdef _WINDOWS +diff -rc alpine-2.21/pith/conf.c alpine-2.21.fancy/pith/conf.c +*** alpine-2.21/pith/conf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/conf.c Sun Feb 5 16:15:20 2017 +*************** +*** 206,211 **** +--- 206,213 ---- + + 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_thread_sort_key[] = "#Sets presentation order of threads in thread index. Choices:\n#arrival, and thread."; ++ + 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\"."; +*************** +*** 528,533 **** +--- 530,537 ---- + NULL, cf_text_fcc_name_rule}, + {"sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_sort_key}, ++ {"thread-sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, ++ NULL, cf_text_thread_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, +*************** +*** 1582,1588 **** + 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; +--- 1586,1592 ---- + 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, thread_def_sort_rev; + long rvl; + PINERC_S *fixedprc = NULL; + FeatureLevel obs_feature_level; +*************** +*** 1607,1612 **** +--- 1611,1617 ---- + GLO_FEATURE_LEVEL = cpystr("sappling"); + GLO_OLD_STYLE_REPLY = cpystr(DF_OLD_STYLE_REPLY); + GLO_SORT_KEY = cpystr(DF_SORT_KEY); ++ GLO_THREAD_SORT_KEY = cpystr(DF_THREAD_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); +*************** +*** 2540,2546 **** + 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; +--- 2545,2551 ---- + 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,0) == -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; +*************** +*** 2549,2554 **** +--- 2554,2570 ---- + else + ps->def_sort_rev = def_sort_rev; + ++ set_current_val(&vars[V_THREAD_SORT_KEY], TRUE, TRUE); ++ if(decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, ++ &thread_def_sort_rev, 1) == -1){ ++ sprintf(tmp_20k_buf, "Sort type \"%s\" is invalid", VAR_THREAD_SORT_KEY); ++ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf); ++ ps->thread_def_sort = SortThread; ++ ps->thread_def_sort_rev = 0; ++ } ++ else ++ ps->thread_def_sort_rev = thread_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++) +*************** +*** 2974,2979 **** +--- 2990,2997 ---- + 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}, ++ {"enhanced-fancy-thread-support", "Enhanced Fancy Thread Support", ++ F_ENHANCED_THREAD, h_config_enhanced_thread, PREF_INDX, 0}, + + /* Viewer prefs */ + {"enable-msg-view-addresses", "Enable Message View Address Links", +*************** +*** 7695,7700 **** +--- 7713,7720 ---- + return(h_config_fcc_rule); + case V_SORT_KEY : + return(h_config_sort_key); ++ case V_THREAD_SORT_KEY : ++ return(h_config_thread_sort_key); + case V_AB_SORT_RULE : + return(h_config_ab_sort_rule); + case V_FLD_SORT_RULE : +diff -rc alpine-2.21/pith/conf.h alpine-2.21.fancy/pith/conf.h +*** alpine-2.21/pith/conf.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/conf.h Sun Feb 5 16:15:20 2017 +*************** +*** 144,149 **** +--- 144,152 ---- + #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_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].current_val.p ++ #define GLO_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].global_val.p ++ #define COM_THREAD_SORT_KEY vars[V_THREAD_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 +diff -rc alpine-2.21/pith/conftype.h alpine-2.21.fancy/pith/conftype.h +*** alpine-2.21/pith/conftype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/conftype.h Sun Feb 5 16:15:20 2017 +*************** +*** 59,64 **** +--- 59,65 ---- + , V_SAVED_MSG_NAME_RULE + , V_FCC_RULE + , V_SORT_KEY ++ , V_THREAD_SORT_KEY + , V_AB_SORT_RULE + , V_FLD_SORT_RULE + , V_GOTO_DEFAULT_RULE +*************** +*** 514,519 **** +--- 515,521 ---- + F_QUELL_TIMEZONE, + F_QUELL_USERAGENT, + F_COLOR_LINE_IMPORTANT, ++ F_ENHANCED_THREAD, + F_SLASH_COLL_ENTIRE, + F_ENABLE_FULL_HDR_AND_TEXT, + F_QUELL_FULL_HDR_RESET, +*************** +*** 779,783 **** +--- 781,786 ---- + + /* exported protoypes */ + ++ #define DF_THREAD_SORT_KEY "thread" + + #endif /* PITH_CONFTYPE_INCLUDED */ +diff -rc alpine-2.21/pith/flag.c alpine-2.21.fancy/pith/flag.c +*** alpine-2.21/pith/flag.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/flag.c Sun Feb 5 16:15:20 2017 +*************** +*** 594,607 **** + + 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){ +--- 594,609 ---- + + was_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0; + ++ thrd = fetch_thread(stream, rawno); ++ + 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(top_thread(stream, thrd->top) == thrd->rawno) + topthrd = thrd; + else +! topthrd = fetch_thread(stream, top_thread(stream, thrd->top)); + } + + if(topthrd){ +diff -rc alpine-2.21/pith/indxtype.h alpine-2.21.fancy/pith/indxtype.h +*** alpine-2.21/pith/indxtype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/indxtype.h Sun Feb 5 16:15:20 2017 +*************** +*** 78,84 **** + 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, +--- 78,84 ---- + iKey, iKeyInit, + iPrefDate, iPrefTime, iPrefDateTime, + iCurPrefDate, iCurPrefTime, iCurPrefDateTime, +! iSize, iSizeComma, iSizeNarrow, iDescripSize, iSizeThread, + iNewsAndTo, iToAndNews, iNewsAndRecips, iRecipsAndNews, + iFromTo, iFromToNotNews, iFrom, iTo, iSender, iCc, iNews, iRecips, + iCurNews, iArrow, +diff -rc alpine-2.21/pith/mailindx.c alpine-2.21.fancy/pith/mailindx.c +*** alpine-2.21/pith/mailindx.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/mailindx.c Sun Feb 5 16:15:20 2017 +*************** +*** 228,233 **** +--- 228,234 ---- + case iSTime: + case iKSize: + case iSize: ++ case iSizeThread: + case iPrioAlpha: + (*answer)[column].req_width = 7; + break; +*************** +*** 455,460 **** +--- 456,462 ---- + {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX}, + {"SIZE", iSize, FOR_INDEX}, + {"SIZECOMMA", iSizeComma, FOR_INDEX}, ++ {"SIZETHREAD", iSizeThread, FOR_INDEX}, + {"SIZENARROW", iSizeNarrow, FOR_INDEX}, + {"KSIZE", iKSize, FOR_INDEX}, + {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, +*************** +*** 953,959 **** + 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 +--- 955,961 ---- + iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4, + iSDateTimeIso24, iSDateTimeIsoS24, + iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424, +! iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, iSizeThread, + iPrio, iPrioBang, iPrioAlpha, iInit, + iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit, + iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek +*************** +*** 1146,1151 **** +--- 1148,1154 ---- + case iTime12: + case iSize: + case iKSize: ++ case iSizeThread: + cdesc->actual_length = 7; + cdesc->adjustment = Right; + break; +*************** +*** 1240,1246 **** + 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 */ +--- 1243,1249 ---- + cdesc->ctype != iNothing; + cdesc++) + if(cdesc->ctype == iSize || cdesc->ctype == iKSize || +! cdesc->ctype == iSizeNarrow || cdesc->ctype == iSizeThread || + cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){ + if(cdesc->actual_length == 0){ + if((fix=cdesc->width) > 0){ /* had this reserved */ +*************** +*** 1626,1635 **** + + /* 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 +--- 1629,1640 ---- + + /* find next thread which is visible */ + do{ ++ unsigned long branch; + if(mn_get_revsort(msgmap) && thrd->prevthd) + thrd = fetch_thread(stream, thrd->prevthd); +! /*branch = get_branch(stream,thrd)*/ +! else if(!mn_get_revsort(msgmap) && thrd->branch) +! thrd = fetch_thread(stream, thrd->branch); + else + thrd = NULL; + } while(thrd +*************** +*** 2041,2053 **** + */ + 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++) +--- 2046,2055 ---- + */ + ice = copy_ice(ice); + ++ thrd = fetch_thread(idata->stream, idata->rawno); + /* is this a collapsed thread index line? */ +! if(!idata->bogus && THREADING()) +! collapsed = thrd && thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno); + + /* calculate contents of the required fields */ + for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) +*************** +*** 2549,2555 **** +--- 2551,2580 ---- + + break; + ++ case iSizeThread: ++ if (!THREADING()){ ++ goto getsize; ++ } else if (collapsed){ ++ l = count_flags_in_thread(idata->stream, thrd, F_NONE); ++ snprintf(str, sizeof(str), "(%lu)", l); ++ } ++ else{ ++ thrd = fetch_thread(idata->stream, idata->rawno); ++ if(!thrd) ++ snprintf(str, sizeof(str), "%s", "Error"); ++ else{ ++ long lengthb; ++ lengthb = get_length_branch(idata->stream, idata->rawno); ++ if (lengthb > 0L) ++ snprintf(str, sizeof(str), "(%lu)", lengthb); ++ else ++ snprintf(str,sizeof(str), "%s", " "); ++ } ++ } ++ break; ++ + case iSize: ++ getsize: + /* 0 ... 9999 */ + if((l = fetch_size(idata)) < 10*1000L) + snprintf(str, sizeof(str), "(%lu)", l); +*************** +*** 5555,5564 **** + + 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 +--- 5580,5587 ---- + + if(pith_opt_condense_thread_cue) + width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, +! this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && +! (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); + + /* + * width is < available strsize and +*************** +*** 6186,6196 **** + 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) +--- 6209,6216 ---- + border = str + width; + if(pith_opt_condense_thread_cue) + width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, +! this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && +! (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); + fptr = str; + + if(thd) +diff -rc alpine-2.21/pith/pattern.c alpine-2.21.fancy/pith/pattern.c +*** alpine-2.21/pith/pattern.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/pattern.c Sun Feb 5 16:15:20 2017 +*************** +*** 1756,1762 **** + 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); +--- 1756,1762 ---- + SortOrder def_sort; + int def_sort_rev; + +! if(decode_sort(p, &def_sort, &def_sort_rev, 0) != -1){ + action->sort_is_set = 1; + action->sortorder = def_sort; + action->revsort = (def_sort_rev ? 1 : 0); +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.fancy/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/pine.hlp Sun Feb 5 16:15:20 2017 +*************** +*** 4258,4263 **** +--- 4258,4264 ---- +

  • OPTION: +
  • OPTION: +
  • OPTION: ++
  • OPTION: +
  • OPTION: +
  • OPTION: +
  • OPTION: +*************** +*** 6198,6203 **** +--- 6199,6361 ---- + <End of help on this topic> + + ++ ======= h_thread_index_sort_arrival ======= ++ ++ ++ SORT OPTION: Arrival ++ ++ ++

    SORT OPTION: Arrival

    ++ ++ The Arrival sort option arranges threads according to the last ++ time that a message was added to it. In this order the last thread ++ contains the most recent message in the folder. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_date ======= ++ ++ ++ SORT OPTION: Date ++ ++ ++

    SORT OPTION: Date

    ++ ++ The Date sort option in the THREAD INDEX screen sorts ++ threads by the date in which messages were sent. The thread containing the ++ last message in this order is displayed last. ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_subj ======= ++ ++ ++ SORT OPTION: Subject ++ ++ ++

    SORT OPTION: Subject

    ++ ++ The Subject sort option has not been defined yet. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_ordsubj ======= ++ ++ ++ SORT OPTION: OrderedSubject ++ ++ ++

    SORT OPTION: OrderedSubject

    ++ ++ The OrderedSubject sort option in the THREAD INDEX screen is ++ the same as sorting by Subject. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_thread ======= ++ ++ ++ SORT OPTION: Thread ++ ++ ++

    SORT OPTION: Thread

    ++ ++ The Thread sort option in the THREAD INDEX screen sorts all ++ messages by the proposed algorithm by Crispin and Murchison. In this ++ method of sorting once threads have been isolated they are sorted by the ++ date of their parents, or if that is missing, the first message in that ++ thread. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_from ======= ++ ++ ++ SORT OPTION: From ++ ++ ++

    SORT OPTION: From

    ++ ++ The From sort option has not been defined yet. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_size ======= ++ ++ ++ SORT OPTION: Size ++ ++ ++

    SORT OPTION: Size

    ++ ++ The Size sort option sorts threads by their size (the number ++ of messages in the thread). This could be used to find conversations ++ where no reply has been sent by any of the participants in the thread ++ (e.g. those whose length is equal to one). Longer threads appear ++ below shorter ones. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_score ======= ++ ++ ++ SORT OPTION: Score ++ ++ ++

    SORT OPTION: Score

    ++ ++ The Score sort option means that threads are sorted according to ++ the maximum score of a message in that thread. A thread all of whose ++ messages contain a smaller score than a message in some other thread is ++ placed in an earlier place in the list of messages for that folder; that ++ is, threads with the highest scores appear at the bottom of the index ++ list. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_to ======= ++ ++ ++ SORT OPTION: To ++ ++ ++

    SORT OPTION: To

    ++ ++ The To sort option has not been defined yet. ++ ++

    ++ <End of help on this topic> ++ ++ ++ ======= h_thread_index_sort_cc ======= ++ ++ ++ SORT OPTION: Cc ++ ++ ++

    SORT OPTION: Cc

    ++ ++ The Cc sort option has not been defined yet. ++ ++

    ++ <End of help on this topic> ++ ++ + ======= h_index_cmd_whereis ======= + + +*************** +*** 19638,19643 **** +--- 19796,19809 ---- +

    +

  • + ++
    SIZETHREAD
    ++
    ++ This token represents the total size of the thread for a collapsed thread ++ or the size of the branch for an expanded thread. The field is omitted for ++ messages that are not top of threads nor branches and it defaults to ++ the SIZE token when your folders is not sorted by thread. ++
    ++ +
    SIZENARROW
    +
    + This token represents the total size, in bytes, of the message. +*************** +*** 23126,23131 **** +--- 23292,23336 ---- + <End of help on this topic> + + ++ ====== h_config_thread_sort_key ===== ++ ++ ++ OPTION: <!--#echo var="VAR_thread-sort-key--> ++ ++ ++

    OPTION:

    ++ ++ This option determines the order in which threads will be displayed. You ++ can choose from the options listed below. Each folder is sorted in one of ++ the sort orders displayed below first, then the thread containing the last ++ message of that sorted list is put at the end of the index. All messages ++ of that thread are "removed" from the sorted list and the ++ process is repeated with the remaining messages in that list. ++ ++

    ++

    ++ ++

    Each type of sort may also be reversed. Normal default is by ++ "Thread". ++ ++

    ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_other_startup ===== + + +*************** +*** 31051,31056 **** +--- 31256,31278 ---- + <End of help on this topic> + + ++ ====== h_config_enhanced_thread ===== ++ ++ ++ FEATURE: <!--#echo var="FEAT_enhanced-fancy-thread-support"--> ++ ++ ++

    FEATURE:

    ++ ++ If this option is set certain commands in Pine will operate in loose ++ threads too. For example, the command ^R marks a thread deleted, but if ++ this feature is set, it will remove all threads that share the same missing ++ parent with this thread. ++ ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_news_cross_deletes ===== + + +diff -rc alpine-2.21/pith/sort.c alpine-2.21.fancy/pith/sort.c +*** alpine-2.21/pith/sort.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/sort.c Sun Feb 5 16:15:20 2017 +*************** +*** 91,97 **** + ----*/ + 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; +--- 91,97 ---- + ----*/ + void + sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort, +! int new_rev, unsigned int flags, int first) + { + long raw_current, i, j; + unsigned long *sort = NULL; +*************** +*** 101,106 **** +--- 101,115 ---- + int current_rev; + MESSAGECACHE *mc; + ++ if (first){ ++ if (new_sort == SortThread) ++ find_msgmap(stream, msgmap, flags, ++ ps_global->thread_cur_sort, new_rev); ++ else ++ sort_folder(stream, msgmap, new_sort, new_rev, flags, 0); ++ return; ++ } ++ + dprint((2, "Sorting by %s%s\n", + sort_name(new_sort), new_rev ? "/reverse" : "")); + +*************** +*** 530,549 **** + * 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); + } +--- 539,558 ---- + * argument also means arrival/reverse. + */ + int +! decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev, int thread) + { + char *sep; + char *fix_this = NULL; +! int x = 0, reverse; + + if(!sort_spec || !*sort_spec){ +! *def_sort = thread ? SortThread : SortArrival; + *def_sort_rev = 0; + return(0); + } + + if(struncmp(sort_spec, "reverse", strlen(sort_spec)) == 0){ +! *def_sort = thread ? SortThread : SortArrival; + *def_sort_rev = 1; + return(0); + } +*************** +*** 572,578 **** + if(ps_global->sort_types[x] == EndofList) + return(-1); + +! *def_sort = ps_global->sort_types[x]; + *def_sort_rev = reverse; + return(0); + } +--- 581,587 ---- + if(ps_global->sort_types[x] == EndofList) + return(-1); + +! *def_sort = ps_global->sort_types[x]; + *def_sort_rev = reverse; + return(0); + } +*************** +*** 689,695 **** + + /* 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)){ +--- 698,706 ---- + + /* set default order */ + the_sort_order = ps_global->def_sort; +! sort_is_rev = the_sort_order == SortThread +! ? (ps_global->thread_def_sort_rev + ps_global->def_sort_rev) % 2 +! : ps_global->def_sort_rev; + + if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ + for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ +*************** +*** 702,710 **** + && 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); + } +--- 713,727 ---- + && pat->action->sort_is_set){ + the_sort_order = pat->action->sortorder; + sort_is_rev = pat->action->revsort; ++ sort_is_rev = the_sort_order == SortThread ++ ? (ps_global->thread_def_sort_rev + pat->action->revsort) % 2 ++ : pat->action->revsort; + } + } + ++ if(the_sort_order == SortThread && !(flags & SRT_MAN)) ++ ps_global->thread_cur_sort = ps_global->thread_def_sort; ++ + sort_folder(ps_global->mail_stream, ps_global->msgmap, +! the_sort_order, sort_is_rev, flags, 1); + } +diff -rc alpine-2.21/pith/sort.h alpine-2.21.fancy/pith/sort.h +*** alpine-2.21/pith/sort.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/sort.h Sun Feb 5 16:15:20 2017 +*************** +*** 23,29 **** + + + #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; +--- 23,29 ---- + + + #define refresh_sort(S,M,F) sort_folder((S), (M), mn_get_sort(M), \ +! mn_get_revsort(M), (F), 1) + + struct global_sort_data { + MSGNO_S *msgmap; +*************** +*** 42,49 **** + + /* 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); + + +--- 42,49 ---- + + /* exported protoypes */ + char *sort_name(SortOrder); +! void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned, int); +! int decode_sort(char *, SortOrder *, int *, int); + void reset_sort_order(unsigned); + + +diff -rc alpine-2.21/pith/state.c alpine-2.21.fancy/pith/state.c +*** alpine-2.21/pith/state.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/state.c Sun Feb 5 16:15:20 2017 +*************** +*** 74,79 **** +--- 74,80 ---- + + p = (struct pine *)fs_get(sizeof (struct pine)); + memset((void *) p, 0, sizeof(struct pine)); ++ p->thread_def_sort = SortDate; + p->def_sort = SortArrival; + p->sort_types[0] = SortSubject; + p->sort_types[1] = SortArrival; +diff -rc alpine-2.21/pith/state.h alpine-2.21.fancy/pith/state.h +*** alpine-2.21/pith/state.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/state.h Sun Feb 5 16:15:20 2017 +*************** +*** 139,144 **** +--- 139,146 ---- + 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 thread_def_sort_rev:1; /* true if reverse sort is default in thread screen */ ++ unsigned msgmap_thread_def_sort_rev:1; /* true if reverse sort is being used in thread screen */ + unsigned restricted:1; + + unsigned tcptimeout:1; /* a tcp timeout is in progress */ +*************** +*** 300,305 **** +--- 302,310 ---- + EditWhich ew_for_srch_take; + + SortOrder def_sort, /* Default sort type */ ++ thread_def_sort, /* Default Sort Type in Thread Screen */ ++ thread_cur_sort, /* current sort style for threads */ ++ msgmap_thread_sort, + sort_types[22]; + + int last_expire_year, last_expire_month; +diff -rc alpine-2.21/pith/thread.c alpine-2.21.fancy/pith/thread.c +*** alpine-2.21/pith/thread.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/thread.c Sun Feb 5 16:15:20 2017 +*************** +*** 30,41 **** + #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 *); +--- 30,47 ---- + #include "../pith/mailcmd.h" + #include "../pith/ablookup.h" + ++ static int erase_thread_info = 1; ++ ++ typedef struct sizethread_t { ++ int count; ++ long pos; ++ } SIZETHREAD_T; + + /* + * Internal prototypes + */ + long *sort_thread_flatten(THREADNODE *, MAILSTREAM *, long *, +! char *, long, PINETHRD_S *, unsigned, int, long, long); + void make_thrdflags_consistent(MAILSTREAM *, MSGNO_S *, PINETHRD_S *, int); + THREADNODE *collapse_threadnode_tree(THREADNODE *); + THREADNODE *collapse_threadnode_tree_sorted(THREADNODE *); +*************** +*** 43,48 **** +--- 49,55 ---- + THREADNODE *insert_tree_in_place(THREADNODE *, THREADNODE *); + unsigned long branch_greatest_num(THREADNODE *, int); + long calculate_visible_threads(MAILSTREAM *); ++ int pine_compare_size_thread(const qsort_t *, const qsort_t *); + + + PINETHRD_S * +*************** +*** 95,114 **** + 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); + } +--- 102,123 ---- + set_flags_for_thread(MAILSTREAM *stream, MSGNO_S *msgmap, int f, PINETHRD_S *thrd, int v) + { + PINETHRD_S *nthrd, *bthrd; ++ unsigned long next = 0L, branch = 0L; + + if(!(stream && thrd && msgmap)) + return; + + set_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), f, v); + +! if(next = get_next(stream,thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + set_flags_for_thread(stream, msgmap, f, nthrd, v); + } + +! +! if(branch = get_branch(stream, thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + set_flags_for_thread(stream, msgmap, f, bthrd, v); + } +*************** +*** 122,128 **** + MESSAGECACHE *mc; + PINELT_S *peltp; + +! if(!(stream && stream->spare)) + return; + + ps_global->view_skipped_index = 0; +--- 131,137 ---- + MESSAGECACHE *mc; + PINELT_S *peltp; + +! if(!(stream && stream->spare) || !erase_thread_info) + return; + + ps_global->view_skipped_index = 0; +*************** +*** 155,161 **** + PINETHRD_S *thrd = NULL; + unsigned long msgno, rawno; + int un_view_thread = 0; +! long raw_current; + char *dup_chk = NULL; + + +--- 164,170 ---- + PINETHRD_S *thrd = NULL; + unsigned long msgno, rawno; + int un_view_thread = 0; +! long raw_current, branch; + char *dup_chk = NULL; + + +*************** +*** 168,177 **** + * 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)); +--- 177,187 ---- + * way. If the dummy node is at the top-level, then its children are + * promoted to the top-level as separate threads. + */ +! collapsed_tree = F_ON(F_ENHANCED_THREAD, ps_global) +! ? copy_tree(tree) +! : (F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) +! ? collapse_threadnode_tree_sorted(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)); +*************** +*** 182,188 **** + (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); +--- 192,198 ---- + (void) sort_thread_flatten(collapsed_tree, stream, + &g_sort.msgmap->sort[1], + dup_chk, mn_get_nmsgs(g_sort.msgmap), +! NULL, THD_TOP, 0, 1L, 0L); + + /* reset the inverse array */ + msgno_reset_isort(g_sort.msgmap); +*************** +*** 340,351 **** + 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; +--- 350,363 ---- + else{ + thrd = fetch_head_thread(stream); + while(thrd){ ++ unsigned long raw = thrd->rawno; ++ unsigned long top = top_thread(stream, raw); + /* + * The top-level threads aren't hidden by collapse. + */ + msgno = mn_raw2m(g_sort.msgmap, thrd->rawno); +! if(msgno && !get_lflag(stream, NULL,thrd->rawno, MN_COLL)) +! set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0); + + if(thrd->next){ + PINETHRD_S *nthrd; +*************** +*** 359,367 **** + MN_COLL)); + } + +! if(thrd->nextthd) +! thrd = fetch_thread(stream, thrd->nextthd); +! else + thrd = NULL; + } + } +--- 371,380 ---- + MN_COLL)); + } + +! while (thrd && top_thread(stream, thrd->rawno) == top +! && thrd->nextthd) +! thrd = fetch_thread(stream, thrd->nextthd); +! if (!(thrd && thrd->nextthd)) + thrd = NULL; + } + } +*************** +*** 412,418 **** + int a_parent_is_collapsed) + { + PINETHRD_S *nthrd, *bthrd; +! unsigned long msgno; + + if(!thrd) + return; +--- 425,431 ---- + int a_parent_is_collapsed) + { + PINETHRD_S *nthrd, *bthrd; +! unsigned long msgno, next, branch; + + if(!thrd) + return; +*************** +*** 430,437 **** + 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 +--- 443,450 ---- + set_lflag(stream, msgmap, msgno, MN_CHID, 0); + } + +! if(next = get_next(stream, thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + make_thrdflags_consistent(stream, msgmap, nthrd, + a_parent_is_collapsed +*************** +*** 440,447 **** + MN_COLL)); + } + +! if(thrd->branch){ +! bthrd = fetch_thread(stream, thrd->branch); + if(bthrd) + make_thrdflags_consistent(stream, msgmap, bthrd, + a_parent_is_collapsed); +--- 453,460 ---- + MN_COLL)); + } + +! if(branch = get_branch(stream, thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + make_thrdflags_consistent(stream, msgmap, bthrd, + a_parent_is_collapsed); +*************** +*** 488,496 **** + 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 */ +--- 501,510 ---- + long * + sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream, + long *entry, char *dup_chk, long maxno, +! PINETHRD_S *thrd, unsigned int flags, +! int adopted, long top, long threadno) + { +! PINETHRD_S *newthrd = NULL, *save_thread = NULL; + + if(node){ + if(node->num > 0L && node->num <= maxno){ /* holes happen */ +*************** +*** 498,503 **** +--- 512,520 ---- + *entry = node->num; + dup_chk[node->num] = 1; + ++ if(adopted == 2) ++ top = node->num; ++ + /* + * Build a richer threading structure that will help us paint + * and operate on threads and subthreads. +*************** +*** 506,525 **** + 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); +--- 523,573 ---- + if(newthrd){ + entry++; + ++ if(adopted == 2) ++ threadno = newthrd->thrdno; ++ if(adopted){ ++ newthrd->toploose = top; ++ newthrd->thrdno = threadno; ++ } ++ adopted = adopted ? 1 : 0; + if(node->next) + entry = sort_thread_flatten(node->next, stream, + entry, dup_chk, maxno, +! newthrd, THD_NEXT, adopted, top, threadno); + + if(node->branch) + entry = sort_thread_flatten(node->branch, stream, + entry, dup_chk, maxno, + newthrd, +! ((flags == THD_TOP) ? THD_TOP +! : THD_BRANCH), +! adopted, top, threadno); + } + } + } ++ else{ ++ adopted = 2; ++ if(node->next) ++ entry = sort_thread_flatten(node->next, stream, entry, dup_chk, ++ maxno, thrd, THD_TOP, adopted, top, threadno); ++ adopted = 0; ++ if(node->branch){ ++ if(entry){ ++ long *last_entry = entry; ++ ++ do{ ++ last_entry--; ++ save_thread = ((PINELT_S *)mail_elt(stream, *last_entry)->sparep)->pthrd; ++ } while (save_thread->parent != 0L); ++ entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, ++ maxno, save_thread, (flags == THD_TOP ? THD_TOP : THD_BRANCH), ++ adopted, top, threadno); ++ } ++ else ++ entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, ++ maxno, NULL, THD_TOP, adopted, top, threadno); ++ } ++ } + } + + return(entry); +*************** +*** 788,794 **** + */ + 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; +--- 836,842 ---- + */ + void + collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, +! long unsigned int msgno, int display) + { + int collapsed, adjust_current = 0; + PINETHRD_S *thrd = NULL, *nthrd; +*************** +*** 841,847 **** + if(!thrd) + return; + +! collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL) && thrd->next; + + if(collapsed){ + msgno = mn_raw2m(msgmap, thrd->rawno); +--- 889,895 ---- + if(!thrd) + return; + +! collapsed = this_thread_is_kolapsed(ps_global, stream, msgmap, thrd->rawno); + + if(collapsed){ + msgno = mn_raw2m(msgmap, thrd->rawno); +*************** +*** 859,871 **** + 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")); + +--- 907,919 ---- + msgno = mn_raw2m(msgmap, thrd->rawno); + if(msgno > 0L && msgno <= mn_get_total(msgmap)){ + set_lflag(stream, msgmap, msgno, MN_COLL, 1); +! if((thrd->next) && ((nthrd = fetch_thread(stream, thrd->next)) != NULL)) + set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID); + + clear_index_cache_ent(stream, msgno, 0); + } + } +! else if(display) + q_status_message(SM_ORDER, 0, 1, + _("No thread to collapse or expand on this line")); + +*************** +*** 952,969 **** + 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); + } +--- 1000,1018 ---- + unsigned long count = 0; + PINETHRD_S *nthrd, *bthrd; + MESSAGECACHE *mc; ++ unsigned long next = 0L, branch = 0L; + + if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) + return count; + +! if(next = get_next(stream, thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + count += count_flags_in_thread(stream, nthrd, flags); + } + +! if(branch = get_branch(stream, thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + count += count_flags_in_thread(stream, bthrd, flags); + } +*************** +*** 1051,1070 **** + 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); + } +--- 1100,1120 ---- + mark_msgs_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap) + { + int count = 0; ++ long next, branch; + PINETHRD_S *nthrd, *bthrd; + MESSAGECACHE *mc; + + if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) + return count; + +! if(next = get_next(stream, thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + count += mark_msgs_in_thread(stream, nthrd, msgmap); + } + +! if(branch = get_branch(stream, thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + count += mark_msgs_in_thread(stream, bthrd, msgmap); + } +*************** +*** 1098,1104 **** + /* 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) +--- 1148,1154 ---- + /* flags to set or clear */ + /* set or clear? */ + { +! unsigned long msgno, next, branch; + PINETHRD_S *nthrd, *bthrd; + + if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) +*************** +*** 1122,1135 **** + 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); + } +--- 1172,1185 ---- + if(msgno > 0L && flags == MN_CHID2 && v == 1) + clear_index_cache_ent(stream, msgno, 0); + +! if(next = get_next(stream, thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + set_thread_lflags(stream, nthrd, msgmap, flags, v); + } + +! if(branch = get_branch(stream,thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + set_thread_lflags(stream, bthrd, msgmap, flags, v); + } +*************** +*** 1218,1236 **** + 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); +--- 1268,1287 ---- + char to_us = ' '; + char branch_to_us = ' '; + PINETHRD_S *nthrd, *bthrd; ++ unsigned long next = 0L, branch = 0L; + MESSAGECACHE *mc; + + if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) + return to_us; + +! if(next = get_next(stream,thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + to_us = to_us_symbol_for_thread(stream, nthrd, consider_flagged); + } + + if(((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+')) +! && (branch = get_branch(stream, thrd))){ + bthrd = fetch_thread(stream, thrd->branch); + if(bthrd) + branch_to_us = to_us_symbol_for_thread(stream, bthrd, consider_flagged); +*************** +*** 1280,1286 **** + break; + } + +! if(to_us != '+' && resent_to_us(&idata)) + to_us = '+'; + + if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) +--- 1331,1337 ---- + break; + } + +! if(to_us != '+' && !idata.bogus && resent_to_us(&idata)) + to_us = '+'; + + if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) +*************** +*** 1328,1334 **** + + 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); +--- 1379,1386 ---- + + 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); +*************** +*** 1368,1375 **** + 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; +--- 1420,1427 ---- + if(rawno) + thrd = fetch_thread(stream, rawno); + +! if(thrd && thrd->top && top_thread(stream,thrd->top) != thrd->rawno) +! thrd = fetch_thread(stream, top_thread(stream,thrd->top)); + + if(!thrd) + return 0; +*************** +*** 1433,1439 **** + thrd = fetch_thread(stream, rawno); + + if(thrd && thrd->top) +! topthrd = fetch_thread(stream, thrd->top); + + if(!topthrd) + return 0; +--- 1485,1491 ---- + thrd = fetch_thread(stream, rawno); + + if(thrd && thrd->top) +! topthrd = fetch_thread(stream, top_thread(stream,thrd->top)); + + if(!topthrd) + return 0; +*************** +*** 1539,1544 **** +--- 1591,1597 ---- + set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgset) + { + PINETHRD_S *nthrd, *bthrd; ++ unsigned long next, branch; + + if(!(stream && thrd)) + return; +*************** +*** 1547,1561 **** + && (!(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); + } + } +--- 1600,2221 ---- + && (!(msgset && *msgset) || in_searchset(*msgset, thrd->rawno))) + mm_searched(stream, thrd->rawno); + +! if(next= get_next(stream, thrd)){ +! nthrd = fetch_thread(stream, next); + if(nthrd) + set_search_bit_for_thread(stream, nthrd, msgset); + } + +! if(branch = get_branch(stream, thrd)){ +! bthrd = fetch_thread(stream, branch); + if(bthrd) + set_search_bit_for_thread(stream, bthrd, msgset); + } + } ++ ++ /* ++ * Make a copy of c-client's THREAD tree ++ */ ++ THREADNODE * ++ copy_tree(THREADNODE *tree) ++ { ++ THREADNODE *newtree = NULL; ++ ++ if(tree){ ++ newtree = mail_newthreadnode(NULL); ++ newtree->num = tree->num; ++ if(tree->next) ++ newtree->next = copy_tree(tree->next); ++ ++ if(tree->branch) ++ newtree->branch = copy_tree(tree->branch); ++ } ++ return(newtree); ++ } ++ ++ long ++ top_thread(MAILSTREAM *stream, long rawmsgno) ++ { ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno; ++ ++ if(!stream) ++ return -1L; ++ ++ if(rawmsgno) ++ thrd = fetch_thread(stream, rawmsgno); ++ ++ if(!thrd) ++ return -1L; ++ ++ return F_ON(F_ENHANCED_THREAD, ps_global) ++ ? (thrd->toploose ? thrd->toploose : thrd->top) ++ : thrd->top; ++ } ++ ++ void ++ move_top_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) ++ { ++ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream, rawmsgno))); ++ } ++ ++ long ++ top_this_thread(MAILSTREAM *stream, long rawmsgno) ++ { ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno; ++ ++ if(!stream) ++ return -1L; ++ ++ if(rawmsgno) ++ thrd = fetch_thread(stream, rawmsgno); ++ ++ if(!thrd) ++ return -1L; ++ ++ return thrd->top; ++ } ++ ++ void ++ move_top_this_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) ++ { ++ mn_set_cur(msgmap,mn_raw2m(msgmap, top_this_thread(stream, rawmsgno))); ++ } ++ ++ int ++ thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) ++ { ++ int collapsed; ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno, orig, orig_rawno; ++ ++ if(!stream) ++ return -1; ++ ++ orig = mn_get_cur(msgmap); ++ move_top_thread(stream, msgmap, rawmsgno); ++ rawno = orig_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return -1; ++ ++ while(collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno)) ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) ++ || !(rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_rawno != top_thread(stream, rawno))) ++ break; ++ ++ mn_set_cur(msgmap,orig); /* return home */ ++ ++ return collapsed; ++ } ++ ++ /* this function tells us if the thread (or branch in the case of loose threads) ++ * is collapsed ++ */ ++ ++ int ++ this_thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) ++ { ++ int collapsed; ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno, orig; ++ ++ if(!stream) ++ return -1; ++ ++ rawno = rawmsgno; ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return -1; ++ ++ collapsed = get_lflag(stream, NULL, rawno, MN_COLL | MN_CHID); ++ ++ if (!thrd->next){ ++ if (thrd->rawno != top_thread(stream, thrd->rawno)) ++ collapsed = get_lflag(stream, NULL, rawno, MN_CHID); ++ else ++ collapsed = get_lflag(stream, NULL, rawno, MN_COLL); ++ } ++ ++ return collapsed; ++ } ++ ++ /* ++ * This function assumes that it is called at a top of a thread in its ++ * first call ++ */ ++ ++ int ++ count_this_thread(MAILSTREAM *stream, unsigned long rawno) ++ { ++ unsigned long top, orig_top, topnxt; ++ PINETHRD_S *thrd = NULL; ++ int count = 1; ++ ++ if(!stream) ++ return 0; ++ ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return 0; ++ ++ if (thrd->next) ++ count += count_this_thread(stream, thrd->next); ++ ++ if (thrd->branch) ++ count += count_this_thread(stream, thrd->branch); ++ ++ return count; ++ } ++ ++ int ++ count_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawno) ++ { ++ unsigned long top, orig, orig_top; ++ PINETHRD_S *thrd = NULL; ++ int done = 0, count = 0; ++ ++ if(!stream) ++ return 0; ++ ++ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_thread(stream, msgmap,rawno); ++ top = orig_top = top_thread(stream, rawno); ++ if(top) ++ thrd = fetch_thread(stream, top); ++ ++ if(!thrd) ++ return 0; ++ ++ while (!done){ ++ count += count_this_thread(stream, top); ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || (move_next_this_thread(state, stream, msgmap, 0) <= 0) ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; ++ } ++ mn_set_cur(msgmap,mn_raw2m(msgmap, orig)); ++ return count; ++ } ++ ++ unsigned long ++ get_branch(MAILSTREAM *stream, PINETHRD_S *thrd) ++ { ++ PINETHRD_S *nthrd = NULL; ++ unsigned long top; ++ ++ if (thrd->toploose && thrd->nextthd) ++ nthrd = fetch_thread(stream, thrd->nextthd); ++ if (!nthrd) ++ return thrd->branch; ++ top = top_thread(stream, thrd->rawno); ++ return thrd->branch ++ ? thrd->branch ++ : (F_ON(F_ENHANCED_THREAD, ps_global) ++ ? (top == top_thread(stream, nthrd->rawno) ? thrd->nextthd : 0L) ++ : 0L); ++ } ++ ++ unsigned long ++ get_next(MAILSTREAM *stream, PINETHRD_S *thrd) ++ { ++ return thrd->next; ++ } ++ ++ long ++ get_length_branch(MAILSTREAM *stream, long rawno) ++ { ++ int branchp = 0, done = 0; ++ long top, count = 1L, raw; ++ PINETHRD_S *thrd, *pthrd = NULL, *nthrd; ++ ++ thrd = fetch_thread(stream, rawno); ++ ++ if (!thrd) ++ return -1L; ++ ++ top = thrd->top; ++ ++ if (thrd->parent) ++ pthrd = fetch_thread(stream, thrd->parent); ++ ++ if (thrd->rawno == top) ++ branchp++; ++ ++ if (!branchp && !pthrd){ /* what!!?? */ ++ raw = top; ++ while (!done){ ++ pthrd = fetch_thread(stream, raw); ++ if ((pthrd->next == rawno) || (pthrd->branch == rawno)) ++ done++; ++ else{ ++ if (pthrd->next) ++ raw = pthrd->next; ++ else if (pthrd->branch) ++ raw = pthrd->branch; ++ } ++ } ++ } ++ ++ if (pthrd && pthrd->next == thrd->rawno && thrd->branch) ++ branchp++; ++ ++ if (pthrd && pthrd->next && pthrd->next != thrd->rawno){ ++ nthrd = fetch_thread(stream, pthrd->next); ++ while (nthrd && nthrd->branch && nthrd->branch != thrd->rawno) ++ nthrd = fetch_thread(stream, nthrd->branch); ++ if(nthrd && nthrd->branch && nthrd->branch == thrd->rawno) ++ branchp++; ++ } ++ ++ if(branchp){ ++ int entry = 0; ++ while(thrd && thrd->next){ ++ entry = 1; ++ count++; ++ thrd = fetch_thread(stream, thrd->next); ++ if (thrd->branch) ++ break; ++ } ++ if (entry && thrd->branch) ++ count--; ++ } ++ return branchp ? (count ? count : 1L) : 0L; ++ } ++ ++ int pine_compare_size_thread(const qsort_t *a, const qsort_t *b) ++ { ++ SIZETHREAD_T *s = (SIZETHREAD_T *) a, *t = (SIZETHREAD_T *) b; ++ ++ return s->count == t->count ? s->pos - t->pos : s->count - t->count; ++ } ++ ++ ++ ++ void ++ find_msgmap(MAILSTREAM *stream, MSGNO_S *msgmap, int flags, SortOrder ordersort, unsigned is_rev) ++ { ++ long *old_arrival,*new_arrival; ++ long init_thread, end_thread, current; ++ long i, j, k; ++ long tmsg, ntmsg, nthreads; ++ SIZETHREAD_T *l; ++ PINETHRD_S *thrd; ++ ++ erase_thread_info = 0; ++ current = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ switch(ordersort){ ++ case SortSize: ++ sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); ++ tmsg = mn_get_total(msgmap) + 1; ++ ++ if(tmsg <= 1) ++ return; ++ ++ for (i= 1L, k = 0L; i <= mn_get_total(msgmap); i += count_thread(ps_global, stream, msgmap, msgmap->sort[i]), k++); ++ l = (SIZETHREAD_T *) fs_get(k*sizeof(SIZETHREAD_T)); ++ for (j = 0L, i=1L; j < k && i<= mn_get_total(msgmap); ){ ++ l[j].count = count_thread(ps_global, stream, msgmap, msgmap->sort[i]); ++ l[j].pos = i; ++ i += l[j].count; ++ j++; ++ } ++ qsort((void *)l, (size_t) k, sizeof(SIZETHREAD_T), pine_compare_size_thread); ++ old_arrival = (long *) fs_get(tmsg * sizeof(long)); ++ for(i = 1L, j = 0; j < k; j++){ /* copy thread of length .count */ ++ int p; ++ for(p = 0; p < l[j].count; p++) ++ old_arrival[i++] = msgmap->sort[l[j].pos + p]; ++ } ++ fs_give((void **)&l); ++ break; ++ default: ++ sort_folder(stream, msgmap, ordersort, 0, SRT_VRB, 0); ++ tmsg = mn_get_total(msgmap) + 1; ++ ++ if (tmsg <= 1) ++ return; ++ ++ old_arrival = (long *) fs_get(tmsg * sizeof(long)); ++ for (i= 1L;(i <= mn_get_total(msgmap)) && (old_arrival[i] = msgmap->sort[i]); i++); ++ /* sort by thread */ ++ sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); ++ break; ++ + } -+ if (entry && thrd->branch) -+ count--; -+ } -+ return branchp ? (count ? count : 1L) : 0L; -+} -+ -+int pine_compare_size_thread(const qsort_t *a, const qsort_t *b) -+{ -+ SIZETHREAD_T *s = (SIZETHREAD_T *) a, *t = (SIZETHREAD_T *) b; -+ -+ return s->count == t->count ? s->pos - t->pos : s->count - t->count; -+} -+ -+ -+ -+void -+find_msgmap(MAILSTREAM *stream, MSGNO_S *msgmap, int flags, SortOrder ordersort, unsigned is_rev) -+{ -+ long *old_arrival,*new_arrival; -+ long init_thread, end_thread, current; -+ long i, j, k; -+ long tmsg, ntmsg, nthreads; -+ SIZETHREAD_T *l; ++ ++ ntmsg = mn_get_total(msgmap) + 1; ++ if (tmsg != ntmsg){ /* oh oh, something happened, we better try again */ ++ fs_give((void **)&old_arrival); ++ find_msgmap(stream, msgmap, flags, ordersort, is_rev); ++ return; ++ } ++ ++ /* reconstruct the msgmap */ ++ ++ new_arrival = (long *) fs_get(tmsg * sizeof(long)); ++ memset(new_arrival, 0, tmsg*sizeof(long)); ++ i = mn_get_total(msgmap); ++ /* we copy from the bottom, the last one to be filled is new_arrival[1] */ ++ while (new_arrival[1] == 0){ ++ int done = 0; ++ long n; ++ ++ init_thread = top_thread(stream, old_arrival[i]); ++ thrd = fetch_thread(stream, init_thread); ++ for (n = mn_get_total(msgmap); new_arrival[n] != 0 && !done; n--) ++ done = (new_arrival[n] == init_thread); ++ if (!done){ ++ mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); ++ if(move_next_thread(ps_global, stream, msgmap, 0) <= 0) ++ j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; ++ else ++ j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); ++ end_thread = mn_raw2m(msgmap, init_thread) + j; ++ for(k = 1L; k <= j; k++) ++ new_arrival[tmsg - k] = msgmap->sort[end_thread - k]; ++ tmsg -= j; ++ } ++ i--; ++ } ++ relink_threads(stream, msgmap, new_arrival); ++ for (i = 1; (i <= mn_get_total(msgmap)) ++ && (msgmap->sort[i] = new_arrival[i]); i++); ++ msgno_reset_isort(msgmap); ++ ++ fs_give((void **)&new_arrival); ++ fs_give((void **)&old_arrival); ++ ++ ++ if(is_rev && (mn_get_total(msgmap) > 1L)){ ++ long *rev_sort; ++ long i = 1L, l = mn_get_total(msgmap); ++ ++ rev_sort = (long *) fs_get((mn_get_total(msgmap)+1L) * sizeof(long)); ++ memset(rev_sort, 0, (mn_get_total(msgmap)+1L)*sizeof(long)); ++ while (l > 0L){ ++ if (top_thread(stream, msgmap->sort[l]) == msgmap->sort[l]){ ++ long init_thread = msgmap->sort[l]; ++ long j, k; ++ ++ mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); ++ if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) ++ j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; ++ else ++ j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); ++ for (k = 0L; (k < j) && (rev_sort[i+k] = msgmap->sort[l+k]); k++); ++ i += j; ++ } ++ l--; ++ } ++ relink_threads(stream, msgmap, rev_sort); ++ for (i = 1L; i <= mn_get_total(msgmap); i++) ++ msgmap->sort[i] = rev_sort[i]; ++ msgno_reset_isort(msgmap); ++ fs_give((void **)&rev_sort); ++ } ++ mn_reset_cur(msgmap, first_sorted_flagged(is_rev ? F_NONE : F_SRCHBACK, ++ stream, mn_raw2m(msgmap, current), FSF_SKIP_CHID)); ++ msgmap->top = -1L; ++ ++ sp_set_unsorted_newmail(ps_global->mail_stream, 0); ++ ++ for(i = 1L; i <= ps_global->mail_stream->nmsgs; i++) ++ mail_elt(ps_global->mail_stream, i)->spare7 = 0; ++ ++ mn_set_sort(msgmap, SortThread); ++ mn_set_revsort(msgmap, is_rev); ++ erase_thread_info = 1; ++ clear_index_cache(stream, 0); ++ } ++ ++ void ++ move_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int direction) ++ { ++ long new_cursor, old_cursor = mn_get_cur(msgmap); ++ int rv; + PINETHRD_S *thrd; + -+ erase_thread_info = 0; -+ current = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ switch(ordersort){ -+ case SortSize: -+ sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); -+ tmsg = mn_get_total(msgmap) + 1; -+ -+ if(tmsg <= 1) -+ return; -+ -+ for (i= 1L, k = 0L; i <= mn_get_total(msgmap); i += count_thread(ps_global, stream, msgmap, msgmap->sort[i]), k++); -+ l = (SIZETHREAD_T *) fs_get(k*sizeof(SIZETHREAD_T)); -+ for (j = 0L, i=1L; j < k && i<= mn_get_total(msgmap); ){ -+ l[j].count = count_thread(ps_global, stream, msgmap, msgmap->sort[i]); -+ l[j].pos = i; -+ i += l[j].count; -+ j++; -+ } -+ qsort((void *)l, (size_t) k, sizeof(SIZETHREAD_T), pine_compare_size_thread); -+ old_arrival = (long *) fs_get(tmsg * sizeof(long)); -+ for(i = 1L, j = 0; j < k; j++){ /* copy thread of length .count */ -+ int p; -+ for(p = 0; p < l[j].count; p++) -+ old_arrival[i++] = msgmap->sort[l[j].pos + p]; -+ } -+ fs_give((void **)&l); -+ break; -+ default: -+ sort_folder(stream, msgmap, ordersort, 0, SRT_VRB, 0); -+ tmsg = mn_get_total(msgmap) + 1; -+ -+ if (tmsg <= 1) -+ return; -+ -+ old_arrival = (long *) fs_get(tmsg * sizeof(long)); -+ for (i= 1L;(i <= mn_get_total(msgmap)) && (old_arrival[i] = msgmap->sort[i]); i++); -+ /* sort by thread */ -+ sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); -+ break; -+ -+ } -+ -+ ntmsg = mn_get_total(msgmap) + 1; -+ if (tmsg != ntmsg){ /* oh oh, something happened, we better try again */ -+ fs_give((void **)&old_arrival); -+ find_msgmap(stream, msgmap, flags, ordersort, is_rev); -+ return; -+ } -+ -+ /* reconstruct the msgmap */ -+ -+ new_arrival = (long *) fs_get(tmsg * sizeof(long)); -+ memset(new_arrival, 0, tmsg*sizeof(long)); -+ i = mn_get_total(msgmap); -+ /* we copy from the bottom, the last one to be filled is new_arrival[1] */ -+ while (new_arrival[1] == 0){ -+ int done = 0; -+ long n; -+ -+ init_thread = top_thread(stream, old_arrival[i]); -+ thrd = fetch_thread(stream, init_thread); -+ for (n = mn_get_total(msgmap); new_arrival[n] != 0 && !done; n--) -+ done = (new_arrival[n] == init_thread); -+ if (!done){ -+ mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); -+ if(move_next_thread(ps_global, stream, msgmap, 0) <= 0) -+ j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; -+ else -+ j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); -+ end_thread = mn_raw2m(msgmap, init_thread) + j; -+ for(k = 1L; k <= j; k++) -+ new_arrival[tmsg - k] = msgmap->sort[end_thread - k]; -+ tmsg -= j; -+ } -+ i--; -+ } -+ relink_threads(stream, msgmap, new_arrival); -+ for (i = 1; (i <= mn_get_total(msgmap)) -+ && (msgmap->sort[i] = new_arrival[i]); i++); -+ msgno_reset_isort(msgmap); -+ -+ fs_give((void **)&new_arrival); -+ fs_give((void **)&old_arrival); -+ -+ -+ if(is_rev && (mn_get_total(msgmap) > 1L)){ -+ long *rev_sort; -+ long i = 1L, l = mn_get_total(msgmap); -+ -+ rev_sort = (long *) fs_get((mn_get_total(msgmap)+1L) * sizeof(long)); -+ memset(rev_sort, 0, (mn_get_total(msgmap)+1L)*sizeof(long)); -+ while (l > 0L){ -+ if (top_thread(stream, msgmap->sort[l]) == msgmap->sort[l]){ -+ long init_thread = msgmap->sort[l]; -+ long j, k; -+ -+ mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); -+ if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) -+ j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; -+ else -+ j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); -+ for (k = 0L; (k < j) && (rev_sort[i+k] = msgmap->sort[l+k]); k++); -+ i += j; -+ } -+ l--; -+ } -+ relink_threads(stream, msgmap, rev_sort); -+ for (i = 1L; i <= mn_get_total(msgmap); i++) -+ msgmap->sort[i] = rev_sort[i]; -+ msgno_reset_isort(msgmap); -+ fs_give((void **)&rev_sort); -+ } -+ mn_reset_cur(msgmap, first_sorted_flagged(is_rev ? F_NONE : F_SRCHBACK, -+ stream, mn_raw2m(msgmap, current), FSF_SKIP_CHID)); -+ msgmap->top = -1L; -+ -+ sp_set_unsorted_newmail(ps_global->mail_stream, 0); -+ -+ for(i = 1L; i <= ps_global->mail_stream->nmsgs; i++) -+ mail_elt(ps_global->mail_stream, i)->spare7 = 0; -+ -+ mn_set_sort(msgmap, SortThread); -+ mn_set_revsort(msgmap, is_rev); -+ erase_thread_info = 1; -+ clear_index_cache(stream, 0); -+} -+ -+void -+move_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int direction) -+{ -+ long new_cursor, old_cursor = mn_get_cur(msgmap); -+ int rv; -+ PINETHRD_S *thrd; -+ -+ rv = direction > 0 ? move_next_thread(state, stream, msgmap, 1): -+ move_prev_thread(state, stream, msgmap, 1); -+ if (rv > 0 && THRD_INDX_ENABLED()){ -+ new_cursor = mn_get_cur(msgmap); -+ mn_set_cur(msgmap, old_cursor); -+ unview_thread(state, stream, msgmap); -+ thrd = fetch_thread(stream,mn_m2raw(msgmap, new_cursor)); -+ mn_set_cur(msgmap, new_cursor); -+ view_thread(state, stream, msgmap, 1); -+ state->next_screen = SCREEN_FUN_NULL; -+ } -+} -+ -+void -+relink_threads(MAILSTREAM *stream, MSGNO_S *msgmap, long *new_arrival) -+{ -+ long last_thread = 0L; -+ long i = 0L, j = 1L, k; -+ PINETHRD_S *thrd, *nthrd; -+ -+ while (j <= mn_get_total(msgmap)){ -+ i++; -+ thrd = fetch_thread(stream, new_arrival[j]); -+ if (!thrd) /* sort failed!, better leave from here now!!! */ -+ break; -+ thrd->prevthd = last_thread; -+ thrd->thrdno = i; -+ thrd->head = new_arrival[1]; -+ last_thread = thrd->rawno; -+ mn_set_cur(msgmap, mn_raw2m(msgmap,thrd->top)); -+ k = mn_get_cur(msgmap); -+ if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) -+ j += mn_get_total(msgmap) + 1 - k; -+ else -+ j += mn_get_cur(msgmap) - k; -+ if (!thrd->toploose) -+ thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; -+ else{ -+ int done = 0; -+ while(thrd->nextthd && !done){ -+ thrd->thrdno = i; -+ thrd->head = new_arrival[1]; -+ if (thrd->nextthd) -+ nthrd = fetch_thread(stream, thrd->nextthd); -+ else -+ done++; -+ if(top_thread(stream, thrd->rawno) == top_thread(stream, nthrd->rawno)) -+ thrd = nthrd; -+ else -+ done++; -+ } -+ thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; -+ last_thread = thrd->rawno; -+ } -+ } -+} -+ -+int -+move_next_this_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) -+{ -+ PINETHRD_S *thrd = NULL, *thrdnxt; -+ unsigned long rawno, top; -+ int rv = 1; -+ -+ if(!stream) ++ rv = direction > 0 ? move_next_thread(state, stream, msgmap, 1): ++ move_prev_thread(state, stream, msgmap, 1); ++ if (rv > 0 && THRD_INDX_ENABLED()){ ++ new_cursor = mn_get_cur(msgmap); ++ mn_set_cur(msgmap, old_cursor); ++ unview_thread(state, stream, msgmap); ++ thrd = fetch_thread(stream,mn_m2raw(msgmap, new_cursor)); ++ mn_set_cur(msgmap, new_cursor); ++ view_thread(state, stream, msgmap, 1); ++ state->next_screen = SCREEN_FUN_NULL; ++ } ++ } ++ ++ void ++ relink_threads(MAILSTREAM *stream, MSGNO_S *msgmap, long *new_arrival) ++ { ++ long last_thread = 0L; ++ long i = 0L, j = 1L, k; ++ PINETHRD_S *thrd, *nthrd; ++ ++ while (j <= mn_get_total(msgmap)){ ++ i++; ++ thrd = fetch_thread(stream, new_arrival[j]); ++ if (!thrd) /* sort failed!, better leave from here now!!! */ ++ break; ++ thrd->prevthd = last_thread; ++ thrd->thrdno = i; ++ thrd->head = new_arrival[1]; ++ last_thread = thrd->rawno; ++ mn_set_cur(msgmap, mn_raw2m(msgmap,thrd->top)); ++ k = mn_get_cur(msgmap); ++ if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) ++ j += mn_get_total(msgmap) + 1 - k; ++ else ++ j += mn_get_cur(msgmap) - k; ++ if (!thrd->toploose) ++ thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; ++ else{ ++ int done = 0; ++ while(thrd->nextthd && !done){ ++ thrd->thrdno = i; ++ thrd->head = new_arrival[1]; ++ if (thrd->nextthd) ++ nthrd = fetch_thread(stream, thrd->nextthd); ++ else ++ done++; ++ if(top_thread(stream, thrd->rawno) == top_thread(stream, nthrd->rawno)) ++ thrd = nthrd; ++ else ++ done++; ++ } ++ thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; ++ last_thread = thrd->rawno; ++ } ++ } ++ } ++ ++ int ++ move_next_this_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) ++ { ++ PINETHRD_S *thrd = NULL, *thrdnxt; ++ unsigned long rawno, top; ++ int rv = 1; ++ ++ if(!stream) ++ return -1; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) + return -1; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return -1; -+ -+ top = top_thread(stream, rawno); -+ -+ thrdnxt = (top == rawno) ? fetch_thread(stream, top) : thrd; -+ if (thrdnxt->nextthd) -+ mn_set_cur(msgmap,mn_raw2m(msgmap, thrdnxt->nextthd)); -+ else{ -+ rv = 0; -+ if (display) -+ q_status_message(SM_ORDER, 0, 1, "No more Threads to advance"); -+ } -+ return rv; -+} -+ -+int -+move_next_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) -+{ -+ int collapsed, rv = 1, done = 0; -+ PINETHRD_S *thrd = NULL; -+ unsigned long orig, orig_top, top; -+ -+ if(!stream) -+ return 0; -+ -+ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ move_top_thread(stream, msgmap,orig); -+ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ -+ if(top) -+ thrd = fetch_thread(stream, top); -+ -+ if(!thrd) -+ return 0; -+ -+ while (rv > 0 && !done){ -+ rv = move_next_this_thread(state, stream, msgmap, display); -+ if (F_OFF(F_ENHANCED_THREAD, state) -+ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) -+ || (orig_top != top_thread(stream, top))) -+ done++; -+ } -+ if (display){ -+ if (rv > 0 && SEP_THRDINDX()) -+ q_status_message(SM_ORDER, 0, 2, "Viewing next thread"); -+ if (!rv) -+ q_status_message(SM_ORDER, 0, 2, "No more threads to advance"); -+ } -+ if(rv <= 0){ -+ rv = 0; -+ mn_set_cur(msgmap, mn_raw2m(msgmap, orig)); -+ } -+ -+ return rv; -+} -+ -+int -+move_prev_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) -+{ -+ PINETHRD_S *thrd = NULL; -+ unsigned long rawno, top; -+ int rv = 1; -+ -+ if(!stream) -+ return -1; -+ -+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); -+ if(rawno) -+ thrd = fetch_thread(stream, rawno); -+ -+ if(!thrd) -+ return -1; -+ ++ + top = top_thread(stream, rawno); -+ -+ if (top != rawno) -+ mn_set_cur(msgmap,mn_raw2m(msgmap, top)); -+ else if (thrd->prevthd) -+ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,thrd->prevthd))); -+ else -+ rv = 0; -+ if (display){ -+ if (rv && SEP_THRDINDX()) -+ q_status_message(SM_ORDER, 0, 2, "Viewing previous thread"); -+ if (!rv) -+ q_status_message(SM_ORDER, 0, 2, "No more threads to go back"); ++ ++ thrdnxt = (top == rawno) ? fetch_thread(stream, top) : thrd; ++ if (thrdnxt->nextthd) ++ mn_set_cur(msgmap,mn_raw2m(msgmap, thrdnxt->nextthd)); ++ else{ ++ rv = 0; ++ if (display) ++ q_status_message(SM_ORDER, 0, 1, "No more Threads to advance"); + } -+ + return rv; -+} -+ -+/* add more keys to this list */ -+int -+allowed_thread_key(SortOrder sort) -+{ -+ return sort == SortArrival || sort == SortDate -+ || sort == SortScore || sort == SortThread -+ || sort == SortSize; -+} -+ -Index: alpine-2.20/pith/thread.h -=================================================================== ---- alpine-2.20.orig/pith/thread.h -+++ alpine-2.20/pith/thread.h -@@ -37,6 +37,7 @@ typedef struct pine_thrd { - 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 toploose; /* top of this thread, if is loose */ - unsigned long head; /* head of the whole thread list */ - } PINETHRD_S; - -@@ -92,7 +93,7 @@ void erase_threading_info(MAILSTRE - 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 collapse_or_expand(struct pine *, MAILSTREAM *, MSGNO_S *, unsigned long, int); - 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); -@@ -106,6 +107,24 @@ int view_thread(struct pine *, MAI - 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 **); -- -+void find_msgmap(MAILSTREAM *, MSGNO_S *, int, SortOrder, unsigned); -+void move_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+void relink_threads(MAILSTREAM *, MSGNO_S *, long *); -+long top_thread(MAILSTREAM *, long); -+long top_this_thread(MAILSTREAM *, long); -+long get_length_branch(MAILSTREAM *, long); -+unsigned long get_next(MAILSTREAM *,PINETHRD_S *); -+unsigned long get_branch(MAILSTREAM *,PINETHRD_S *); -+int count_thread(struct pine *, MAILSTREAM *, MSGNO_S *, long); -+int count_this_thread(MAILSTREAM *, unsigned long); -+int this_thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); -+int thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); -+int move_prev_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+int move_next_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+int move_next_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); -+void move_top_thread(MAILSTREAM *, MSGNO_S *, long); -+void move_top_this_thread(MAILSTREAM *, MSGNO_S *, long); -+THREADNODE *copy_tree(THREADNODE *); -+int allowed_thread_key(SortOrder sort); - - #endif /* PITH_THREAD_INCLUDED */ -Index: alpine-2.20/web/src/alpined.d/alpined.c -=================================================================== ---- alpine-2.20.orig/web/src/alpined.d/alpined.c -+++ alpine-2.20/web/src/alpined.d/alpined.c -@@ -2755,7 +2755,7 @@ PEConfigCmd(ClientData clientData, Tcl_I - init_save_defaults(); - break; - case V_SORT_KEY: -- decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev); -+ decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev, 0); - break; - case V_VIEW_HDR_COLORS : - set_custom_spec_colors(ps_global); -@@ -6331,7 +6331,7 @@ PEMailboxCmd(ClientData clientData, Tcl_ - && mn_get_revsort(sp_msgmap(ps_global->mail_stream)) == reversed)) - sort_folder(ps_global->mail_stream, sp_msgmap(ps_global->mail_stream), - ps_global->sort_types[i], -- reversed, 0); -+ reversed, 0, 1); - - break; - } ++ } ++ ++ int ++ move_next_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) ++ { ++ int collapsed, rv = 1, done = 0; ++ PINETHRD_S *thrd = NULL; ++ unsigned long orig, orig_top, top; ++ ++ if(!stream) ++ return 0; ++ ++ orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ move_top_thread(stream, msgmap,orig); ++ top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ ++ if(top) ++ thrd = fetch_thread(stream, top); ++ ++ if(!thrd) ++ return 0; ++ ++ while (rv > 0 && !done){ ++ rv = move_next_this_thread(state, stream, msgmap, display); ++ if (F_OFF(F_ENHANCED_THREAD, state) ++ || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) ++ || (orig_top != top_thread(stream, top))) ++ done++; ++ } ++ if (display){ ++ if (rv > 0 && SEP_THRDINDX()) ++ q_status_message(SM_ORDER, 0, 2, "Viewing next thread"); ++ if (!rv) ++ q_status_message(SM_ORDER, 0, 2, "No more threads to advance"); ++ } ++ if(rv <= 0){ ++ rv = 0; ++ mn_set_cur(msgmap, mn_raw2m(msgmap, orig)); ++ } ++ ++ return rv; ++ } ++ ++ int ++ move_prev_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) ++ { ++ PINETHRD_S *thrd = NULL; ++ unsigned long rawno, top; ++ int rv = 1; ++ ++ if(!stream) ++ return -1; ++ ++ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); ++ if(rawno) ++ thrd = fetch_thread(stream, rawno); ++ ++ if(!thrd) ++ return -1; ++ ++ top = top_thread(stream, rawno); ++ ++ if (top != rawno) ++ mn_set_cur(msgmap,mn_raw2m(msgmap, top)); ++ else if (thrd->prevthd) ++ mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,thrd->prevthd))); ++ else ++ rv = 0; ++ if (display){ ++ if (rv && SEP_THRDINDX()) ++ q_status_message(SM_ORDER, 0, 2, "Viewing previous thread"); ++ if (!rv) ++ q_status_message(SM_ORDER, 0, 2, "No more threads to go back"); ++ } ++ ++ return rv; ++ } ++ ++ /* add more keys to this list */ ++ int ++ allowed_thread_key(SortOrder sort) ++ { ++ return sort == SortArrival || sort == SortDate ++ || sort == SortScore || sort == SortThread ++ || sort == SortSize; ++ } ++ +diff -rc alpine-2.21/pith/thread.h alpine-2.21.fancy/pith/thread.h +*** alpine-2.21/pith/thread.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/pith/thread.h Sun Feb 5 16:15:20 2017 +*************** +*** 38,43 **** +--- 38,44 ---- + 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 toploose; /* top of this thread, if is loose */ + unsigned long head; /* head of the whole thread list */ + } PINETHRD_S; + +*************** +*** 93,99 **** + 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); +--- 94,100 ---- + 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, int); + 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); +*************** +*** 107,112 **** + 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 */ +--- 108,131 ---- + 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 **); +! void find_msgmap(MAILSTREAM *, MSGNO_S *, int, SortOrder, unsigned); +! void move_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! void relink_threads(MAILSTREAM *, MSGNO_S *, long *); +! long top_thread(MAILSTREAM *, long); +! long top_this_thread(MAILSTREAM *, long); +! long get_length_branch(MAILSTREAM *, long); +! unsigned long get_next(MAILSTREAM *,PINETHRD_S *); +! unsigned long get_branch(MAILSTREAM *,PINETHRD_S *); +! int count_thread(struct pine *, MAILSTREAM *, MSGNO_S *, long); +! int count_this_thread(MAILSTREAM *, unsigned long); +! int this_thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); +! int thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); +! int move_prev_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! int move_next_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! int move_next_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +! void move_top_thread(MAILSTREAM *, MSGNO_S *, long); +! void move_top_this_thread(MAILSTREAM *, MSGNO_S *, long); +! THREADNODE *copy_tree(THREADNODE *); +! int allowed_thread_key(SortOrder sort); + + #endif /* PITH_THREAD_INCLUDED */ +diff -rc alpine-2.21/web/src/alpined.d/alpined.c alpine-2.21.fancy/web/src/alpined.d/alpined.c +*** alpine-2.21/web/src/alpined.d/alpined.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fancy/web/src/alpined.d/alpined.c Sun Feb 5 16:15:20 2017 +*************** +*** 2755,2761 **** + init_save_defaults(); + break; + case V_SORT_KEY: +! decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev); + break; + case V_VIEW_HDR_COLORS : + set_custom_spec_colors(ps_global); +--- 2755,2761 ---- + init_save_defaults(); + break; + case V_SORT_KEY: +! decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev, 0); + break; + case V_VIEW_HDR_COLORS : + set_custom_spec_colors(ps_global); +*************** +*** 6331,6337 **** + && mn_get_revsort(sp_msgmap(ps_global->mail_stream)) == reversed)) + sort_folder(ps_global->mail_stream, sp_msgmap(ps_global->mail_stream), + ps_global->sort_types[i], +! reversed, 0); + + break; + } +--- 6331,6337 ---- + && mn_get_revsort(sp_msgmap(ps_global->mail_stream)) == reversed)) + sort_folder(ps_global->mail_stream, sp_msgmap(ps_global->mail_stream), + ps_global->sort_types[i], +! reversed, 0, 1); + + break; + } diff --git a/chappa-fillpara.patch b/chappa-fillpara.patch new file mode 100644 index 0000000..aa0af4c --- /dev/null +++ b/chappa-fillpara.patch @@ -0,0 +1,4114 @@ +diff -rc alpine-2.21/alpine/mailview.c alpine-2.21.fillpara/alpine/mailview.c +*** alpine-2.21/alpine/mailview.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/alpine/mailview.c Sun Feb 5 16:15:23 2017 +*************** +*** 204,210 **** +--- 204,218 ---- + int pcpine_view_cursor(int, long); + #endif + ++ static char *prefix; ++ #define NO_FLOWED 0x0000 ++ #define IS_FLOWED 0x0001 ++ #define DELETEQUO 0x0010 ++ #define COLORAQUO 0x0100 ++ #define RAWSTRING 0x1000 + ++ int is_word (char *, int, int); ++ int is_mailbox(char *, int, int); + + /*---------------------------------------------------------------------- + Format a buffer with the text of the current message for browser +*************** +*** 295,300 **** +--- 303,319 ---- + else + ps->unseen_in_view = !mc->seen; + ++ prefix = reply_quote_str(env); ++ /* Make sure the prefix is not only made of spaces, so that we do not ++ * paint the screen incorrectly ++ */ ++ if (prefix && *prefix){ ++ int i; ++ for (i = 0; isspace((unsigned char) prefix[i]); i++); ++ if (i == strlen(prefix)) ++ fs_give((void **)&prefix); ++ } ++ + init_handles(&handles); + + store = so_get(src, NULL, EDIT_ACCESS); +*************** +*** 479,484 **** +--- 498,505 ---- + } + while(ps->next_screen == SCREEN_FUN_NULL); + ++ if (prefix && *prefix) ++ fs_give((void **)&prefix); + if(we_cancel) + cancel_busy_cue(-1); + +diff -rc alpine-2.21/pico/basic.c alpine-2.21.fillpara/pico/basic.c +*** alpine-2.21/pico/basic.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/basic.c Sun Feb 5 16:15:23 2017 +*************** +*** 26,34 **** + * framing, are hard. + */ + #include "headers.h" +! + #include "osdep/terminal.h" + + + /* + * Move the cursor to the +--- 26,35 ---- + * framing, are hard. + */ + #include "headers.h" +! #include "../pith/osdep/color.h" + #include "osdep/terminal.h" + ++ int indent_match(char **, LINE *, char *, int, int); + + /* + * Move the cursor to the +*************** +*** 285,291 **** + gotobop(int f, int n) + { + int quoted, qlen; +! UCS qstr[NLINE], qstr2[NLINE]; + + if (n < 0) /* the other way...*/ + return(gotoeop(f, -n)); +--- 286,292 ---- + gotobop(int f, int n) + { + int quoted, qlen; +! char qstr[NLINE], qstr2[NLINE], ind_str[NLINE], pqstr[NLINE];; + + if (n < 0) /* the other way...*/ + return(gotoeop(f, -n)); +*************** +*** 297,302 **** +--- 298,311 ---- + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = 0; + } ++ ++ if (indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp,ind_str, NLINE, 0)){ ++ if (n){ /* look for another paragraph ? */ ++ curwp->w_dotp = lback(curwp->w_dotp); ++ continue; ++ } ++ break; ++ } + + /* scan line by line until we come to a line ending with + * a or or +*************** +*** 304,321 **** + * PLUS: if there's a quote string, a quoted-to-non-quoted + * line transition. + */ +! quoted = quote_match(glo_quote_str, curwp->w_dotp, qstr, NLINE); +! qlen = quoted ? ucs4_strlen(qstr) : 0; + while(lback(curwp->w_dotp) != curbp->b_linep + && llength(lback(curwp->w_dotp)) > qlen +! && quoted == quote_match(glo_quote_str, + lback(curwp->w_dotp), +! qstr2, NLINE) +! && !ucs4_strcmp(qstr, qstr2) +! && lgetc(curwp->w_dotp, qlen).c != TAB +! && lgetc(curwp->w_dotp, qlen).c != ' ') + curwp->w_dotp = lback(curwp->w_dotp); + + if(n){ + /* keep looking */ + if(lback(curwp->w_dotp) == curbp->b_linep) +--- 313,371 ---- + * PLUS: if there's a quote string, a quoted-to-non-quoted + * line transition. + */ +! quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 0); +! qlen = quoted ? strlen(qstr) : 0; + while(lback(curwp->w_dotp) != curbp->b_linep + && llength(lback(curwp->w_dotp)) > qlen +! && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lback(curwp->w_dotp), +! qstr2, NLINE, 0)) +! && !strcmp(qstr, qstr2) /* processed string */ +! && (quoted == quote_match(default_qstr(glo_quote_str, 1), +! lback(curwp->w_dotp), qstr2, NLINE, 1)) +! && !strcmp(qstr, qstr2) /* raw string */ +! && !indent_match(default_qstr(glo_quote_str, 1), +! lback(curwp->w_dotp),ind_str, NLINE, 0) +! && !ISspace(lgetc(curwp->w_dotp, qlen).c)) + curwp->w_dotp = lback(curwp->w_dotp); + ++ /* ++ * Ok, we made it here and we assume that we are at the begining ++ * of the paragraph. Let's double check this now. In order to do ++ * so we shell check if the first line was indented in a special ++ * way. ++ */ ++ if(lback(curwp->w_dotp) == curbp->b_linep) ++ break; ++ else{ ++ int i, j; ++ ++ /* ++ * First we test if the preceding line is indented. ++ * for the following test we need to have the raw values, ++ * not the processed values ++ */ ++ quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1); ++ quote_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp), qstr2, NLINE, 1); ++ for (i = 0, j = 0; ++ qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]); i++, j++); ++ for (; ISspace(qstr2[i]); i++); ++ for (; ISspace(qstr[j]); j++); ++ if ((indent_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp), ++ ind_str, NLINE, 1) ++ && (strlenis(qstr2) ++ + strlenis(ind_str) >= strlenis(qstr))) ++ || (lback(curwp->w_dotp) != curbp->b_linep ++ && llength(lback(curwp->w_dotp)) > qlen ++ && (quoted == quote_match(default_qstr(glo_quote_str, 1), ++ lback(curwp->w_dotp), pqstr, NLINE, 0)) ++ && !strcmp(qstr, pqstr) ++ && !ISspace(lgetc(curwp->w_dotp, qlen).c) ++ && (strlenis(qstr2) > strlenis(qstr))) ++ && !qstr2[i] && !qstr[j]) ++ curwp->w_dotp = lback(curwp->w_dotp); ++ } ++ + if(n){ + /* keep looking */ + if(lback(curwp->w_dotp) == curbp->b_linep) +*************** +*** 328,334 **** + else{ + /* leave cursor on first word in para */ + curwp->w_doto = 0; +! while(ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto).c)) + if(++curwp->w_doto >= llength(curwp->w_dotp)){ + curwp->w_doto = 0; + curwp->w_dotp = lforw(curwp->w_dotp); +--- 378,384 ---- + else{ + /* leave cursor on first word in para */ + curwp->w_doto = 0; +! while(ISspace(lgetc(curwp->w_dotp, curwp->w_doto).c)) + if(++curwp->w_doto >= llength(curwp->w_dotp)){ + curwp->w_doto = 0; + curwp->w_dotp = lforw(curwp->w_dotp); +*************** +*** 351,358 **** + int + gotoeop(int f, int n) + { +! int quoted, qlen; +! UCS qstr[NLINE], qstr2[NLINE]; + + if (n < 0) /* the other way...*/ + return(gotobop(f, -n)); +--- 401,409 ---- + int + gotoeop(int f, int n) + { +! int quoted, qlen, indented, changeqstr = 0; +! int i,j, fli = 0; /* fli = first line indented a boolean variable */ +! char qstr[NLINE], qstr2[NLINE], ind_str[NLINE]; + + if (n < 0) /* the other way...*/ + return(gotobop(f, -n)); +*************** +*** 365,388 **** + break; + } + +! /* scan line by line until we come to a line ending with +! * a or or +! * +! * PLUS: if there's a quote string, a quoted-to-non-quoted +! * line transition. + */ +! quoted = quote_match(glo_quote_str, +! curwp->w_dotp, qstr, NLINE); +! qlen = quoted ? ucs4_strlen(qstr) : 0; + + while(curwp->w_dotp != curbp->b_linep + && llength(lforw(curwp->w_dotp)) > qlen +! && (quoted == quote_match(glo_quote_str, +! lforw(curwp->w_dotp), +! qstr2, NLINE) +! && !ucs4_strcmp(qstr, qstr2)) +! && lgetc(lforw(curwp->w_dotp), qlen).c != TAB +! && lgetc(lforw(curwp->w_dotp), qlen).c != ' ') + curwp->w_dotp = lforw(curwp->w_dotp); + + curwp->w_doto = llength(curwp->w_dotp); +--- 416,485 ---- + break; + } + +! /* +! * We need to figure out if this line is the first line of +! * a paragraph that has been indented in a special way. If this +! * is the case, we advance one more line before we use the +! * algorithm below +! */ +! +! if(curwp->w_dotp != curbp->b_linep){ +! quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1); +! quote_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp), qstr2, NLINE, 1); +! indented = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str, +! NLINE, 1); +! if (strlenis(qstr) +! + strlenis(ind_str) < strlenis(qstr2)){ +! curwp->w_doto = llength(curwp->w_dotp); +! if(n){ /* this line is a paragraph by itself */ +! curwp->w_dotp = lforw(curwp->w_dotp); +! continue; +! } +! break; +! } +! for (i=0,j=0; qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]);i++,j++); +! for (; ISspace(qstr[i]); i++); +! for (; ISspace(qstr2[j]); j++); +! if (!qstr[i] && !qstr2[j] && indented){ +! fli++; +! if (indent_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp), +! ind_str, NLINE, 0)){ +! if (n){ /* look for another paragraph ? */ +! curwp->w_dotp = lforw(curwp->w_dotp); +! continue; +! } +! } +! else{ +! if (!lisblank(lforw(curwp->w_dotp))) +! curwp->w_dotp = lforw(curwp->w_dotp); +! } +! } +! } +! +! /* scan line by line until we come to a line ending with +! * a or or +! * +! * PLUS: if there's a quote string, a quoted-to-non-quoted +! * line transition. +! */ +! /* if the first line is indented (fli == 1), then the test below +! is on the second line, and in that case we will need the raw +! string, not the processed string + */ +! quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, fli); +! qlen = quoted ? strlen(qstr) : 0; + + while(curwp->w_dotp != curbp->b_linep + && llength(lforw(curwp->w_dotp)) > qlen +! && (quoted == quote_match(default_qstr(glo_quote_str, 1), +! lforw(curwp->w_dotp), qstr2, NLINE, fli)) +! && !strcmp(qstr, qstr2) +! && (quoted == quote_match(default_qstr(glo_quote_str, 1), +! lforw(curwp->w_dotp), qstr2, NLINE, 1)) +! && !strcmp(qstr, qstr2) +! && !indent_match(default_qstr(glo_quote_str, 1), +! lforw(curwp->w_dotp), ind_str, NLINE, 0) +! && !ISspace(lgetc(lforw(curwp->w_dotp), qlen).c)) + curwp->w_dotp = lforw(curwp->w_dotp); + + curwp->w_doto = llength(curwp->w_dotp); +diff -rc alpine-2.21/pico/efunc.h alpine-2.21.fillpara/pico/efunc.h +*** alpine-2.21/pico/efunc.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/efunc.h Sun Feb 5 16:15:23 2017 +*************** +*** 251,260 **** + extern int fillpara(int, int); + extern int fillbuf(int, int); + extern int inword(void); +! extern int quote_match(UCS *, LINE *, UCS *, size_t); + extern int ucs4_isalnum(UCS); + extern int ucs4_isalpha(UCS); + extern int ucs4_isspace(UCS); + extern int ucs4_ispunct(UCS); + + #endif /* EFUNC_H */ +--- 251,266 ---- + extern int fillpara(int, int); + extern int fillbuf(int, int); + extern int inword(void); +! extern int quote_match(char **, LINE *, char *, size_t, int); +! extern void flatten_qstring(QSTRING_S *, char *, int); +! extern void free_qs(QSTRING_S **); +! extern QSTRING_S *do_quote_match (char **, char *, char *, char *, char *, int, int); +! extern QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **); +! extern int indent_match(char **, LINE *, char *, int, int); + extern int ucs4_isalnum(UCS); + extern int ucs4_isalpha(UCS); + extern int ucs4_isspace(UCS); + extern int ucs4_ispunct(UCS); + + #endif /* EFUNC_H */ ++ +diff -rc alpine-2.21/pico/line.c alpine-2.21.fillpara/pico/line.c +*** alpine-2.21/pico/line.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/line.c Sun Feb 5 16:15:23 2017 +*************** +*** 611,626 **** + lisblank(LINE *line) + { + int n = 0; +! UCS qstr[NLINE]; + +! n = (glo_quote_str +! && quote_match(glo_quote_str, line, qstr, NLINE)) +! ? ucs4_strlen(qstr) : 0; + + for(; n < llength(line); n++) +! if(!ucs4_isspace(lgetc(line, n).c) +! || lgetc(line, n).c >= 0xff +! || (unsigned char) lgetc(line,n).c != NBSPC) + return(FALSE); + + return(TRUE); +--- 611,622 ---- + lisblank(LINE *line) + { + int n = 0; +! char qstr[NLINE]; + +! n = quote_match(default_qstr(glo_quote_str, 1), line, qstr, NLINE, 1); + + for(; n < llength(line); n++) +! if(!ISspace(lgetc(line, n).c)) + return(FALSE); + + return(TRUE); +diff -rc alpine-2.21/pico/osdep/color.h alpine-2.21.fillpara/pico/osdep/color.h +*** alpine-2.21/pico/osdep/color.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/osdep/color.h Sun Feb 5 16:15:23 2017 +*************** +*** 33,38 **** +--- 33,39 ---- + void pico_toggle_color(int); + void pico_set_nfg_color(void); + void pico_set_nbg_color(void); ++ char **default_qstr(void *, int); + + + #endif /* PICO_OSDEP_COLOR_INCLUDED */ +diff -rc alpine-2.21/pico/search.c alpine-2.21.fillpara/pico/search.c +*** alpine-2.21/pico/search.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/search.c Sun Feb 5 16:15:23 2017 +*************** +*** 36,41 **** +--- 36,42 ---- + int readpattern(char *, int, int); + int replace_pat(UCS *, int *, int); + int replace_all(UCS *, UCS *, int); ++ int deletepara(int, int); + void reverse_line(LINE *); + void reverse_buffer(void); + void reverse_ucs4(UCS *); +*************** +*** 71,77 **** + {"^W", N_("Start of Para"), (CTRL|'W')}, + {"^O", N_("End of Para"), (CTRL|'O')}, + {"^U", N_("FullJustify"), (CTRL|'U')}, +! {NULL, NULL, 0}, + {NULL, NULL, 0} + }; + +--- 72,78 ---- + {"^W", N_("Start of Para"), (CTRL|'W')}, + {"^O", N_("End of Para"), (CTRL|'O')}, + {"^U", N_("FullJustify"), (CTRL|'U')}, +! {"^P", N_("Delete Para"), (CTRL|'P')}, + {NULL, NULL, 0} + }; + +*************** +*** 220,228 **** + + case (CTRL|'P'): + if(flags & SR_ORIGMEN){ +! /* Undefined still */ + } +! if(flags & SR_OPTNMEN){ + if(flags & SR_FORWARD){ + flags &= ~SR_FORWARD; + flags |= SR_BACKWRD; +--- 221,231 ---- + + case (CTRL|'P'): + if(flags & SR_ORIGMEN){ +! deletepara(0, 1); +! mlerase(); +! FWS_RETURN(TRUE); + } +! else if(flags & SR_OPTNMEN){ + if(flags & SR_FORWARD){ + flags &= ~SR_FORWARD; + flags |= SR_BACKWRD; +*************** +*** 1418,1420 **** +--- 1421,1445 ---- + if(bsearch) reverse_ucs4(orig); + return utf8; + } ++ ++ int ++ deletepara(int f, int n) /* Delete the current paragraph */ ++ { ++ if(curbp->b_mode&MDVIEW) /* don't allow this command if */ ++ return(rdonly()); /* we are in read only mode */ ++ ++ if(!lisblank(curwp->w_dotp)) ++ gotobop(FALSE, 1); ++ ++ curwp->w_markp = curwp->w_dotp; ++ curwp->w_marko = 0; ++ ++ gotoeop(FALSE, 1); ++ if (curwp->w_dotp != curbp->b_linep){ /* if we are not at the end of buffer */ ++ curwp->w_dotp = lforw(curwp->w_dotp); /* get one more line */ ++ curwp->w_doto = 0; /* but only the beginning */ ++ } ++ killregion(f,n); ++ return(TRUE); ++ } ++ +diff -rc alpine-2.21/pico/word.c alpine-2.21.fillpara/pico/word.c +*** alpine-2.21/pico/word.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pico/word.c Sun Feb 5 16:15:23 2017 +*************** +*** 25,34 **** + */ + + #include "headers.h" +! + + int fpnewline(UCS *quote); +! int fillregion(UCS *qstr, REGION *addedregion); + int setquotelevelinregion(int quotelevel, REGION *addedregion); + int is_user_separator(UCS c); + +--- 25,34 ---- + */ + + #include "headers.h" +! #include "../pith/osdep/color.h" + + int fpnewline(UCS *quote); +! int fillregion(UCS *qstr, UCS *istr, REGION *addedregion); + int setquotelevelinregion(int quotelevel, REGION *addedregion); + int is_user_separator(UCS c); + +*************** +*** 431,555 **** + return 0; + } + +! void +! do_quote_match(UCS *q, LINE *l, UCS *buf, size_t buflen) +! { +! register int i, j; +! int qstart, qend, k; +! +! /* +! * The method for determining the quote string is: +! * 1) strip leading and trailing whitespace from q +! * 2) add all leading whitespace to buf +! * 3) check for q +! * 4) if q, append q to buf and any trailing whitespace +! * 5) repeat steps 3 and 4 as necessary +! * +! * q in the future could be made to be an array of (UCS *)'s +! * (">" and whatever the user's quote_string is) +! */ +! +! *buf = '\0'; + +! if(l == NULL) +! return; + +! /* count leading whitespace as part of the quote */ +! for(j = 0; j <= llength(l) && lgetc(l, j).c == ' ' && j+1 < buflen; j++) +! buf[j] = lgetc(l, j).c; +! buf[j] = '\0'; +! +! if(q == NULL || *q == '\0') +! return; +! +! /* pare down q so it contains no leading or trailing whitespace */ +! for(i = 0; q[i] == ' '; i++); +! qstart = i; +! for(i = ucs4_strlen(q); i > 0 && q[i-1] == ' '; i--); +! qend = i; +! +! /* for quote strings that are blanks, chop buf to the length of q */ +! if(qend <= qstart){ +! if(ucs4_strlen(q) < buflen) +! buf[ucs4_strlen(q)] = '\0'; +! return; +! } +! +! while(j <= llength(l)){ +! for(i = qstart; j <= llength(l) && i < qend; i++, j++) +! if(q[i] != lgetc(l, j).c) +! return; +! +! if(i >= qend){ +! if(ucs4_strlen(buf) + qend - qstart < (buflen - 1)) +! ucs4_strncat(buf, q + qstart, qend - qstart); +! } +! +! /* +! * if we're this far along, we've matched a quote string, +! * and should now add the following white space. +! */ +! for(k = ucs4_strlen(buf); +! j <= llength(l) && lgetc(l,j).c == ' ' && (k + 1 < buflen); +! j++, k++){ +! buf[k] = lgetc(l,j).c; +! } +! buf[k] = '\0'; +! +! if(j > llength(l)) +! return; +! } + } + + /* + * Return number of quotes if whatever starts the line matches the quote string + */ + int +! quote_match(UCS *q, LINE *gl, UCS *bufl, size_t buflen) + { +! LINE *nl = gl != curbp->b_linep ? lforw(gl) : NULL; +! LINE *pl = lback(gl) != curbp->b_linep ? lback(gl) : NULL; +! UCS bufp[NSTRING], bufn[NSTRING]; +! int i, j, qstart, qend; +! int quoted_line = 0; +! +! do_quote_match(q, pl, bufp, sizeof(bufp)); /* previous line */ +! do_quote_match(q, gl, bufl, buflen); /* given line */ +! do_quote_match(q, nl, bufn, sizeof(bufn)); /* next line */ +! +! if(!ucs4_strcmp(bufp, bufl) || !ucs4_strcmp(bufl, bufn)) +! return ucs4_strlen(bufl); +! +! /* is this line quoted? */ +! if(q && *q){ +! /* pare down q so it contains no leading or trailing whitespace */ +! for(i = 0; q[i] == ' '; i++); +! qstart = i; +! for(i = ucs4_strlen(q); i > 0 && q[i-1] == ' '; i--); +! qend = i; +! for(i = 0; i < llength(gl) +! && i + qstart < qend +! && lgetc(gl, i).c == q[i+qstart]; i++); +! if(i + qstart == qend) +! quoted_line = 1; +! } +! +! /* compare bufl and bufn */ +! for(i = 0; bufl[i] && bufn[i] && bufl[i] == bufn[i]; i++); +! +! /* go back to last non-space character */ +! for(; i > 0 && bufl[i-1] == ' '; i--); +! +! /* do bufl and bufn differ only in spaces? */ +! for(j = i; bufl[j] && bufl[j] == ' '; j++); +! +! /* if they differ only on trailing spaces, chop bufl to agree with bufn */ +! if (!bufl[j] ) +! bufl[Pmaster && quoted_line ? (j > i ? i+1 : i) : i] = '\0'; + +! return ucs4_strlen(bufl); + } + + + /* Justify the entire buffer instead of just a paragraph */ + int +--- 431,595 ---- + return 0; + } + +! /* Support of indentation of paragraphs */ +! #define is_indent_char(c) (((c) == '.' || (c) == '}' || (c) == RPAREN || \ +! (c) == '*' || (c) == '+' || is_a_digit(c) || \ +! ISspace(c) || (c) == '-' || \ +! (c) == ']') ? 1 : 0) +! #define allowed_after_digit(c,word,k) ((((c) == '.' && \ +! allowed_after_period(next((word),(k)))) ||\ +! (c) == RPAREN || (c) == '}' || (c) == ']' ||\ +! ISspace(c) || is_a_digit(c) || \ +! ((c) == '-' ) && \ +! allowed_after_dash(next((word),(k)))) \ +! ? 1 : 0) +! #define allowed_after_period(c) (((c) == RPAREN || (c) == '}' || (c) == ']' ||\ +! ISspace(c) || (c) == '-' || \ +! is_a_digit(c)) ? 1 : 0) +! #define allowed_after_parenth(c) (ISspace(c) ? 1 : 0) +! #define allowed_after_space(c) (ISspace(c) ? 1 : 0) +! #define allowed_after_braces(c) (ISspace(c) ? 1 : 0) +! #define allowed_after_star(c) ((ISspace(c) || (c) == RPAREN ||\ +! (c) == ']' || (c) == '}') ? 1 : 0) +! #define allowed_after_dash(c) ((ISspace(c) || is_a_digit(c)) ? 1 : 0) +! #define EOLchar(c) (((c) == '.' || (c) == ':' || (c) == '?' ||\ +! (c) == '!') ? 1 : 0) +! +! int indent_match(char **, LINE *, char *, int, int); +! +! /* Extended justification support */ +! #define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':') +! #define is_cword(c) ((((c) >= 'a') && ((c) <= 'z')) || \ +! (((c) >= 'A') && ((c) <= 'Z')) || \ +! (((c) >= '0') && ((c) <= '9')) || \ +! ((c) == ' ') || ((c) == '?') || \ +! ((c) == '@') || ((c) == '.') || \ +! ((c) == '!') || ((c) == '\'') || \ +! ((c) == ',') || ((c) == '\"') ? 1 : 0) +! #define isaquote(c) ((c) == '\"' || (c) == '\'') +! #define is8bit(c) ((((int) (c)) & 0x80) ? 1 : 0) +! #define iscontrol(c) (iscntrl(((int) (c)) & 0x7f) ? 1 : 0) +! #define forbidden(c) (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\ +! ((c) == ',') || ((c) == '.') || ((c) == '-') ||\ +! ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\ +! ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\ +! (((c) >= '0') && ((c) <= '9')) || ((c) == '?')) +! #define is_cletter(c) ((((c) >= 'a') && ((c) <= 'z'))) ||\ +! ((((c) >= 'A') && ((c) <= 'Z'))||\ +! is8bit(c)) +! #define is_cnumber(c) ((c) >= '0' && (c) <= '9') +! #define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c)) +! #define allwd_after_qsword(c) (((c) != '\\') && ((c) != RPAREN) && ((c) != '/')) +! #define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0) +! #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) +! #define now(w,i) ((w)[(i)]) +! #define is_qsword(c) (((c) == ':') || ((c) == RPAREN) ? 1 : 0) +! #define is_colon(c) (((c) == ':') ? 1 : 0) +! #define is_rarrow(c) (((c) == '>') ? 1 : 0) +! #define is_tilde(c) (((c) == '~') ? 1 : 0) +! #define is_dash(c) (((c) == '-') ? 1 : 0) +! #define is_pound(c) (((c) == '#') ? 1 : 0) +! #define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) +! #define is_allowed(c) (is_cquote(c) || is_cword(c) || is_dash(c) || \ +! is_pound(c)) +! #define qs_allowed(a) (((a)->qstype != qsGdb) && ((a)->qstype != qsProg)) +! +! /* Internal justification functions */ +! QSTRING_S *qs_quote_match(char **, LINE *, char *, int); +! int ucs4_widthis(UCS *); +! int ucs4_strlenis(UCS *); +! void linencpy(char *, LINE *, int); + +! void +! linencpy(word, l, buflen) +! char word[NSTRING]; +! LINE *l; +! int buflen; +! { +! int i; +! UCS ucs_word[NSTRING]; +! char *utf_word; +! +! word[0] = '\0'; +! if(l){ +! for (i = 0; i < buflen && i < llength(l) +! && (ucs_word[i] = lgetc(l,i).c); i++); +! ucs_word[i == buflen ? i-1 : i] = '\0'; +! utf_word = ucs4_to_utf8_cpystr(ucs_word); +! strncpy(word, utf_word, (NSTRING < buflen ? NSTRING : buflen)); +! word[NSTRING-1] = '\0'; +! if(utf_word) fs_give((void **)&utf_word); +! } +! } +! +! /* +! * This function returns the quote string as a structure. In this way we +! * have two ways to get the quote string: as a char * or as a QSTRING_S * +! * directly. +! */ +! QSTRING_S * +! qs_quote_match(char **q, LINE *l, char *rqstr, int rqstrlen) +! { +! char GLine[NSTRING], NLine[NSTRING], PLine[NSTRING]; +! LINE *nl = l != curbp->b_linep ? lforw(l) : NULL; +! LINE *pl = lback(l) != curbp->b_linep ? lback(l) : NULL; +! int plb = 1; +! +! linencpy(GLine, l, NSTRING); +! linencpy(NLine, nl, NSTRING); +! +! if (pl){ +! linencpy(PLine, pl, NSTRING); +! if(lback(pl) != curbp->b_linep){ +! char PPLine[NSTRING]; + +! linencpy(PPLine, lback(pl), NSTRING); +! plb = line_isblank(q, PLine, GLine, PPLine, NSTRING); +! } +! } +! return do_quote_match(q, GLine, NLine, PLine, rqstr, rqstrlen, plb); + } + + /* + * Return number of quotes if whatever starts the line matches the quote string ++ * rqstr is a pointer to raw qstring; buf points to processed qstring + */ + int +! quote_match(char **q, LINE *l, char *buf, size_t buflen, int raw) + { +! QSTRING_S *qs; +! char rqstr[NSTRING]; + +! qs = qs_quote_match(q, l, rqstr, NSTRING); +! if(qs) +! record_quote_string(qs); +! flatten_qstring(qs, buf, buflen); +! if (qs) free_qs(&qs); +! +! if(raw){ +! strncpy(buf, rqstr, buflen < NSTRING ? buflen : NSTRING); +! buf[buflen-1] = '\0'; +! } +! +! return buf && buf[0] ? strlen(buf) : 0; + } + ++ int ucs4_widthis(UCS *ucstr) ++ { ++ int i, rv = 0; ++ for (i = 0; ucstr && ucstr[i]; i++) ++ rv += ((ucstr[i] == TAB) ? (~rv & 0x07) + 1 : wcellwidth((UCS) ucstr[i])); ++ return rv; ++ } ++ ++ int ucs4_strlenis(UCS *ucs_qstr) ++ { ++ char *str = ucs4_to_utf8_cpystr(ucs_qstr); ++ int i = (int) strlenis(str); ++ ++ if(str) fs_give((void **)&str); ++ return i; ++ } + + /* Justify the entire buffer instead of just a paragraph */ + int +*************** +*** 804,809 **** +--- 844,850 ---- + } + + if(action == 'R' && curwp->w_markp){ ++ char qstrfl[NSTRING]; + /* let yank() know that it may be restoring a paragraph */ + thisflag |= CFFILL; + +*************** +*** 815,835 **** + swap_mark_and_dot_if_mark_comes_first(); + + /* determine if we're justifying quoted text or not */ +! qstr = quote_match(glo_quote_str, +! curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp, +! qstr2, NSTRING) +! && *qstr2 ? qstr2 : NULL; +! + + /* + * Fillregion moves dot to the end of the filled region. + */ +! if(!fillregion(qstr, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); +! } +! else if(action == 'P'){ + + /* + * Justfiy the current paragraph. +--- 856,881 ---- + swap_mark_and_dot_if_mark_comes_first(); + + /* determine if we're justifying quoted text or not */ +! qstr = (glo_quote_str +! && quote_match(default_qstr(glo_quote_str, 1), +! (curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp), +! qstrfl, NSTRING, 0) +! && *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL; + + /* + * Fillregion moves dot to the end of the filled region. + */ +! if(!fillregion(qstr, NULL, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); +! +! if(qstr) +! fs_give((void **)&qstr); +! } +! else if(action == 'P'){ +! char ind_str[NSTRING], qstrfl[NSTRING]; +! UCS *istr; + + /* + * Justfiy the current paragraph. +*************** +*** 841,856 **** + if(gotoeop(FALSE, 1) == FALSE) + return(FALSE); + +- /* determine if we're justifying quoted text or not */ +- qstr = quote_match(glo_quote_str, +- curwp->w_dotp, qstr2, NSTRING) +- && *qstr2 ? qstr2 : NULL; +- + setmark(0,0); /* mark last line of para */ + + /* jump back to the beginning of the paragraph */ + gotobop(FALSE, 1); + + /* let yank() know that it may be restoring a paragraph */ + thisflag |= (CFFILL | CFFLPA); + +--- 887,902 ---- + if(gotoeop(FALSE, 1) == FALSE) + return(FALSE); + + setmark(0,0); /* mark last line of para */ + + /* jump back to the beginning of the paragraph */ + gotobop(FALSE, 1); + ++ istr = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str, NSTRING, 0) ++ && *ind_str ? utf8_to_ucs4_cpystr(ind_str) : NULL; ++ qstr = (quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstrfl, NSTRING, 0) ++ && *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL; ++ + /* let yank() know that it may be restoring a paragraph */ + thisflag |= (CFFILL | CFFLPA); + +*************** +*** 864,872 **** + /* + * Fillregion moves dot to the end of the filled region. + */ +! if(!fillregion(qstr, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); + + /* Leave cursor on first char of first line after justified region */ +--- 910,924 ---- + /* + * Fillregion moves dot to the end of the filled region. + */ +! if(!fillregion(qstr, istr, &addedregion)) + return(FALSE); + ++ if(qstr) ++ fs_give((void **)&qstr); ++ ++ if(istr) ++ fs_give((void **)&istr); ++ + set_last_region_added(&addedregion); + + /* Leave cursor on first char of first line after justified region */ +*************** +*** 908,923 **** + * can delete it and restore the saved part. + */ + int +! fillregion(UCS *qstr, REGION *addedregion) + { + long c, sz, last_char = 0; +! int i, j, qlen, same_word, + spaces, word_len, word_ind, line_len, ww; + int starts_midline = 0; + int ends_midline = 0; + int offset_into_start; + LINE *line_before_start, *lp; +! UCS line_last, word[NSTRING]; + REGION region; + + /* if region starts midline insert a newline */ +--- 960,975 ---- + * can delete it and restore the saved part. + */ + int +! fillregion(UCS *qstr, UCS *istr, REGION *addedregion) + { + long c, sz, last_char = 0; +! int i, j, qlen, same_word, qi, pqi, qlenis, + spaces, word_len, word_ind, line_len, ww; + int starts_midline = 0; + int ends_midline = 0; + int offset_into_start; + LINE *line_before_start, *lp; +! UCS line_last, word[NSTRING], quoid[NSTRING], qstr2[NSTRING]; + REGION region; + + /* if region starts midline insert a newline */ +*************** +*** 928,933 **** +--- 980,1014 ---- + if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp)) + ends_midline++; + ++ for (i = 0; (i < NSTRING) && qstr && (quoid[i] = qstr[i]); i++); ++ for (j = 0; ((i + j) < NSTRING) && istr && (quoid[i] = istr[j]); i++,j++); ++ quoid[i] = '\0'; ++ qi = ucs4_strlen(quoid); ++ if (istr) /* strip trailing spaces */ ++ for (;ISspace(quoid[qi - 1]); qi--); ++ quoid[qi] = '\0'; /* we have closed quoid at "X" in the first line */ ++ ++ if (ucs4_strlenis(quoid) > fillcol) ++ return FALSE; /* Too wide, we can't justify this! */ ++ ++ if (qstr && istr){ ++ for (i = ucs4_strlen(qstr) - 1; ISspace(qstr[i]); i--); ++ qstr[i + 1] = '\0'; /* qstrfl */ ++ } ++ qlen = ucs4_strlen(qstr); /* qstrfl*/ ++ qlenis = ucs4_strlenis(qstr); ++ ++ for(i = 0, qstr2[0] = '\0'; qstr && qstr[i] && (qstr2[i] = qstr[i]); i++); ++ ++ if (istr && ((j = ucs4_widthis(quoid) - ucs4_widthis(qstr)) > 0)){ ++ pqi = ucs4_strlen(qstr); ++ for (i = 0; (i < j) && (qstr2[pqi + i] = ' '); i++); ++ if (ISspace(istr[ucs4_strlen(istr) - 1])) ++ qstr2[pqi + i++] = ' '; ++ qstr2[pqi + i] = '\0'; ++ qstr = qstr2; ++ } ++ + /* cut the paragraph into our fill buffer */ + fdelete(); + if(!getregion(®ion, curwp->w_markp, curwp->w_marko)) +*************** +*** 944,971 **** + + /* Now insert it back wrapped */ + spaces = word_len = word_ind = line_len = same_word = 0; +- qlen = qstr ? ucs4_strlen(qstr) : 0; + + /* Beginning with leading quoting... */ +! if(qstr){ +! i = 0; +! while(qstr[i]){ +! ww = wcellwidth(qstr[i]); +! line_len += (ww >= 0 ? ww : 1); +! linsert(1, qstr[i++]); +! } + + line_last = ' '; /* no word-flush space! */ + } + + /* remove first leading quotes if any */ + if(starts_midline) + i = 0; +! else +! for(i = qlen; (c = fremove(i)) == ' ' || c == TAB; i++){ + linsert(1, line_last = (UCS) c); + line_len += ((c == TAB) ? (~line_len & 0x07) + 1 : 1); + } + + /* then digest the rest... */ + while((c = fremove(i++)) >= 0){ +--- 1025,1060 ---- + + /* Now insert it back wrapped */ + spaces = word_len = word_ind = line_len = same_word = 0; + + /* Beginning with leading quoting... */ +! if(qstr || istr){ +! for(i = 0; quoid[i] != '\0' ; i++) +! linsert(1, quoid[i]); + + line_last = ' '; /* no word-flush space! */ ++ line_len = ucs4_strlenis(quoid); /* we demand a recount! */ + } + + /* remove first leading quotes if any */ + if(starts_midline) + i = 0; +! else{ +! if(qstr || istr){ +! for (i = 0; (c = fremove(i)) != '\0'; i++){ +! word[i] = c; +! word[i+1] = '\0'; +! if(ucs4_strlenis(word) >= ucs4_strlenis(quoid)) +! break; +! } +! i++; +! } +! else +! i = 0; +! for(; ISspace(c = fremove(i)); i++){ + linsert(1, line_last = (UCS) c); + line_len += ((c == TAB) ? (~line_len & 0x07) + 1 : 1); + } ++ } + + /* then digest the rest... */ + while((c = fremove(i++)) >= 0){ +*************** +*** 986,1006 **** + + case TAB : + case ' ' : + spaces++; + break; + + default : + if(spaces){ /* flush word? */ +! if((line_len - qlen > 0) + && line_len + word_len + 1 > fillcol +! && ((ucs4_isspace(line_last)) + || (linsert(1, ' '))) + && same_word == 0 + && (line_len = fpnewline(qstr))) + line_last = ' '; /* no word-flush space! */ + + if(word_len){ /* word to write? */ +! if(line_len && !ucs4_isspace(line_last)){ + linsert(1, ' '); /* need padding? */ + line_len++; + } +--- 1075,1096 ---- + + case TAB : + case ' ' : ++ case NBSP: + spaces++; + break; + + default : + if(spaces){ /* flush word? */ +! if((line_len - qlenis > 0) + && line_len + word_len + 1 > fillcol +! && ((ISspace(line_last)) + || (linsert(1, ' '))) + && same_word == 0 + && (line_len = fpnewline(qstr))) + line_last = ' '; /* no word-flush space! */ + + if(word_len){ /* word to write? */ +! if(line_len && !ISspace(line_last)){ + linsert(1, ' '); /* need padding? */ + line_len++; + } +*************** +*** 1022,1029 **** + + if(word_ind + 1 >= NSTRING){ + /* Magic! Fake that we output a wrapped word */ +! if((line_len - qlen > 0) && same_word == 0){ +! if(!ucs4_isspace(line_last)) + linsert(1, ' '); + line_len = fpnewline(qstr); + } +--- 1112,1119 ---- + + if(word_ind + 1 >= NSTRING){ + /* Magic! Fake that we output a wrapped word */ +! if((line_len - qlenis > 0) && same_word == 0){ +! if(!ISspace(line_last)) + linsert(1, ' '); + line_len = fpnewline(qstr); + } +*************** +*** 1045,1056 **** + } + + if(word_len){ +! if((line_len - qlen > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){ +! if(!ucs4_isspace(line_last)) + linsert(1, ' '); + (void) fpnewline(qstr); + } +! else if(line_len && !ucs4_isspace(line_last)) + linsert(1, ' '); + + for(j = 0; j < word_ind; j++) +--- 1135,1146 ---- + } + + if(word_len){ +! if((line_len - qlenis > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){ +! if(!ISspace(line_last)) + linsert(1, ' '); + (void) fpnewline(qstr); + } +! else if(line_len && !ISspace(line_last)) + linsert(1, ' '); + + for(j = 0; j < word_ind; j++) +*************** +*** 1108,1118 **** + int len; + + lnewline(); +! for(len = 0; quote && *quote; quote++){ + int ww; + +! ww = wcellwidth(*quote); +! len += (ww >= 0 ? ww : 1); + linsert(1, *quote); + } + +--- 1198,1208 ---- + int len; + + lnewline(); +! for(len = ucs4_strlenis(quote); quote && *quote; quote++){ + int ww; + +! /* ww = wcellwidth(*quote); +! len += (ww >= 0 ? ww : 1);*/ + linsert(1, *quote); + } + +*************** +*** 1256,1260 **** +--- 1346,1390 ---- + markregion(1); + } + ++ /* ++ * This puts us at the end of the quoted region instead ++ * of on the following line. This makes it convenient ++ * for the user to follow a quotelevel adjustment with ++ * a Justify if desired. ++ */ ++ if(backuptoprevline){ ++ curwp->w_doto = 0; ++ backchar(0, 1); ++ } ++ ++ if(ends_midline){ /* doesn't need fixing otherwise */ ++ unmarkbuffer(); ++ markregion(1); ++ } ++ + return (TRUE); + } ++ ++ /* ++ * If there is an indent string this function returns ++ * its length ++ */ ++ int ++ indent_match(char **q, LINE *l, char *buf, int buflen, int raw) ++ { ++ char GLine[NSTRING]; ++ int i, k, plb; ++ ++ k = quote_match(q,l, buf, buflen, raw); ++ linencpy(GLine, l, NSTRING); ++ plb = (lback(l) != curbp->b_linep) ? lisblank(lback(l)) : 1; ++ if (!plb){ ++ i = llength(lback(l)) - 1; ++ for (; i >= 0 && ISspace(lgetc(lback(l), i).c); i--); ++ if (EOLchar(lgetc(lback(l), i).c)) ++ plb++; ++ } ++ ++ return get_indent_raw_line(q, GLine, buf, buflen, k, plb); ++ } ++ +diff -rc alpine-2.21/pith/charconv/utf8.c alpine-2.21.fillpara/pith/charconv/utf8.c +*** alpine-2.21/pith/charconv/utf8.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/charconv/utf8.c Sun Feb 5 16:15:23 2017 +*************** +*** 1061,1066 **** +--- 1061,1116 ---- + + + /* ++ * Returns the screen cells width of the UTF-8 string argument, treating tabs ++ * in a special way. ++ */ ++ unsigned ++ utf8_widthis(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){ ++ /* ++ * 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 = (ucs == TAB) ? ((~width & 0x07) + 1) : 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 +diff -rc alpine-2.21/pith/charconv/utf8.h alpine-2.21.fillpara/pith/charconv/utf8.h +*** alpine-2.21/pith/charconv/utf8.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/charconv/utf8.h Sun Feb 5 16:15:23 2017 +*************** +*** 81,86 **** +--- 81,87 ---- + UCS *ucs4_strchr(UCS *s, UCS c); + UCS *ucs4_strrchr(UCS *s, UCS c); + unsigned utf8_width(char *); ++ unsigned utf8_widthis(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 *); +diff -rc alpine-2.21/pith/color.c alpine-2.21.fillpara/pith/color.c +*** alpine-2.21/pith/color.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/color.c Sun Feb 5 16:15:23 2017 +*************** +*** 21,27 **** + #include "../pith/state.h" + #include "../pith/conf.h" + #include "../pith/filter.h" +! + + char * + color_embed(char *fg, char *bg) +--- 21,28 ---- + #include "../pith/state.h" + #include "../pith/conf.h" + #include "../pith/filter.h" +! #include "../pith/mailview.h" +! #include "../pico/estruct.h" + + char * + color_embed(char *fg, char *bg) +*************** +*** 70,92 **** + 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; + + /* +--- 71,180 ---- + struct quote_colors *next; + }; + ++ int ++ is_word (buf, i, j) ++ char buf[NSTRING]; ++ int i, j; ++ { ++ return i <= j && is_letter(buf[i]) ? ++ (i < j ? is_word(buf,i+1,j) : 1) : 0; ++ } ++ ++ int ++ is_mailbox(buf,i,j) ++ char buf[NSTRING]; ++ int i, j; ++ { ++ return i <= j && (is_letter(buf[i]) || is_digit(buf[i]) || buf[i] == '.') ++ ? (i < j ? is_mailbox(buf,i+1,j) : 1) : 0; ++ } ++ ++ int ++ next_level_quote(buf, line, i, is_flowed) ++ char *buf; ++ char **line; ++ int i; ++ int is_flowed; ++ { ++ int j; ++ ++ if (!single_level(buf[i])){ ++ if(is_mailbox(buf,i,i)){ ++ for (j = i; buf[j] && !isspace(buf[j]); j++); ++ if (is_word(buf,i,j-1) || is_mailbox(buf,i,j-1)) ++ j += isspace(buf[j]) ? 2 : 1; ++ } ++ else{ ++ switch(buf[i]){ ++ case ':' : ++ if (next(buf,i) != RPAREN) ++ j = i + 1; ++ else ++ j = i + 2; ++ break; ++ ++ case '-' : ++ if (next(buf,i) != '-') ++ j = i + 2; ++ else ++ j = i + 3; ++ break; ++ ++ case '+' : ++ case '*' : ++ if (next(buf,i) != ' ') ++ j = i + 2; ++ else ++ j = i + 3; ++ break; ++ ++ default : ++ for (j = i; buf[j] && !isspace(buf[j]) ++ && (!single_level(buf[i]) && !is_letter(buf[j])); j++); ++ ++ j += isspace(buf[j]) ? 1 : 0; ++ break; ++ } ++ } ++ if (line && *line) ++ (*line) += j - i; ++ } ++ else{ ++ j = i+1; ++ if (line && *line) ++ (*line)++; ++ } ++ if(!is_flowed){ ++ if(line && *line) ++ for(; isspace((unsigned char)*(*line)); (*line)++); ++ for (i = j; isspace((unsigned char) buf[i]); i++); ++ } ++ else i = j; ++ if (is_flowed && i != j) ++ buf[i] = '\0'; ++ return i; ++ } + + int + color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg) + { +! int countem = 0, i, j = 0; + struct variable *vars = ps_global->vars; +! char *p, buf[NSTRING] = {'\0'}; + struct quote_colors *colors = NULL, *cp, *next; + COLOR_PAIR *col = NULL; + int is_flowed = is_flowed_msg ? *((int *)is_flowed_msg) : 0; ++ int code; ++ ++ code = (is_flowed ? IS_FLOWED : NO_FLOWED) | COLORAQUO; ++ select_quote(linenum, line, ins, (void *) &code); ++ strncpy(buf, tmp_20k_buf, NSTRING < SIZEOF_20KBUF ? NSTRING : SIZEOF_20KBUF); ++ buf[sizeof(buf)-1] = '\0'; + + p = line; +! for(i = 0; isspace((unsigned char)buf[i]); i++, p++); + +! if(buf[i]){ + struct quote_colors *c; + + /* +*************** +*** 135,141 **** + free_color_pair(&col); + + cp = NULL; +! while(*p == '>'){ + cp = (cp && cp->next) ? cp->next : colors; + + if(countem > 0) +--- 223,229 ---- + free_color_pair(&col); + + cp = NULL; +! while(buf[i]){ + cp = (cp && cp->next) ? cp->next : colors; + + if(countem > 0) +*************** +*** 145,154 **** + + countem = (countem == 1) ? 0 : countem; + +! p++; +! if(!is_flowed) +! for(; isspace((unsigned char)*p); p++) +! ; + } + + if(colors){ +--- 233,241 ---- + + countem = (countem == 1) ? 0 : countem; + +! i = next_level_quote(buf, &p, i, is_flowed); +! for (; isspace((unsigned char)*p); p++); +! for (; isspace((unsigned char)buf[i]); i++); + } + + if(colors){ +*************** +*** 211,217 **** + } + } + +! return(0); + } + + +--- 298,304 ---- + } + } + +! return(1); + } + + +diff -rc alpine-2.21/pith/color.h alpine-2.21.fillpara/pith/color.h +*** alpine-2.21/pith/color.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/color.h Sun Feb 5 16:15:23 2017 +*************** +*** 22,27 **** +--- 22,45 ---- + #include "../pith/pattern.h" + #include "../pith/osdep/color.h" + ++ #define NO_FLOWED 0x0000 ++ #define IS_FLOWED 0x0001 ++ #define DELETEQUO 0x0010 ++ #define COLORAQUO 0x0100 ++ #define RAWSTRING 0x1000 ++ ++ /* This is needed for justification, I will move it to a better place later ++ * or maybe not ++ */ ++ #define is_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) ++ ++ #define is_letter(c) (((c) >= 'a' && (c) <= 'z') || \ ++ ((c) >= 'A' && (c) <= 'Z')) ++ ++ #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) ++ ++ #define single_level(c) (((c) == '>') || ((c) == '|') || ((c) == '~') || \ ++ ((c) == ']')) + + typedef struct spec_color_s { + int inherit; /* this isn't a color, it is INHERIT */ +*************** +*** 81,86 **** +--- 99,105 ---- + /* exported protoypes */ + char *color_embed(char *, char *); + int colorcmp(char *, char *); ++ int next_level_quote(char *, char **, int, int); + int color_a_quote(long, char *, LT_INS_S **, void *); + void free_spec_colors(SPEC_COLOR_S **); + +diff -rc alpine-2.21/pith/filter.c alpine-2.21.fillpara/pith/filter.c +*** alpine-2.21/pith/filter.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/filter.c Sun Feb 5 16:15:23 2017 +*************** +*** 46,51 **** +--- 46,52 ---- + #include "../pith/conf.h" + #include "../pith/store.h" + #include "../pith/color.h" ++ #include "../pith/osdep/color.h" + #include "../pith/escapes.h" + #include "../pith/pipe.h" + #include "../pith/status.h" +*************** +*** 9269,9274 **** +--- 9270,9280 ---- + margin_r, + indent; + char special[256]; ++ long curlinenum; /* current line number */ ++ int curqstrpos; /* current position in quote string */ ++ long linenum; /* line number */ ++ long qstrlen; /* multiples of 100 */ ++ char **qstrln; /* qstrln[i] = quote string line i - 1 */ + } WRAP_S; + + #define WRAP_MARG_L(F) (((WRAP_S *)(F)->opt)->margin_l) +*************** +*** 9310,9315 **** +--- 9316,9327 ---- + #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_CURLINE(F) (((WRAP_S *)(F)->opt)->curlinenum) ++ #define WRAP_CURPOS(F) (((WRAP_S *)(F)->opt)->curqstrpos) ++ #define WRAP_LINENUM(F) (((WRAP_S *)(F)->opt)->linenum) ++ #define WRAP_QSTRLEN(F) (((WRAP_S *)(F)->opt)->qstrlen) ++ #define WRAP_QSTRN(F) (((WRAP_S *)(F)->opt)->qstrln) ++ #define WRAP_QSTR(F, N) (((WRAP_S *)(F)->opt)->qstrln[(N)]) + #define WRAP_PUTC(F,C,W) { \ + if((F)->linep == WRAP_LASTC(F)){ \ + size_t offset = (F)->linep - (F)->line; \ +*************** +*** 9387,9392 **** +--- 9399,9406 ---- + case CCR : /* CRLF or CR in text ? */ + state = BOL; /* either way, handle start */ + ++ WRAP_CURLINE(f)++; ++ WRAP_CURPOS(f) = 0; + if(WRAP_FLOW(f)){ + /* wrapped line? */ + if(f->f2 == 0 && WRAP_SPC_LEN(f) && WRAP_TRL_SPC(f)){ +*************** +*** 9480,9486 **** + + case BOL : + if(WRAP_FLOW(f)){ +! if(c == '>'){ + WRAP_FL_QC(f) = 1; /* init it */ + state = FL_QLEV; /* go collect it */ + } +--- 9494,9504 ---- + + case BOL : + if(WRAP_FLOW(f)){ +! if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) +! && WRAP_QSTR(f, WRAP_CURLINE(f)) +! && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] +! && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){ +! WRAP_CURPOS(f)++; + WRAP_FL_QC(f) = 1; /* init it */ + state = FL_QLEV; /* go collect it */ + } +*************** +*** 9494,9500 **** + } + + /* 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; +--- 9512,9527 ---- + } + + /* quote level change implies new paragraph */ +! if (WRAP_CURLINE(f) > 0 +! && WRAP_CURLINE(f) < WRAP_QSTRLEN(f) +! && (WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL +! || WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL) +! && ((WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL && +! WRAP_QSTR(f, WRAP_CURLINE(f) - 1) == NULL) +! || (WRAP_QSTR(f, WRAP_CURLINE(f)) == NULL && +! WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL) +! || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)), +! WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){ + WRAP_FL_QD(f) = 0; + if(WRAP_HARD(f) == 0){ + WRAP_HARD(f) = 1; +*************** +*** 9546,9553 **** + break; + + case FL_QLEV : +! if(c == '>'){ /* another level */ +! WRAP_FL_QC(f)++; + } + else { + /* if EMBEDed, process it and return here */ +--- 9573,9584 ---- + break; + + case FL_QLEV : +! if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) +! && WRAP_QSTR(f, WRAP_CURLINE(f)) +! && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] +! && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){ +! WRAP_CURPOS(f)++; +! WRAP_FL_QC(f)++; /* another level */ + } + else { + /* if EMBEDed, process it and return here */ +*************** +*** 9559,9565 **** + } + + /* 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 */ +--- 9590,9605 ---- + } + + /* quote level change signals new paragraph */ +! if (WRAP_CURLINE(f) > 0 +! && WRAP_CURLINE(f) < WRAP_QSTRLEN(f) +! && (WRAP_QSTR(f, WRAP_CURLINE(f)) +! || WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) +! && ((WRAP_QSTR(f, WRAP_CURLINE(f)) && +! !WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) +! || (!WRAP_QSTR(f, WRAP_CURLINE(f)) && +! WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) +! || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)), +! WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){ + WRAP_FL_QD(f) = WRAP_FL_QC(f); + if(WRAP_HARD(f) == 0){ /* add hard newline */ + WRAP_HARD(f) = 1; /* hard newline */ +*************** +*** 9616,9621 **** +--- 9656,9668 ---- + state = FL_SIG; + break; + ++ case ' ' : /* what? */ ++ if (WRAP_CURLINE(f) < WRAP_QSTRLEN(f) ++ && WRAP_QSTR(f, WRAP_CURLINE(f))){ ++ WRAP_SPC_LEN(f)++; ++ so_writec(' ', WRAP_SPACES(f)); ++ } ++ + default : /* something else */ + state = DFL; + goto case_dfl; /* handle c like DFL */ +*************** +*** 9632,9638 **** + &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 */ + } + +--- 9679,9685 ---- + &eob); /* note any embedded*/ + wrap_eol(f, 1, &ip, &eib, + &op, &eob); /* plunk down newline */ +! wrap_bol(f, 1, WRAP_FLOW(f), &ip, &eib, + &op, &eob); /* write any prefix */ + } + +*************** +*** 10129,10135 **** + 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 */ + } + +--- 10176,10182 ---- + wrap_flush_embed(f, &ip, &eib, &op, &eob); + wrap_eol(f, 1, &ip, &eib, &op, + &eob); /* plunk down newline */ +! wrap_bol(f,1,WRAP_FLOW(f), &ip, &eib, &op, + &eob); /* write any prefix */ + } + +*************** +*** 10202,10207 **** +--- 10249,10261 ---- + if(WRAP_COLOR(f)) + free_color_pair(&WRAP_COLOR(f)); + ++ { long i; ++ for (i = 0L; i < WRAP_QSTRLEN(f); i++) ++ if (WRAP_QSTR(f,i)) ++ fs_give((void **) &(WRAP_QSTR(f,i))); ++ fs_give((void **)&WRAP_QSTRN(f)); ++ } ++ + fs_give((void **) &f->line); /* free temp line buffer */ + so_give(&WRAP_SPACES(f)); + fs_give((void **) &f->opt); /* free wrap widths struct */ +*************** +*** 10552,10558 **** + { + 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); +--- 10606,10613 ---- + { + int j, i; + COLOR_PAIR *col = NULL; +! char *prefix = NULL, *last_prefix = NULL, *wrap_qstr = NULL; +! int level = 0, oldj, len; + + if(ps_global->VAR_QUOTE_REPLACE_STRING){ + get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0); +*************** +*** 10561,10570 **** + 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, +--- 10616,10637 ---- + last_prefix = NULL; + } + } +! +! if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) && WRAP_QSTR(f, WRAP_CURLINE(f))) +! wrap_qstr = cpystr(WRAP_QSTR(f, WRAP_CURLINE(f))); +! len = wrap_qstr ? strlen(wrap_qstr) : 0; +! +! for (j = wrap_qstr && *wrap_qstr == ' ' ? 1 : 0; +! j < len && isspace((unsigned char)wrap_qstr[j]); j++){ +! GF_PUTC_GLO(f->next, wrap_qstr[j]); +! f->n += ((wrap_qstr[j] == TAB) ? (~f->n & 0x07) + 1 : 1); +! } +! +! for(; j < len && level < len; level++){ +! oldj = j; +! j = next_level_quote(wrap_qstr, (char **)NULL, j, WRAP_FLOW(f)); + if(WRAP_USE_CLR(f)){ +! if((level % 3) == 0 + && ps_global->VAR_QUOTE1_FORE_COLOR + && ps_global->VAR_QUOTE1_BACK_COLOR + && (col = new_color_pair(ps_global->VAR_QUOTE1_FORE_COLOR, +*************** +*** 10572,10578 **** + && 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, +--- 10639,10645 ---- + && pico_is_good_colorpair(col)){ + GF_COLOR_PUTC(f, col); + } +! else if((level % 3) == 1 + && ps_global->VAR_QUOTE2_FORE_COLOR + && ps_global->VAR_QUOTE2_BACK_COLOR + && (col = new_color_pair(ps_global->VAR_QUOTE2_FORE_COLOR, +*************** +*** 10580,10586 **** + && 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, +--- 10647,10653 ---- + && pico_is_good_colorpair(col)){ + GF_COLOR_PUTC(f, col); + } +! else if((level % 3) == 2 + && ps_global->VAR_QUOTE3_FORE_COLOR + && ps_global->VAR_QUOTE3_BACK_COLOR + && (col = new_color_pair(ps_global->VAR_QUOTE3_FORE_COLOR, +*************** +*** 10594,10636 **** + } + } + + 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; + } +--- 10661,10707 ---- + } + } + ++ if (j > 1 && wrap_qstr[j-1] == ' ') ++ j -= 1; ++ + 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_widthis(prefix); + } + else{ +! for (i = oldj; i < j; i++) +! GF_PUTC_GLO(f->next, wrap_qstr[i]); +! f->n += j - oldj; + } + } + else{ +! for (i = oldj; i < j; i++) +! GF_PUTC_GLO(f->next, wrap_qstr[i]); +! f->n += j - oldj; + } ++ for (i = j; isspace((unsigned char)wrap_qstr[i]); i++) ++ GF_PUTC_GLO(f->next, ' '); ++ f->n += i - j; ++ for (; isspace((unsigned char)wrap_qstr[j]); j++); + } + if(j && WRAP_LV_FLD(f)){ + GF_PUTC_GLO(f->next, ' '); + f->n++; + } +! else if(j && !value_is_space(wrap_qstr) && last_prefix){ + for(i = 0; last_prefix[i]; i++) + GF_PUTC_GLO(f->next, last_prefix[i]); +! f->n += utf8_widthis(last_prefix); + } + + if(prefix) + fs_give((void **)&prefix); + if(last_prefix) + fs_give((void **)&last_prefix); ++ if (wrap_qstr) ++ fs_give((void **)&wrap_qstr); + + return 0; + } +*************** +*** 10662,10667 **** +--- 10733,10744 ---- + 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; ++ wrap->curlinenum = 0L; ++ wrap->curqstrpos = 0; ++ wrap->linenum = 0L; ++ wrap->qstrlen = 100L; ++ wrap->qstrln = (char **) fs_get(100*sizeof(char *)); ++ memset(wrap->qstrln, 0, 100*sizeof(char *)); + + return((void *) wrap); + } +*************** +*** 11105,11111 **** +--- 11182,11396 ---- + } \ + } + ++ #define ADD_QUOTE_STRING(F) { \ ++ int len = tmp_20k_buf[0] ? strlen(tmp_20k_buf) + 1 : 0; \ ++ FILTER_S *fltr; \ ++ \ ++ for(fltr = (F); fltr && fltr->f != gf_wrap; fltr = fltr->next); \ ++ if (fltr){ \ ++ if (WRAP_LINENUM(fltr) >= WRAP_QSTRLEN(fltr)){ \ ++ fs_resize((void **)&WRAP_QSTRN(fltr), \ ++ (WRAP_QSTRLEN(fltr) + 100) * sizeof(char *)); \ ++ memset(WRAP_QSTRN(fltr)+WRAP_QSTRLEN(fltr), 0, \ ++ 100*sizeof(char*)); \ ++ WRAP_QSTRLEN(fltr) += 100L; \ ++ } \ ++ if (len){ \ ++ WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = \ ++ (char *) fs_get(len*sizeof(char)); \ ++ WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = cpystr(tmp_20k_buf);\ ++ } \ ++ WRAP_LINENUM(fltr)++; \ ++ } \ ++ } ++ ++ int end_of_line(char *line) ++ { ++ int i; ++ ++ for(i= 0; line && line[i]; i++){ ++ if((line[i] == '\015' && line[i+1] == '\012') || line[i] == '\012') ++ break; ++ } ++ return i; ++ } ++ ++ /* This macro is used in gf_quote_test. It receives a return code ++ from a filter. All filters that will print something must send ++ return code 0, except color_a_quote which must send return code ++ 1 ++ */ ++ ++ #define GF_ADD_QUOTED_LINE(F, line) \ ++ { \ ++ LT_INS_S *ins = NULL, *insp; \ ++ int done; \ ++ char *gline, *cline;\ ++ unsigned char ch;\ ++ register char *cp;\ ++ register int l;\ ++ \ ++ for (gline = cline = line; gline && cline; ){\ ++ if(cline = strchr(gline,'\012'))\ ++ *cline = '\0';\ ++ done = (*((LINETEST_S *) (F)->opt)->f)((F)->n++, gline, &ins,\ ++ ((LINETEST_S *) (F)->opt)->local);\ ++ if (done < 2){ \ ++ if(done == 1)\ ++ ADD_QUOTE_STRING((F));\ ++ for(insp = ins, cp = gline; *cp ; ){\ ++ if(insp && cp == insp->where){\ ++ if(insp->len > 0){ \ ++ for(l = 0; l < insp->len; l++){\ ++ ch = (unsigned char) insp->text[l];\ ++ GF_PUTC((F)->next, ch);\ ++ }\ ++ 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++){\ ++ ch = (unsigned char) insp->text[l];\ ++ GF_PUTC((F)->next, ch);\ ++ }\ ++ insp = insp->next;\ ++ }\ ++ gf_line_test_free_ins(&ins);\ ++ if(cline){ \ ++ *cline = '\012';\ ++ gline += cline - gline + 1;\ ++ }\ ++ GF_PUTC((F)->next, '\015');\ ++ GF_PUTC((F)->next, '\012');\ ++ }\ ++ }\ ++ } ++ /* test second line of old line first */ ++ #define SECOND_LINE_QUOTE_TEST(line, F) \ ++ {\ ++ *p = '\0';\ ++ i = end_of_line((F)->oldline); \ ++ if (((F)->oldline)[i]){\ ++ i += (((F)->oldline)[i] == '\015') ? 2 : 1;\ ++ line = (F)->oldline + i;\ ++ i = end_of_line(line); \ ++ if(line[i])\ ++ line[i] = '\0'; \ ++ }\ ++ for (i = 0; ((F)->line) \ ++ && (i < LINE_TEST_BLOCK) \ ++ && (i < SIZEOF_20KBUF)\ ++ && ((F)->line)[i] \ ++ && (((F)->line)[i] != '\015')\ ++ && (((F)->line)[i] != '\012')\ ++ && (tmp_20k_buf[i] = ((F)->line)[i]); i++);\ ++ tmp_20k_buf[i] = '\0';\ ++ GF_ADD_QUOTED_LINE((F), line);\ ++ } ++ ++ #define FIRST_LINE_QUOTE_TEST(line, F)\ ++ {\ ++ *p = '\0';\ ++ line = (F)->line;\ ++ if ((F)->oldline)\ ++ fs_give((void **)&(F)->oldline);\ ++ (F)->oldline = cpystr(line);\ ++ i = end_of_line(line); \ ++ if (line[i]){ \ ++ j = (line[i] == '\015') ? 2 : 1;\ ++ line[i] = '\0'; \ ++ i += j; \ ++ }\ ++ for (j = 0; ((F)->line) \ ++ && ((i + j) < LINE_TEST_BLOCK) \ ++ && (j < SIZEOF_20KBUF) \ ++ && ((F)->line)[i + j] \ ++ && (((F)->line)[i + j] != '\015')\ ++ && (((F)->line)[i + j] != '\012')\ ++ && (tmp_20k_buf[j] = ((F)->line)[i + j]); j++);\ ++ tmp_20k_buf[j] = '\0';\ ++ GF_ADD_QUOTED_LINE((F), line);\ ++ } ++ ++ ++ void ++ gf_quote_test(f, flg) ++ FILTER_S *f; ++ int flg; ++ { ++ register char *p = f->linep; ++ register char *eobuf = GF_LINE_TEST_EOB(f); ++ char *line = NULL; ++ int i, j; ++ GF_INIT(f, f->next); ++ ++ if(flg == GF_DATA){ ++ register unsigned char c; ++ register int state = f->f1; ++ ++ while(GF_GETC(f, c)){ ++ ++ GF_LINE_TEST_ADD(f, c); ++ if(c == '\012') ++ state++; ++ if(state == 2){ /* two full lines read */ ++ state = 0; ++ ++ /* first process the second line of an old line */ ++ if (f->oldline && f->oldline[0]) ++ SECOND_LINE_QUOTE_TEST(line, f); ++ ++ /* now we process the first line */ ++ FIRST_LINE_QUOTE_TEST(line, f); ++ ++ p = f->line; ++ } ++ } ++ ++ f->f1 = state; ++ GF_END(f, f->next); ++ } ++ else if(flg == GF_EOD){ ++ /* first process the second line of an old line */ ++ if (f->oldline && f->oldline[0]) ++ SECOND_LINE_QUOTE_TEST(line, f); ++ ++ /* now we process the first line */ ++ FIRST_LINE_QUOTE_TEST(line, f); ++ ++ /* We are out of data. In this case we have processed the second ++ * line of an oldline, then the first line of a line, but we need ++ * to process the second line of the given line. We do this by ++ * processing it now!. ++ */ ++ if (line[i]){ ++ tmp_20k_buf[0] = '\0'; /* No next line */ ++ GF_ADD_QUOTED_LINE(f, line+i); ++ } ++ ++ fs_give((void **) &f->oldline); /* free old line buffer */ ++ fs_give((void **) &f->line); /* free line buffer */ ++ fs_give((void **) &f->opt); /* free test struct */ ++ GF_FLUSH(f->next); ++ (*f->next->f)(f->next, GF_EOD); ++ } ++ else if(flg == GF_RESET){ ++ 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; ++ } + + /* + * this simple filter accumulates characters until a newline, offers it +diff -rc alpine-2.21/pith/filter.h alpine-2.21.fillpara/pith/filter.h +*** alpine-2.21/pith/filter.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/filter.h Sun Feb 5 16:15:23 2017 +*************** +*** 216,221 **** +--- 216,222 ---- + void *gf_prepend_editorial_opt(prepedtest_t, char *); + void gf_nvtnl_local(FILTER_S *, int); + void gf_local_nvtnl(FILTER_S *, int); ++ void gf_quote_test(FILTER_S *, int); + void *gf_url_hilite_opt(URL_HILITE_S *, HANDLE_S **, int); + + +diff -rc alpine-2.21/pith/filttype.h alpine-2.21.fillpara/pith/filttype.h +*** alpine-2.21/pith/filttype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/filttype.h Sun Feb 5 16:15:23 2017 +*************** +*** 36,41 **** +--- 36,43 ---- + unsigned char t; /* temporary char */ + char *line; /* place for temporary storage */ + char *linep; /* pointer into storage space */ ++ char *oldline; /* the previous line to "line" */ ++ char *oldlinep; /* the previous line to "line" */ + void *opt; /* optional per instance data */ + void *data; /* misc internal data pointer */ + unsigned char queue[1 + GF_MAXBUF]; +diff -rc alpine-2.21/pith/mailview.c alpine-2.21.fillpara/pith/mailview.c +*** alpine-2.21/pith/mailview.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/mailview.c Sun Feb 5 16:15:23 2017 +*************** +*** 52,58 **** + #include "../pith/escapes.h" + #include "../pith/keyword.h" + #include "../pith/smime.h" +! + + #define FBUF_LEN (50) + +--- 52,62 ---- + #include "../pith/escapes.h" + #include "../pith/keyword.h" + #include "../pith/smime.h" +! #include "../pith/osdep/color.h" +! #include "../pico/osdep/color.h" +! #include "../pico/estruct.h" +! #include "../pico/pico.h" +! #include "../pico/efunc.h" + + #define FBUF_LEN (50) + +*************** +*** 284,290 **** + && 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) +--- 288,294 ---- + && pico_usingcolor() + && ps_global->VAR_SIGNATURE_FORE_COLOR + && ps_global->VAR_SIGNATURE_BACK_COLOR){ +! gf_link_filter(gf_quote_test, gf_line_test_opt(color_signature, &is_in_sig)); + } + + if((flgs & FM_DISPLAY) +*************** +*** 292,299 **** + && 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; +--- 296,305 ---- + && pico_usingcolor() + && ps_global->VAR_QUOTE1_FORE_COLOR + && ps_global->VAR_QUOTE1_BACK_COLOR){ +! gf_link_filter(gf_quote_test, gf_line_test_opt(color_a_quote, NULL)); + } ++ else ++ gf_link_filter(gf_quote_test,gf_line_test_opt(select_quote, NULL)); + + if(!(flgs & FM_NOWRAP)){ + wrapflags = (flgs & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN) : GFW_NONE; +*************** +*** 1098,1124 **** + 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, +--- 1104,1191 ---- + 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, i, j,same_qstr = 0, plb; + COLOR_PAIR *col = NULL; ++ static char GLine[NSTRING] = {'\0'}; ++ static char PLine[NSTRING] = {'\0'}; ++ static char PPLine[NSTRING] = {'\0'}; ++ char NLine[NSTRING] = {'\0'}; ++ char rqstr[NSTRING] = {'\0'}; ++ char *p, *q; ++ static char *buf, buf2[NSTRING] = {'\0'}; ++ QSTRING_S *qs; ++ static int qstrlen = 0; + + if(is_in_sig == NULL) + return 0; + ++ if (linenum > 0){ ++ strncpy(PLine, GLine, sizeof(PLine)); ++ PLine[sizeof(PLine)-1] = '\0'; ++ } ++ ++ if(p = strchr(tmp_20k_buf, '\015')) *p = '\0'; ++ strncpy(NLine, tmp_20k_buf, sizeof(NLine)); ++ NLine[sizeof(NLine) - 1] = '\0'; ++ if (p) *p = '\015'; ++ ++ strncpy(GLine, line, sizeof(GLine)); ++ GLine[sizeof(GLine) - 1] = '\0'; ++ ++ ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix ++ ? (void *) ps_global->prefix : (void *) ">", 0); ++ plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING); ++ qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb); ++ if(linenum > 0) ++ strncpy(PPLine, PLine, NSTRING); ++ strncpy(buf2, rqstr, NSTRING); ++ i = buf2 && buf2[0] ? strlen(buf2) : 0; ++ free_qs(&qs); ++ ++ /* determine if buf and buf2 are the same quote string */ ++ if (!struncmp(buf, buf2, qstrlen)){ ++ for (j = qstrlen; buf2[j] && isspace((unsigned char)buf2[j]); j++); ++ if (!buf2[j] || buf2[j] == '|' || (buf2[j] == '*' && buf2[j+1] != '>')) ++ same_qstr++; ++ } ++ + in_sig_block = (int *) is_in_sig; + +! if (*in_sig_block != OUT_SIG_BLOCK){ +! if (line && *line && (strlen(line) >= qstrlen) && same_qstr) +! line += qstrlen; +! else if (strlen(line) < qstrlen) +! line += i; +! else if (!same_qstr) +! *in_sig_block = OUT_SIG_BLOCK; +! } +! else +! line += i; +! +! if(!strcmp(line, SIGDASHES) || !strcmp(line, "--")){ +! *in_sig_block = START_SIG_BLOCK; +! buf = (char *) fs_get((i + 1)*sizeof(char)); +! buf = cpystr(buf2); +! qstrlen = i; +! } +! 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){ ++ qstrlen = 0; /* reset back in case there's another paragraph */ ++ if (buf) ++ fs_give((void **)&buf); ++ } ++ + if(*in_sig_block != OUT_SIG_BLOCK + && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR + && (col = new_color_pair(VAR_SIGNATURE_FORE_COLOR, +*************** +*** 1678,1683 **** +--- 1745,1821 ---- + } + + ++ /* This filter gives a quote string of a line. It sends its reply back to the ++ calling filter in the tmp_20k_buf variable. This filter replies with ++ the full quote string including tailing spaces if any. It is the ++ responsibility of the calling filter to figure out if thos spaces are ++ useful for that filter or if they should be removed before doing any ++ useful work. For example, color_a_quote does not require the trailing ++ spaces, but gf_wrap does. ++ */ ++ int ++ select_quote(long linenum, char *line, LT_INS_S **ins, void *local) ++ { ++ int i, plb, *code; ++ char rqstr[NSTRING] = {'\0'}, buf[NSTRING] = {'\0'}; ++ char GLine[NSTRING] = {'\0'}, PLine[NSTRING] = {'\0'}; ++ char PPLine[NSTRING] = {'\0'}, NLine[NSTRING] = {'\0'}; ++ static char GLine1[NSTRING] = {'\0'}; ++ static char PLine1[NSTRING] = {'\0'}; ++ static char PPLine1[NSTRING] = {'\0'}; ++ static char GLine2[NSTRING] = {'\0'}; ++ static char PLine2[NSTRING] = {'\0'}; ++ static char PPLine2[NSTRING] = {'\0'}; ++ QSTRING_S *qs; ++ int buflen = NSTRING < SIZEOF_20KBUF ? NSTRING - 1: SIZEOF_20KBUF - 1; ++ int who, raw; ++ ++ code = (int *)local; ++ who = code ? (*code & COLORAQUO) : 0; /* may I ask who is calling? */ ++ raw = code ? (*code & RAWSTRING) : 0; /* return raw string */ ++ strncpy(GLine, (who ? GLine1 : GLine2), buflen); ++ strncpy(PLine, (who ? PLine1 : PLine2), buflen); ++ strncpy(PPLine, (who ? PPLine1 : PPLine2), buflen); ++ ++ if (linenum > 0) ++ strncpy(PLine, GLine, buflen); ++ ++ strncpy(NLine, tmp_20k_buf, buflen); ++ ++ if (line) ++ strncpy(GLine, line, buflen); ++ else ++ GLine[0] = '\0'; ++ ++ ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix ++ ? (void *) ps_global->prefix : (void *) ">", 0); ++ plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING); ++ ++ qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb); ++ if (raw) ++ strncpy(buf, rqstr, NSTRING); ++ else ++ flatten_qstring(qs, buf, NSTRING); ++ if(qs) ++ record_quote_string(qs); ++ free_qs(&qs); ++ ++ /* do not paint an extra level for a line with a >From string at the ++ * begining of it ++ */ ++ if (buf[0]){ ++ i = strlen(buf); ++ if (strlen(line) >= i + 6 && !strncmp(line+i-1,">From ", 6)) ++ buf[i - 1] = '\0'; ++ } ++ strncpy(tmp_20k_buf, buf, buflen); ++ if (linenum > 0) ++ strncpy((who ? PPLine1 : PPLine2), PLine, buflen); ++ strncpy((who ? GLine1 : GLine2), GLine, buflen); ++ strncpy((who ? PLine1 : PLine2), PLine, buflen); ++ return 1; ++ } ++ + + #define UES_LEN 12 + #define UES_MAX 32 +diff -rc alpine-2.21/pith/mailview.h alpine-2.21.fillpara/pith/mailview.h +*** alpine-2.21/pith/mailview.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/mailview.h Sun Feb 5 16:15:23 2017 +*************** +*** 142,147 **** +--- 142,148 ---- + char *display_parameters(PARAMETER *); + char *pine_fetch_header(MAILSTREAM *, long, char *, char **, long); + int color_signature(long, char *, LT_INS_S **, void *); ++ int select_quote(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); +diff -rc alpine-2.21/pith/osdep/color.c alpine-2.21.fillpara/pith/osdep/color.c +*** alpine-2.21/pith/osdep/color.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/osdep/color.c Sun Feb 5 16:15:23 2017 +*************** +*** 32,38 **** + + #include + #include "./color.h" +! + + + /* +--- 32,40 ---- + + #include + #include "./color.h" +! #include "./collate.h" +! #include "../charconv/utf8.h" +! #include "../../c-client/c-client.h" + + + /* +*************** +*** 92,94 **** +--- 94,1368 ---- + { + return(pico_set_colors(col ? col->fg : NULL, col ? col->bg : NULL, flags)); + } ++ ++ ++ /* ++ * Extended Justification support also does not belong here ++ * but otherwise webpine will not build, so we move everything ++ * here. Hopefully this will be the permanent place for these ++ * routines. These routines used to be in pico/word.c ++ */ ++ #define NSTRING 256 ++ #include "../../include/general.h" ++ ++ /* Support of indentation of paragraphs */ ++ #define is_indent_char(c) (((c) == '.' || (c) == '}' || (c) == RPAREN || \ ++ (c) == '*' || (c) == '+' || is_a_digit(c) || \ ++ ISspace(c) || (c) == '-' || \ ++ (c) == ']') ? 1 : 0) ++ #define allowed_after_digit(c,word,k) ((((c) == '.' && \ ++ allowed_after_period(next((word),(k)))) ||\ ++ (c) == RPAREN || (c) == '}' || (c) == ']' ||\ ++ ISspace(c) || is_a_digit(c) || \ ++ ((c) == '-' ) && \ ++ allowed_after_dash(next((word),(k)))) \ ++ ? 1 : 0) ++ #define allowed_after_period(c) (((c) == RPAREN || (c) == '}' || (c) == ']' ||\ ++ ISspace(c) || (c) == '-' || \ ++ is_a_digit(c)) ? 1 : 0) ++ #define allowed_after_parenth(c) (ISspace(c) ? 1 : 0) ++ #define allowed_after_space(c) (ISspace(c) ? 1 : 0) ++ #define allowed_after_braces(c) (ISspace(c) ? 1 : 0) ++ #define allowed_after_star(c) ((ISspace(c) || (c) == RPAREN ||\ ++ (c) == ']' || (c) == '}') ? 1 : 0) ++ #define allowed_after_dash(c) ((ISspace(c) || is_a_digit(c)) ? 1 : 0) ++ #define EOLchar(c) (((c) == '.' || (c) == ':' || (c) == '?' ||\ ++ (c) == '!') ? 1 : 0) ++ ++ ++ /* Extended justification support */ ++ #define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':') ++ #define is_cword(c) ((((c) >= 'a') && ((c) <= 'z')) || \ ++ (((c) >= 'A') && ((c) <= 'Z')) || \ ++ (((c) >= '0') && ((c) <= '9')) || \ ++ ((c) == ' ') || ((c) == '?') || \ ++ ((c) == '@') || ((c) == '.') || \ ++ ((c) == '!') || ((c) == '\'') || \ ++ ((c) == ',') || ((c) == '\"') ? 1 : 0) ++ #define isaquote(c) ((c) == '\"' || (c) == '\'') ++ #define is8bit(c) ((((int) (c)) & 0x80) ? 1 : 0) ++ #define iscontrol(c) (iscntrl(((int) (c)) & 0x7f) ? 1 : 0) ++ #define forbidden(c) (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\ ++ ((c) == ',') || ((c) == '.') || ((c) == '-') ||\ ++ ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\ ++ ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\ ++ (((c) >= '0') && ((c) <= '9')) || ((c) == '?')) ++ #define is_cletter(c) ((((c) >= 'a') && ((c) <= 'z'))) ||\ ++ ((((c) >= 'A') && ((c) <= 'Z'))||\ ++ is8bit(c)) ++ #define is_cnumber(c) ((c) >= '0' && (c) <= '9') ++ #define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c)) ++ #define allwd_after_qsword(c) (((c) != '\\') && ((c) != RPAREN) && ((c) != '/')) ++ #define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0) ++ #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) ++ #define now(w,i) ((w)[(i)]) ++ #define is_qsword(c) (((c) == ':') || ((c) == RPAREN) ? 1 : 0) ++ #define is_colon(c) (((c) == ':') ? 1 : 0) ++ #define is_rarrow(c) (((c) == '>') ? 1 : 0) ++ #define is_tilde(c) (((c) == '~') ? 1 : 0) ++ #define is_dash(c) (((c) == '-') ? 1 : 0) ++ #define is_pound(c) (((c) == '#') ? 1 : 0) ++ #define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) ++ #define is_allowed(c) (is_cquote(c) || is_cword(c) || is_dash(c) || \ ++ is_pound(c)) ++ #define qs_allowed(a) (((a)->qstype != qsGdb) && ((a)->qstype != qsProg)) ++ ++ /* Internal justification functions */ ++ QSTRING_S *is_quote(char **, char *, int); ++ QSTRING_S *qs_normal_part(QSTRING_S *); ++ QSTRING_S *qs_remove_trailing_spaces(QSTRING_S *); ++ QSTRING_S *trim_qs_from_cl(QSTRING_S *, QSTRING_S *, QSTRING_S *); ++ QSTRING_S *fix_qstring(QSTRING_S *, QSTRING_S *, QSTRING_S *); ++ QSTRING_S *fix_qstring_allowed(QSTRING_S *, QSTRING_S *, QSTRING_S *); ++ QSTRING_S *qs_add(char **, char *, QStrType, int, int, int, int); ++ QSTRING_S *remove_qsword(QSTRING_S *); ++ QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **); ++ void free_qs(QSTRING_S **); ++ int word_is_prog(char *); ++ int qstring_is_normal(QSTRING_S *); ++ int exists_good_part(QSTRING_S *); ++ int strcmp_qs(char *, char *); ++ int count_levels_qstring(QSTRING_S *); ++ int same_qstring(QSTRING_S *, QSTRING_S *); ++ int isaword(char *,int ,int); ++ int isamailbox(char *,int ,int); ++ int double_check_qstr(char *); ++ ++ int ++ word_is_prog(char *word) ++ { ++ static char *list1[] = {"#include", ++ "#define", ++ "#ifdef", ++ "#ifndef", ++ "#elif", ++ "#if", ++ NULL}; ++ static char *list2[] = {"#else", ++ "#endif", ++ NULL}; ++ int i, j = strlen(word), k, rv = 0; ++ ++ for(i = 0; rv == 0 && list1[i] && (k = strlen(list1[i])) && k < j; i++) ++ if(!strncmp(list1[i], word, k) && ISspace(word[k])) ++ rv++; ++ ++ if(rv) ++ return rv; ++ ++ for(i = 0; rv == 0 && list2[i] && (k = strlen(list2[i])) && k <= j; i++) ++ if(!strncmp(list2[i], word, k) && (!word[k] || ISspace(word[k]))) ++ rv++; ++ ++ return rv; ++ } ++ ++ /* ++ * This function creates a qstring pointer with the information that ++ * is_quote handles to it. ++ * Parameters: ++ * qs - User supplied quote string ++ * word - The line of text that the user is trying to read/justify ++ * beginw - Where we need to start copying from ++ * endw - Where we end copying ++ * offset - Any offset in endw that we need to account for ++ * typeqs - type of the string to be created ++ * neednext - boolean, indicating if we need to compute the next field ++ * of leave it NULL ++ * ++ * It is a mistake to call this function if beginw >= endw + offset. ++ * Please note the equality sign in the above inequality (this is because ++ * we always assume that qstring->value != ""). ++ */ ++ QSTRING_S * ++ qs_add(char **qs, char word[NSTRING], QStrType typeqs, int beginw, int endw, ++ int offset, int neednext) ++ { ++ QSTRING_S *qstring, *nextqs; ++ int i; ++ ++ qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S)); ++ memset (qstring, 0, sizeof(QSTRING_S)); ++ qstring->qstype = qsNormal; ++ ++ if (beginw == 0){ ++ beginw = endw + offset; ++ qstring->qstype = typeqs; ++ } ++ ++ nextqs = neednext ? is_quote(qs, word+beginw, 1) : NULL; ++ ++ qstring->value = (char *) malloc((beginw+1)*sizeof(char)); ++ strncpy(qstring->value, word, beginw); ++ qstring->value[beginw] = '\0'; ++ ++ qstring->next = nextqs; ++ ++ return qstring; ++ } ++ ++ int ++ qstring_is_normal(QSTRING_S *cl) ++ { ++ for (;cl && (cl->qstype == qsNormal); cl = cl->next); ++ return cl ? 0 : 1; ++ } ++ ++ /* ++ * Given a quote string, this function returns the part that is the leading ++ * normal part of it. (the normal part is the part that is tagged qsNormal, ++ * that is to say, the one that is not controversial at all (like qsString ++ * for example). ++ */ ++ QSTRING_S * ++ qs_normal_part(QSTRING_S *cl) ++ { ++ ++ if (!cl) /* nothing in, nothing out */ ++ return cl; ++ ++ if (cl->qstype != qsNormal) ++ free_qs(&cl); ++ ++ if (cl) ++ cl->next = qs_normal_part(cl->next); ++ ++ return cl; ++ } ++ ++ /* ++ * this function removes trailing spaces from a quote string, but leaves the ++ * last one if there are trailing spaces ++ */ ++ QSTRING_S * ++ qs_remove_trailing_spaces(QSTRING_S *cl) ++ { ++ QSTRING_S *rl = cl; ++ if (!cl) /* nothing in, nothing out */ ++ return cl; ++ ++ if (cl->next) ++ cl->next = qs_remove_trailing_spaces(cl->next); ++ else{ ++ if (value_is_space(cl->value)) ++ free_qs(&cl); ++ else{ ++ int i, l; ++ i = l = strlen(cl->value) - 1; ++ while (cl->value && cl->value[i] ++ && ISspace(cl->value[i])) ++ i--; ++ i += (i < l) ? 2 : 1; ++ cl->value[i] = '\0'; ++ } ++ } ++ return cl; ++ } ++ ++ /* ++ * This function returns if two strings are the same quote string. ++ * The call is not symmetric. cl must preceed the line nl. This function ++ * should be called for comparing the last part of cl and nl. ++ */ ++ int ++ strcmp_qs(char *valuecl, char *valuenl) ++ { ++ int j; ++ ++ for (j = 0; valuecl[j] && (valuecl[j] == valuenl[j]); j++); ++ return !strcmp(valuecl, valuenl) ++ || (valuenl[j] && value_is_space(valuenl+j) ++ && value_is_space(valuecl+j) ++ && strlenis(valuecl+j) >= strlenis(valuenl+j)) ++ || (!valuenl[j] && value_is_space(valuecl+j)); ++ } ++ ++ int ++ count_levels_qstring(QSTRING_S *cl) ++ { ++ int count; ++ for (count = 0; cl ; count++, cl = cl->next); ++ ++ return count; ++ } ++ ++ int ++ value_is_space(char *value) ++ { ++ for (; value && *value && ISspace(*value); value++); ++ ++ return value && *value ? 0 : 1; ++ } ++ ++ void ++ free_qs(QSTRING_S **cl) ++ { ++ if (!(*cl)) ++ return; ++ ++ if ((*cl)->next) ++ free_qs(&((*cl)->next)); ++ ++ (*cl)->next = (QSTRING_S *) NULL; ++ ++ if ((*cl)->value) ++ free((void *)(*cl)->value); ++ (*cl)->value = (char *) NULL; ++ free((void *)(*cl)); ++ *cl = (QSTRING_S *) NULL; ++ } ++ ++ /* ++ * This function returns the number of agreements between ++ * cl and nl. The call is not symmetric. cl must be the line ++ * preceding nl. ++ */ ++ int ++ same_qstring(QSTRING_S *cl, QSTRING_S *nl) ++ { ++ int same = 0, done = 0; ++ ++ for (;cl && nl && !done; cl = cl->next, nl = nl->next) ++ if (cl->qstype == nl->qstype ++ && (!strcmp(cl->value, nl->value) ++ || (!cl->next && strcmp_qs(cl->value, nl->value)))) ++ same++; ++ else ++ done++; ++ return same; ++ } ++ ++ QSTRING_S * ++ trim_qs_from_cl(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) ++ { ++ QSTRING_S *cqstring = pl ? pl : nl; ++ QSTRING_S *tl = pl ? pl : nl; ++ int p, c; ++ ++ if (qstring_is_normal(tl)) ++ return tl; ++ ++ p = same_qstring(pl ? pl : cl, pl ? cl : nl); ++ ++ for (c = 1; c < p; c++, cl = cl->next, tl = tl->next); ++ ++ /* ++ * cl->next and tl->next differ, it may be because cl->next does not ++ * exist or tl->next does not exist or simply both exist but are ++ * different. In this last case, it may be that cl->next->value is made ++ * of spaces. If this is the case, tl advances once more. ++ */ ++ ++ if (tl->next){ ++ if (cl && cl->next && value_is_space(cl->next->value)) ++ tl = tl->next; ++ if (tl->next) ++ free_qs(&(tl->next)); ++ } ++ ++ if (!p) ++ free_qs(&cqstring); ++ ++ return cqstring; ++ } ++ ++ /* This function trims cl so that it returns a real quote string based ++ * on information gathered from the previous and next lines. pl and cl are ++ * also trimmed, but that is done in another function, not here. ++ */ ++ QSTRING_S * ++ fix_qstring(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) ++ { ++ QSTRING_S *cqstring = cl, *nqstring = nl, *pqstring = pl; ++ int c, n; ++ ++ if (qstring_is_normal(cl)) ++ return cl; ++ ++ c = count_levels_qstring(cl); ++ n = same_qstring(cl,nl); ++ ++ if (!n){ /* no next line or no agreement with next line */ ++ int p = same_qstring(pl, cl); /* number of agreements between pl and cl */ ++ QSTRING_S *tl; /* test line */ ++ ++ /* ++ * Here p <= c, so either p < c or p == c. If p == c, we are done, ++ * and return cl. If not, there are two cases, either p == 0 or ++ * 0 < p < c. In the first case, we do not have enough evidence ++ * to return anything other than the normal part of cl, in the second ++ * case we can only return p levels of cl. ++ */ ++ ++ if (p == c) ++ tl = cqstring; ++ else{ ++ if (p){ ++ for (c = 1; c < p; c++) ++ cl = cl->next; ++ free_qs(&(cl->next)); ++ tl = cqstring; ++ } ++ else{ ++ int done = 0; ++ QSTRING_S *al = cl; /* another line */ ++ /* ++ * Ok, we really don't have enough evidence to return anything, ++ * different from the normal part of cl, but it could be possible ++ * that we may want to accept the not-normal part, so we better ++ * make an extra test to determine what needs to be freed ++ */ ++ while (pl && cl && cl->qstype == pl->qstype ++ && !strucmp(cl->value, pl->value)){ ++ cl = cl->next; ++ pl = pl->next; ++ } ++ if (pl && cl && cl->qstype == pl->qstype ++ && strcmp_qs(pl->value, cl->value)) ++ cl = cl->next; /* next level differs only in spaces */ ++ while (!done){ ++ while (cl && cl->qstype == qsNormal) ++ cl = cl->next; ++ if (cl){ ++ if ((cl->qstype == qsString) ++ && (cl->value[strlen(cl->value) - 1] == '>')) ++ cl = cl->next; ++ else done++; ++ } ++ else done++; ++ } ++ if (al == cl){ ++ free_qs(&(cl)); ++ tl = cl; ++ } ++ else { ++ while (al && (al->next != cl)) ++ al = al->next; ++ cl = al; ++ if (cl && cl->next) ++ free_qs(&(cl->next)); ++ tl = cqstring; ++ } ++ } ++ } ++ return tl; ++ } ++ if (n + 1 < c){ /* if there are not enough agreements */ ++ int p = same_qstring(pl, cl); /* number of agreement between pl and cl */ ++ QSTRING_S *tl; /* test line */ ++ /* ++ * There's no way we can use cl in this case, but we can use ++ * part of cl, this is if pl does not have more agreements ++ * with cl. ++ */ ++ if (p == c) ++ tl = cqstring; ++ else{ ++ int m = p < n ? n : p; ++ for (c = 1; c < m; c++){ ++ pl = pl ? pl->next : (QSTRING_S *) NULL; ++ nl = nl ? nl->next : (QSTRING_S *) NULL; ++ cl = cl->next; ++ } ++ if (p == n && pl && pl->next && nl && nl->next ++ && ((cl->next->qstype == pl->next->qstype) ++ || (cl->next->qstype == nl->next->qstype)) ++ && (strcmp_qs(cl->next->value, pl->next->value) ++ || strcmp_qs(pl->next->value, cl->next->value) ++ || strcmp_qs(cl->next->value, nl->next->value) ++ || strcmp_qs(nl->next->value, cl->next->value))) ++ cl = cl->next; /* next level differs only in spaces */ ++ if (cl->next) ++ free_qs(&(cl->next)); ++ tl = cqstring; ++ } ++ return tl; ++ } ++ if (n + 1 == c){ ++ int p = same_qstring(pl, cl); ++ QSTRING_S *tl; /* test line */ ++ ++ /* ++ * p <= c, so p <= n+1, which means p < n + 1 or p == n + 1. ++ * If p < n + 1, then p <= n. ++ * so we have three possibilities: ++ * p == n + 1 or p == n or p < n. ++ * In the first case we copy p == n + 1 == c levels, in the second ++ * and third case we copy n levels, and check if we can copy the ++ * n + 1 == c level. ++ */ ++ if (p == n + 1) /* p == c, in the above sense of c */ ++ tl = cl; /* use cl, this is enough evidence */ ++ else{ ++ for (c = 1; c < n; c++) ++ cl = cl->next; ++ /* ++ * Here c == n, we only have one more level of cl, and at least one ++ * more level of nl ++ */ ++ if (cl->next->qstype == qsNormal) ++ cl = cl->next; ++ if (cl->next) ++ free_qs(&(cl->next)); ++ tl = cqstring; ++ } ++ return tl; ++ } ++ if (n == c) /* Yeah!!! */ ++ return cqstring; ++ return 0; ++ } ++ ++ QSTRING_S * ++ fix_qstring_allowed(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) ++ { ++ if(!cl) ++ return (QSTRING_S *) NULL; ++ ++ if (qs_allowed(cl)) ++ cl->next = fix_qstring_allowed(cl->next, (nl ? nl->next : NULL), ++ (pl ? pl->next : NULL)); ++ else ++ if((nl && cl->qstype == nl->qstype) || (pl && cl->qstype == pl->qstype) ++ || (!nl && !pl)) ++ free_qs(&cl); ++ return cl; ++ } ++ ++ /* ++ * This function flattens the quote string returned to us by is_quote. A ++ * crash in this function implies a bug elsewhere. ++ */ ++ void ++ flatten_qstring(QSTRING_S *qs, char *buff, int bufflen) ++ { ++ int i, j; ++ if(!buff || bufflen <= 0) ++ return; ++ ++ for (i = 0; qs; qs = qs->next) ++ for (j = 0; i < bufflen - 1 ++ && (qs->value[j]) && (buff[i++] = qs->value[j]); j++); ++ buff[i] = '\0'; ++ } ++ ++ extern int list_len; ++ ++ ++ int ++ double_check_qstr(char *q) ++ { ++ if(!q || !*q) ++ return 0; ++ ++ return (*q == '#') ? 1 : 0; ++ } ++ ++ /* ++ * Given a string, we return the position where the function thinks that ++ * the quote string is over, if you are ever thinking of fixing something, ++ * you got to the right place. Memory freed by caller. Experience shows ++ * that it only makes sense to initialize memory when we need it, not at ++ * the start of this function. ++ */ ++ QSTRING_S * ++ is_quote (char **qs,char *word, int been_here) ++ { ++ int i = 0, j, nxt, prev, finished = 0, offset; ++ unsigned char c; ++ QSTRING_S *qstring = (QSTRING_S *) NULL; ++ ++ if (word == NULL || word[0] == '\0') ++ return (QSTRING_S *) NULL; ++ ++ while (!finished){ ++ /* ++ * Before we apply our rules, let's advance past the quote string ++ * given by the user, this will avoid not recognition of the ++ * user's indent string and application of the arbitrary rules ++ * below. Notice that this step may bring bugs into this ++ * procedure, but these bugs will only appear if the indent string ++ * is really really strange and the text to be justified ++ * cooperates a lot too, so in general this will not be a problem. ++ * If you are concerned about this bug, simply remove the ++ * following lines after this comment and before the "switch" ++ * command below and use a more normal quote string!. ++ */ ++ for(j = 0; j < list_len; j++){ ++ if(!double_check_qstr(qs[j])){ ++ i += advance_quote_string(qs[j], word, i); ++ if (!word[i]) /* went too far? */ ++ return qs_add(qs, word, qsNormal, 0, i, 0, 0); ++ } ++ else ++ break; ++ } ++ ++ switch (c = (unsigned char) now(word,i)){ ++ case NBSP: ++ case TAB : ++ case ' ' : { QSTRING_S *nextqs, *d; ++ ++ for (; ISspace(word[i]); i++); /* FIX ME */ ++ nextqs = is_quote(qs,word+i, 1); ++ /* ++ * Merge qstring and nextqs, since this is an artificial ++ * separation, unless nextqs is of different type. ++ * What this means in practice is that if ++ * qs->qstype == qsNormal and qs->next != NULL, then ++ * qs->next->qstype != qsNormal. ++ * ++ * Can't use qs_add to merge because it could lead ++ * to an infinite loop (e.g a line "^ ^"). ++ */ ++ i += nextqs && nextqs->qstype == qsNormal ++ ? strlen(nextqs->value) : 0; ++ qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S)); ++ memset (qstring, 0, sizeof(QSTRING_S)); ++ qstring->value = (char *) malloc((i+1)*sizeof(char)); ++ strncpy(qstring->value, word, i); ++ qstring->value[i] = '\0'; ++ qstring->qstype = qsNormal; ++ if(nextqs && nextqs->qstype == qsNormal){ ++ d = nextqs->next; ++ nextqs->next = NULL; ++ qstring->next = d; ++ free_qs(&nextqs); ++ } ++ else ++ qstring->next = nextqs; ++ ++ return qstring; ++ } ++ break; ++ case RPAREN: /* parenthesis ')' */ ++ if ((i != 0) || ((i == 0) && been_here)) ++ i++; ++ else ++ if (i == 0) ++ return qs_add(qs, word, qsChar, i, i, 1, 1); ++ else ++ finished++; ++ break; ++ ++ case ':': /* colon */ ++ case '~': nxt = next(word,i); ++ if ((is_tilde(c) && (nxt == '/')) ++ || (is_colon(c) && !is_cquote(nxt) ++ && !is_cword(nxt) && nxt != RPAREN)) ++ finished++; ++ else if (is_cquote(c) ++ || is_cquote(nxt) ++ || (c != '~' && nxt == RPAREN) ++ || (i != 0 && ISspace(nxt)) ++ || is_cquote(prev = before(word,i)) ++ || (ISspace(prev) && !is_tilde(c)) ++ || (is_tilde(c) && nxt != '/')) ++ i++; ++ else if (i == 0 && been_here) ++ return qs_add(qs, word, qsChar, i, i, 1, 1); ++ else ++ finished++; ++ break; ++ ++ case '<' : ++ case '=' : ++ case '-' : offset = is_cquote(nxt = next(word,i)) ? 2 ++ : (nxt == c && is_cquote(next(word,i+1))) ? 3 : -1; ++ ++ if (offset > 0) ++ return qs_add(qs, word, qsString, i, i, offset, 1); ++ else ++ finished++; ++ break; ++ ++ case '[' : ++ case '+' : /* accept +>, *> */ ++ case '*' : if (is_rarrow(nxt = next(word, i)) || /* stars */ ++ (ISspace(nxt) && is_rarrow(next(word,i+1)))) ++ i++; ++ else ++ finished++; ++ break; ++ ++ case '^' : ++ case '!' : ++ case '%' : if (next(word,i) != c) ++ return qs_add(qs, word, qsChar, i, i+1, 0, 1); ++ else ++ finished++; ++ break; ++ ++ case '_' : if(ISspace(next(word, i))) ++ return qs_add(qs, word, qsChar, i, i+1, 0, 1); ++ else ++ finished++; ++ break; ++ ++ case '#' : { QStrType qstype = qsChar; ++ if((nxt = next(word, i)) != c){ ++ if(isdigit((int) nxt)) ++ qstype = qsGdb; ++ else ++ if(word_is_prog(word)) ++ qstype = qsProg; ++ return qs_add(qs, word, qstype, i, i+1, 0, 1); ++ } ++ else ++ finished++; ++ break; ++ } ++ ++ default: ++ if (is_cquote(c)) ++ i++; ++ else if (is_cletter(c)){ ++ for (j = i; (is_cletter(nxt = next(word,j)) || is_cnumber(nxt)) ++ && !(ISspace(nxt));j++); ++ /* ++ * The whole reason why we are splitting the quote ++ * string is so that we will be able to accept quote ++ * strings that are strange in some way. Here we got to ++ * a point in which a quote string might exist, but it ++ * could be strange, so we need to create a "next" field ++ * for the quote string to warn us that something ++ * strange is coming. We need to confirm if this is a ++ * good choice later. For now we will let it pass. ++ */ ++ if (isaword(word,i,j) || isamailbox(word,i,j)){ ++ int offset; ++ QStrType qstype; ++ ++ offset = (is_cquote(c = next(word,j)) ++ || (c == RPAREN)) ? 2 ++ : ((ISspace(c) ++ && is_cquote(next(word,j+1))) ? 3 : -1); ++ ++ qstype = (is_cquote(c) || (c == RPAREN)) ++ ? (is_qsword(c) ? qsWord : qsString) ++ : ((ISspace(c) && is_cquote(next(word,j+1))) ++ ? (is_qsword(next(word,j+1)) ++ ? qsWord : qsString) ++ : qsString); ++ ++ /* ++ * qsWords are valid quote strings only when ++ * they are followed by text. ++ */ ++ if (offset > 0 && qstype == qsWord && ++ !allwd_after_qsword(now(word,j + offset))) ++ offset = -1; ++ ++ if (offset > 0) ++ return qs_add(qs, word, qstype, i, j, offset, 1); ++ } ++ finished++; ++ } ++ else{ ++ if(i > 0) ++ return qs_add(qs, word, qsNormal, 0, i, 0, 1); ++ else if(!forbidden(c)) ++ return qs_add(qs, word, qsChar, 0, 1, 0, 1); ++ else /* chao pescao */ ++ finished++; ++ } ++ break; ++ } /* End Switch */ ++ } /* End while */ ++ if (i > 0) ++ qstring = qs_add(qs, word, qsNormal, 0, i, 0, 0); ++ return qstring; ++ } ++ ++ int ++ isaword(char word[NSTRING], int i, int j) ++ { ++ return i <= j && is_cletter(word[i]) ? ++ (i < j ? isaword(word,i+1,j) : 1) : 0; ++ } ++ ++ int ++ isamailbox(char word[NSTRING], int i, int j) ++ { ++ return i <= j && (is_cletter(word[i]) || is_a_digit(word[i]) ++ || word[i] == '.') ++ ? (i < j ? isamailbox(word,i+1,j) : 1) : 0; ++ } ++ ++ /* ++ This routine removes the last part that is qsword or qschar that is not ++ followed by a normal part. This means that if a qsword or qschar is ++ followed by a qsnormal (or qsstring), we accept the qsword (or qschar) ++ as part of a quote string. ++ */ ++ QSTRING_S * ++ remove_qsword(QSTRING_S *cl) ++ { ++ QSTRING_S *np = cl; ++ QSTRING_S *cp = np; /* this variable trails cl */ ++ ++ while(1){ ++ while (cl && cl->qstype == qsNormal) ++ cl = cl->next; ++ ++ if (cl){ ++ if (((cl->qstype == qsWord) || (cl->qstype == qsChar)) ++ && !exists_good_part(cl)){ ++ if (np == cl) /* qsword or qschar at the beginning */ ++ free_qs(&cp); ++ else{ ++ while (np->next != cl) ++ np = np->next; ++ free_qs(&(np->next)); ++ } ++ break; ++ } ++ else ++ cl = cl->next; ++ } ++ else ++ break; ++ } ++ return cp; ++ } ++ ++ int ++ exists_good_part (QSTRING_S *cl) ++ { ++ return (cl ? (((cl->qstype != qsWord) && (cl->qstype != qsChar) ++ && qs_allowed(cl) && !value_is_space(cl->value)) ++ ? 1 : exists_good_part(cl->next)) ++ : 0); ++ } ++ ++ int ++ line_isblank(char **q, char *GLine, char *NLine, char *PLine, int buflen) ++ { ++ int n = 0; ++ QSTRING_S *cl; ++ char qstr[NSTRING]; ++ ++ cl = do_raw_quote_match(q, GLine, NLine, PLine, NULL, NULL); ++ ++ flatten_qstring(cl, qstr, NSTRING); ++ ++ free_qs(&cl); ++ ++ for(n = strlen(qstr); n < buflen && GLine[n]; n++) ++ if(!ISspace((unsigned char) GLine[n])) ++ return(FALSE); ++ ++ return(TRUE); ++ } ++ ++ QSTRING_S * ++ do_raw_quote_match(char **q, char *GLine, char *NLine, char *PLine, QSTRING_S **nlp, QSTRING_S **plp) ++ { ++ QSTRING_S *cl, *nl = NULL, *pl = NULL; ++ char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING]; ++ int emptypl = 0, emptynl = 0; ++ ++ if (!(cl = is_quote(q, GLine, 0))) /* if nothing in, nothing out */ ++ return cl; ++ ++ nl = is_quote(q, NLine, 0); /* Next Line */ ++ if (nlp) *nlp = nl; ++ pl = is_quote(q, PLine, 0); /* Previous Line */ ++ if (plp) *plp = pl; ++ /* ++ * If there's nothing in the preceeding or following line ++ * there is not enough information to accept it or discard it. In this ++ * case it's likely to be an isolated line, so we better accept it ++ * if it does not look like a word. ++ */ ++ flatten_qstring(pl, pbuf, NSTRING); ++ emptypl = (!PLine || !PLine[0] || ++ (pl && value_is_space(pbuf)) && !PLine[strlen(pbuf)]) ? 1 : 0; ++ if (emptypl){ ++ flatten_qstring(nl, nbuf, NSTRING); ++ emptynl = (!NLine || !NLine[0] || ++ (nl && value_is_space(nbuf) && !NLine[strlen(nbuf)])) ? 1 : 0; ++ if (emptynl){ ++ cl = remove_qsword(cl); ++ if((cl = fix_qstring_allowed(cl, NULL, NULL)) != NULL) ++ cl = qs_remove_trailing_spaces(cl); ++ free_qs(&nl); ++ free_qs(&pl); ++ if(nlp) *nlp = NULL; ++ if(plp) *plp = NULL; ++ ++ return cl; ++ } ++ } ++ ++ /* ++ * If either cl, nl or pl contain suspicious characters that may make ++ * them (or not) be quote strings, we need to fix them, so that the ++ * next pass will be done correctly. ++ */ ++ ++ cl = fix_qstring(cl, nl, pl); ++ nl = trim_qs_from_cl(cl, nl, NULL); ++ pl = trim_qs_from_cl(cl, NULL, pl); ++ if((cl = fix_qstring_allowed(cl, nl, pl)) != NULL){ ++ nl = trim_qs_from_cl(cl, nl, NULL); ++ pl = trim_qs_from_cl(cl, NULL, pl); ++ } ++ else{ ++ free_qs(&nl); ++ free_qs(&pl); ++ } ++ if(nlp) ++ *nlp = nl; ++ else ++ free_qs(&nl); ++ if(plp) ++ *plp = pl; ++ else ++ free_qs(&pl); ++ return cl; ++ } ++ ++ QSTRING_S * ++ do_quote_match(char **q, char *GLine, char *NLine, char *PLine, char *rqstr, ++ int rqstrlen, int plb) ++ { ++ QSTRING_S *cl, *nl = NULL, *pl = NULL; ++ int c, n, p,i, j, NewP, NewC, NewN, clength, same = 0; ++ char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING]; ++ ++ if(rqstr) ++ *rqstr = '\0'; ++ ++ /* if nothing in, nothing out */ ++ cl = do_raw_quote_match(q, GLine, NLine, PLine, &nl, &pl); ++ if(cl == NULL){ ++ free_qs(&nl); ++ free_qs(&pl); ++ return cl; ++ } ++ ++ flatten_qstring(cl, rqstr, rqstrlen); ++ flatten_qstring(cl, buf, NSTRING); ++ flatten_qstring(nl, nbuf, NSTRING); ++ flatten_qstring(pl, pbuf, NSTRING); ++ ++ /* ++ * Once upon a time, is_quote used to return the length of the quote ++ * string that it had found. One day, not long ago, black hand came ++ * and changed all that, and made is_quote return a quote string ++ * divided in several fields, making the algorithm much more ++ * complicated. Fortunately black hand left a few comments in the ++ * source code to make it more understandable. Because of this change ++ * we need to compute the lengths of the quote strings separately ++ */ ++ c = buf && buf[0] ? strlen(buf) : 0; ++ n = nbuf && nbuf[0] ? strlen(nbuf) : 0; ++ p = pbuf && pbuf[0] ? strlen(pbuf) : 0; ++ /* ++ * When quote strings contain only blank spaces (ascii code 32) the ++ * above count is equal to the length of the quote string, but if ++ * there are TABS, the length of the quote string as seen by the user ++ * is different than the number that was just computed. Because of ++ * this we demand a recount (hmm.. unless you are in Florida, where ++ * recounts are forbidden) ++ */ ++ NewP = strlenis(pbuf); ++ NewC = strlenis(buf); ++ NewN = strlenis(nbuf); ++ ++ /* ++ * For paragraphs with spaces in the first line, but no space in the ++ * quote string of the second line, we make sure we choose the quote ++ * string without a space at the end of it. ++ */ ++ if ((NLine && !NLine[0]) ++ && ((PLine && !PLine[0]) ++ || (((same = same_qstring(pl, cl)) != 0) ++ && (same != count_levels_qstring(cl))))) ++ cl = qs_remove_trailing_spaces(cl); ++ else ++ if (NewC > NewN){ ++ int agree = 0; ++ for (j = 0; (j < n) && (GLine[j] == NLine[j]); j++); ++ clength = j; ++ /* clength is the common length in which Gline and Nline agree */ ++ /* j < n means that they do not agree fully */ ++ /* GLine = " \tText" ++ NLine = " Text" */ ++ if(j == n) ++ agree++; ++ if (clength < n){ /* see if buf and nbuf are padded with spaces and tabs */ ++ for (i = clength; i < n && ISspace(NLine[i]); i++); ++ if (i == n){/* padded NLine until the end of spaces? */ ++ for (i = clength; i < c && ISspace(GLine[i]); i++); ++ if (i == c) /* Padded CLine until the end of spaces? */ ++ agree++; ++ } ++ } ++ if (agree){ ++ for (j = clength; j < c && ISspace(GLine[j]); j++); ++ if (j == c){ ++ /* ++ * If we get here, it means that the current line has the same ++ * quote string (visually) than the next line, but both of them ++ * are padded with different amount of TABS or spaces at the end. ++ * The current line (GLine) has more spaces/TABs than the next ++ * line. This is the typical situation that is found at the ++ * begining of a paragraph. We need to check this, however, by ++ * checking the previous line. This avoids that we confuse ++ * ourselves with being in the last line of a paragraph. ++ * Example when it should not free_qs(cl) ++ * " Text in Paragraph 1" (PLine) ++ * " Text in Paragraph 1" (GLine) ++ * " Other Paragraph Number 2" (NLine) ++ * ++ * Example when it should free_qs(cl): ++ * ":) " (PLine) p = 3, j = 3 ++ * ":) Text" (GLine) c = 5 ++ * ":) More text" (NLine) n = 3 ++ * ++ * Example when it should free_qs(cl): ++ * ":) " (PLine) p = 3, j = 3 ++ * ":) > > > Text" (GLine) c = 11 ++ * ":) > > > More text" (NLine) n = 9 ++ * ++ * Example when it should free_qs(cl): ++ * ":) :) " (PLine) p = 6, j = 3 ++ * ":) > > > Text" (GLine) c = 11 ++ * ":) > > > More text" (NLine) n = 9 ++ * ++ * Example when it should free_qs(cl): ++ * ":) > > > " (PLine) p = 13, j = 11 ++ * ":) > > > Text" (GLine) c = 11 ++ * ":) > > > More text" (NLine) n = 9 ++ * ++ * The following example is very interesting. The "Other Text" ++ * line below should free the quote string an make it equal to the ++ * quote string of the line below it, but any algorithm trying ++ * to advance past that line should make it stop there, so ++ * we need one more check, to check the raw quote string and the ++ * processed quote string at the same time. ++ * FREE qs in this example. ++ * " Some Text" (PLine) p = 3, j = 0 ++ * "\tOther Text" (GLine) c = 1 ++ * " More Text" (NLine) n = 3 ++ * ++ */ ++ for (j = 0; (j < p) && (GLine[j] == PLine[j]); j++); ++ if ((p != c || j != p) && NLine[n]) ++ if(!get_indent_raw_line(q, PLine, nbuf, NSTRING, p, plb) ++ || NewP + strlenis(nbuf) != NewC){ ++ free_qs(&cl); ++ free_qs(&pl); ++ return nl; ++ } ++ } ++ } ++ } ++ ++ free_qs(&nl); ++ free_qs(&pl); ++ ++ return cl; ++ } ++ ++ /* ++ * Given a line, an initial position, and a quote string, we advance the ++ * current line past the quote string, including arbitraty spaces ++ * contained in the line, except that it removes trailing spaces. We do ++ * not handle TABs, if any, contained in the quote string. At least not ++ * yet. ++ * ++ * Arguments: q - quote string ++ * l - a line to process ++ * i - position in the line to start processing. i = 0 is the ++ * begining of that line. ++ */ ++ int ++ advance_quote_string(char *q, char l[NSTRING], int i) ++ { ++ int n = 0, j = 0, is = 0, es = 0; ++ int k, m, p, adv; ++ char qs[NSTRING] = {'\0'}; ++ if(!q || !*q) ++ return(0); ++ for (p = strlen(q); (p > 0) && (q[p - 1] == ' '); p--, es++); ++ if (!p){ /* string contains only spaces */ ++ for (k = 0; ISspace(l[i + k]); k++); ++ k -= k % es; ++ return k; ++ } ++ for (is = 0; ISspace(q[is]); is++); /* count initial spaces */ ++ for (m = 0 ; is + m < p ; m++) ++ qs[m] = q[is + m]; /* qs = quote string without any space at the end */ ++ /* advance as many spaces as there are at the begining */ ++ for (k = 0; ISspace(l[i + j]); k++, j++); ++ /* now find the visible string in the line */ ++ for (m = 0; qs[m] && l[i + j] == qs[m]; m++, j++); ++ if (!qs[m]){ /* no match */ ++ /* ++ * So far we have advanced at least "is" spaces, plus the visible ++ * string "qs". Now we need to advance the trailing number of ++ * spaces "es". If we can do that, we have found the quote string. ++ */ ++ for (p = 0; ISspace(l[i + j + p]); p++); ++ adv = advance_quote_string(q, l, i + j + ((p < es) ? p : es)); ++ n = ((p < es) ? 0 : es) + k + m + adv; ++ } ++ return n; ++ } ++ ++ /* ++ * This function returns the effective length in screen of the quote ++ * string. If the string contains a TAB character, it is added here, if ++ * not, the length returned is the length of the string ++ */ ++ int strlenis(char *qstr) ++ { ++ int i, rv = 0; ++ for (i = 0; qstr && qstr[i]; i++) ++ rv += ((qstr[i] == TAB) ? (~rv & 0x07) + 1 : 1); ++ return rv; ++ } ++ ++ int ++ is_indent (char word[NSTRING], int plb) ++ { ++ int i = 0, finished = 0, j, k, digit = 0, bdigits = -1, alpha = 0; ++ unsigned char c, nxt; ++ ++ if (!word || !word[0]) ++ return i; ++ ++ for (i = 0, j = 0; ISspace(word[i]); i++, j++); ++ while ((i < NSTRING - 2) && !finished){ ++ switch (c = (unsigned char) now(word,i)){ ++ case NBSP: ++ case TAB : ++ case ' ' : for (; ISspace(word[i]); i++); ++ if (!is_indent_char(now(word,i))) ++ finished++; ++ break; ++ ++ case '+' : ++ case '.' : ++ case ']' : ++ case '*' : ++ case '}' : ++ case '-' : ++ case RPAREN: ++ nxt = (unsigned char) next(word,i); ++ if ((c == '.' && allowed_after_period(nxt) && alpha) ++ || (c == '*' && allowed_after_star(nxt)) ++ || (c == '}' && allowed_after_braces(nxt)) ++ || (c == '-' && allowed_after_dash(nxt)) ++ || (c == '+' && allowed_after_dash(nxt)) ++ || (c == RPAREN && allowed_after_parenth(nxt)) ++ || (c == ']' && allowed_after_parenth(nxt))) ++ i++; ++ else ++ finished++; ++ break; ++ ++ case 0xE2: /* bullet? */ ++ nxt = (unsigned char) next(word,i); ++ if(nxt == 0x80){ ++ nxt = (unsigned char) next(word,i+1); ++ if(nxt == 0xA2){ ++ nxt = (unsigned char) next(word,i+2); ++ if(allowed_after_star(nxt)) ++ i += 3; ++ else ++ finished++; ++ } ++ else finished++; ++ } ++ else finished++; ++ break; ++ ++ default : if (is_a_digit(c) && plb){ ++ if (bdigits < 0) ++ bdigits = i; /* first digit */ ++ for (k = i; is_a_digit(now(word,k)); k++); ++ if (k - bdigits > 2){ /* more than 2 digits? */ ++ i = bdigits; /* too many! */ ++ finished++; ++ } ++ else{ ++ if(allowed_after_digit(now(word,k),word,k)){ ++ alpha++; ++ i = k; ++ } ++ else{ ++ i = bdigits; ++ finished++; ++ } ++ } ++ } ++ else ++ finished++; ++ break; ++ ++ } ++ } ++ if (i == j) ++ i = 0; /* there must be something more than spaces in an indent string */ ++ return i; ++ } ++ ++ int ++ get_indent_raw_line(char **q, char *GLine, char *buf, int buflen, int k, int plb) ++ { ++ int i, j; ++ char testline[1024]; ++ ++ if(k > 0){ ++ for(j = 0; GLine[j] != '\0'; j++){ ++ testline[j] = GLine[j]; ++ testline[j+1] = '\0'; ++ if(strlenis(testline) >= strlenis(buf)) ++ break; ++ } ++ k = ++j; /* reset k */ ++ } ++ i = is_indent(GLine+k, plb); ++ ++ for (j = 0; j < i && j < buflen && (buf[j] = GLine[j + k]); j++); ++ buf[j] = '\0'; ++ ++ return i; ++ } ++ ++ /* support for remembering quote strings across messages */ ++ char **allowed_qstr = NULL; ++ int list_len = 0; ++ ++ void ++ free_allowed_qstr(void) ++ { ++ int i; ++ char **q = allowed_qstr; ++ ++ if(q == NULL) ++ return; ++ ++ for(i = 0; i < list_len; i++) ++ fs_give((void **)&q[i]); ++ ++ fs_give((void **)q); ++ list_len = 0; ++ } ++ ++ void ++ add_allowed_qstr(void *q, int type) ++ { ++ int i; ++ ++ if(allowed_qstr == NULL){ ++ allowed_qstr = malloc(sizeof(char *)); ++ list_len = 0; ++ } ++ ++ if(type == 0){ ++ allowed_qstr[list_len] = malloc((1+strlen((char *)q))*sizeof(char)); ++ strcpy(allowed_qstr[list_len], (char *)q); ++ } ++ else ++ allowed_qstr[list_len] = (char *) ucs4_to_utf8_cpystr((UCS *)q); ++ ++ fs_resize((void **)&allowed_qstr, (++list_len + 1)*sizeof(char *)); ++ allowed_qstr[list_len] = NULL; ++ } ++ ++ void ++ record_quote_string (QSTRING_S *qs) ++ { ++ int i, j, k; ++ ++ for(; qs && qs->value; qs = qs->next){ ++ j = 0; ++ for (; ;){ ++ k = j; ++ for(i = 0; i < list_len; i++){ ++ j += advance_quote_string(allowed_qstr[i], qs->value, j); ++ for(; ISspace(qs->value[j]); j++); ++ } ++ if(k == j) ++ break; ++ } ++ if(qs->value[j] != '\0') ++ add_allowed_qstr((void *)(qs->value + j), 0); ++ } ++ } ++ ++ /* type utf8: code 0; ucs4: code 1. */ ++ char ** ++ default_qstr(void *q, int type) ++ { ++ if(allowed_qstr == NULL) ++ add_allowed_qstr(q, type); ++ ++ return allowed_qstr; ++ } ++ +diff -rc alpine-2.21/pith/osdep/color.h alpine-2.21.fillpara/pith/osdep/color.h +*** alpine-2.21/pith/osdep/color.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/osdep/color.h Sun Feb 5 16:15:23 2017 +*************** +*** 17,22 **** +--- 17,40 ---- + #ifndef PITH_OSDEP_COLOR_INCLUDED + #define PITH_OSDEP_COLOR_INCLUDED + ++ /* ++ * struct that will help us determine what the quote string of a line ++ * is. The "next" field indicates the presence of a possible continuation. ++ * The idea is that if a continuation fails, we free it and check for the ++ * remaining structure left ++ */ ++ ++ typedef enum {qsNormal, qsString, qsWord, qsChar, qsGdb, qsProg, qsText} QStrType; ++ ++ typedef struct QSTRING { ++ char *value; /* possible quote string */ ++ QStrType qstype; /* type of quote string */ ++ struct QSTRING *next; /* possible continuation */ ++ } QSTRING_S; ++ ++ #define UCH(c) ((unsigned char) (c)) ++ #define NBSP UCH('\240') ++ #define ISspace(c) (UCH(c) == ' ' || UCH(c) == TAB || UCH(c) == NBSP) + + #define RGBLEN 11 + #define MAXCOLORLEN 11 /* longest string a color can be */ +*************** +*** 93,98 **** + 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 */ +--- 111,124 ---- + char *pico_get_last_bg_color(void); + char *color_to_canonical_name(char *); + int pico_count_in_color_table(void); +! int is_indent(char *, int); +! int get_indent_raw_line (char **, char *, char *, int, int, int); +! int line_isblank(char **, char *, char *, char *, int); +! int strlenis(char *); +! int value_is_space(char *); +! int advance_quote_string(char *, char *, int); +! void free_allowed_qstr(void); +! void add_allowed_qstr(void *, int); +! void record_quote_string (QSTRING_S *); + + #endif /* PITH_OSDEP_COLOR_INCLUDED */ +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.fillpara/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/pine.hlp Sun Feb 5 16:15:23 2017 +*************** +*** 7446,7451 **** +--- 7446,7491 ---- + "type the character ^". + +

    ++ This version of Alpine contains an enhanced algorithm for justification, ++ which allows you to justify text that contains more complicated quote ++ strings. This algorithm is based on pragmatics, rather than on a theory, ++ and seems to work well with most messages. Below you will find technical ++ information on how this algorithm works. ++ ++

    ++ When justifying, Alpine goes through each line of the text and tries to ++ determine for each line what the quote string of that line is. The quote ++ string you provided is always recognized. Among other characters ++ recognized is ">". ++ ++

    ++ Some other constructions of quote strings are recognized only if they ++ appear enough in the text. For example "Peter :" is only ++ recognized if it appears in two consecutive lines. ++ ++

    ++ Additionaly, Alpine recognizes indent-strings and justifies text in a ++ paragraph to the right of indent-string, padding with spaces if necessary. ++ An indent string is one which you use to delimit elements of a list. For ++ example, if you were to write a list of groceries, one may write: ++ ++

      ++
    • Fruit ++
    • Bread ++
    • Eggs ++
    ++ ++

    ++ In this case the character "*" is the indent-string. Aline ++ recognizes numbers (0, 1, 2.5, etc) also as indent-strings, and certain ++ combinations of spaces, periods, and parenthesis. In any case, numbers are ++ recognized ONLY if the line preceeding the given line is empty or ++ ends in one of the characters "." or ":". ++ In addition to the explanation of what constitutes a paragraph above, a ++ new paragraph is recognized when an indent-string is found in it (and ++ validated according to the above stated rules). ++ ++

    + <End of help on this topic> + + +diff -rc alpine-2.21/pith/reply.c alpine-2.21.fillpara/pith/reply.c +*** alpine-2.21/pith/reply.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/reply.c Sun Feb 5 16:15:23 2017 +*************** +*** 2735,2740 **** +--- 2735,2743 ---- + if(flow_res && ps_global->reply.use_flowed) + wrapflags |= GFW_FLOW_RESULT; + ++ filters[filtcnt].filter = gf_quote_test; ++ filters[filtcnt++].data = gf_line_test_opt(select_quote, NULL); ++ + filters[filtcnt].filter = gf_wrap; + /* + * The 80 will cause longer lines than what is likely +*************** +*** 2828,2834 **** + dq.do_color = 0; + dq.delete_all = 1; + +! filters[filtcnt].filter = gf_line_test; + filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); + } + +--- 2831,2837 ---- + dq.do_color = 0; + dq.delete_all = 1; + +! filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); + } + +diff -rc alpine-2.21/pith/state.c alpine-2.21.fillpara/pith/state.c +*** alpine-2.21/pith/state.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/state.c Sun Feb 5 16:15:23 2017 +*************** +*** 225,230 **** +--- 225,232 ---- + if((*pps)->kw_colors) + free_spec_colors(&(*pps)->kw_colors); + ++ free_allowed_qstr(); ++ + if((*pps)->atmts){ + int i; + +diff -rc alpine-2.21/pith/state.h alpine-2.21.fillpara/pith/state.h +*** alpine-2.21/pith/state.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/state.h Sun Feb 5 16:15:23 2017 +*************** +*** 249,254 **** +--- 249,256 ---- + SPEC_COLOR_S *hdr_colors; /* list of configed colors for view */ + SPEC_COLOR_S *index_token_colors; /* list of configed colors for index */ + ++ char *prefix; /* prefix for fillpara */ ++ char **list_qstr; /* list of known quote strings */ + short init_context; + + struct { +diff -rc alpine-2.21/pith/text.c alpine-2.21.fillpara/pith/text.c +*** alpine-2.21/pith/text.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fillpara/pith/text.c Sun Feb 5 16:15:23 2017 +*************** +*** 92,98 **** + 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; +--- 92,98 ---- + char *err, *charset; + int filtcnt = 0, error_found = 0, column, wrapit; + int is_in_sig = OUT_SIG_BLOCK; +! int is_flowed_msg = 0, add_me = 1, doraw = RAWSTRING; + int is_delsp_yes = 0; + int filt_only_c0 = 0; + char *parmval; +*************** +*** 180,186 **** + && 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); + } +--- 180,186 ---- + && pico_usingcolor() + && VAR_SIGNATURE_FORE_COLOR + && VAR_SIGNATURE_BACK_COLOR){ +! filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(color_signature, + &is_in_sig); + } +*************** +*** 198,206 **** + && 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")){ +--- 198,206 ---- + && pico_usingcolor() + && VAR_QUOTE1_FORE_COLOR + && VAR_QUOTE1_BACK_COLOR){ +! add_me = 0; +! filters[filtcnt].filter = gf_quote_test; +! filters[filtcnt++].data = gf_line_test_opt(color_a_quote, &is_flowed_msg); + } + } + else if(!strucmp(att->body->subtype, "richtext")){ +*************** +*** 281,286 **** +--- 281,291 ---- + } + } + ++ if (add_me){ ++ filters[filtcnt].filter = gf_quote_test; ++ filters[filtcnt++].data = gf_line_test_opt(select_quote, &doraw); ++ } ++ + /* + * If the message is not flowed, we do the quote suppression before + * the wrapping, because the wrapping does not preserve the quote +*************** +*** 305,311 **** + 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 +--- 310,316 ---- + dq.handlesp = handlesp; + dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor()); + +! filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); + } + if(ps_global->VAR_QUOTE_REPLACE_STRING +*************** +*** 364,370 **** + 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); + } + +--- 369,375 ---- + dq.handlesp = handlesp; + dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor()); + +! filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); + } + +*************** +*** 569,575 **** + { + DELQ_S *dq; + char *lp; +! int i, lines, not_a_quote = 0; + size_t len; + + dq = (DELQ_S *) local; +--- 574,580 ---- + { + DELQ_S *dq; + char *lp; +! int i, lines, not_a_quote = 0, code; + size_t len; + + dq = (DELQ_S *) local; +*************** +*** 589,594 **** +--- 594,601 ---- + for(i = dq->indent_length; i > 0 && !not_a_quote && *lp; i--) + if(*lp++ != SPACE) + not_a_quote++; ++ while(isspace((unsigned char) *lp)) ++ lp++; + + /* skip over leading tags */ + while(!not_a_quote +*************** +*** 628,640 **** + } + } + +! /* 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){ +--- 635,646 ---- + } + } + +! len = lp - line; +! if(strlen(tmp_20k_buf) > len) +! strcpy(tmp_20k_buf, tmp_20k_buf+len); +! code = (dq->is_flowed ? IS_FLOWED : NO_FLOWED) | DELETEQUO; +! select_quote(linenum, lp, ins, &code); +! if (!not_a_quote && !tmp_20k_buf[0]) + not_a_quote++; + + if(not_a_quote){ diff --git a/chappa-fromheader.patch b/chappa-fromheader.patch new file mode 100644 index 0000000..88aa6f7 --- /dev/null +++ b/chappa-fromheader.patch @@ -0,0 +1,159 @@ +diff -rc alpine-2.21/alpine/send.c alpine-2.21.fromheader/alpine/send.c +*** alpine-2.21/alpine/send.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/alpine/send.c Sun Feb 5 16:15:24 2017 +*************** +*** 913,919 **** + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"From : ", "From", h_composer_from, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, +! 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"Reply-To: ", "Reply To", h_composer_reply_to, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, +--- 913,919 ---- + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"From : ", "From", h_composer_from, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, +! 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"Reply-To: ", "Reply To", h_composer_reply_to, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, +*************** +*** 2367,2372 **** +--- 2367,2377 ---- + he->rich_header = 0; + } + } ++ if (F_ON(F_ALLOW_CHANGING_FROM, ps_global) && ++ !ps_global->never_allow_changing_from){ ++ he->display_it = 1; /* show it */ ++ he->rich_header = 0; ++ } + + he_from = he; + break; +*************** +*** 3104,3110 **** + if(outgoing->return_path) + mail_free_address(&outgoing->return_path); + +! outgoing->return_path = rfc822_cpy_adr(outgoing->from); + + /* + * Don't ever believe the sender that is there. +--- 3109,3117 ---- + if(outgoing->return_path) + mail_free_address(&outgoing->return_path); + +! outgoing->return_path = F_ON(F_USE_DOMAIN_NAME,ps_global) +! ? rfc822_cpy_adr(generate_from()) +! : rfc822_cpy_adr(outgoing->from); + + /* + * Don't ever believe the sender that is there. +diff -rc alpine-2.21/pith/conf.c alpine-2.21.fromheader/pith/conf.c +*** alpine-2.21/pith/conf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/pith/conf.c Sun Feb 5 16:15:24 2017 +*************** +*** 2884,2889 **** +--- 2884,2891 ---- + 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}, ++ {"return-path-uses-domain-name", NULL, ++ F_USE_DOMAIN_NAME, h_config_use_domain, 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", +diff -rc alpine-2.21/pith/conftype.h alpine-2.21.fromheader/pith/conftype.h +*** alpine-2.21/pith/conftype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/pith/conftype.h Sun Feb 5 16:15:24 2017 +*************** +*** 399,404 **** +--- 399,405 ---- + F_AUTO_REPLY_TO, + F_VERBOSE_POST, + F_FCC_ON_BOUNCE, ++ F_USE_DOMAIN_NAME, + F_SEND_WO_CONFIRM, + F_USE_SENDER_NOT_X, + F_BLANK_KEYMENU, +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.fromheader/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/pith/pine.hlp Sun Feb 5 16:15:24 2017 +*************** +*** 28940,28945 **** +--- 28940,28961 ---- + <End of help on this topic> + + ++ ====== h_config_use_domain ===== ++ ++ ++ FEATURE: <!--#echo var="FEAT_return-path-uses-domain-name"--> ++ ++ ++

    FEATURE:

    ++ ++ If you enable this configuration option Pine will use your domain name and your ++ username in that domain name to construct your Return-Path header, if not Pine ++ will use the address that you have set in the From: field to construct it. ++ ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_use_sender_not_x ===== + + +diff -rc alpine-2.21/pith/send.c alpine-2.21.fromheader/pith/send.c +*** alpine-2.21/pith/send.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/pith/send.c Sun Feb 5 16:15:24 2017 +*************** +*** 53,59 **** + /* 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}, +--- 53,59 ---- + /* name::type::canedit::writehdr::localcopy::rcptto */ + PINEFIELD pf_template[] = { + {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */ +! {"From", Address, 1, 1, 1, 0}, + {"Reply-To", Address, 0, 1, 1, 0}, + {TONAME, Address, 1, 1, 1, 1}, + {CCNAME, Address, 1, 1, 1, 1}, +*************** +*** 1232,1238 **** + *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; +--- 1232,1238 ---- + *p = *(p+4); + + pf->type = pf_template[i].type; +! pf->canedit = (i == N_FROM) ? CAN_EDIT(ps_global) : pf_template[i].canedit; + pf->rcptto = pf_template[i].rcptto; + pf->writehdr = pf_template[i].writehdr; + pf->localcopy = pf_template[i].localcopy; +diff -rc alpine-2.21/pith/send.h alpine-2.21.fromheader/pith/send.h +*** alpine-2.21/pith/send.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.fromheader/pith/send.h Sun Feb 5 16:15:24 2017 +*************** +*** 159,164 **** +--- 159,166 ---- + unsigned text_written:1; + }; + ++ #define CAN_EDIT(x) (!((x)->never_allow_changing_from) && \ ++ F_ON(F_ALLOW_CHANGING_FROM, (x))) + + #define TONAME "To" + #define CCNAME "cc" diff --git a/chappa-ignoresize.patch b/chappa-ignoresize.patch deleted file mode 100644 index 3715815..0000000 --- a/chappa-ignoresize.patch +++ /dev/null @@ -1,112 +0,0 @@ ---- - alpine/mailcmd.c | 3 +++ - pith/conf.c | 2 ++ - pith/conftype.h | 1 + - pith/pine.hlp | 35 +++++++++++++++++++++++++++++++++++ - pith/save.c | 1 + - 5 files changed, 42 insertions(+) - -Index: alpine-2.20/alpine/mailcmd.c -=================================================================== ---- alpine-2.20.orig/alpine/mailcmd.c -+++ alpine-2.20/alpine/mailcmd.c -@@ -3491,6 +3491,9 @@ save_size_changed_prompt(long msgno, int - {-1, 0, NULL, NULL} - }; - -+ if(F_ON(F_IGNORE_SIZE, ps_global)) -+ return 'y'; -+ - if(flags & SSCP_INIT || flags & SSCP_END){ - if(flags & SSCP_END && possible_corruption) - q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results"); -Index: alpine-2.20/pith/conf.c -=================================================================== ---- alpine-2.20.orig/pith/conf.c -+++ alpine-2.20/pith/conf.c -@@ -3140,6 +3140,8 @@ feature_list(int index) - 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}, -+ {"ignore-size-changes", NULL, -+ F_IGNORE_SIZE, h_config_ignore_size, PREF_MISC, 0}, - {"maildrops-preserve-state", NULL, - F_MAILDROPS_PRESERVE_STATE, h_config_maildrops_preserve_state, - PREF_MISC, 0}, -Index: alpine-2.20/pith/conftype.h -=================================================================== ---- alpine-2.20.orig/pith/conftype.h -+++ alpine-2.20/pith/conftype.h -@@ -347,6 +347,7 @@ typedef enum { - F_FORCE_ARROW, - F_PRUNE_USES_ISO, - F_ALT_ED_NOW, -+ F_IGNORE_SIZE, - F_SHOW_DELAY_CUE, - F_CANCEL_CONFIRM, - F_AUTO_OPEN_NEXT_UNREAD, -Index: alpine-2.20/pith/pine.hlp -=================================================================== ---- alpine-2.20.orig/pith/pine.hlp -+++ alpine-2.20/pith/pine.hlp -@@ -3565,6 +3565,7 @@ There are also additional details on -

  • FEATURE: -
  • FEATURE: -
  • FEATURE: -+
  • FEATURE: -
  • FEATURE: -
  • FEATURE: -
  • FEATURE: -@@ -31183,6 +31184,40 @@ but that is not implemented. - -

    - <End of help on this topic> -+ -+ -+====== h_config_ignore_size ===== -+ -+ -+FEATURE: <!--#echo var="FEAT_ignore-size-changes"--> -+ -+ -+

    FEATURE:

    -+ -+When you have an account residing in an IMAP server, Alpine gets the size of -+each message from the server. However, when Alpine saves a message residing -+in an IMAP server, Alpine computes the size of the message independently. If -+these two numbers do not match for a message, Alpine asks you if you still -+want to take the risk of saving the message, since data corruption or loss -+of data could result of this save. -+ -+

    -+Sometimes the root of this problem is that the server is defective, and -+there will not be loss of information when saving such message. Enabling -+this feature will make Aline ignore such error and continue saving the -+message. If you can determine that this is the case, enable this feature -+so that the saving operation will succeed. An example of a defective server -+is the Gmail IMAP server. Another example is some versions of the Exchange -+server. -+ -+

    -+It is recommended that this feature be disabled most of the time and only -+enabled when you find a server which you can determine that has the above -+mentioned defect, but be disabled again after making this operation -+succeed. -+ -+

    -+<End of help on this topic> - - - ====== h_config_force_low_speed ===== -Index: alpine-2.20/pith/save.c -=================================================================== ---- alpine-2.20.orig/pith/save.c -+++ alpine-2.20/pith/save.c -@@ -1157,6 +1157,7 @@ long save_fetch_append_cb(MAILSTREAM *st - snprintf(buf, sizeof(buf), - "Message to save shrank: source msg # %ld may be saved incorrectly", - mn_raw2m(pkg->msgmap, raw)); -+ if(F_OFF(F_IGNORE_SIZE, ps_global)) - q_status_message(SM_ORDER, 0, 3, buf); - } - else{ diff --git a/chappa-insertpat.patch b/chappa-insertpat.patch index 6bfd587..32cea62 100644 --- a/chappa-insertpat.patch +++ b/chappa-insertpat.patch @@ -1,43 +1,40 @@ ---- - pico/display.c | 12 ++++++++++++ - pico/search.c | 4 ++++ - 2 files changed, 16 insertions(+) - -Index: alpine-2.20/pico/display.c -=================================================================== ---- alpine-2.20.orig/pico/display.c -+++ alpine-2.20/pico/display.c -@@ -1760,6 +1760,18 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf - - continue; - -+ case (CTRL|'N'): /* Insert pattern */ -+ if (pat[0] != '\0'){ -+ ucs4_strncpy(buf+ucs4_strlen(buf), pat, NPAT); -+ pputs(pat,1); -+ b = &buf[ucs4_strlen(buf)]; -+ dline.vused += ucs4_strlen(pat); -+ changed = TRUE; -+ } -+ else -+ (*term.t_beep)(); -+ continue; -+ - case (CTRL|'G') : /* CTRL-G help */ - if(term.t_mrow == 0 && km_popped == 0){ - movecursor(term.t_nrow-2, 0); -Index: alpine-2.20/pico/search.c -=================================================================== ---- alpine-2.20.orig/pico/search.c -+++ alpine-2.20/pico/search.c -@@ -81,6 +81,10 @@ N_(" brackets. This string is th - N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"), - N_(" search to be made with the default value."), - " ", -+N_("~ Hitting ~^~N will reinsert the last string you searched for"), -+N_(" so that you can edit it (in case you made a mistake entering the"), -+N_(" search pattern the first time)."), -+" ", - N_(" The text search is not case sensitive, and will examine the"), - N_(" entire message."), - " ", +diff -rc alpine-2.21/pico/display.c alpine-2.21.insertpat/pico/display.c +*** alpine-2.21/pico/display.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.insertpat/pico/display.c Sun Feb 5 16:15:26 2017 +*************** +*** 2205,2210 **** +--- 2205,2222 ---- + + continue; + ++ case (CTRL|'N'): /* Insert pattern */ ++ if (pat[0] != '\0'){ ++ ucs4_strncpy(buf+ucs4_strlen(buf), pat, NPAT); ++ pputs(pat,1); ++ b = &buf[ucs4_strlen(buf)]; ++ dline.vused += ucs4_strlen(pat); ++ changed = TRUE; ++ } ++ else ++ (*term.t_beep)(); ++ continue; ++ + case (CTRL|'G') : /* CTRL-G help */ + if(term.t_mrow == 0 && km_popped == 0){ + movecursor(term.t_nrow-2, 0); +diff -rc alpine-2.21/pico/search.c alpine-2.21.insertpat/pico/search.c +*** alpine-2.21/pico/search.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.insertpat/pico/search.c Sun Feb 5 16:15:26 2017 +*************** +*** 122,127 **** +--- 122,131 ---- + N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"), + N_(" search to be made with the default value."), + " ", ++ N_("~ Hitting ~^~N will reinsert the last string you searched for"), ++ N_(" so that you can edit it (in case you made a mistake entering the"), ++ N_(" search pattern the first time)."), ++ " ", + N_(" The text search is not case sensitive, and will examine the"), + N_(" entire message."), + " ", diff --git a/chappa-maildir.patch b/chappa-maildir.patch index 6e5343a..feaf3fe 100644 --- a/chappa-maildir.patch +++ b/chappa-maildir.patch @@ -1,3715 +1,3844 @@ ---- - README.maildir | 149 ++ - alpine/alpine.c | 5 - alpine/confscroll.c | 6 - imap/src/c-client/mail.c | 11 - imap/src/c-client/mail.h | 4 - imap/src/osdep/unix/Makefile | 8 - imap/src/osdep/unix/dummy.c | 48 - imap/src/osdep/unix/maildir.c | 2671 ++++++++++++++++++++++++++++++++++++++++++ - imap/src/osdep/unix/maildir.h | 226 +++ - imap/src/osdep/unix/os_cyg.h | 1 - pith/conf.c | 30 - pith/conf.h | 4 - pith/conftype.h | 6 - pith/init.c | 3 - pith/pattern.c | 30 - pith/pine.hlp | 139 ++ - pith/send.c | 10 - 17 files changed, 3334 insertions(+), 17 deletions(-) - -Index: alpine-2.20/README.maildir -=================================================================== ---- /dev/null -+++ alpine-2.20/README.maildir -@@ -0,0 +1,149 @@ -+--------------------------------------- -+ -+Maildir Driver for Alpine 2.0 -+By Eduardo Chappa -+ -+ -+--------------------------------------- -+1. General Information About This Patch -+--------------------------------------- -+ -+This patch adds support for the maildir format to Alpine. We take the -+approach that this patch is one more driver among the number of formats -+supported by Alpine (more generally c-client). This approach differs from -+older versions of similar patches, in that once a maildir patch was -+applied, it was assumed that all your folders would be created in the -+maildir format. -+ -+This patch does not assume that maildir is a preferred format, instead -+puts maildir in equal footing with other formats (mbox, mbx, mix, etc), -+and so a maildir folder in the mail/ collection is treated in the same way -+as any other folder in any other format. In other words, just by reading -+the name of a folder, or opening it, or doing any operation with it, you -+can not know in which format the folder is. -+ -+This implies that if you want to add a folder in the maildir format to the -+mail/ collection, then you must add by pressing "A" in the folder list -+collection and enter "#driver.md/mail/name_maildir_folder". -+ -+If you only want to use maildir, however, you can do so too. In this case, -+you must create a maildir collection. In that collection, only maildir -+folders will be listed. If there is any folder in any other format, that -+folder will be ignored. In another words, any folder listed there is in -+maildir format and can be accessed through that collection, conversely, -+any folder not listed there is not in maildir format and there is no way -+to access it using this collection. -+ -+In order to create a maildir collection, you could press M S L, and "A" to -+add a collection. Fill in the required fields as follows: -+ -+Nickname : Anything -+Server : -+Path : #md/relative/path/to/maildir/collection/ -+View : -+ -+For example, if "path" is set to "#md/mail/", then Alpine will look for your -+maildir folders that are in ~/mail/. -+ -+The code in this patch is mostly based in code for the unix driver plus -+some combinations of the mh, mbx and nntp drivers for the c-client -+library. Those drivers were designed by Mark Crispin, and bugs in this -+code are not his bugs, but my own. -+ -+ I got all the specification for this patch from -+http://cr.yp.to/proto/maildir.html. If you know of a place with a better -+specification for maildir format please let me know. The method this patch -+uses to create a unique filename for a message is one of the "old -+fashioned" methods. I realize that this is old fashioned, but it is -+portable, and portability is the main reason why I decided to use an old -+fashioned method (most methods are not portable. See the word -+"Unfortunately" in that document). -+ -+-------------- -+2. Other Goals -+-------------- -+ -+ It is intended that this code will work well with any application -+written using the c-client library. Of paramount importance is to make the -+associated imap server work well when the server accesses a folder in -+Maildir format. The program mailutil should also work flawlessly with this -+implemetation of the driver. -+ -+ It is intended that this driver be fast and stable. We intend not to -+patch Alpine to make this driver do its work, unless such patching is for -+fixing bugs in Alpine or to pass parameters to the driver. -+ -+------------------------------------------------------------------------ -+3. What are the known bugs of this implementation of the Maildir driver? -+------------------------------------------------------------------------ -+ -+ I don't know any at this time. There have been bugs before, though, but -+I try to fix bugs as soon as they are reported. -+ -+---------- -+4. On UIDs -+---------- -+ -+ This patch keeps uids in the name of the file that contains the message, -+by adding a ",u=" string to the file name to save the uid of a message. A -+file is kept between sessions to save information on the last uid assigned -+and its time of validity. Only one session with writing access can write -+uids, all others must wait for the other session to assign them. The -+session assigning uids creates a ".uidtemp" file which other sessions must -+not disturb. -+ -+ Uid support appeared in Alpine 1.00 (snapshot 925), and is experimental, -+please report any problems. -+ -+---------------------------------------------- -+5. Configuring Alpine and Setting up a Maildir -+---------------------------------------------- -+ -+Once this approach was chosen, it implied the following: -+ -+ * This patch assumes that your INBOX is located at "$HOME/Maildir". -+ This is a directory which should have three subdirectories "cur", -+ "tmp" and "new". Mail is delivered to 'new' and read from 'cur'. I -+ have added a configuration option "maildir-location" which can be -+ used to tell Alpine where your Maildir inbox is, in case your system -+ does not use the above directory (e.g. your system may use -+ "~/.maildir"). In this case define that variable to be the name of -+ the directory where your e-mail is being delivered (e.g. -+ ".maildir"). -+ -+ * If you want to use the above configuration as your inbox, you must -+ define your inbox-path as "#md/inbox" (no quotes). You can define -+ the inbox-path like above even if you have changed the -+ maildir-location variable. That's the whole point of that variable. -+ -+------------------------------------------- -+6. What about Courier/Dovecot file systems? -+------------------------------------------- -+ -+In a courier file system all folders are subfolders of a root folder -+called INBOX. Normally INBOX is located at ~/Maildir and subfolders are -+"dot" directories in ~/Maildir. For example ~/Maildir/.Trash is a -+subfolder of INBOX and is accessed with the nickname "INBOX.Trash". -+ -+You can not access folders in this way unless you preceed them with the -+string "#mc/". The purpose of the string "#mc/" is to warn Alpine that a -+collection in the Courier format is going to be accessed. Therefore, you -+can SELECT a folder like "#mc/INBOX.Trash", but not "INBOX.Trash" -+ -+You can access a collection through a server, but if you want to access a -+collection of folders created using the Courier server, you MUST edit your -+".pinerc" file and enter the definition of the collection as follows: -+ -+folder-collections="Anything you want" #mc/INBOX.[] -+ -+You can replace the string "#mc/INBOX." by something different, for example -+"#mc/Courier/." will make Alpine search for your collection in ~/Courier. -+ -+You can not add this setting directly into Alpine because Alpine fails to -+accept this value from its input, but it takes it correctly when it is -+added through the ".pinerc" file. -+ -+You can access your inbox as "#mc/INBOX" or "#md/INBOX". Both definitions -+point to the same place. -+ -+Last Updated May 28, 2011 -Index: alpine-2.20/alpine/alpine.c -=================================================================== ---- alpine-2.20.orig/alpine/alpine.c -+++ alpine-2.20/alpine/alpine.c -@@ -560,6 +560,11 @@ main(int argc, char **argv) - if(F_ON(F_MAILDROPS_PRESERVE_STATE, ps_global)) - mail_parameters(NULL, SET_SNARFPRESERVE, (void *) TRUE); - -+#ifndef _WINDOWS -+ rv = F_ON(F_COURIER_FOLDER_LIST, ps_global) ? 1 : 0; -+ mail_parameters(NULL,SET_COURIERSTYLE, (void *) &rv); -+#endif -+ - rvl = 0L; - if(pine_state->VAR_NNTPRANGE){ - if(!SVAR_NNTPRANGE(pine_state, rvl, tmp_20k_buf, SIZEOF_20KBUF)) -Index: alpine-2.20/alpine/confscroll.c -=================================================================== ---- alpine-2.20.orig/alpine/confscroll.c -+++ alpine-2.20/alpine/confscroll.c -@@ -5556,6 +5556,12 @@ fix_side_effects(struct pine *ps, struct - (void *)var->current_val.p); - } - #endif -+#ifndef _WINDOWS -+ else if(var == &ps->vars[V_MAILDIR_LOCATION]){ -+ if(var->current_val.p && var->current_val.p[0]) -+ mail_parameters(NULL, SET_MDINBOXPATH, (void *)var->current_val.p); -+ } -+#endif - else if(revert && standard_radio_var(ps, var)){ - - cur_rule_value(var, TRUE, FALSE); -Index: alpine-2.20/imap/src/c-client/mail.c -=================================================================== ---- alpine-2.20.orig/imap/src/c-client/mail.c -+++ alpine-2.20/imap/src/c-client/mail.c -@@ -1011,7 +1011,7 @@ long mail_create (MAILSTREAM *stream,cha - MAILSTREAM *ts; - char *s,*t,tmp[MAILTMPLEN]; - size_t i; -- DRIVER *d; -+ DRIVER *d, *md; - /* never allow names with newlines */ - if (s = strpbrk (mailbox,"\015\012")) { - MM_LOG ("Can't create mailbox with such a name",ERROR); -@@ -1035,6 +1035,8 @@ long mail_create (MAILSTREAM *stream,cha - return NIL; - } - -+ /* Hack, we should do this better, but it works */ -+ for (md = maildrivers; md && strcmp (md->name, "md"); md = md->next); - /* see if special driver hack */ - if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) && - ((mailbox[2] == 'r') || (mailbox[2] == 'R')) && -@@ -1065,6 +1067,13 @@ long mail_create (MAILSTREAM *stream,cha - (((*mailbox == '{') || (*mailbox == '#')) && - (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT)))) - d = stream->dtb; -+ else if(mailbox[0] == '#' -+ && (mailbox[1] == 'm' || mailbox[1] == 'M') -+ && (mailbox[2] == 'd' || mailbox[2] == 'D' -+ || mailbox[2] == 'c' || mailbox[2] == 'C') -+ && mailbox[3] == '/' -+ && mailbox[4] != '\0') -+ return (*md->create)(stream, mailbox); - else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb; - else { /* failed utterly */ - sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox); -Index: alpine-2.20/imap/src/c-client/mail.h -=================================================================== ---- alpine-2.20.orig/imap/src/c-client/mail.h -+++ alpine-2.20/imap/src/c-client/mail.h -@@ -354,6 +354,10 @@ - #define SET_SCANCONTENTS (long) 573 - #define GET_MHALLOWINBOX (long) 574 - #define SET_MHALLOWINBOX (long) 575 -+#define GET_COURIERSTYLE (long) 576 -+#define SET_COURIERSTYLE (long) 577 -+#define SET_MDINBOXPATH (long) 578 -+#define GET_MDINBOXPATH (long) 579 - - /* Driver flags */ - -Index: alpine-2.20/imap/src/osdep/unix/Makefile -=================================================================== ---- alpine-2.20.orig/imap/src/osdep/unix/Makefile -+++ alpine-2.20/imap/src/osdep/unix/Makefile -@@ -147,7 +147,7 @@ DEFAULTAUTHENTICATORS=ext md5 pla log - # However, mh needs to be before any sysinbox formats (such as mmdf or unix) - # since otherwise INBOX won't work correctly when mh_allow_inbox is set. - # --DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile -+DEFAULTDRIVERS=maildir courier imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile - CHUNKSIZE=65536 - - # Normally no need to change any of these -@@ -156,7 +156,7 @@ ARCHIVE=c-client.a - BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \ - dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ - rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ -- unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o -+ unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o maildir.o - CFLAGS=-g - - CAT=cat -@@ -293,7 +293,7 @@ cvx: # Convex - - cyg: # Cygwin - note that most local file drivers don't work!! - $(BUILD) `$(CAT) SPECIALS` OS=$@ \ -- DEFAULTDRIVERS="imap nntp pop3 mbx unix phile" \ -+ DEFAULTDRIVERS="imap nntp pop3 mbx unix maildir phile" \ - SIGTYPE=psx CHECKPW=cyg LOGINPW=cyg CRXTYPE=std \ - SPOOLDIR=/var \ - ACTIVEFILE=/usr/local/news/lib/active \ -@@ -911,7 +911,7 @@ tenex.o: mail.h misc.h osdep.h dummy.h - unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h - utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c - utf8aux.o: mail.h misc.h osdep.h utf8.h -- -+maildir.o: mail.h misc.h osdep.h maildir.h dummy.h - - # OS-dependent - -Index: alpine-2.20/imap/src/osdep/unix/dummy.c -=================================================================== ---- alpine-2.20.orig/imap/src/osdep/unix/dummy.c -+++ alpine-2.20/imap/src/osdep/unix/dummy.c -@@ -103,13 +103,19 @@ MAILSTREAM dummyproto = {&dummydriver}; - * Accepts: mailbox name - * Returns: our driver if name is valid, NIL otherwise - */ -- -+char * maildir_remove_root(char *); - DRIVER *dummy_valid (char *name) - { -- char *s,tmp[MAILTMPLEN]; -+ char *s,tmp[MAILTMPLEN], *rname; - struct stat sbuf; -+ -+ if(strlen(name) > MAILTMPLEN) -+ name[MAILTMPLEN] = '\0'; -+ -+ strcpy(tmp, name); -+ rname = maildir_remove_root(tmp); - /* must be valid local mailbox */ -- if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) { -+ if (rname && *rname && (*rname != '{') && (s = mailboxfile (tmp,rname))) { - /* indeterminate clearbox INBOX */ - if (!*s) return &dummydriver; - else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) { -@@ -118,8 +124,9 @@ DRIVER *dummy_valid (char *name) - return &dummydriver; - } - /* blackbox INBOX does not exist yet */ -- else if (!compare_cstring (name,"INBOX")) return &dummydriver; -+ else if (!compare_cstring (rname,"INBOX")) return &dummydriver; - } -+ if(rname) fs_give((void **)&rname); - return NIL; - } - -@@ -452,6 +459,8 @@ long dummy_create (MAILSTREAM *stream,ch - { - char *s,tmp[MAILTMPLEN]; - long ret = NIL; -+ if(!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4)) -+ return maildir_create(stream, mailbox); - /* validate name */ - if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) { - sprintf (tmp,"Can't create %.80s: invalid name",mailbox); -@@ -517,6 +526,14 @@ long dummy_delete (MAILSTREAM *stream,ch - { - struct stat sbuf; - char *s,tmp[MAILTMPLEN]; -+ if (!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4) -+ || is_valid_maildir(&mailbox)){ -+ char tmp[MAILTMPLEN] = {'\0'}; -+ strcpy(tmp, mailbox); -+ if(tmp[strlen(tmp) - 1] != '/') -+ tmp[strlen(tmp)] = '/'; -+ return maildir_delete(stream, tmp); -+ } - if (!(s = dummy_file (tmp,mailbox))) { - sprintf (tmp,"Can't delete - invalid name: %.80s",s); - MM_LOG (tmp,ERROR); -@@ -542,12 +559,23 @@ long dummy_delete (MAILSTREAM *stream,ch - long dummy_rename (MAILSTREAM *stream,char *old,char *newname) - { - struct stat sbuf; -- char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN]; -+ char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN], *rold, *rnewname; -+ -+ if(strlen(old) > MAILTMPLEN) -+ old[MAILTMPLEN] = '\0'; -+ -+ if(strlen(newname) > MAILTMPLEN) -+ newname[MAILTMPLEN] = '\0'; -+ -+ strcpy(tmp, old); -+ rold = maildir_remove_root(tmp); -+ strcpy(tmp, newname); -+ rnewname = maildir_remove_root(tmp); - /* no trailing / allowed */ -- if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) || -+ if (!dummy_file (oldname,rold) || !(s = dummy_file (mbx,rnewname)) || - stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] && - ((sbuf.st_mode & S_IFMT) != S_IFDIR))) { -- sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname); -+ sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",rold,rnewname); - MM_LOG (mbx,ERROR); - return NIL; - } -@@ -563,14 +591,16 @@ long dummy_rename (MAILSTREAM *stream,ch - } - } - /* rename of non-ex INBOX creates dest */ -- if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf)) -+ if (!compare_cstring (rold,"INBOX") && stat (oldname,&sbuf)) - return dummy_create (NIL,mbx); - if (rename (oldname,mbx)) { -- sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname, -+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",rold,rnewname, - strerror (errno)); - MM_LOG (tmp,ERROR); - return NIL; - } -+ if(rold) fs_give((void **)&rold); -+ if(rnewname) fs_give((void **)&rnewname); - return T; /* return success */ - } - -Index: alpine-2.20/imap/src/osdep/unix/maildir.c -=================================================================== ---- /dev/null -+++ alpine-2.20/imap/src/osdep/unix/maildir.c -@@ -0,0 +1,2671 @@ -+/* -+ * Maildir driver for Alpine 2.20 -+ * -+ * Written by Eduardo Chappa -+ * Last Update: June 10, 2014 -+ * -+ */ -+ -+#include -+#include -+#include -+extern int errno; /* just in case */ -+#include "mail.h" -+#include -+#include -+#include -+#include "osdep.h" -+#include "rfc822.h" -+#include "fdstring.h" -+#include "misc.h" -+#include "dummy.h" -+#include "maildir.h" -+ -+/* Driver dispatch used by MAIL */ -+DRIVER maildirdriver = { -+ "md", /* driver name, yes it's md, not maildir */ -+ DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ -+ (DRIVER *) NIL, /* next driver */ -+ maildir_valid, /* mailbox is valid for us */ -+ maildir_parameters, /* manipulate parameters */ -+ NIL, /* scan mailboxes */ -+ maildir_list, /* find mailboxes */ -+ maildir_lsub, /* find subscribed mailboxes */ -+ maildir_sub, /* subscribe to mailbox */ -+ maildir_unsub, /* unsubscribe from mailbox */ -+ maildir_create, /* create mailbox */ -+ maildir_delete, /* delete mailbox */ -+ maildir_rename, /* rename mailbox */ -+ mail_status_default, /* status of mailbox */ -+ maildir_open, /* open mailbox */ -+ maildir_close, /* close mailbox */ -+ maildir_fast, /* fetch message "fast" attributes */ -+ NIL, /* fetch message flags */ -+ NIL, /* fetch overview */ -+ NIL, /* fetch message structure */ -+ maildir_header, /* fetch message header */ -+ maildir_text, /* fetch message body */ -+ NIL, /* fetch partial message text */ -+ NIL, /* unique identifier */ -+ NIL, /* message number */ -+ NIL, /* modify flags */ -+ maildir_flagmsg, /* per-message modify flags */ -+ NIL, /* search for message based on criteria */ -+ NIL, /* sort messages */ -+ NIL, /* thread messages */ -+ maildir_ping, /* ping mailbox to see if still alive */ -+ maildir_check, /* check for new messages */ -+ maildir_expunge, /* expunge deleted messages */ -+ maildir_copy, /* copy messages to another mailbox */ -+ maildir_append, /* append string message to mailbox */ -+ NIL /* garbage collect stream */ -+}; -+ -+ -+DRIVER courierdriver = { -+ "mc", /* Why a separate driver? So that createproto will work */ -+ DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ -+ (DRIVER *) NIL, /* next driver */ -+ maildir_valid, /* mailbox is valid for us */ -+ maildir_parameters, /* manipulate parameters */ -+ NIL, /* scan mailboxes */ -+ courier_list, /* find mailboxes */ -+ maildir_lsub, /* find subscribed mailboxes */ -+ maildir_sub, /* subscribe to mailbox */ -+ maildir_unsub, /* unsubscribe from mailbox */ -+ maildir_create, /* create mailbox */ -+ maildir_delete, /* delete mailbox */ -+ maildir_rename, /* rename mailbox */ -+ mail_status_default, /* status of mailbox */ -+ maildir_open, /* open mailbox */ -+ maildir_close, /* close mailbox */ -+ maildir_fast, /* fetch message "fast" attributes */ -+ NIL, /* fetch message flags */ -+ NIL, /* fetch overview */ -+ NIL, /* fetch message structure */ -+ maildir_header, /* fetch message header */ -+ maildir_text, /* fetch message body */ -+ NIL, /* fetch partial message text */ -+ NIL, /* unique identifier */ -+ NIL, /* message number */ -+ NIL, /* modify flags */ -+ maildir_flagmsg, /* per-message modify flags */ -+ NIL, /* search for message based on criteria */ -+ NIL, /* sort messages */ -+ NIL, /* thread messages */ -+ maildir_ping, /* ping mailbox to see if still alive */ -+ maildir_check, /* check for new messages */ -+ maildir_expunge, /* expunge deleted messages */ -+ maildir_copy, /* copy messages to another mailbox */ -+ maildir_append, /* append string message to mailbox */ -+ NIL /* garbage collect stream */ -+}; -+ -+MAILSTREAM maildirproto = {&maildirdriver}; /* prototype stream */ -+MAILSTREAM courierproto = {&courierdriver}; /* prototype stream */ -+ -+long maildir_dirfmttest (char *name) -+{ -+ int i; -+ for (i = 0; mdstruct[i] && strcmp(name, mdstruct[i]); i++); -+ return (i < EndDir) || !strcmp(name, MDDIR) -+ || !strncmp(name, MDUIDLAST, strlen(MDUIDLAST)) -+ || !strncmp(name, MDUIDTEMP, strlen(MDUIDTEMP)) ? LONGT : NIL; -+} -+ -+void -+md_domain_name(void) -+{ -+ int i, j; -+ -+ strcpy(mdlocaldomain, mylocalhost ()); -+ for (i = 0; mdlocaldomain[i] != '\0' ;) -+ if(mdlocaldomain[i] == '/' || mdlocaldomain[i] == ':'){ -+ for(j = strlen(mdlocaldomain); j >= i; j--) -+ mdlocaldomain[j+4] = mdlocaldomain[j]; -+ mdlocaldomain[i++] = '\\'; -+ mdlocaldomain[i++] = '0'; -+ if(mdlocaldomain[i] == '/'){ -+ mdlocaldomain[i++] = '5'; -+ mdlocaldomain[i++] = '7'; -+ } else { -+ mdlocaldomain[i++] = '7'; -+ mdlocaldomain[i++] = '2'; -+ } -+ } -+ else -+ i++; -+} -+ -+char * -+myrootdir(char *name) -+{ -+return myhomedir(); -+} -+ -+char * -+mdirpath(void) -+{ -+ char *path = maildir_parameters(GET_MDINBOXPATH, NIL); -+ return path ? (path[0] ? path : ".") : "Maildir"; -+} -+ -+/* remove the "#md/" or "#mc/" part from a folder name -+ * memory freed by caller -+ */ -+char * -+maildir_remove_root (char *name) -+{ -+ int courier = IS_COURIER(name), offset; -+ char realname[MAILTMPLEN]; -+ -+ offset = maildir_valid_name(name) ? (name[3] == '/' ? 4 : 3) : 0; -+ if(courier) -+ courier_realname(name+offset, realname); -+ else -+ strcpy(realname, name+offset); -+ return cpystr(realname); -+} -+ -+ -+/* Check validity of the name, we accept: -+ * a) #md/directory/folder -+ * b) #md/inbox -+ * A few considerations: We can only accept as valid -+ * a) names that start with #md/ and the directory exists or -+ * b) names that do not start with #md/ but are maildir directories (have -+ * the /cur, /tmp and /new structure) -+ */ -+int maildir_valid_name (char *name) -+{ -+ char tmpname[MAILTMPLEN] = {'\0'}; -+ -+ if (mdfpath) -+ fs_give((void **)&mdfpath); -+ if (name && (name[0] != '#')) -+ snprintf(tmpname, sizeof(tmpname), "%s%s",MDPREFIX(CCLIENT), name); -+ mdfpath = cpystr(tmpname[0] ? tmpname : name); -+ -+ return IS_CCLIENT(name) || IS_COURIER(name); -+} -+ -+/* Check if the directory whose path is given by name is a valid maildir -+ * directory (contains /cur, /tmp and /new) -+ */ -+int maildir_valid_dir (char *name) -+{ -+ int len; -+ DirNamesType i; -+ struct stat sbuf; -+ char tmp[MAILTMPLEN]; -+ -+ if(name[strlen(name) - 1] == '/') -+ name[strlen(name) - 1] = '\0'; -+ len = strlen(name); -+ for (i = Cur; i != EndDir; i++){ -+ MDFLD(tmp, name, i); -+ if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)) -+ break; -+ } -+ name[len] = '\0'; -+ return (i == EndDir) ? T : NIL; -+} -+ -+void courier_realname(char *name, char *realname) -+{ -+ int i,j; -+ -+ if(!name) -+ return; -+ -+ for (i = 0, j = 0; i < MAILTMPLEN && j < strlen(name); j++, i++){ -+ realname[i] = name[j]; -+ if(name[j] == '/' && name[j+1] != '.' && name[j+1] != '%' -+ && name[j+1] != '*') -+ realname[++i] = '.'; -+ } -+ if(realname[i-1] == '.') -+ i--; -+ realname[i] = '\0'; -+} -+ -+ -+/* given a maildir folder, return its path. Memory freed by caller. Directory -+ * does not contain the trailing slash "/". On error NULL is returned. -+ */ -+int maildir_file_path (char *name, char *tmp, size_t sizeoftmp) -+{ -+ char *maildirpath = mdirpath(), *rname; -+ int courier = IS_COURIER(name); -+ -+ /* There are several ways in which the path can come, so we will handle -+ them here. First we deal with #mc/ or #md/ prefix by removing the -+ prefix, if any */ -+ -+ if(strlen(name) >= MAILTMPLEN) -+ name[MAILTMPLEN] = '\0'; -+ strcpy(tmp, name); -+ rname = maildir_remove_root(tmp); -+ tmp[0] = '\0'; /* just in case something fails */ -+ -+ if (strlen(myrootdir(rname)) + -+ max(strlen(rname), strlen(maildirpath)) > sizeoftmp){ -+ errno = ENAMETOOLONG; -+ snprintf(tmp, sizeoftmp, "Error opening \"%s\": %s", rname, strerror (errno)); -+ mm_log(tmp,ERROR); -+ if(rname) fs_give((void **)&rname); -+ return NIL; -+ } -+ -+ /* There are two ways in which the name can come here, either as a -+ full path or not. If it is not a full path it can come in two ways, -+ either as a file system path (Maildir/.Drafts) or as a maildir path -+ (INBOX.Drafts) -+ */ -+ -+ if(*rname == '/'){ /* full path */ -+ strncpy(tmp, rname, sizeoftmp); /* do nothing */ -+ tmp[sizeoftmp-1] = '\0'; -+ } -+ else -+ snprintf (tmp, sizeoftmp, "%s/%s%s%s", myrootdir (rname), -+ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) -+ ? rname : maildirpath, -+ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) -+ ? "" : (courier ? "/" : ""), -+ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) -+ ? "" : (*(rname+5) == MDSEPARATOR(courier) ? rname+5 : "")); -+ if(rname) fs_give((void **)&rname); -+ return tmp[0] ? T : NIL; -+} -+ -+/* This function is given a full path for a mailbox and returns -+ * if it is a valid maildir transformed to canonical notation -+ */ -+int -+is_valid_maildir (char **name) -+{ -+ if (!strncmp(*name, myrootdir (*name), strlen(myrootdir(*name)))){ -+ (*name) += strlen(myrootdir(*name)); -+ if (**name == '/') (*name)++; -+ } -+ return maildir_valid(*name) ? T : NIL; -+} -+ -+/* Check validity of mailbox. This routine does not send errors to log, other -+ * routines calling this one may do so, though -+ */ -+ -+DRIVER *maildir_valid (char *name) -+{ -+ char tmpname[MAILTMPLEN]; -+ -+ maildir_file_path(name, tmpname, sizeof(tmpname)); -+ -+ return maildir_valid_dir(tmpname) -+ ? (IS_COURIER(name) ? &courierdriver : &maildirdriver) : NIL; -+} -+ -+void maildir_fast (MAILSTREAM *stream,char *sequence,long flags) -+{ -+ unsigned long i; -+ MESSAGECACHE *elt; -+ /* get sequence */ -+ if (stream && LOCAL && ((flags & FT_UID) ? -+ mail_uid_sequence (stream,sequence) : -+ mail_sequence (stream,sequence))) -+ for (i = 1L; i <= stream->nmsgs; i++) { -+ if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) && -+ !(elt->day && elt->rfc822_size)) { -+ ENVELOPE **env = NIL; -+ ENVELOPE *e = NIL; -+ if (!stream->scache) env = &elt->private.msg.env; -+ else if (stream->msgno == i) env = &stream->env; -+ else env = &e; -+ if (!*env || !elt->rfc822_size) { -+ STRING bs; -+ unsigned long hs; -+ char *ht = (*stream->dtb->header) (stream,i,&hs,NIL); -+ -+ if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST, -+ stream->dtb->flags); -+ if (!elt->rfc822_size) { -+ (*stream->dtb->text) (stream,i,&bs,FT_PEEK); -+ elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs); -+ } -+ } -+ -+ if (!elt->day && *env && (*env)->date) -+ mail_parse_date (elt,(*env)->date); -+ -+ if (!elt->day) elt->day = elt->month = 1; -+ mail_free_envelope (&e); -+ } -+ } -+} -+ -+int -+maildir_eliminate_duplicate (char *name, struct direct ***flist, unsigned long *nfiles) -+{ -+ int i, j, k, error = 0, scanr; -+ char new[MAILTMPLEN], old[MAILTMPLEN], tmp[MAILTMPLEN], *str; -+ struct direct **names = NIL; -+ -+ if((scanr = maildir_doscandir(name, &names, CCLIENT)) < 0) -+ return -1; -+ -+ if(nfiles) *nfiles = scanr; -+ for(i = 0, j = 1, k = 0; j < scanr; i++, j++){ -+ if(k) -+ names[i] = names[i+k]; -+ if(same_maildir_file(names[i]->d_name, names[j]->d_name)){ -+ int d, f, r, s; -+ maildir_getflag(names[i]->d_name, &d, &f, &r, &s, NIL); -+ snprintf(old, sizeof(old), "%s/%s", name, names[i]->d_name); -+ snprintf(new, sizeof(new), "%s/.%s", name, names[i]->d_name); -+ if(rename(old, new) < 0 && errno != EEXIST) -+ error++; -+ if(!error){ -+ for(; j < scanr -+ && same_maildir_file(names[i]->d_name, names[j]->d_name) -+ ; j++, k++){ -+ maildir_getflag(names[j]->d_name, (d ? NIL : &d), -+ (f ? NIL : &f), (r ? NIL : &r), (s ? NIL : &s), NIL); -+ snprintf(tmp, sizeof(tmp), "%s/%s", name, names[j]->d_name); -+ if(unlink(tmp) < 0){ /* Hmmm... a problem, let's see */ -+ struct stat sbuf; -+ if (stat(tmp, &sbuf) == 0 && (sbuf.st_mode & S_IFMT) == S_IFREG) -+ error++; -+ } -+ } -+ if((str = strrchr(names[i]->d_name,FLAGSEP)) != NULL) *str = '\0'; -+ snprintf (old, sizeof(old), "%s/%s%s%s%s%s%s", name, names[i]->d_name, MDSEP(2), -+ MDFLAG(Draft, d), MDFLAG(Flagged, f), MDFLAG(Replied, r), -+ MDFLAG(Seen, s)); -+ if(rename(new, old) < 0) -+ error++; -+ } -+ } -+ -+ } -+ if(k > 0) -+ fs_give((void **)&names); -+ else -+ *flist = names; -+ return error ? -1 : k; -+} -+ -+int -+maildir_doscandir(char *name, struct direct ***flist, int flag) -+{ -+return scandir(name, flist, -+ flag == CCLIENT ? maildir_select : courier_dir_select, -+ flag == CCLIENT ? maildir_namesort : courier_dir_sort); -+} -+ -+/* -+ * return all files in a given directory. This is a separate call -+ * so that if there are warnings during compilation this only appears once. -+ */ -+unsigned long -+maildir_scandir (char *name, struct direct ***flist, -+ unsigned long *nfiles, int *scand, int flag) -+{ -+ struct stat sbuf; -+ int rv = -2; /* impossible value */ -+ -+ if (scand) -+ *scand = -1; /* assume error for safety */ -+ *nfiles = 0; -+ if((stat(name,&sbuf) < 0) -+ || (flag == CCLIENT -+ && ((rv = maildir_eliminate_duplicate(name, flist, nfiles)) < 0))) -+ return 0L; -+ -+ if (scand && (rv > 0 || rv == -2)) -+ *nfiles = maildir_doscandir(name, flist, flag); -+ -+ if(scand) *scand = *nfiles; -+ -+ return (unsigned long) sbuf.st_ctime; -+} -+ -+/* Does a message with given name exists (or was it removed)? -+ * Returns: 1 - yes, such message exist, -+ * 0 - No, that message does not exist anymore -+ * -+ * Parameters: stream, name of mailbox, new name if his message does not -+ * exist. -+ */ -+ -+int maildir_message_exists(MAILSTREAM *stream, char *name, char *newfile) -+{ -+ char tmp[MAILTMPLEN]; -+ int gotit = NIL; -+ DIR *dir; -+ struct direct *d; -+ struct stat sbuf; -+ -+ /* First check directly if it exists, if not there, look for it */ -+ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], name); -+ if ((stat(tmp, &sbuf) == 0) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) -+ return T; -+ -+ if (!(dir = opendir (LOCAL->path[Cur]))) -+ return NIL; -+ -+ while ((d = readdir(dir)) && gotit == NIL){ -+ if (d->d_name[0] == '.') -+ continue; -+ if (same_maildir_file(d->d_name, name)){ -+ gotit = T; -+ strcpy(newfile, d->d_name); -+ } -+ } -+ closedir(dir); -+ return gotit; -+} -+ -+/* Maildir open */ +diff -rc alpine-2.21/README.maildir alpine-2.21.maildir/README.maildir +*** alpine-2.21/README.maildir Sun Feb 5 16:15:21 2017 +--- alpine-2.21.maildir/README.maildir Sun Feb 5 16:15:21 2017 +*************** +*** 0 **** +--- 1,149 ---- ++ --------------------------------------- + -+MAILSTREAM *maildir_open (MAILSTREAM *stream) -+{ -+ char tmp[MAILTMPLEN]; -+ struct stat sbuf; -+ -+ if (!stream) return &maildirproto; -+ if (stream->local) fatal ("maildir recycle stream"); -+ md_domain_name(); /* get domain name for maildir files in mdlocaldomain */ -+ if(mypid == (pid_t) 0) -+ mypid = getpid(); -+ if (!stream->rdonly){ -+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged = -+ stream->perm_answered = stream->perm_draft = T; -+ } -+ stream->local = (MAILDIRLOCAL *) fs_get (sizeof (MAILDIRLOCAL)); -+ memset(LOCAL, 0, sizeof(MAILDIRLOCAL)); -+ LOCAL->fd = -1; -+ -+ LOCAL->courier = IS_COURIER(stream->mailbox); -+ strcpy(tmp, stream->mailbox); -+ if (maildir_file_path (stream->mailbox, tmp, sizeof(tmp))) -+ LOCAL->dir = cpystr (tmp); -+ LOCAL->candouid = maildir_can_assign_uid(stream); -+ maildir_read_uid(stream, &stream->uid_last, &stream->uid_validity); -+ if (LOCAL->dir){ -+ LOCAL->path = (char **) fs_get(EndDir*sizeof(char *)); -+ MDFLD(tmp, LOCAL->dir, Cur); LOCAL->path[Cur] = cpystr (tmp); -+ MDFLD(tmp, LOCAL->dir, New); LOCAL->path[New] = cpystr (tmp); -+ MDFLD(tmp, LOCAL->dir, Tmp); LOCAL->path[Tmp] = cpystr (tmp); -+ if (stat (LOCAL->path[Cur],&sbuf) < 0) { -+ snprintf (tmp, sizeof(tmp), "Can't open folder %s: %s", -+ stream->mailbox,strerror (errno)); -+ mm_log (tmp,ERROR); -+ maildir_close(stream, 0); -+ return NIL; ++ Maildir Driver for Alpine 2.0 ++ By Eduardo Chappa ++ ++ ++ --------------------------------------- ++ 1. General Information About This Patch ++ --------------------------------------- ++ ++ This patch adds support for the maildir format to Alpine. We take the ++ approach that this patch is one more driver among the number of formats ++ supported by Alpine (more generally c-client). This approach differs from ++ older versions of similar patches, in that once a maildir patch was ++ applied, it was assumed that all your folders would be created in the ++ maildir format. ++ ++ This patch does not assume that maildir is a preferred format, instead ++ puts maildir in equal footing with other formats (mbox, mbx, mix, etc), ++ and so a maildir folder in the mail/ collection is treated in the same way ++ as any other folder in any other format. In other words, just by reading ++ the name of a folder, or opening it, or doing any operation with it, you ++ can not know in which format the folder is. ++ ++ This implies that if you want to add a folder in the maildir format to the ++ mail/ collection, then you must add by pressing "A" in the folder list ++ collection and enter "#driver.md/mail/name_maildir_folder". ++ ++ If you only want to use maildir, however, you can do so too. In this case, ++ you must create a maildir collection. In that collection, only maildir ++ folders will be listed. If there is any folder in any other format, that ++ folder will be ignored. In another words, any folder listed there is in ++ maildir format and can be accessed through that collection, conversely, ++ any folder not listed there is not in maildir format and there is no way ++ to access it using this collection. ++ ++ In order to create a maildir collection, you could press M S L, and "A" to ++ add a collection. Fill in the required fields as follows: ++ ++ Nickname : Anything ++ Server : ++ Path : #md/relative/path/to/maildir/collection/ ++ View : ++ ++ For example, if "path" is set to "#md/mail/", then Alpine will look for your ++ maildir folders that are in ~/mail/. ++ ++ The code in this patch is mostly based in code for the unix driver plus ++ some combinations of the mh, mbx and nntp drivers for the c-client ++ library. Those drivers were designed by Mark Crispin, and bugs in this ++ code are not his bugs, but my own. ++ ++ I got all the specification for this patch from ++ http://cr.yp.to/proto/maildir.html. If you know of a place with a better ++ specification for maildir format please let me know. The method this patch ++ uses to create a unique filename for a message is one of the "old ++ fashioned" methods. I realize that this is old fashioned, but it is ++ portable, and portability is the main reason why I decided to use an old ++ fashioned method (most methods are not portable. See the word ++ "Unfortunately" in that document). ++ ++ -------------- ++ 2. Other Goals ++ -------------- ++ ++ It is intended that this code will work well with any application ++ written using the c-client library. Of paramount importance is to make the ++ associated imap server work well when the server accesses a folder in ++ Maildir format. The program mailutil should also work flawlessly with this ++ implemetation of the driver. ++ ++ It is intended that this driver be fast and stable. We intend not to ++ patch Alpine to make this driver do its work, unless such patching is for ++ fixing bugs in Alpine or to pass parameters to the driver. ++ ++ ------------------------------------------------------------------------ ++ 3. What are the known bugs of this implementation of the Maildir driver? ++ ------------------------------------------------------------------------ ++ ++ I don't know any at this time. There have been bugs before, though, but ++ I try to fix bugs as soon as they are reported. ++ ++ ---------- ++ 4. On UIDs ++ ---------- ++ ++ This patch keeps uids in the name of the file that contains the message, ++ by adding a ",u=" string to the file name to save the uid of a message. A ++ file is kept between sessions to save information on the last uid assigned ++ and its time of validity. Only one session with writing access can write ++ uids, all others must wait for the other session to assign them. The ++ session assigning uids creates a ".uidtemp" file which other sessions must ++ not disturb. ++ ++ Uid support appeared in Alpine 1.00 (snapshot 925), and is experimental, ++ please report any problems. ++ ++ ---------------------------------------------- ++ 5. Configuring Alpine and Setting up a Maildir ++ ---------------------------------------------- ++ ++ Once this approach was chosen, it implied the following: ++ ++ * This patch assumes that your INBOX is located at "$HOME/Maildir". ++ This is a directory which should have three subdirectories "cur", ++ "tmp" and "new". Mail is delivered to 'new' and read from 'cur'. I ++ have added a configuration option "maildir-location" which can be ++ used to tell Alpine where your Maildir inbox is, in case your system ++ does not use the above directory (e.g. your system may use ++ "~/.maildir"). In this case define that variable to be the name of ++ the directory where your e-mail is being delivered (e.g. ++ ".maildir"). ++ ++ * If you want to use the above configuration as your inbox, you must ++ define your inbox-path as "#md/inbox" (no quotes). You can define ++ the inbox-path like above even if you have changed the ++ maildir-location variable. That's the whole point of that variable. ++ ++ ------------------------------------------- ++ 6. What about Courier/Dovecot file systems? ++ ------------------------------------------- ++ ++ In a courier file system all folders are subfolders of a root folder ++ called INBOX. Normally INBOX is located at ~/Maildir and subfolders are ++ "dot" directories in ~/Maildir. For example ~/Maildir/.Trash is a ++ subfolder of INBOX and is accessed with the nickname "INBOX.Trash". ++ ++ You can not access folders in this way unless you preceed them with the ++ string "#mc/". The purpose of the string "#mc/" is to warn Alpine that a ++ collection in the Courier format is going to be accessed. Therefore, you ++ can SELECT a folder like "#mc/INBOX.Trash", but not "INBOX.Trash" ++ ++ You can access a collection through a server, but if you want to access a ++ collection of folders created using the Courier server, you MUST edit your ++ ".pinerc" file and enter the definition of the collection as follows: ++ ++ folder-collections="Anything you want" #mc/INBOX.[] ++ ++ You can replace the string "#mc/INBOX." by something different, for example ++ "#mc/Courier/." will make Alpine search for your collection in ~/Courier. ++ ++ You can not add this setting directly into Alpine because Alpine fails to ++ accept this value from its input, but it takes it correctly when it is ++ added through the ".pinerc" file. ++ ++ You can access your inbox as "#mc/INBOX" or "#md/INBOX". Both definitions ++ point to the same place. ++ ++ Last Updated May 28, 2011 +diff -rc alpine-2.21/alpine/alpine.c alpine-2.21.maildir/alpine/alpine.c +*** alpine-2.21/alpine/alpine.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/alpine/alpine.c Sun Feb 5 16:15:20 2017 +*************** +*** 576,581 **** +--- 576,586 ---- + if(F_ON(F_MAILDROPS_PRESERVE_STATE, ps_global)) + mail_parameters(NULL, SET_SNARFPRESERVE, (void *) TRUE); + ++ #ifndef _WINDOWS ++ rv = F_ON(F_COURIER_FOLDER_LIST, ps_global) ? 1 : 0; ++ mail_parameters(NULL,SET_COURIERSTYLE, (void *) &rv); ++ #endif ++ + rvl = 0L; + if(pine_state->VAR_NNTPRANGE){ + if(!SVAR_NNTPRANGE(pine_state, rvl, tmp_20k_buf, SIZEOF_20KBUF)) +diff -rc alpine-2.21/alpine/confscroll.c alpine-2.21.maildir/alpine/confscroll.c +*** alpine-2.21/alpine/confscroll.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/alpine/confscroll.c Sun Feb 5 16:15:20 2017 +*************** +*** 5545,5550 **** +--- 5545,5556 ---- + (void *)var->current_val.p); + } + #endif ++ #ifndef _WINDOWS ++ else if(var == &ps->vars[V_MAILDIR_LOCATION]){ ++ if(var->current_val.p && var->current_val.p[0]) ++ mail_parameters(NULL, SET_MDINBOXPATH, (void *)var->current_val.p); + } -+ } -+ -+ if(maildir_file_path (stream->mailbox, tmp, sizeof(tmp))){ -+ fs_give ((void **) &stream->mailbox); -+ stream->mailbox = cpystr(tmp); -+ } -+ -+ LOCAL->buf = (char *) fs_get (CHUNKSIZE); -+ LOCAL->buflen = CHUNKSIZE - 1; -+ stream->sequence++; -+ stream->nmsgs = stream->recent = 0L; -+ -+ maildir_parse_folder(stream, 1); -+ -+ return stream; -+} -+ -+/* Maildir initial parsing of the folder */ -+void -+maildir_parse_folder (MAILSTREAM *stream, int full) -+{ -+ char tmp[MAILTMPLEN]; -+ struct direct **namescur = NIL, **namesnew = NIL; -+ unsigned long i, nfilescur = 0L, nfilesnew = 0L, oldpos, newpos, total; -+ int scan_err, rescan, loop = 0; -+ -+ if (!stream) /* what??? */ -+ return; -+ -+ MM_CRITICAL(stream); -+ -+ maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); -+ if (scan_err < 0) -+ maildir_abort(stream); -+ -+ /* Scan old messages first, escoba! */ -+ if(stream->rdonly || -+ (LOCAL && ((maildir_initial_check(stream, Cur) == 0) -+ || nfilesnew > 0L))){ -+ LOCAL->scantime = maildir_scandir (LOCAL->path[Cur], &namescur, &nfilescur, -+ &scan_err, CCLIENT); -+ if (scan_err < 0){ -+ if(namesnew){ -+ for(i = 0L; i < nfilesnew; i++) -+ fs_give((void **)&namesnew[i]); -+ fs_give((void **) &namesnew); -+ } -+ maildir_abort(stream); -+ } ++ #endif + else if(revert && standard_radio_var(ps, var)){ + + cur_rule_value(var, TRUE, FALSE); +diff -rc alpine-2.21/imap/src/c-client/mail.c alpine-2.21.maildir/imap/src/c-client/mail.c +*** alpine-2.21/imap/src/c-client/mail.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/imap/src/c-client/mail.c Sun Feb 5 16:15:20 2017 +*************** +*** 1017,1023 **** + MAILSTREAM *ts; + char *s,*t,tmp[MAILTMPLEN]; + size_t i; +! DRIVER *d; + /* never allow names with newlines */ + if ((s = strpbrk (mailbox,"\015\012")) != NULL) { + MM_LOG ("Can't create mailbox with such a name",ERROR); +--- 1017,1023 ---- + MAILSTREAM *ts; + char *s,*t,tmp[MAILTMPLEN]; + size_t i; +! DRIVER *d, *md; + /* never allow names with newlines */ + if ((s = strpbrk (mailbox,"\015\012")) != NULL) { + MM_LOG ("Can't create mailbox with such a name",ERROR); +*************** +*** 1041,1046 **** +--- 1041,1048 ---- + return NIL; + } + ++ /* Hack, we should do this better, but it works */ ++ for (md = maildrivers; md && strcmp (md->name, "md"); md = md->next); + /* see if special driver hack */ + if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) && + ((mailbox[2] == 'r') || (mailbox[2] == 'R')) && +*************** +*** 1071,1076 **** +--- 1073,1085 ---- + (((*mailbox == '{') || (*mailbox == '#')) && + (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT)))) + d = stream->dtb; ++ else if(mailbox[0] == '#' ++ && (mailbox[1] == 'm' || mailbox[1] == 'M') ++ && (mailbox[2] == 'd' || mailbox[2] == 'D' ++ || mailbox[2] == 'c' || mailbox[2] == 'C') ++ && mailbox[3] == '/' ++ && mailbox[4] != '\0') ++ return (*md->create)(stream, mailbox); + else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb; + else { /* failed utterly */ + sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox); +diff -rc alpine-2.21/imap/src/c-client/mail.h alpine-2.21.maildir/imap/src/c-client/mail.h +*** alpine-2.21/imap/src/c-client/mail.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/imap/src/c-client/mail.h Sun Feb 5 16:15:20 2017 +*************** +*** 366,371 **** +--- 366,375 ---- + #define SET_SCANCONTENTS (long) 573 + #define GET_MHALLOWINBOX (long) 574 + #define SET_MHALLOWINBOX (long) 575 ++ #define GET_COURIERSTYLE (long) 576 ++ #define SET_COURIERSTYLE (long) 577 ++ #define SET_MDINBOXPATH (long) 578 ++ #define GET_MDINBOXPATH (long) 579 + + /* Driver flags */ + +diff -rc alpine-2.21/imap/src/osdep/unix/Makefile alpine-2.21.maildir/imap/src/osdep/unix/Makefile +*** alpine-2.21/imap/src/osdep/unix/Makefile Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/imap/src/osdep/unix/Makefile Sun Feb 5 16:15:20 2017 +*************** +*** 147,153 **** + # However, mh needs to be before any sysinbox formats (such as mmdf or unix) + # since otherwise INBOX won't work correctly when mh_allow_inbox is set. + # +! DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile + CHUNKSIZE=65536 + + # Normally no need to change any of these +--- 147,153 ---- + # However, mh needs to be before any sysinbox formats (such as mmdf or unix) + # since otherwise INBOX won't work correctly when mh_allow_inbox is set. + # +! DEFAULTDRIVERS=maildir courier imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile + CHUNKSIZE=65536 + + # Normally no need to change any of these +*************** +*** 156,162 **** + BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \ + dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ + rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ +! unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o + CFLAGS=-g + + CAT=cat +--- 156,162 ---- + BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \ + dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ + rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ +! unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o maildir.o + CFLAGS=-g + + CAT=cat +*************** +*** 293,299 **** + + cyg: # Cygwin - note that most local file drivers don't work!! + $(BUILD) `$(CAT) SPECIALS` OS=$@ \ +! DEFAULTDRIVERS="imap nntp pop3 mbx unix phile" \ + SIGTYPE=psx CHECKPW=cyg LOGINPW=cyg CRXTYPE=std \ + SPOOLDIR=/var \ + ACTIVEFILE=/usr/local/news/lib/active \ +--- 293,299 ---- + + cyg: # Cygwin - note that most local file drivers don't work!! + $(BUILD) `$(CAT) SPECIALS` OS=$@ \ +! DEFAULTDRIVERS="imap nntp pop3 mbx unix maildir phile" \ + SIGTYPE=psx CHECKPW=cyg LOGINPW=cyg CRXTYPE=std \ + SPOOLDIR=/var \ + ACTIVEFILE=/usr/local/news/lib/active \ +*************** +*** 911,917 **** + unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h + utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c + utf8aux.o: mail.h misc.h osdep.h utf8.h +! + + # OS-dependent + +--- 911,917 ---- + unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h + utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c + utf8aux.o: mail.h misc.h osdep.h utf8.h +! maildir.o: mail.h misc.h osdep.h maildir.h dummy.h + + # OS-dependent + +diff -rc alpine-2.21/imap/src/osdep/unix/dummy.c alpine-2.21.maildir/imap/src/osdep/unix/dummy.c +*** alpine-2.21/imap/src/osdep/unix/dummy.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/imap/src/osdep/unix/dummy.c Sun Feb 5 16:15:20 2017 +*************** +*** 103,115 **** + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ +! + DRIVER *dummy_valid (char *name) + { +! char *s,tmp[MAILTMPLEN]; + struct stat sbuf; + /* must be valid local mailbox */ +! if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) { + /* indeterminate clearbox INBOX */ + if (!*s) return &dummydriver; + else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) { +--- 103,121 ---- + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ +! char * maildir_remove_root(char *); + DRIVER *dummy_valid (char *name) + { +! char *s,tmp[MAILTMPLEN], *rname; + struct stat sbuf; ++ ++ if(strlen(name) > MAILTMPLEN) ++ name[MAILTMPLEN] = '\0'; ++ ++ strcpy(tmp, name); ++ rname = maildir_remove_root(tmp); + /* must be valid local mailbox */ +! if (rname && *rname && (*rname != '{') && (s = mailboxfile (tmp,rname))) { + /* indeterminate clearbox INBOX */ + if (!*s) return &dummydriver; + else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) { +*************** +*** 118,125 **** + return &dummydriver; + } + /* blackbox INBOX does not exist yet */ +! else if (!compare_cstring (name,"INBOX")) return &dummydriver; + } + return NIL; + } + +--- 124,132 ---- + return &dummydriver; + } + /* blackbox INBOX does not exist yet */ +! else if (!compare_cstring (rname,"INBOX")) return &dummydriver; + } ++ if(rname) fs_give((void **)&rname); + return NIL; + } + +*************** +*** 452,457 **** +--- 459,466 ---- + { + char *s,tmp[MAILTMPLEN]; + long ret = NIL; ++ if(!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4)) ++ return maildir_create(stream, mailbox); + /* validate name */ + if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) { + sprintf (tmp,"Can't create %.80s: invalid name",mailbox); +*************** +*** 517,522 **** +--- 526,539 ---- + { + struct stat sbuf; + char *s,tmp[MAILTMPLEN]; ++ if (!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4) ++ || is_valid_maildir(&mailbox)){ ++ char tmp[MAILTMPLEN] = {'\0'}; ++ strcpy(tmp, mailbox); ++ if(tmp[strlen(tmp) - 1] != '/') ++ tmp[strlen(tmp)] = '/'; ++ return maildir_delete(stream, tmp); + } -+ if(LOCAL && (maildir_initial_check(stream, New) == 0) -+ && (nfilescur > 0L)){ -+ while(LOCAL && loop < 10){ -+ if(nfilesnew == 0L) -+ maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); -+ if (scan_err < 0){ -+ if(namesnew){ -+ for(i = 0L; i < nfilesnew; i++) -+ fs_give((void **)&namesnew[i]); -+ fs_give((void **) &namesnew); -+ } -+ maildir_abort(stream); -+ break; -+ } -+ for(i = 0L, rescan = 0, newpos = oldpos = 0L; -+ newpos < nfilescur && i < nfilesnew; i++){ -+ if(maildir_message_in_list(namesnew[i]->d_name, namescur, oldpos, -+ nfilescur - 1L, &newpos)){ -+ oldpos = newpos; -+ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[New], namesnew[i]->d_name); -+ if(unlink(tmp) < 0) -+ scan_err = -1; -+ rescan++; -+ } -+ else -+ newpos = oldpos; -+ } -+ if(scan_err < 0) -+ maildir_abort(stream); -+ if(rescan == 0) -+ break; -+ else{ /* restart */ -+ if(namesnew){ -+ for(i = 0L; i < nfilesnew; i++) -+ fs_give((void **)&namesnew[i]); -+ fs_give((void **) &namesnew); -+ } -+ nfilesnew = 0L; -+ loop++; -+ } -+ } -+ } -+ if(loop == 10) -+ maildir_abort(stream); -+ if(LOCAL){ -+ if(stream->rdonly) -+ stream->recent = 0L; -+ total = namescur || stream->rdonly -+ ? maildir_parse_dir(stream, 0L, Cur, namescur, -+ nfilescur, full) : stream->nmsgs; -+ stream->nmsgs = maildir_parse_dir(stream, total, New, namesnew, -+ nfilesnew, full); -+ } -+ if(namesnew){ -+ for(i = 0L; i < nfilesnew; i++) -+ fs_give((void **)&namesnew[i]); -+ fs_give((void **) &namesnew); -+ } -+ if(namescur){ -+ for(i = 0L; i < nfilescur; i++) -+ fs_give((void **)&namescur[i]); -+ fs_give((void **) &namescur); -+ } -+ MM_NOCRITICAL(stream); -+} -+ -+int -+maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype) -+{ -+ char *tmp; -+ struct stat sbuf; -+ -+ if (access (LOCAL->path[dirtype], R_OK|W_OK|X_OK) != 0){ -+ maildir_abort(stream); -+ return -1; -+ } -+ -+ if (dirtype != New && -+ (stat(LOCAL->path[Cur], &sbuf) < 0 || sbuf.st_ctime == LOCAL->scantime)) -+ return -1; -+ return 0; -+} -+ -+ -+/* Return the number of messages in the directory, while filling the -+ * elt structure. -+ */ -+ -+unsigned long -+maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, -+ DirNamesType dirtype, struct direct **names, -+ unsigned long nfiles, int full) -+{ -+ char tmp[MAILTMPLEN], file[MAILTMPLEN], newfile[MAILTMPLEN], *mdstr; -+ struct stat sbuf; -+ unsigned long i, new = 0L, l, uid_last; -+ unsigned long recent = stream ? stream->recent : 0L; -+ int d = 0, f = 0, r = 0, s = 0, t = 0; -+ int we_compute, in_list; -+ int silent = stream ? stream->silent : NIL; -+ MESSAGECACHE *elt; -+ -+ if (dirtype == Cur && !stream->rdonly) -+ for (i = 1L; i <= stream->nmsgs;){ -+ elt = mail_elt(stream, i); -+ in_list = elt && elt->private.spare.ptr && nfiles > 0L -+ ? (MDPOS(elt) < nfiles -+ ? same_maildir_file(MDFILE(elt), names[MDPOS(elt)]->d_name) -+ : NIL) -+ || maildir_message_in_list(MDFILE(elt), names, 0L, -+ nfiles - 1L, &MDPOS(elt)) -+ : NIL; -+ if (!in_list){ -+ if (elt->private.spare.ptr) -+ maildir_free_file ((void **) &elt->private.spare.ptr); -+ -+ if (elt->recent) --recent; -+ mail_expunged(stream,i); -+ } -+ else i++; -+ } -+ -+ stream->silent = T; -+ uid_last = 0L; -+ for (we_compute = 0, i = l = 1L; l <= nfiles; l++){ -+ unsigned long pos, uid; -+ if (dirtype == New && !stream->rdonly){ /* move new messages to cur */ -+ pos = l - 1L; -+ snprintf (file, sizeof(file), "%s/%s", LOCAL->path[New], names[pos]->d_name); -+ if(lstat(file,&sbuf) == 0) -+ switch(sbuf.st_mode & S_IFMT){ -+ case S_IFREG: -+ strcpy(tmp, names[pos]->d_name); -+ if((mdstr = strstr(tmp,MDSEP(3))) -+ || (mdstr = strstr(tmp,MDSEP(2)))) -+ *(mdstr+1) = '2'; -+ else -+ strcat(tmp, MDSEP(2)); -+ snprintf(newfile, sizeof(newfile), "%s/%s", LOCAL->path[Cur], tmp); -+ if(rename (file, newfile) != 0){ -+ mm_log("Unable to read new mail!", WARN); -+ continue; -+ } -+ unlink (file); -+ new++; -+ break; -+ case S_IFLNK: /* clean up, clean up, everybody, everywhere */ -+ if(unlink(file) < 0){ -+ if(LOCAL->link == NIL){ -+ mm_log("Unable to remove symbolic link", WARN); -+ LOCAL->link = T; -+ } -+ } -+ continue; -+ break; -+ default: -+ if(LOCAL && LOCAL->link == NIL){ -+ mm_log("Unrecognized file or link in folder", WARN); -+ LOCAL->link = T; -+ } -+ continue; -+ break; -+ } -+ } -+ mail_exists(stream, i + nmsgs); -+ elt = mail_elt(stream, i + nmsgs); -+ pos = (elt && elt->private.spare.ptr) ? MDPOS(elt) : l - 1L; -+ if (dirtype == New) elt->recent = T; -+ maildir_getflag(names[pos]->d_name, &d, &f, &r ,&s, &t); -+ if (elt->private.spare.ptr) -+ maildir_free_file_only ((void **)&elt->private.spare.ptr); -+ else{ -+ maildir_get_file((MAILDIRFILE **)&elt->private.spare.ptr); -+ we_compute++; -+ } -+ MDFILE(elt) = cpystr(names[pos]->d_name); -+ MDPOS(elt) = pos; -+ MDLOC(elt) = dirtype; -+ if (dirtype == Cur){ /* deal with UIDs */ -+ if(elt->private.uid == 0L) -+ elt->private.uid = maildir_get_uid(MDFILE(elt)); -+ if(elt->private.uid <= uid_last){ -+ uid = (we_compute ? uid_last : stream->uid_last) + 1L; -+ if(LOCAL->candouid) -+ maildir_assign_uid(stream, i + nmsgs, uid); -+ else -+ elt->private.uid = uid; -+ } -+ else -+ uid = elt->private.uid; -+ uid_last = uid; -+ if(uid_last > stream->uid_last) -+ stream->uid_last = uid_last; -+ } -+ if(dirtype == New && !stream->rdonly){ -+ maildir_free_file_only((void **)&elt->private.spare.ptr); -+ MDFILE(elt) = cpystr(tmp); -+ MDSIZE(elt) = sbuf.st_size; -+ MDMTIME(elt) = sbuf.st_mtime; -+ MDLOC(elt) = Cur; -+ } -+ if (elt->draft != d || elt->flagged != f || -+ elt->answered != r || elt->seen != s || elt->deleted != t){ -+ elt->draft = d; elt->flagged = f; elt->answered = r; -+ elt->seen = s; elt->deleted = t; -+ if (!we_compute && !stream->rdonly) -+ MM_FLAGS(stream, i+nmsgs); -+ } -+ maildir_get_date(stream, i+nmsgs); -+ elt->valid = T; -+ i++; -+ } -+ stream->silent = silent; -+ if(LOCAL->candouid && dirtype == Cur) -+ maildir_read_uid(stream, NULL, &stream->uid_validity); -+ if (dirtype == New && stream->rdonly) -+ new = nfiles; -+ mail_exists(stream, nmsgs + ((dirtype == New) ? new : nfiles)); -+ mail_recent(stream, recent + ((dirtype == New) ? new : 0L)); -+ -+ return (nmsgs + (dirtype == New ? new : nfiles)); -+} -+ -+long maildir_ping (MAILSTREAM *stream) -+{ -+ maildir_parse_folder(stream, 0); -+ if(stream && LOCAL){ -+ if(LOCAL->candouid < 0) -+ LOCAL->candouid++; -+ else if(LOCAL->candouid) -+ maildir_uid_renew_tempfile(stream); -+ else /* try again to get uids */ -+ LOCAL->candouid = maildir_can_assign_uid(stream); -+ } -+ return stream && LOCAL ? LONGT : NIL; -+} -+ -+int maildir_select (const struct direct *name) -+{ -+ return (name->d_name[0] != '.'); -+} -+ -+/* -+ * Unfortunately, there is no way to sort by arrival in this driver, this -+ * means that opening a folder in this driver using the scandir function -+ * will always make this driver slower than any driver that has a natural -+ * way of sorting by arrival (like a flat file format, "mbox", "mbx", etc). -+ */ -+int maildir_namesort (const struct direct **d1, const struct direct **d2) -+{ -+ const struct direct *e1 = *(const struct direct **) d1; -+ const struct direct *e2 = *(const struct direct **) d2; -+ -+ return comp_maildir_file((char *) e1->d_name, (char *) e2->d_name); -+} -+ -+/* Maildir close */ -+ -+void maildir_close (MAILSTREAM *stream, long options) -+{ -+ MESSAGECACHE *elt; -+ unsigned long i; -+ int silent = stream ? stream->silent : 0; -+ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); -+ -+ if (!stream) return; -+ -+ for (i = 1L; i <= stream->nmsgs; i++) -+ if((elt = (MESSAGECACHE *) (*mc)(stream,i,CH_ELT)) && elt->private.spare.ptr) -+ maildir_free_file ((void **) &elt->private.spare.ptr); -+ stream->silent = T; -+ if (options & CL_EXPUNGE) maildir_expunge (stream, NIL, NIL); -+ maildir_abort(stream); -+ if (mdfpath) fs_give((void **)&mdfpath); -+ if (mypid) mypid = (pid_t) 0; -+ stream->silent = silent; -+} -+ -+void maildir_check (MAILSTREAM *stream) -+{ -+ if (maildir_ping (stream)) mm_log ("Check completed",(long) NIL); -+} -+ -+long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs, long flags) -+{ -+ char tmp[MAILTMPLEN]; -+ unsigned long i; -+ MESSAGECACHE *elt; -+ char *s; -+ /* UID call "impossible" */ -+ if (flags & FT_UID || !LOCAL) return NIL; -+ elt = mail_elt (stream, msgno); -+ -+ if (!(flags & FT_PEEK) && !elt->seen){ -+ elt->seen = T; -+ maildir_flagmsg (stream, elt); -+ MM_FLAGS(stream, elt->msgno); -+ } -+ -+ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); -+ if (LOCAL->fd < 0) /* if file closed ? */ -+ LOCAL->fd = open(tmp,O_RDONLY,NIL); -+ -+ if (LOCAL->fd < 0 && (errno == EACCES || errno == ENOENT)){ -+ INIT (bs, mail_string, "", 0); -+ elt->rfc822_size = 0L; -+ return NIL; -+ } -+ -+ s = maildir_text_work(stream, elt, &i, flags); -+ INIT (bs, mail_string, s, i); -+ return LONGT; -+} -+ -+char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, -+ unsigned long *length,long flags) -+{ -+ FDDATA d; -+ STRING bs; -+ char *s,tmp[CHUNK]; -+ unsigned long msgno = elt->msgno; -+ static int try = 0; -+ -+ if (length) -+ *length = 0L; -+ LOCAL->buf[0] = '\0'; -+ -+ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); -+ if (LOCAL->fd < 0) /* if file closed ? */ -+ LOCAL->fd = open(tmp,O_RDONLY,NIL); -+ -+ if (LOCAL->fd < 0){ /* flag change? */ -+ if (try < 5){ -+ try++; -+ if (maildir_update_elt_maildirp(stream, msgno) > 0) -+ try = 0; -+ return maildir_text_work(stream, mail_elt(stream, msgno),length, flags); -+ } -+ try = 0; -+ return NULL; -+ } -+ -+ lseek (LOCAL->fd, elt->private.msg.text.offset,L_SET); -+ -+ if (flags & FT_INTERNAL) { /* initial data OK? */ -+ if (elt->private.msg.text.text.size > LOCAL->buflen) { -+ fs_give ((void **) &LOCAL->buf); -+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = -+ elt->private.msg.text.text.size) + 1); -+ } -+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size); -+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0'; -+ } -+ else { -+ if (elt->rfc822_size > LOCAL->buflen) { -+ fs_give ((void **) &LOCAL->buf); -+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1); -+ } -+ d.fd = LOCAL->fd; /* yes, set up file descriptor */ -+ d.pos = elt->private.msg.text.offset; -+ d.chunk = tmp; /* initial buffer chunk */ -+ d.chunksize = CHUNK; -+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size); -+ for (s = LOCAL->buf; SIZE (&bs);) switch (CHR (&bs)) { -+ case '\r': /* carriage return seen */ -+ *s++ = SNX (&bs); /* copy it and any succeeding LF */ -+ if (SIZE (&bs) && (CHR (&bs) == '\n')) *s++ = SNX (&bs); -+ break; -+ case '\n': -+ *s++ = '\r'; /* insert a CR */ -+ default: -+ *s++ = SNX (&bs); /* copy characters */ -+ } -+ *s = '\0'; /* tie off buffer */ -+ *length = s - (char *) LOCAL->buf; /* calculate length */ -+ } -+ close(LOCAL->fd); LOCAL->fd = -1; -+ return LOCAL->buf; -+} -+ -+/* maildir parse, fill the elt structure... well not all of it... */ -+unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, -+ DirNamesType dirtype) -+{ -+ char *b, *s, *t, c; -+ char tmp[MAILTMPLEN]; -+ struct stat sbuf; -+ unsigned long i, len; -+ int d, f, r, se, dt; -+ MESSAGECACHE *elt; -+ -+ elt = mail_elt (stream,msgno); -+ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), dirtype); -+ if(stat(tmp, &sbuf) == 0) -+ MDSIZE(elt) = sbuf.st_size; -+ -+ maildir_get_date(stream, msgno); -+ maildir_getflag(MDFILE(elt), &d, &f, &r ,&se, &dt); -+ elt->draft = d; elt->flagged = f; elt->answered = r; elt->seen = se; -+ elt->deleted = dt; elt->valid = T; -+ if (LOCAL->fd < 0) /* if file closed ? */ -+ LOCAL->fd = open(tmp,O_RDONLY,NIL); -+ -+ if (LOCAL->fd >= 0){ -+ s = (char *) fs_get (MDSIZE(elt) + 1); -+ read (LOCAL->fd,s,MDSIZE(elt)); -+ s[MDSIZE(elt)] = '\0'; -+ t = s + strlen(s); /* make t point to the end of s */ -+ for (i = 0L, b = s; b < t && !(i && (*b == '\n')); i = (*b++ == '\n')); -+ len = (*b ? ++b : b) - s; -+ elt->private.msg.header.text.size = -+ elt->private.msg.text.offset = len; -+ elt->private.msg.text.text.size = MDSIZE(elt) - len; -+ for (i = 0L, b = s, c = *b; b && -+ ((c < '\016' && ((c == '\012' && ++i) -+ ||(c == '\015' && *(b+1) == '\012' && ++b && (i +=2)))) -+ || b < t); i++, c= *++b); -+ elt->rfc822_size = i; -+ fs_give ((void **) &s); -+ close(LOCAL->fd); LOCAL->fd = -1; -+ } -+ return elt->rfc822_size; -+} -+ -+int -+maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno) -+{ -+ struct direct **names = NIL; -+ unsigned long i, nfiles, pos; -+ int d = 0, f = 0 , r = 0, s = 0, t = 0, in_list, scan_err; -+ MESSAGECACHE *elt; -+ -+ maildir_scandir (LOCAL->path[Cur], &names, &nfiles, &scan_err, CCLIENT); -+ -+ elt = mail_elt (stream,msgno); -+ -+ in_list = nfiles > 0L -+ ? maildir_message_in_list(MDFILE(elt), names, 0L, nfiles - 1L, &pos) -+ : NIL; -+ -+ if (in_list && pos >= 0L && pos < nfiles -+ && !strcmp(MDFILE(elt), names[pos]->d_name)){ -+ in_list = NIL; -+ maildir_abort(stream); -+ } -+ -+ if (in_list && pos >= 0L && pos < nfiles){ -+ maildir_free_file_only((void **)&elt->private.spare.ptr); -+ MDFILE(elt) = cpystr(names[pos]->d_name); -+ maildir_getflag(MDFILE(elt), &d, &f, &r ,&s, &t); -+ if (elt->draft != d || elt->flagged != f || -+ elt->answered != r || elt->seen != s || elt->deleted != t){ -+ elt->draft = d; elt->flagged = f; elt->answered = r; -+ elt->seen = s; elt->deleted = t; -+ MM_FLAGS(stream, msgno); -+ } -+ } -+ for (i = 0L; i < nfiles; i++) -+ fs_give((void **) &names[i]); -+ if (names) -+ fs_give((void **) &names); -+ return in_list ? 1 : -1; -+} -+ -+/* Maildir fetch message header */ -+ -+char *maildir_header (MAILSTREAM *stream,unsigned long msgno, -+ unsigned long *length, long flags) -+{ -+ char tmp[MAILTMPLEN], *s; -+ MESSAGECACHE *elt; -+ static int try = 0; -+ -+ if (length) *length = 0; -+ if (flags & FT_UID || !LOCAL) return ""; /* UID call "impossible" */ -+ elt = mail_elt (stream,msgno); -+ if(elt->private.msg.header.text.size == 0) -+ maildir_parse_message(stream, msgno, MDLOC(elt)); -+ -+ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); -+ if (LOCAL->fd < 0) -+ LOCAL->fd = open (tmp,O_RDONLY,NIL); -+ -+ if (LOCAL->fd < 0 && errno == EACCES){ -+ mm_log ("Message exists but can not be read. Envelope and body lost!",ERROR); -+ return NULL; -+ } -+ -+ if (LOCAL->fd < 0){ /* flag change? */ -+ if (try < 5){ -+ try++; -+ if (maildir_update_elt_maildirp(stream, msgno) > 0) -+ try = 0; -+ return maildir_header(stream, msgno, length, flags); -+ } -+ try = 0; -+ return NULL; -+ } -+ -+ if (flags & FT_INTERNAL){ -+ if(elt->private.msg.header.text.size > LOCAL->buflen){ -+ fs_give ((void **) &LOCAL->buf); -+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = -+ elt->private.msg.header.text.size) + 1); -+ } -+ read (LOCAL->fd, (void *)LOCAL->buf, elt->private.msg.header.text.size); -+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0'; -+ } -+ else{ -+ s = (char *) fs_get(elt->private.msg.header.text.size+1); -+ read (LOCAL->fd, (void *)s, elt->private.msg.header.text.size); -+ s[elt->private.msg.header.text.size] = '\0'; -+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s, -+ elt->private.msg.header.text.size); -+ fs_give ((void **) &s); -+ } -+ elt->private.msg.text.offset = elt->private.msg.header.text.size; -+ elt->private.msg.text.text.size = MDSIZE(elt) - elt->private.msg.text.offset; -+ close(LOCAL->fd); LOCAL->fd = -1; -+ return LOCAL->buf; -+} -+ -+/* Maildir find list of subscribed mailboxes -+ * Accepts: mail stream -+ * pattern to search -+ */ -+ -+void maildir_list (MAILSTREAM *stream,char *ref, char *pat) -+{ -+ char *s,test[MAILTMPLEN],file[MAILTMPLEN]; -+ long i = 0L; -+ -+ if((!pat || !*pat) && maildir_canonicalize (test,ref,"*") -+ && maildir_valid_name(test)){ /* there is a #md/ leading here */ -+ for (i = 3L; test[i] && test[i] != '/'; i++); -+ if ((s = strchr (test+i+1,'/')) != NULL) *++s = '\0'; -+ else test[0] = '\0'; -+ mm_list (stream,'/',test, LATT_NOSELECT); -+ } -+ else if (maildir_canonicalize (test,ref,pat)) { -+ if (test[3] == '/') { /* looking down levels? */ -+ /* yes, found any wildcards? */ -+ if ((s = strpbrk (test,"%*")) != NULL){ -+ /* yes, copy name up to that point */ -+ strncpy (file,test+4,i = s - (test+4)); -+ file[i] = '\0'; /* tie off */ -+ } -+ else strcpy (file,test+4);/* use just that name then */ -+ /* find directory name */ -+ if ((s = strrchr (file, '/')) != NULL){ -+ *s = '\0'; /* found, tie off at that point */ -+ s = file; -+ } -+ /* do the work */ -+ if(IS_COURIER(test)) -+ courier_list_work (stream,s,test,0); -+ else -+ maildir_list_work (stream,s,test,0); -+ } -+ /* always an INBOX */ -+ if (!compare_cstring (test,"#MD/INBOX")) -+ mm_list (stream,NIL,"#MD/INBOX",LATT_NOINFERIORS); -+ if (!compare_cstring (test,"#MC/INBOX")) -+ mm_list (stream,NIL,"#MC/INBOX",LATT_NOINFERIORS); -+ } -+} -+ -+void courier_list (MAILSTREAM *stream,char *ref, char *pat) -+{ -+/* I am too lazy to do anything. Do you care to ask maildir list, please? -+ The real reason why this is a dummy function is because we do not want to -+ see the same folder listed twice. -+*/ -+} -+ -+/* For those that want to hide things, we give them a chance to do so */ -+void *maildir_parameters (long function, void *value) -+{ -+ void *ret = NIL; -+ switch ((int) function) { -+ case SET_MDINBOXPATH: -+ if(strlen((char *) value ) > 49) -+ strcpy(myMdInboxDir, "Maildir"); -+ else -+ strcpy(myMdInboxDir, (char *) value); -+ case GET_MDINBOXPATH: -+ if (myMdInboxDir[0] == '\0') strcpy(myMdInboxDir,"Maildir"); -+ ret = (void *) myMdInboxDir; -+ break; -+ case SET_COURIERSTYLE: -+ CourierStyle = (long) value; -+ case GET_COURIERSTYLE: -+ ret = (void *) CourierStyle; -+ break; -+ case GET_DIRFMTTEST: -+ ret = (void *) maildir_dirfmttest; -+ break; -+ default: -+ break; -+ } -+ return ret; -+} -+ -+int maildir_create_folder(char *mailbox) -+{ -+ char tmp[MAILTMPLEN], err[MAILTMPLEN]; -+ DirNamesType i; -+ -+ for (i = Cur; i != EndDir; i++){ -+ MDFLD(tmp, mailbox, i); -+ if (mkdir(tmp, 0700) && errno != EEXIST){ /* try to make new dir */ -+ snprintf (err, sizeof(err), "Can't create %s: %s", tmp, strerror(errno)); -+ mm_log (err,ERROR); -+ return NIL; -+ } -+ } -+ return T; -+} -+ -+int maildir_create_work(char *mailbox, int loop) -+{ -+ char *s, c, err[MAILTMPLEN], tmp[MAILTMPLEN], tmp2[MAILTMPLEN], mbx[MAILTMPLEN]; -+ int fnlen, create_dir = 0, courier, mv; -+ struct stat sbuf; -+ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL); -+ -+ courier = IS_COURIER(mailbox); -+ strcpy(mbx, mailbox); -+ mv = maildir_valid(mbx) ? 1 : 0; -+ maildir_file_path(mailbox, tmp, sizeof(tmp)); -+ if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ -+ create_dir++; -+ mailbox[strlen(mailbox) - 1] = '\0'; -+ } -+ -+ if(!loop && courier){ -+ if(mv){ -+ if(create_dir){ -+ if(style == CCLIENT) -+ strcpy (err,"Can not create directory: folder exists. Create subfolder"); -+ else -+ strcpy(err,"Folder and Directory already exist"); + if (!(s = dummy_file (tmp,mailbox))) { + sprintf (tmp,"Can't delete - invalid name: %.80s",s); + MM_LOG (tmp,ERROR); +*************** +*** 542,553 **** + long dummy_rename (MAILSTREAM *stream,char *old,char *newname) + { + struct stat sbuf; +! char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN]; + /* no trailing / allowed */ +! if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) || + stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] && + ((sbuf.st_mode & S_IFMT) != S_IFDIR))) { +! sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname); + MM_LOG (mbx,ERROR); + return NIL; + } +--- 559,581 ---- + long dummy_rename (MAILSTREAM *stream,char *old,char *newname) + { + struct stat sbuf; +! char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN], *rold, *rnewname; +! +! if(strlen(old) > MAILTMPLEN) +! old[MAILTMPLEN] = '\0'; +! +! if(strlen(newname) > MAILTMPLEN) +! newname[MAILTMPLEN] = '\0'; +! +! strcpy(tmp, old); +! rold = maildir_remove_root(tmp); +! strcpy(tmp, newname); +! rnewname = maildir_remove_root(tmp); + /* no trailing / allowed */ +! if (!dummy_file (oldname,rold) || !(s = dummy_file (mbx,rnewname)) || + stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] && + ((sbuf.st_mode & S_IFMT) != S_IFDIR))) { +! sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",rold,rnewname); + MM_LOG (mbx,ERROR); + return NIL; + } +*************** +*** 563,576 **** + } + } + /* rename of non-ex INBOX creates dest */ +! if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf)) + return dummy_create (NIL,mbx); + if (rename (oldname,mbx)) { +! sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname, + strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + return T; /* return success */ + } + +--- 591,606 ---- + } + } + /* rename of non-ex INBOX creates dest */ +! if (!compare_cstring (rold,"INBOX") && stat (oldname,&sbuf)) + return dummy_create (NIL,mbx); + if (rename (oldname,mbx)) { +! sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",rold,rnewname, + strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } ++ if(rold) fs_give((void **)&rold); ++ if(rnewname) fs_give((void **)&rnewname); + return T; /* return success */ + } + +diff -rc alpine-2.21/imap/src/osdep/unix/maildir.c alpine-2.21.maildir/imap/src/osdep/unix/maildir.c +*** alpine-2.21/imap/src/osdep/unix/maildir.c Sun Feb 5 16:15:21 2017 +--- alpine-2.21.maildir/imap/src/osdep/unix/maildir.c Sun Feb 5 16:15:20 2017 +*************** +*** 0 **** +--- 1,2671 ---- ++ /* ++ * Maildir driver for Alpine 2.20 ++ * ++ * Written by Eduardo Chappa ++ * Last Update: June 10, 2014 ++ * ++ */ ++ ++ #include ++ #include ++ #include ++ extern int errno; /* just in case */ ++ #include "mail.h" ++ #include ++ #include ++ #include ++ #include "osdep.h" ++ #include "rfc822.h" ++ #include "fdstring.h" ++ #include "misc.h" ++ #include "dummy.h" ++ #include "maildir.h" ++ ++ /* Driver dispatch used by MAIL */ ++ DRIVER maildirdriver = { ++ "md", /* driver name, yes it's md, not maildir */ ++ DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ ++ (DRIVER *) NIL, /* next driver */ ++ maildir_valid, /* mailbox is valid for us */ ++ maildir_parameters, /* manipulate parameters */ ++ NIL, /* scan mailboxes */ ++ maildir_list, /* find mailboxes */ ++ maildir_lsub, /* find subscribed mailboxes */ ++ maildir_sub, /* subscribe to mailbox */ ++ maildir_unsub, /* unsubscribe from mailbox */ ++ maildir_create, /* create mailbox */ ++ maildir_delete, /* delete mailbox */ ++ maildir_rename, /* rename mailbox */ ++ mail_status_default, /* status of mailbox */ ++ maildir_open, /* open mailbox */ ++ maildir_close, /* close mailbox */ ++ maildir_fast, /* fetch message "fast" attributes */ ++ NIL, /* fetch message flags */ ++ NIL, /* fetch overview */ ++ NIL, /* fetch message structure */ ++ maildir_header, /* fetch message header */ ++ maildir_text, /* fetch message body */ ++ NIL, /* fetch partial message text */ ++ NIL, /* unique identifier */ ++ NIL, /* message number */ ++ NIL, /* modify flags */ ++ maildir_flagmsg, /* per-message modify flags */ ++ NIL, /* search for message based on criteria */ ++ NIL, /* sort messages */ ++ NIL, /* thread messages */ ++ maildir_ping, /* ping mailbox to see if still alive */ ++ maildir_check, /* check for new messages */ ++ maildir_expunge, /* expunge deleted messages */ ++ maildir_copy, /* copy messages to another mailbox */ ++ maildir_append, /* append string message to mailbox */ ++ NIL /* garbage collect stream */ ++ }; ++ ++ ++ DRIVER courierdriver = { ++ "mc", /* Why a separate driver? So that createproto will work */ ++ DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ ++ (DRIVER *) NIL, /* next driver */ ++ maildir_valid, /* mailbox is valid for us */ ++ maildir_parameters, /* manipulate parameters */ ++ NIL, /* scan mailboxes */ ++ courier_list, /* find mailboxes */ ++ maildir_lsub, /* find subscribed mailboxes */ ++ maildir_sub, /* subscribe to mailbox */ ++ maildir_unsub, /* unsubscribe from mailbox */ ++ maildir_create, /* create mailbox */ ++ maildir_delete, /* delete mailbox */ ++ maildir_rename, /* rename mailbox */ ++ mail_status_default, /* status of mailbox */ ++ maildir_open, /* open mailbox */ ++ maildir_close, /* close mailbox */ ++ maildir_fast, /* fetch message "fast" attributes */ ++ NIL, /* fetch message flags */ ++ NIL, /* fetch overview */ ++ NIL, /* fetch message structure */ ++ maildir_header, /* fetch message header */ ++ maildir_text, /* fetch message body */ ++ NIL, /* fetch partial message text */ ++ NIL, /* unique identifier */ ++ NIL, /* message number */ ++ NIL, /* modify flags */ ++ maildir_flagmsg, /* per-message modify flags */ ++ NIL, /* search for message based on criteria */ ++ NIL, /* sort messages */ ++ NIL, /* thread messages */ ++ maildir_ping, /* ping mailbox to see if still alive */ ++ maildir_check, /* check for new messages */ ++ maildir_expunge, /* expunge deleted messages */ ++ maildir_copy, /* copy messages to another mailbox */ ++ maildir_append, /* append string message to mailbox */ ++ NIL /* garbage collect stream */ ++ }; ++ ++ MAILSTREAM maildirproto = {&maildirdriver}; /* prototype stream */ ++ MAILSTREAM courierproto = {&courierdriver}; /* prototype stream */ ++ ++ long maildir_dirfmttest (char *name) ++ { ++ int i; ++ for (i = 0; mdstruct[i] && strcmp(name, mdstruct[i]); i++); ++ return (i < EndDir) || !strcmp(name, MDDIR) ++ || !strncmp(name, MDUIDLAST, strlen(MDUIDLAST)) ++ || !strncmp(name, MDUIDTEMP, strlen(MDUIDTEMP)) ? LONGT : NIL; ++ } ++ ++ void ++ md_domain_name(void) ++ { ++ int i, j; ++ ++ strcpy(mdlocaldomain, mylocalhost ()); ++ for (i = 0; mdlocaldomain[i] != '\0' ;) ++ if(mdlocaldomain[i] == '/' || mdlocaldomain[i] == ':'){ ++ for(j = strlen(mdlocaldomain); j >= i; j--) ++ mdlocaldomain[j+4] = mdlocaldomain[j]; ++ mdlocaldomain[i++] = '\\'; ++ mdlocaldomain[i++] = '0'; ++ if(mdlocaldomain[i] == '/'){ ++ mdlocaldomain[i++] = '5'; ++ mdlocaldomain[i++] = '7'; ++ } else { ++ mdlocaldomain[i++] = '7'; ++ mdlocaldomain[i++] = '2'; ++ } + } + else -+ strcpy (err, "Can't create mailbox: mailbox already exists"); -+ } -+ else{ -+ if(create_dir) -+ strcpy(err, "Can not create directory. Cread folder instead"); -+ else -+ err[0] = '\0'; -+ } -+ if(err[0]){ -+ mm_log (err,ERROR); -+ return NIL; -+ } -+ } -+ -+ fnlen = strlen(tmp); -+ if ((s = strrchr(mailbox,MDSEPARATOR(courier))) != NULL){ -+ c = *++s; -+ *s = '\0'; -+ if ((stat(tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && -+ !maildir_create_work (mailbox, ++loop)) -+ return NIL; -+ *s = c; -+ } -+ tmp[fnlen] = '\0'; -+ -+ if (mkdir(tmp,0700) && errno != EEXIST) -+ return NIL; -+ -+ if (create_dir) -+ mailbox[fnlen] = '/'; -+ -+ if (create_dir){ -+ if(style == CCLIENT){ -+ if(!courier){ -+ FILE *fp = NULL; -+ snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, MDDIR); -+ if ((fp = fopen(tmp2,"w")) == NULL){ -+ snprintf (err, sizeof(err), "Problem creating %s: %s", tmp2, strerror(errno)); -+ mm_log (err,ERROR); -+ return NIL; -+ } -+ fclose(fp); -+ } -+ } -+ return T; -+ } -+ else -+ return maildir_create_folder(tmp); -+} -+ -+long maildir_create (MAILSTREAM *stream,char *mailbox) -+{ -+ char tmp[MAILTMPLEN], err[MAILTMPLEN]; -+ int rv, create_dir; -+ -+ create_dir = mailbox ? -+ (mailbox[strlen(mailbox) - 1] == -+ MDSEPARATOR(IS_COURIER(mailbox))) : 0; -+ maildir_file_path(mailbox, tmp, sizeof(tmp)); -+ strcpy(tmp, mailbox); -+ rv = maildir_create_work(mailbox, 0); -+ strcpy(mailbox, tmp); -+ if (rv == 0){ -+ snprintf (err, sizeof(err), "Can't create %s %s", -+ (create_dir ? "directory" : "mailbox"), mailbox); -+ mm_log (err,ERROR); -+ } -+ return rv ? LONGT : NIL; -+} -+ -+#define MAXTRY 10000 -+void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) -+{ -+ char oldfile[MAILTMPLEN],newfile[MAILTMPLEN],fn[MAILTMPLEN]; -+ char *s; -+ int ren, try = 0; -+ -+ if (elt->valid){ -+ for (try = 1; try > 0 && try < MAXTRY; try++){ -+ /* build the new filename */ -+ snprintf (oldfile, sizeof(oldfile), "%s/%s",LOCAL->path[Cur], MDFILE(elt)); -+ fn[0] = '\0'; -+ if ((ren = maildir_message_exists(stream, MDFILE(elt), fn)) == 0){ -+ errno = ENOENT; -+ try = MAXTRY; -+ } -+ if (*fn) /* new oldfile! */ -+ snprintf (oldfile,sizeof(oldfile),"%s/%s", LOCAL->path[Cur], fn); -+ if ((s = strrchr (MDFILE(elt), FLAGSEP))) *s = '\0'; -+ snprintf (fn, sizeof(fn), "%s%s%s%s%s%s%s", MDFILE(elt), MDSEP(2), -+ MDFLAG(Draft, elt->draft), MDFLAG(Flagged, elt->flagged), -+ MDFLAG(Replied, elt->answered), MDFLAG(Seen, elt->seen), -+ MDFLAG(Trashed, elt->deleted)); -+ snprintf (newfile, sizeof(newfile), "%s/%s",LOCAL->path[Cur],fn); -+ if (ren != 0 && rename (oldfile,newfile) >= 0) -+ try = -1; -+ } -+ -+ if (try > 0){ -+ snprintf(oldfile, sizeof(oldfile), "Unable to write flags to disk: %s", -+ (errno == ENOENT) ? "message is gone!" : strerror (errno)); -+ mm_log(oldfile,ERROR); -+ return; -+ } -+#ifdef __CYGWIN__ -+ utime(LOCAL->path[Cur], NIL); /* make sure next scan will catch the change */ -+#endif -+ maildir_free_file_only ((void **) &elt->private.spare.ptr); -+ MDFILE(elt) = cpystr (fn); -+ } -+} -+ -+long maildir_expunge (MAILSTREAM *stream, char *sequence, long options) -+{ -+ long ret; -+ MESSAGECACHE *elt; -+ unsigned long i, n = 0L; -+ unsigned long recent = stream->recent; -+ char tmp[MAILTMPLEN]; -+ -+ mm_critical (stream); /* go critical */ -+ ret = sequence ? ((options & EX_UID) ? -+ mail_uid_sequence (stream,sequence) : -+ mail_sequence (stream,sequence)) : LONGT; -+ if(ret == 0L) -+ return 0L; -+ for (i = 1L; i <= stream->nmsgs;){ -+ elt = mail_elt (stream,i); -+ if (elt->deleted && (sequence ? elt->sequence : T)){ -+ snprintf (tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], MDFILE(elt)); -+ if (unlink (tmp) < 0) {/* try to delete the message */ -+ snprintf (tmp, sizeof(tmp), "Expunge of message %ld failed, aborted: %s",i, -+ strerror (errno)); -+ if (!stream->silent) -+ mm_log (tmp,WARN); -+ break; -+ } -+ if (elt->private.spare.ptr) -+ maildir_free_file ((void **) &elt->private.spare.ptr); -+ if (elt->recent) --recent;/* if recent, note one less recent message */ -+ mail_expunged (stream,i); /* notify upper levels */ -+ n++; /* count up one more expunged message */ -+ } -+ else i++; -+ } -+ if(n){ /* output the news if any expunged */ -+ snprintf (tmp, sizeof(tmp), "Expunged %ld messages", n); -+ if (!stream->silent) -+ mm_log (tmp,(long) NIL); -+ } -+ else -+ if (!stream->silent) -+ mm_log ("No messages deleted, so no update needed",(long) NIL); -+ mm_nocritical (stream); /* release critical */ -+ /* notify upper level of new mailbox size */ -+ mail_exists (stream, stream->nmsgs); -+ mail_recent (stream, recent); -+ return ret; -+} -+ -+long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) -+{ -+ STRING st; -+ MESSAGECACHE *elt; -+ unsigned long len; -+ int fd; -+ unsigned long i; -+ struct stat sbuf; -+ char tmp[MAILTMPLEN], flags[MAILTMPLEN], path[MAILTMPLEN], *s; -+ /* copy the messages */ -+ if ((options & CP_UID) ? mail_uid_sequence (stream, sequence) : -+ mail_sequence (stream,sequence)) -+ for (i = 1L; i <= stream->nmsgs; i++) -+ if ((elt = mail_elt (stream,i))->sequence){ -+ MSGPATH(path, LOCAL->dir, MDFILE(elt), MDLOC(elt)); -+ if (((fd = open (path,O_RDONLY,NIL)) < 0) -+ ||((!elt->rfc822_size && -+ ((stat(path, &sbuf) < 0) || !S_ISREG (sbuf.st_mode))))) -+ return NIL; -+ if(!elt->rfc822_size) -+ MDSIZE(elt) = sbuf.st_size; -+ s = (char *) fs_get(MDSIZE(elt) + 1); -+ read (fd,s,MDSIZE(elt)); -+ s[MDSIZE(elt)] = '\0'; -+ close (fd); -+ len = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen, s, MDSIZE(elt)); -+ INIT (&st,mail_string, LOCAL->buf, len); -+ elt->rfc822_size = len; -+ fs_give ((void **)&s); -+ -+ flags[0] = flags[1] = '\0'; -+ if (elt->seen) strcat (flags," \\Seen"); -+ if (elt->draft) strcat (flags," \\Draft"); -+ if (elt->deleted) strcat (flags," \\Deleted"); -+ if (elt->flagged) strcat (flags," \\Flagged"); -+ if (elt->answered) strcat (flags," \\Answered"); -+ flags[0] = '('; /* open list */ -+ strcat (flags,")"); /* close list */ -+ mail_date (tmp,elt); /* generate internal date */ -+ if (!mail_append_full (NIL, mailbox, flags, tmp, &st)) -+ return NIL; -+ if (options & CP_MOVE) elt->deleted = T; -+ } -+ return LONGT; /* return success */ -+} -+ -+long maildir_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) -+{ -+ int fd, k, done, fail; -+ STRING *message; -+ char c,*s, *flags, *date; -+ char tmp[MAILTMPLEN],file[MAILTMPLEN],path1[MAILTMPLEN],path2[MAILTMPLEN]; -+ MESSAGECACHE elt; -+ long i, size = 0L, ret = LONGT, f; -+ unsigned long uf, ti; -+ static unsigned int transact = 0; -+ struct stat sbuf; -+ -+ if (!maildir_valid(mailbox)) { -+ snprintf (tmp, sizeof(tmp), "Not a valid Maildir mailbox: %s", mailbox); -+ mm_log (tmp,ERROR); -+ return NIL; -+ } -+ -+ if (!*mdlocaldomain) -+ md_domain_name(); /* get domain name for maildir files in mdlocaldomain now! */ -+ -+ if (mypid == (pid_t) 0) -+ mypid = getpid(); -+ -+ if (!stream){ -+ stream = &maildirproto; -+ -+ for (k = 0; k < NUSERFLAGS && stream->user_flags[k]; ++k) -+ fs_give ((void **) &stream->user_flags[k]); ++ i++; + } -+ -+ if (!(*af)(stream, data, &flags, &date, &message)) return NIL; -+ -+ mm_critical (stream); /* go critical */ -+ do { -+ fail = done = 0; /* we have not determined name of message file yet */ -+ if (!SIZE (message)) { /* guard against zero-length */ -+ mm_log ("Append of zero-length message", ERROR); -+ ret = NIL; -+ break; -+ } -+ -+ if (date && !mail_parse_date(&elt,date)){ -+ snprintf (tmp, sizeof(tmp), "Bad date in append: %.80s", date); -+ mm_log (tmp, ERROR); -+ ret = NIL; -+ break; -+ } -+ -+ if(date){ -+ struct tm tm; -+ -+ tm.tm_sec = elt.seconds; -+ tm.tm_min = elt.minutes; -+ tm.tm_hour = elt.hours; -+ tm.tm_mday = elt.day; -+ tm.tm_mon = elt.month - 1; -+ tm.tm_year = BASEYEAR + elt.year - 1900; -+ -+ ti = mktime(&tm); -+ } else ti = time(0); -+ -+ f = mail_parse_flags (stream,flags,&uf); -+ do { -+ /* build file name we will use */ -+ snprintf (file, sizeof(file), "%lu.%d_%09u.%s%s%s%s%s%s", -+ ti, mypid, transact++, mdlocaldomain, (f ? MDSEP(2) : ""), -+ MDFLAG(Draft, f&fDRAFT), MDFLAG(Flagged, f&fFLAGGED), -+ MDFLAG(Replied, f&fANSWERED), MDFLAG(Seen, f&fSEEN)); -+ /* build tmp file name */ -+ if (maildir_file_path(mailbox, tmp, sizeof(tmp))) /* copy in TMP */ -+ MSGPATH(path1, tmp, file, Tmp); -+ /* build final filename to use */ -+ if (maildir_file_path(mailbox, tmp, sizeof(tmp))) -+ MSGPATH(path2, tmp, file, New); /* copy in NEW */ -+ if(stat(path1, &sbuf) < 0 && errno == ENOENT -+ && stat(path2, &sbuf) < 0 && errno == ENOENT) -+ done++; -+ else -+ fail++; -+ if(fail == 1000){ -+ snprintf (tmp, sizeof(tmp), "Failure to create append message name"); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ } while (done == 0); -+ -+ if ((fd = open (path1,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) { -+ snprintf (tmp, sizeof(tmp), "Can't open append mailbox: %s", strerror (errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ for (size = 0,i = SIZE (message),s = (char *) fs_get (i + 1); i; --i) -+ if ((c = SNX (message)) != '\015') s[size++] = c; -+ if ((write (fd, s, size) < 0) || fsync (fd)) { -+ unlink (path1); /* delete message */ -+ snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); -+ mm_log (tmp, ERROR); -+ ret = NIL; -+ } -+ fs_give ((void **) &s); /* flush the buffer */ -+ close (fd); /* close the file */ -+ -+ if (rename (path1,path2) < 0) { -+ snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); -+ mm_log (tmp, ERROR); -+ ret = NIL; -+ } -+ unlink (path1); -+ if(date){ -+ time_t tp[2]; -+ tp[0] = tp[1] = ti; -+ utime (path2,tp); -+ } -+ -+ if (ret) -+ if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL; -+ -+ } while (ret && message); /* write the data */ -+ mm_nocritical (stream); /* release critical */ -+ return ret; -+} -+ -+long maildir_delete (MAILSTREAM *stream,char *mailbox) -+{ -+ DIR *dirp; -+ struct direct *d; -+ int i, remove_dir = 0, mddir = 0, rv, error = 0; -+ char tmp[MAILTMPLEN],tmp2[MAILTMPLEN], realname[MAILTMPLEN]; -+ struct stat sbuf; -+ int courier = IS_COURIER(mailbox); -+ -+ if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ -+ remove_dir++; -+ mailbox[strlen(mailbox) -1] = '\0'; -+ } -+ -+ if (!maildir_valid(mailbox)){ -+ maildir_file_path(mailbox, tmp, sizeof(tmp)); -+ if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)){ -+ snprintf(tmp, sizeof(tmp), "Can not remove %s", mailbox); -+ error++; -+ } -+ } -+ -+ if (!error && remove_dir && !maildir_dir_is_empty(mailbox)){ -+ snprintf(tmp, sizeof(tmp), "Can not remove directory %s/: directory not empty", mailbox); -+ error++; -+ } -+ -+ if(error){ -+ mm_log (tmp,ERROR); -+ return NIL; -+ } -+ -+ maildir_close(stream,0); /* even if stream was NULL */ -+ -+ maildir_file_path(mailbox, realname, sizeof(realname)); -+ -+ if (remove_dir){ -+ snprintf(tmp, sizeof(tmp), "%s/%s", realname, MDDIR); -+ if ((rv = stat (tmp,&sbuf)) == 0 && S_ISREG(sbuf.st_mode)) -+ rv = unlink(tmp); -+ else if (errno == ENOENT) -+ rv = 0; -+ if (rv != 0){ -+ snprintf(tmp, sizeof(tmp), "Can not remove %s/%s: %s", tmp2, MDDIR, strerror(errno)); -+ mm_log (tmp,ERROR); -+ return NIL; -+ } -+ if (!maildir_valid(realname) && rmdir(realname) != 0){ -+ snprintf(tmp, sizeof(tmp), "Can not remove %s/: %s", mailbox, strerror(errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ return LONGT; -+ } -+ /* else remove just the folder. Remove all hidden files, except MDDIR */ -+ for (i = Cur; i != EndDir; i++){ -+ MDFLD(tmp, realname, i); -+ -+ if (!(dirp = opendir (tmp))){ -+ snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", mailbox, strerror(errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ -+ while ((d = readdir(dirp)) != NULL){ -+ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..")){ -+ snprintf(tmp2, sizeof(tmp2), "%s/%s", tmp, d->d_name); -+ if (unlink(tmp2) != 0){ -+ snprintf(tmp2, sizeof(tmp2), "Can not remove %s: %s", mailbox, strerror(errno)); -+ mm_log (tmp2, ERROR); -+ return NIL; -+ } -+ } -+ } -+ closedir(dirp); -+ if (rmdir(tmp) != 0){ -+ snprintf(tmp, sizeof(tmp), "Can not remove %s: %s", mailbox, strerror(errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ } -+ /* -+ * ok we have removed all subdirectories of the folder mailbox, Remove the -+ * hidden files. -+ */ -+ -+ if(!(dirp = opendir (realname))){ -+ snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", realname, strerror(errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ -+ while ((d = readdir(dirp)) != NULL){ -+ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") -+ && (!strcmp(d->d_name, MDDIR) -+ || !strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST)) -+ || !strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)))){ -+ if(strcmp(d->d_name, MDDIR) == 0) -+ mddir++; -+ snprintf(tmp, sizeof(tmp), "%s/%s", realname, d->d_name); -+ if (unlink(tmp) != 0) -+ error++; -+ } -+ } -+ closedir(dirp); -+ if (error || -+ (maildir_dir_is_empty(mailbox) && mddir == 0 && rmdir(realname) < 0)){ -+ snprintf(tmp, sizeof(tmp), "Can not remove folder %s: %s", mailbox, strerror(errno)); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ return LONGT; -+} -+ -+long maildir_rename (MAILSTREAM *stream, char *old, char *new) -+{ -+ char tmp[MAILTMPLEN], tmpnew[MAILTMPLEN], realold[MAILTMPLEN]; -+ char realnew[MAILTMPLEN]; -+ int courier = IS_COURIER(old) && IS_COURIER(new); -+ int i; -+ long rv = LONGT; -+ COURIER_S *cdir; -+ -+ if((IS_COURIER(old) || IS_COURIER(new)) && !courier){ -+ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s", old, new); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ -+ if (!maildir_valid(old)){ -+ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s: folder not in maildir format",old); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ maildir_file_path(old, realold, sizeof(realold)); -+ if (!maildir_valid_name(new) && new[0] == '#'){ -+ snprintf (tmp, sizeof(tmp), "Cannot rename mailbox %s: folder not in maildir format", new); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ maildir_file_path(new, realnew, sizeof(realnew)); -+ if (access(tmpnew,F_OK) == 0){ /* new mailbox name must not exist */ -+ snprintf (tmp, sizeof(tmp), "Cannot rename to mailbox %s: destination already exists", new); -+ mm_log (tmp, ERROR); -+ return NIL; -+ } -+ -+ if(!courier){ -+ if (rename(realold, realnew)){ /* try to rename the directory */ -+ snprintf(tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, -+ strerror(errno)); -+ mm_log(tmp,ERROR); -+ return NIL; -+ } -+ return LONGT; /* return success */ -+ } -+ -+ cdir = courier_list_dir(old); -+ for (i = 0; cdir && i < cdir->total; i++){ -+ if(strstr(cdir->data[i]->name, old)){ -+ snprintf(tmp, sizeof(tmp), "%s%s", new, cdir->data[i]->name+strlen(old)); -+ maildir_file_path(cdir->data[i]->name, realold, sizeof(realold)); -+ maildir_file_path(tmp, realnew, sizeof(realnew)); -+ if (rename(realold, realnew)){ -+ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, -+ strerror(errno)); -+ mm_log(tmp,ERROR); -+ rv = NIL; -+ } -+ } -+ } -+ courier_free_cdir(&cdir); -+ return rv; -+} -+ -+long maildir_sub(MAILSTREAM *stream,char *mailbox) -+{ -+ return sm_subscribe(mailbox); -+} -+ -+long maildir_unsub(MAILSTREAM *stream,char *mailbox) -+{ -+ return sm_unsubscribe(mailbox); -+} -+ -+void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat) -+{ -+ void *sdb = NIL; -+ char *s, test[MAILTMPLEN], tmp[MAILTMPLEN]; -+ /* get canonical form of name */ -+ if (maildir_canonicalize (test, ref, pat) && (s = sm_read (tmp, &sdb))) { -+ do if (pmatch_full (s, test, '/')) mm_lsub (stream, '/', s, NIL); -+ while ((s = sm_read (tmp, &sdb)) != NULL); /* until no more subscriptions */ -+ } -+} -+ -+long maildir_canonicalize (char *pattern,char *ref,char *pat) -+{ -+ if (ref && *ref) { /* have a reference */ -+ strcpy (pattern,ref); /* copy reference to pattern */ -+ /* # overrides mailbox field in reference */ -+ if (*pat == '#') strcpy (pattern,pat); -+ /* pattern starts, reference ends, with / */ -+ else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/')) -+ strcat (pattern,pat + 1); /* append, omitting one of the period */ -+ -+ else strcat (pattern,pat); /* anything else is just appended */ -+ } -+ else strcpy (pattern,pat); /* just have basic name */ -+ return maildir_valid_name(pattern) ? LONGT : NIL; -+} -+ -+void maildir_list_work (MAILSTREAM *stream,char *dir,char *pat,long level) -+{ -+ DIR *dp; -+ struct direct *d; -+ struct stat sbuf; -+ char curdir[MAILTMPLEN],name[MAILTMPLEN], tmp[MAILTMPLEN]; -+ char realpat[MAILTMPLEN]; -+ long i; -+ char *maildirpath = mdirpath(); -+ -+ snprintf(curdir, sizeof(curdir), "%s/%s/", myrootdir(pat), dir ? dir : maildirpath); -+ if ((dp = opendir (curdir)) != NULL){ -+ if (dir) snprintf (name, sizeof(name), "%s%s/",MDPREFIX(CCLIENT),dir); -+ else strcpy (name, pat); -+ -+ if (level == 0 && !strpbrk(pat,"%*")){ -+ if(maildir_valid(pat)){ -+ i = maildir_contains_folder(pat, NULL) -+ ? LATT_HASCHILDREN -+ : (maildir_is_dir(pat, NULL) -+ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS); -+ maildir_file_path(pat, realpat, sizeof(realpat)); -+ i += maildir_any_new_msgs(realpat) -+ ? LATT_MARKED : LATT_UNMARKED; -+ mm_list (stream,'/', pat, i); -+ } -+ else -+ if(pat[strlen(pat) - 1] == '/') -+ mm_list (stream,'/', pat, LATT_NOSELECT); -+ } -+ -+ while ((d = readdir (dp)) != NULL) -+ if(strcmp(d->d_name, ".") && strcmp(d->d_name,"..") -+ && strcmp(d->d_name, MDNAME(Cur)) -+ && strcmp(d->d_name, MDNAME(Tmp)) -+ && strcmp(d->d_name, MDNAME(New))){ -+ -+ if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); -+ else strcpy(tmp, d->d_name); -+ -+ if(pmatch_full (tmp, pat,'/')){ -+ snprintf(tmp, sizeof(tmp), "%s/%s/%s", myrootdir(d->d_name), -+ (dir ? dir : maildirpath), d->d_name); -+ if(stat (tmp,&sbuf) == 0 -+ && ((sbuf.st_mode & S_IFMT) == S_IFDIR)){ -+ if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); -+ else strcpy(tmp, d->d_name); -+ i = maildir_valid(tmp) -+ ? (maildir_contains_folder(dir, d->d_name) -+ ? LATT_HASCHILDREN -+ : (maildir_is_dir(dir, d->d_name) -+ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS)) -+ : LATT_NOSELECT; -+ i += maildir_any_new_msgs(tmp) -+ ? LATT_MARKED : LATT_UNMARKED; -+ mm_list (stream,'/',tmp, i); -+ strcat (tmp, "/"); -+ if(dmatch (tmp, pat,'/') && -+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL))){ -+ snprintf(tmp, sizeof(tmp), "%s/%s",dir,d->d_name); -+ maildir_list_work (stream,tmp,pat,level+1); -+ } -+ } -+ } -+ } -+ closedir (dp); -+ } -+} -+ -+void courier_list_work (MAILSTREAM *stream, char *dir, char *pat, long level) -+{ -+ char c, curdir[MAILTMPLEN], tmp[MAILTMPLEN]; -+ char realname[MAILTMPLEN], realpat[MAILTMPLEN] = {'\0'}; -+ int i, found; -+ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL), j; -+ char *maildirpath = mdirpath(); -+ COURIER_S *cdir; -+ -+ if(!strpbrk(pat,"%*")){ /* a mailbox */ -+ maildir_file_path(pat, curdir, sizeof(curdir)); -+ i = strlen(curdir) - 1; -+ if(curdir[i] == '/') -+ curdir[i] = '\0'; -+ cdir = courier_list_dir(curdir); -+ if(cdir){ -+ found = 0; j = 0L; -+ if(maildir_valid_name(pat)){ -+ for(i = 0; !found && i < cdir->total; i++) -+ if(strstr(curdir, cdir->data[i]->name)){ -+ if(strlen(curdir) < strlen(cdir->data[i]->name)) -+ found += 2; -+ else if(strlen(curdir) == strlen(cdir->data[i]->name)) -+ found -= 1; -+ } -+ if(found > 0) -+ j = LATT_HASCHILDREN; -+ else if(found == 0) -+ j = (style == COURIER) ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; -+ } -+ else -+ j = LATT_NOSELECT; -+ j += maildir_any_new_msgs(curdir) ? LATT_MARKED : LATT_UNMARKED; -+ if (found) -+ mm_list (stream, '.', pat, j); -+ courier_free_cdir(&cdir); -+ } -+ return; -+ } -+ -+ strcpy(tmp,pat + 4); /* a directory */ -+ j = strlen(pat) - 1; -+ maildir_file_path(pat, realpat, sizeof(realpat)); -+ c = pat[j]; -+ pat[j] = '\0'; -+ realname[0] = '\0'; -+ if(dir) -+ maildir_file_path(dir, realname, sizeof(realname)); -+ snprintf(curdir, sizeof(curdir), "%s%s%s/%s", (dir ? "" : myrootdir(pat)), (dir ? "" : "/"), -+ (dir ? realname : maildirpath), (dir ? "" : ".")); -+ snprintf(tmp, sizeof(tmp), "%s%s/.", MDPREFIX(COURIER), dir ? dir : maildirpath); -+ if (level == 0 && tmp && pmatch_full (tmp, realpat, '.')) -+ mm_list (stream,'.', tmp, LATT_NOSELECT); -+ -+ cdir = courier_list_dir(pat); -+ pat[j] = c; -+ for (i = 0; cdir && i < cdir->total; i++) -+ if(pmatch_full (cdir->data[i]->name, pat, '.')){ -+ snprintf(tmp, sizeof(tmp), "%s.", cdir->data[i]->name); -+ courier_list_info(&cdir, tmp, i); -+ mm_list (stream,'.',cdir->data[i]->name, cdir->data[i]->attribute); -+ } -+ courier_free_cdir(&cdir); -+} -+ -+int -+same_maildir_file(char *name1, char *name2) -+{ -+ char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN]; -+ char *s; -+ -+ strcpy(tmp1, name1 ? name1 : ""); -+ strcpy(tmp2, name2 ? name2 : ""); -+ if ((s = strrchr(tmp1, FLAGSEP)) != NULL) -+ *s = '\0'; -+ if (((s = strrchr(tmp1, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) -+ *s = '\0'; -+ if ((s = strrchr(tmp2, FLAGSEP)) != NULL) -+ *s = '\0'; -+ if (((s = strrchr(tmp2, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) -+ *s = '\0'; -+ -+ return !strcmp(tmp1, tmp2); -+} -+ -+unsigned long antoul(char *seed) -+{ -+ int i, error = 0; -+ unsigned long val = 0L, rv1 = 0L, t; -+ char c, *p; -+ if(!seed) -+ return 0L; -+ t = strtoul(seed, &p, 10); -+ if(p && (*p == '.' || *p == '_')) -+ return t; -+ /* else */ -+ if((p = strchr(seed,'.')) != NULL) -+ *p = '\0'; -+ error = (strlen(seed) > 6); /* too long */ -+ for(i= strlen(seed)-1; error == 0 && i >= 0; i--){ -+ c = seed[i]; -+ if (c >= 'A' && c <= 'Z') val = c - 'A'; -+ else if (c >= 'a' && c <= 'z') val = c - 'a' + 26; -+ else if (c >= '0' && c <= '9') val = c - '0' + 26 + 26; -+ else if (c == '-') val = c - '-' + 26 + 26 + 10; -+ else if (c == '_') val = c - '_' + 26 + 26 + 10 + 1; -+ else error++; -+ rv1 = val + (rv1 << 6); ++ ++ char * ++ myrootdir(char *name) ++ { ++ return myhomedir(); + } -+ if(p) -+ *p = '.'; -+ return error ? 0L : rv1; -+} -+ -+unsigned long mdfntoul (char *name) -+{ -+ unsigned long t; -+ char *r, last; -+ -+ if((*name == '_') && ((r = strpbrk(name,".,%+")) != NULL)){ /* Grrr!!! */ -+ last = *r; -+ *r = '\0'; -+ t = antoul(r+1); -+ *r = last; -+ } -+ else -+ t = antoul(name); -+ return t; -+} -+ -+int comp_maildir_file(char *name1, char *name2) -+{ -+ int uset1 = 1, uset2 = 1, i, j, cmp; -+ unsigned long t1, t2; -+ char *s1, *s2; -+ -+ if (!(name1 && *name1)) -+ return (name2 && *name2) ? (*name2 == FLAGSEP ? 0 : -1) : 0; -+ -+ if (!(name2 && *name2)) -+ return (name1 && *name1) ? (*name1 == FLAGSEP ? 0 : 1) : 0; -+ -+ if((cmp = strcmp(name1,name2)) == 0) -+ return 0; -+ -+ t1 = strtoul(name1, &s1, 10); -+ t2 = strtoul(name2, &s2, 10); -+ -+ if(!s1 || *s1 != '.') -+ uset1 = 0; -+ -+ if(!s2 || *s2 != '.') -+ uset2 = 0; -+ -+ if(uset1 && uset2) /* normal sort order */ -+ return (t1 < t2) ? -1 : (t1 > t2 ? 1 : (cmp < 0 ? -1 : 1)); -+ -+ /* If we make it here we say Grrrr.... first, then we try to figure out -+ * how to sort this mess. -+ * These are the rules. -+ * If there is a number at the beginning it is bigger than anything else. -+ * If there are digits, then the number of digits decides which one is bigger. -+ */ -+ -+ for(i = 0; isdigit(name1[i]); i++); -+ for(j = 0; isdigit(name2[j]); j++); -+ -+ return(uset1 ? 1 -+ : (uset2 ? -1 -+ : (i < j ? -1 : (i > j ? 1 : (cmp < 0 ? -1 : 1))))); -+} -+ -+void -+maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t) -+{ -+ char tmp[MAILTMPLEN], *b; -+ int offset = 0; -+ int tmpd, tmpf, tmpr, tmps, tmpt; -+ -+ if(d) *d = 0; -+ if(f) *f = 0; -+ if(r) *r = 0; -+ if(s) *s = 0; -+ if(t) *t = 0; -+ -+ tmpd = tmpf = tmpr = tmps = tmpt = NIL; /* no flags set by default */ -+ strcpy(tmp,name); -+ while ((b = strrchr(tmp+offset, FLAGSEP)) != NULL){ -+ char flag,last; -+ int k; -+ if (!++b) break; -+ switch (*b){ -+ case '1': -+ case '2': -+ case '3': flag = *b; b += 2; -+ for (k = 0; b[k] && b[k] != FLAGSEP && b[k] != ','; k++); -+ last = b[k]; -+ b[k] = '\0'; -+ if (flag == '2' || flag == '3'){ -+ tmpd = strchr (b, MDFLAGC(Draft)) ? T : NIL; -+ tmpf = strchr (b, MDFLAGC(Flagged)) ? T : NIL; -+ tmpr = strchr (b, MDFLAGC(Replied)) ? T : NIL; -+ tmps = strchr (b, MDFLAGC(Seen)) ? T : NIL; -+ tmpt = strchr (b, MDFLAGC(Trashed)) ? T : NIL; -+ } -+ b[k] = last; -+ b += k; -+ for (; tmp[offset] && tmp[offset] != FLAGSEP; offset++); -+ offset++; -+ break; -+ default: break; /* Should we crash?... Nahhh */ -+ } -+ } -+ if(d) *d = tmpd; -+ if(f) *f = tmpf; -+ if(r) *r = tmpr; -+ if(s) *s = tmps; -+ if(t) *t = tmpt; -+} -+ -+int -+maildir_message_in_list(char *msgname, struct direct **names, -+ unsigned long bottom, unsigned long top, unsigned long *pos) -+{ -+ unsigned long middle = (bottom + top)/2; -+ int test; -+ -+ if (!msgname) -+ return NIL; -+ -+ if (pos) *pos = middle; -+ -+ if (same_maildir_file(msgname, names[middle]->d_name)) -+ return T; -+ -+ if (middle == bottom){ /* 0 <= 0 < 1 */ -+ int rv = NIL; -+ if (same_maildir_file(msgname, names[middle]->d_name)){ -+ rv = T; -+ if (pos) *pos = middle; -+ } -+ else -+ if (same_maildir_file(msgname, names[top]->d_name)){ -+ rv = T; -+ if (pos) *pos = top; -+ } -+ return rv; -+ } -+ -+ test = comp_maildir_file(msgname, names[middle]->d_name); -+ -+ if (top <= bottom) -+ return test ? NIL : T; -+ -+ if (test < 0 ) /* bottom < msgname < middle */ -+ return maildir_message_in_list(msgname, names, bottom, middle, pos); -+ else if (test > 0) /* middle < msgname < top */ -+ return maildir_message_in_list(msgname, names, middle, top, pos); -+ else return T; -+} -+ -+void -+maildir_abort(MAILSTREAM *stream) -+{ -+ if (LOCAL){ -+ DirNamesType i; -+ -+ if(LOCAL->candouid) -+ maildir_read_uid(stream, NULL, &stream->uid_validity); -+ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir); -+ for (i = Cur; i < EndDir; i++) -+ if(LOCAL->path[i]) fs_give ((void **) &LOCAL->path[i]); -+ fs_give ((void **) &LOCAL->path); -+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); -+ if(LOCAL->uidtempfile){ -+ unlink(LOCAL->uidtempfile); -+ fs_give ((void **) &LOCAL->uidtempfile); -+ } -+ fs_give ((void **) &stream->local); -+ } -+ if (mdfpath) fs_give((void **)&mdfpath); -+ stream->dtb = NIL; -+} -+ -+int -+maildir_contains_folder(char *dirname, char *name) -+{ -+ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN]; -+ int rv = 0; -+ DIR *dir; -+ struct direct *d; -+ -+ maildir_file_path(dirname, tmp2, sizeof(tmp2)); -+ if(name){ -+ strcat(tmp2,"/"); -+ strcat(tmp2, name); -+ } -+ -+ if (!(dir = opendir (tmp2))) -+ return NIL; -+ -+ while ((d = readdir(dir)) != NULL){ -+ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") -+ && strcmp(d->d_name, MDNAME(Cur)) -+ && strcmp(d->d_name, MDNAME(Tmp)) -+ && strcmp(d->d_name, MDNAME(New))){ -+ -+ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); -+ if(maildir_valid(tmp)){ -+ rv++; -+ break; -+ } -+ } -+ } -+ closedir(dir); -+ return rv; -+} -+ -+int -+maildir_is_dir(char *dirname, char *name) -+{ -+ char tmp[MAILTMPLEN]; -+ struct stat sbuf; -+ -+ maildir_file_path(dirname, tmp, sizeof(tmp)); -+ if(name){ -+ strcat(tmp, "/"); -+ strcat(tmp, name); -+ } -+ strcat(tmp, "/"); -+ strcat(tmp, MDDIR); -+ -+ return ((stat(tmp, &sbuf) == 0) && S_ISREG (sbuf.st_mode)) ? 1 : 0; -+} -+ -+int -+maildir_dir_is_empty(char *mailbox) -+{ -+ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], tmp3[MAILTMPLEN],*s; -+ int rv = 1, courier = IS_COURIER(mailbox); -+ DIR *dir; -+ struct direct *d; -+ struct stat sbuf; -+ -+ maildir_file_path(mailbox, tmp2, sizeof(tmp2)); -+ -+ if(courier){ -+ strcpy(tmp3, tmp2); -+ if(s = strrchr(tmp2, '/')) -+ *s = '\0'; -+ } -+ -+ if (!(dir = opendir (tmp2))) -+ return rv; -+ -+ if(courier){ -+ while((d = readdir(dir)) != NULL){ -+ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); -+ if(!strncmp(tmp, tmp3, strlen(tmp3)) -+ && tmp[strlen(tmp3)] == '.'){ -+ rv = 0; -+ break; -+ } -+ } -+ } -+ else -+ while ((d = readdir(dir)) != NULL){ -+ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); -+ if (strcmp(d->d_name, ".") -+ && strcmp(d->d_name,"..") -+ && strcmp(d->d_name, MDNAME(Cur)) -+ && strcmp(d->d_name, MDNAME(Tmp)) -+ && strcmp(d->d_name, MDNAME(New)) -+ && strcmp(d->d_name, MDDIR) -+ && strcmp(d->d_name, MDUIDVALIDITY) -+ && !(d->d_name[0] == '.' -+ && stat (tmp,&sbuf) == 0 -+ && S_ISREG(sbuf.st_mode))){ -+ rv = 0; -+ break; -+ } -+ } -+ closedir(dir); -+ return rv; -+} -+ -+void -+maildir_get_file (MAILDIRFILE **mdfile) -+{ -+ MAILDIRFILE *md; -+ -+ md = (MAILDIRFILE *) fs_get(sizeof(MAILDIRFILE)); -+ memset(md, 0, sizeof(MAILDIRFILE)); -+ *mdfile = md; -+} -+ -+void -+maildir_free_file (void **mdfile) -+{ -+ MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; -+ -+ if (md){ -+ if (md->name) fs_give((void **)&md->name); -+ fs_give((void **)&md); -+ } -+} -+ -+void -+maildir_free_file_only (void **mdfile) -+{ -+ MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; -+ -+ if (md && md->name) -+ fs_give((void **)&md->name); -+} -+ -+int -+maildir_any_new_msgs(char *mailbox) -+{ -+ char tmp[MAILTMPLEN]; -+ int rv = NIL; -+ DIR *dir; -+ struct direct *d; -+ -+ MDFLD(tmp, mailbox, New); -+ -+ if (!(dir = opendir (tmp))) -+ return rv; -+ -+ while ((d = readdir(dir)) != NULL){ -+ if (d->d_name[0] == '.') -+ continue; -+ rv = T; -+ break; -+ } -+ closedir(dir); -+ return rv; -+} -+ -+ -+void -+maildir_get_date(MAILSTREAM *stream, unsigned long msgno) -+{ -+ MESSAGECACHE *elt; -+ struct tm *t; -+ time_t ti; -+ int i,k; -+ -+ elt = mail_elt (stream,msgno); -+ if(elt && elt->year != 0) -+ return; -+ if ((ti = mdfntoul(MDFILE(elt))) > 0L && (t = gmtime(&ti))){ -+ i = t->tm_hour * 60 + t->tm_min; -+ k = t->tm_yday; -+ t = localtime(&ti); -+ i = t->tm_hour * 60 + t->tm_min - i; -+ if((k = t->tm_yday - k) != 0) -+ i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60; -+ k = abs (i); -+ elt->hours = t->tm_hour; -+ elt->minutes = t->tm_min; -+ elt->seconds = t->tm_sec; -+ elt->day = t->tm_mday; elt->month = t->tm_mon + 1; -+ elt->year = t->tm_year - (BASEYEAR - 1900); -+ elt->zoccident = (k == i) ? 0 : 1; -+ elt->zhours = k/60; -+ elt->zminutes = k % 60; -+ } -+} -+ -+/* Support for Courier Style directories -+ When this code is complete there will be two types of support, which -+ will be configurable. The problem is the following: In Courier style -+ folder structure, a "folder" may have a subfolder called -+ "folder.subfolder", which is not natural in the file system in the -+ sense that I can not stat for "folder.subfolder" wihtout knowing what -+ "subfolder" is. It needs to be guessed. Because of this I need to look -+ in the list of folders if there is a folder with a name -+ "folder.subfolder", before I can say if the folder is dual or not. One -+ can avoid this annoyance if one ignores the problem by declaring that -+ every folder is dual. I will however code as the default the more -+ complicated idea of scaning the containing directory each time it is -+ modified and search for subfolders, and list the entries it found. -+ */ -+ -+int courier_dir_select (const struct direct *name) -+{ -+ return name->d_name[0] == '.' && (strlen(name->d_name) > 2 -+ || (strlen(name->d_name) == 2 && name->d_name[1] != '.')); -+} -+ -+int courier_dir_sort (const struct direct **d1, const struct direct **d2) -+{ -+ const struct direct *e1 = *(const struct direct **) d1; -+ const struct direct *e2 = *(const struct direct **) d2; -+ -+ return strcmp((char *) e1->d_name, (char *) e2->d_name); -+} -+ -+void courier_free_cdir (COURIER_S **cdir) -+{ -+ int i; -+ -+ if (!*cdir) -+ return; -+ -+ if ((*cdir)->path) fs_give((void **)&((*cdir)->path)); -+ for (i = 0; i < (*cdir)->total; i++) -+ if((*cdir)->data[i]->name) fs_give((void **)&((*cdir)->data[i]->name)); -+ fs_give((void **)&((*cdir)->data)); -+ fs_give((void **)&(*cdir)); -+} -+ -+COURIER_S *courier_get_cdir (int total) -+{ -+ COURIER_S *cdir; -+ -+ cdir = (COURIER_S *)fs_get(sizeof(COURIER_S)); -+ memset(cdir, 0, sizeof(COURIER_S)); -+ cdir->data = (COURIERLOCAL **) fs_get(total*sizeof(COURIERLOCAL *)); -+ memset(cdir->data, 0, sizeof(COURIERLOCAL *)); -+ cdir->total = total; -+ return cdir; -+} -+ -+int courier_search_list(COURIERLOCAL **data, char *name, int first, int last) -+{ -+ int try = (first + last)/2; -+ -+ if(!strstr(data[try]->name, name)){ -+ if(first == try) /* first == last || first + 1 == last */ -+ return strstr(data[last]->name, name) ? 1 : 0; -+ if(strcmp(data[try]->name, name) < 0) /*data[try] < name < data[end] */ -+ return courier_search_list(data, name, try, last); -+ else /* data[begin] < name < data[try] */ -+ return courier_search_list(data, name, first, try); -+ } -+ return 1; -+} -+ -+/* Lists all directories that are subdirectories of a given directory */ -+ -+COURIER_S *courier_list_dir(char *curdir) -+{ -+ struct direct **names = NIL; -+ struct stat sbuf; -+ unsigned long ndir; -+ COURIER_S *cdir = NULL; -+ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], pathname[MAILTMPLEN], -+ realname[MAILTMPLEN]; -+ int i, j, scand, td; -+ -+ /* There are two cases, either curdir is -+ #mc/INBOX. #mc/INBOX.foo -+ or -+ #mc/Maildir/. #mc/Maildir/.foo -+ */ -+ strcpy(tmp,curdir + 4); -+ if(!strncmp(ucase(tmp), "INBOX", 5)) -+ strcpy(tmp, "#mc/INBOX."); -+ else{ -+ strcpy(tmp, curdir); -+ for (i = strlen(tmp) - 1; tmp[i] && tmp[i] != '/'; i--); -+ tmp[i+2] = '\0'; /* keep the last "." intact */ -+ } -+ maildir_file_path(tmp, realname, sizeof(realname)); -+ maildir_scandir (realname, &names, &ndir, &scand, COURIER); -+ -+ if (scand > 0){ -+ cdir = courier_get_cdir(ndir); -+ cdir->path = cpystr(realname); -+ for(i = 0, j = 0; i < ndir; i++){ -+ td = realname[strlen(realname) - 1] == '.' -+ && *names[i]->d_name == '.'; -+ snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, names[i]->d_name+1); -+ snprintf(pathname, sizeof(pathname), "%s%s", realname, names[i]->d_name + td); -+ if(stat(pathname, &sbuf) == 0 && S_ISDIR(sbuf.st_mode)){ -+ cdir->data[j] = (COURIERLOCAL *) fs_get(sizeof(COURIERLOCAL)); -+ cdir->data[j++]->name = cpystr(tmp2); -+ } -+ fs_give((void **)&names[i]); -+ } -+ cdir->total = j; -+ if(cdir->total == 0) -+ courier_free_cdir(&cdir); -+ } -+ if(names) -+ fs_give((void **) &names); -+ return cdir; -+} -+ -+void -+courier_list_info(COURIER_S **cdirp, char *data, int i) -+{ -+ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL); -+ COURIER_S *cdir = *cdirp; -+ -+ if(maildir_valid(cdir->data[i]->name)){ -+ if(courier_search_list(cdir->data, data, 0, cdir->total - 1)) -+ cdir->data[i]->attribute = LATT_HASCHILDREN; -+ else -+ cdir->data[i]->attribute = (style == COURIER) -+ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; -+ } ++ ++ char * ++ mdirpath(void) ++ { ++ char *path = maildir_parameters(GET_MDINBOXPATH, NIL); ++ return path ? (path[0] ? path : ".") : "Maildir"; ++ } ++ ++ /* remove the "#md/" or "#mc/" part from a folder name ++ * memory freed by caller ++ */ ++ char * ++ maildir_remove_root (char *name) ++ { ++ int courier = IS_COURIER(name), offset; ++ char realname[MAILTMPLEN]; ++ ++ offset = maildir_valid_name(name) ? (name[3] == '/' ? 4 : 3) : 0; ++ if(courier) ++ courier_realname(name+offset, realname); + else -+ cdir->data[i]->attribute = LATT_NOSELECT; -+ cdir->data[i]->attribute += maildir_any_new_msgs(cdir->data[i]->name) -+ ? LATT_MARKED : LATT_UNMARKED; -+} -+ -+/* UID Support */ -+/* Yes, I know I procastinated a lot about this, but here it is finally */ -+ -+/* return code: -+ bigger than zero: this session can assign uids -+ zero: this session will not assign uid -+ smaller than zero: this session temporarily suspends assigning uids -+ */ -+int -+maildir_can_assign_uid (MAILSTREAM *stream) -+{ -+ unsigned int rv = 0; -+ int ownuid, existuid; -+ unsigned long t; -+ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], *p, *s; -+ DIR *dir; -+ struct direct *d; -+ -+ if(!stream || stream->rdonly -+ || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) -+ return 0; -+ -+ if(mypid == (pid_t) 0) -+ mypid = getpid(); -+ -+ snprintf(tmp, sizeof(tmp), "%s.%d", MDUIDTEMP, mypid); -+ -+ ownuid = existuid = 0; -+ s = NULL; -+ while ((d = readdir(dir)) != NULL){ -+ if(strncmp(d->d_name, tmp, strlen(tmp)) == 0){ -+ existuid++; ownuid++; -+ if(ownuid > 1){ -+ snprintf(tmp2, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); -+ unlink(tmp2); -+ if(s){ -+ snprintf(tmp2, sizeof(tmp2), "%s/%s", LOCAL->dir, s); -+ unlink(tmp2); -+ fs_give((void **)&s); -+ } -+ } -+ else -+ s = cpystr(d->d_name); ++ strcpy(realname, name+offset); ++ return cpystr(realname); ++ } ++ ++ ++ /* Check validity of the name, we accept: ++ * a) #md/directory/folder ++ * b) #md/inbox ++ * A few considerations: We can only accept as valid ++ * a) names that start with #md/ and the directory exists or ++ * b) names that do not start with #md/ but are maildir directories (have ++ * the /cur, /tmp and /new structure) ++ */ ++ int maildir_valid_name (char *name) ++ { ++ char tmpname[MAILTMPLEN] = {'\0'}; ++ ++ if (mdfpath) ++ fs_give((void **)&mdfpath); ++ if (name && (name[0] != '#')) ++ snprintf(tmpname, sizeof(tmpname), "%s%s",MDPREFIX(CCLIENT), name); ++ mdfpath = cpystr(tmpname[0] ? tmpname : name); ++ ++ return IS_CCLIENT(name) || IS_COURIER(name); ++ } ++ ++ /* Check if the directory whose path is given by name is a valid maildir ++ * directory (contains /cur, /tmp and /new) ++ */ ++ int maildir_valid_dir (char *name) ++ { ++ int len; ++ DirNamesType i; ++ struct stat sbuf; ++ char tmp[MAILTMPLEN]; ++ ++ if(name[strlen(name) - 1] == '/') ++ name[strlen(name) - 1] = '\0'; ++ len = strlen(name); ++ for (i = Cur; i != EndDir; i++){ ++ MDFLD(tmp, name, i); ++ if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)) ++ break; + } -+ else if(strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)) == 0) -+ existuid++; -+ } -+ -+ closedir(dir); -+ if(s) -+ fs_give((void **)&s); -+ -+ if(ownuid == 1 && existuid == 1) -+ rv = 1; -+ -+ if(ownuid == 0 && existuid == 0){ /* nobody owns the uid? */ -+ FILE *fp; -+ snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); -+ if(fp = fopen(tmp, "w")){ -+ fclose(fp); -+ if(LOCAL->uidtempfile) -+ fs_give((void **)&LOCAL->uidtempfile); -+ LOCAL->uidtempfile = cpystr(tmp); -+ } -+ rv = 1; -+ } -+ -+ if(ownuid == 0 && existuid > 0) /* someone else owns uid assignment */ -+ return 0; -+ -+ /* if we own the uid, check that we do not own it more than once -+ * or that we share ownership. If any of these situations happens, -+ * give up the ownership until we can recover it -+ */ -+ -+ if(ownuid > 0){ -+ if(ownuid > 1) /* impossible, two lock files for the same session */ -+ return (-1)*ownuid; -+ -+ if(ownuid != existuid){ /* lock files for different sessions */ -+ if(LOCAL->uidtempfile){ -+ unlink(LOCAL->uidtempfile); -+ fs_give((void **)&LOCAL->uidtempfile); -+ } -+ return (-1)*ownuid; -+ } -+ } -+ -+ return rv; -+} -+ -+void -+maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, -+ unsigned long *uid_validity) -+{ -+ int createuid, deleteuid = 0; -+ char tmp[MAILTMPLEN], *s = NULL; -+ DIR *dir; -+ struct direct *d; -+ -+ if(uid_last) *uid_last = 0L; -+ if(uid_last && uid_validity) *uid_validity = time(0); -+ if(!stream || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) -+ return; -+ -+ while ((d = readdir(dir)) != NULL){ -+ if(!strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST))) -+ break; -+ } -+ createuid = d == NULL ? 1 : 0; -+ if(uid_last == NULL) -+ deleteuid++; -+ if(d){ -+ if(uid_last){ -+ s = d->d_name + strlen(MDUIDLAST) + 1; -+ *uid_last = strtoul(s, &s, 10); -+ if(!s || *s != '.'){ -+ deleteuid++; -+ createuid++; -+ *uid_last = 0L; -+ } ++ name[len] = '\0'; ++ return (i == EndDir) ? T : NIL; ++ } ++ ++ void courier_realname(char *name, char *realname) ++ { ++ int i,j; ++ ++ if(!name) ++ return; ++ ++ for (i = 0, j = 0; i < MAILTMPLEN && j < strlen(name); j++, i++){ ++ realname[i] = name[j]; ++ if(name[j] == '/' && name[j+1] != '.' && name[j+1] != '%' ++ && name[j+1] != '*') ++ realname[++i] = '.'; ++ } ++ if(realname[i-1] == '.') ++ i--; ++ realname[i] = '\0'; ++ } ++ ++ ++ /* given a maildir folder, return its path. Memory freed by caller. Directory ++ * does not contain the trailing slash "/". On error NULL is returned. ++ */ ++ int maildir_file_path (char *name, char *tmp, size_t sizeoftmp) ++ { ++ char *maildirpath = mdirpath(), *rname; ++ int courier = IS_COURIER(name); ++ ++ /* There are several ways in which the path can come, so we will handle ++ them here. First we deal with #mc/ or #md/ prefix by removing the ++ prefix, if any */ ++ ++ if(strlen(name) >= MAILTMPLEN) ++ name[MAILTMPLEN] = '\0'; ++ strcpy(tmp, name); ++ rname = maildir_remove_root(tmp); ++ tmp[0] = '\0'; /* just in case something fails */ ++ ++ if (strlen(myrootdir(rname)) + ++ max(strlen(rname), strlen(maildirpath)) > sizeoftmp){ ++ errno = ENAMETOOLONG; ++ snprintf(tmp, sizeoftmp, "Error opening \"%s\": %s", rname, strerror (errno)); ++ mm_log(tmp,ERROR); ++ if(rname) fs_give((void **)&rname); ++ return NIL; + } -+ if(s && *s == '.'){ -+ if(uid_validity){ -+ s++; -+ *uid_validity = strtoul(s, &s, 10); -+ if(s && *s != '\0'){ -+ *uid_validity = time(0); -+ deleteuid++; -+ createuid++; -+ } -+ } ++ ++ /* There are two ways in which the name can come here, either as a ++ full path or not. If it is not a full path it can come in two ways, ++ either as a file system path (Maildir/.Drafts) or as a maildir path ++ (INBOX.Drafts) ++ */ ++ ++ if(*rname == '/'){ /* full path */ ++ strncpy(tmp, rname, sizeoftmp); /* do nothing */ ++ tmp[sizeoftmp-1] = '\0'; ++ } ++ else ++ snprintf (tmp, sizeoftmp, "%s/%s%s%s", myrootdir (rname), ++ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) ++ ? rname : maildirpath, ++ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) ++ ? "" : (courier ? "/" : ""), ++ strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) ++ ? "" : (*(rname+5) == MDSEPARATOR(courier) ? rname+5 : "")); ++ if(rname) fs_give((void **)&rname); ++ return tmp[0] ? T : NIL; ++ } ++ ++ /* This function is given a full path for a mailbox and returns ++ * if it is a valid maildir transformed to canonical notation ++ */ ++ int ++ is_valid_maildir (char **name) ++ { ++ if (!strncmp(*name, myrootdir (*name), strlen(myrootdir(*name)))){ ++ (*name) += strlen(myrootdir(*name)); ++ if (**name == '/') (*name)++; ++ } ++ return maildir_valid(*name) ? T : NIL; ++ } ++ ++ /* Check validity of mailbox. This routine does not send errors to log, other ++ * routines calling this one may do so, though ++ */ ++ ++ DRIVER *maildir_valid (char *name) ++ { ++ char tmpname[MAILTMPLEN]; ++ ++ maildir_file_path(name, tmpname, sizeof(tmpname)); ++ ++ return maildir_valid_dir(tmpname) ++ ? (IS_COURIER(name) ? &courierdriver : &maildirdriver) : NIL; ++ } ++ ++ void maildir_fast (MAILSTREAM *stream,char *sequence,long flags) ++ { ++ unsigned long i; ++ MESSAGECACHE *elt; ++ /* get sequence */ ++ if (stream && LOCAL && ((flags & FT_UID) ? ++ mail_uid_sequence (stream,sequence) : ++ mail_sequence (stream,sequence))) ++ for (i = 1L; i <= stream->nmsgs; i++) { ++ if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) && ++ !(elt->day && elt->rfc822_size)) { ++ ENVELOPE **env = NIL; ++ ENVELOPE *e = NIL; ++ if (!stream->scache) env = &elt->private.msg.env; ++ else if (stream->msgno == i) env = &stream->env; ++ else env = &e; ++ if (!*env || !elt->rfc822_size) { ++ STRING bs; ++ unsigned long hs; ++ char *ht = (*stream->dtb->header) (stream,i,&hs,NIL); ++ ++ if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST, ++ stream->dtb->flags); ++ if (!elt->rfc822_size) { ++ (*stream->dtb->text) (stream,i,&bs,FT_PEEK); ++ elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs); ++ } ++ } ++ ++ if (!elt->day && *env && (*env)->date) ++ mail_parse_date (elt,(*env)->date); ++ ++ if (!elt->day) elt->day = elt->month = 1; ++ mail_free_envelope (&e); ++ } ++ } ++ } ++ ++ int ++ maildir_eliminate_duplicate (char *name, struct direct ***flist, unsigned long *nfiles) ++ { ++ int i, j, k, error = 0, scanr; ++ char new[MAILTMPLEN], old[MAILTMPLEN], tmp[MAILTMPLEN], *str; ++ struct direct **names = NIL; ++ ++ if((scanr = maildir_doscandir(name, &names, CCLIENT)) < 0) ++ return -1; ++ ++ if(nfiles) *nfiles = scanr; ++ for(i = 0, j = 1, k = 0; j < scanr; i++, j++){ ++ if(k) ++ names[i] = names[i+k]; ++ if(same_maildir_file(names[i]->d_name, names[j]->d_name)){ ++ int d, f, r, s; ++ maildir_getflag(names[i]->d_name, &d, &f, &r, &s, NIL); ++ snprintf(old, sizeof(old), "%s/%s", name, names[i]->d_name); ++ snprintf(new, sizeof(new), "%s/.%s", name, names[i]->d_name); ++ if(rename(old, new) < 0 && errno != EEXIST) ++ error++; ++ if(!error){ ++ for(; j < scanr ++ && same_maildir_file(names[i]->d_name, names[j]->d_name) ++ ; j++, k++){ ++ maildir_getflag(names[j]->d_name, (d ? NIL : &d), ++ (f ? NIL : &f), (r ? NIL : &r), (s ? NIL : &s), NIL); ++ snprintf(tmp, sizeof(tmp), "%s/%s", name, names[j]->d_name); ++ if(unlink(tmp) < 0){ /* Hmmm... a problem, let's see */ ++ struct stat sbuf; ++ if (stat(tmp, &sbuf) == 0 && (sbuf.st_mode & S_IFMT) == S_IFREG) ++ error++; ++ } ++ } ++ if((str = strrchr(names[i]->d_name,FLAGSEP)) != NULL) *str = '\0'; ++ snprintf (old, sizeof(old), "%s/%s%s%s%s%s%s", name, names[i]->d_name, MDSEP(2), ++ MDFLAG(Draft, d), MDFLAG(Flagged, f), MDFLAG(Replied, r), ++ MDFLAG(Seen, s)); ++ if(rename(new, old) < 0) ++ error++; ++ } ++ } ++ ++ } ++ if(k > 0) ++ fs_give((void **)&names); ++ else ++ *flist = names; ++ return error ? -1 : k; ++ } ++ ++ int ++ maildir_doscandir(char *name, struct direct ***flist, int flag) ++ { ++ return scandir(name, flist, ++ flag == CCLIENT ? maildir_select : courier_dir_select, ++ flag == CCLIENT ? maildir_namesort : courier_dir_sort); ++ } ++ ++ /* ++ * return all files in a given directory. This is a separate call ++ * so that if there are warnings during compilation this only appears once. ++ */ ++ unsigned long ++ maildir_scandir (char *name, struct direct ***flist, ++ unsigned long *nfiles, int *scand, int flag) ++ { ++ struct stat sbuf; ++ int rv = -2; /* impossible value */ ++ ++ if (scand) ++ *scand = -1; /* assume error for safety */ ++ *nfiles = 0; ++ if((stat(name,&sbuf) < 0) ++ || (flag == CCLIENT ++ && ((rv = maildir_eliminate_duplicate(name, flist, nfiles)) < 0))) ++ return 0L; ++ ++ if (scand && (rv > 0 || rv == -2)) ++ *nfiles = maildir_doscandir(name, flist, flag); ++ ++ if(scand) *scand = *nfiles; ++ ++ return (unsigned long) sbuf.st_ctime; ++ } ++ ++ /* Does a message with given name exists (or was it removed)? ++ * Returns: 1 - yes, such message exist, ++ * 0 - No, that message does not exist anymore ++ * ++ * Parameters: stream, name of mailbox, new name if his message does not ++ * exist. ++ */ ++ ++ int maildir_message_exists(MAILSTREAM *stream, char *name, char *newfile) ++ { ++ char tmp[MAILTMPLEN]; ++ int gotit = NIL; ++ DIR *dir; ++ struct direct *d; ++ struct stat sbuf; ++ ++ /* First check directly if it exists, if not there, look for it */ ++ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], name); ++ if ((stat(tmp, &sbuf) == 0) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) ++ return T; ++ ++ if (!(dir = opendir (LOCAL->path[Cur]))) ++ return NIL; ++ ++ while ((d = readdir(dir)) && gotit == NIL){ ++ if (d->d_name[0] == '.') ++ continue; ++ if (same_maildir_file(d->d_name, name)){ ++ gotit = T; ++ strcpy(newfile, d->d_name); ++ } ++ } ++ closedir(dir); ++ return gotit; ++ } ++ ++ /* Maildir open */ ++ ++ MAILSTREAM *maildir_open (MAILSTREAM *stream) ++ { ++ char tmp[MAILTMPLEN]; ++ struct stat sbuf; ++ ++ if (!stream) return &maildirproto; ++ if (stream->local) fatal ("maildir recycle stream"); ++ md_domain_name(); /* get domain name for maildir files in mdlocaldomain */ ++ if(mypid == (pid_t) 0) ++ mypid = getpid(); ++ if (!stream->rdonly){ ++ stream->perm_seen = stream->perm_deleted = stream->perm_flagged = ++ stream->perm_answered = stream->perm_draft = T; ++ } ++ stream->local = (MAILDIRLOCAL *) fs_get (sizeof (MAILDIRLOCAL)); ++ memset(LOCAL, 0, sizeof(MAILDIRLOCAL)); ++ LOCAL->fd = -1; ++ ++ LOCAL->courier = IS_COURIER(stream->mailbox); ++ strcpy(tmp, stream->mailbox); ++ if (maildir_file_path (stream->mailbox, tmp, sizeof(tmp))) ++ LOCAL->dir = cpystr (tmp); ++ LOCAL->candouid = maildir_can_assign_uid(stream); ++ maildir_read_uid(stream, &stream->uid_last, &stream->uid_validity); ++ if (LOCAL->dir){ ++ LOCAL->path = (char **) fs_get(EndDir*sizeof(char *)); ++ MDFLD(tmp, LOCAL->dir, Cur); LOCAL->path[Cur] = cpystr (tmp); ++ MDFLD(tmp, LOCAL->dir, New); LOCAL->path[New] = cpystr (tmp); ++ MDFLD(tmp, LOCAL->dir, Tmp); LOCAL->path[Tmp] = cpystr (tmp); ++ if (stat (LOCAL->path[Cur],&sbuf) < 0) { ++ snprintf (tmp, sizeof(tmp), "Can't open folder %s: %s", ++ stream->mailbox,strerror (errno)); ++ mm_log (tmp,ERROR); ++ maildir_close(stream, 0); ++ return NIL; ++ } ++ } ++ ++ if(maildir_file_path (stream->mailbox, tmp, sizeof(tmp))){ ++ fs_give ((void **) &stream->mailbox); ++ stream->mailbox = cpystr(tmp); ++ } ++ ++ LOCAL->buf = (char *) fs_get (CHUNKSIZE); ++ LOCAL->buflen = CHUNKSIZE - 1; ++ stream->sequence++; ++ stream->nmsgs = stream->recent = 0L; ++ ++ maildir_parse_folder(stream, 1); ++ ++ return stream; ++ } ++ ++ /* Maildir initial parsing of the folder */ ++ void ++ maildir_parse_folder (MAILSTREAM *stream, int full) ++ { ++ char tmp[MAILTMPLEN]; ++ struct direct **namescur = NIL, **namesnew = NIL; ++ unsigned long i, nfilescur = 0L, nfilesnew = 0L, oldpos, newpos, total; ++ int scan_err, rescan, loop = 0; ++ ++ if (!stream) /* what??? */ ++ return; ++ ++ MM_CRITICAL(stream); ++ ++ maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); ++ if (scan_err < 0) ++ maildir_abort(stream); ++ ++ /* Scan old messages first, escoba! */ ++ if(stream->rdonly || ++ (LOCAL && ((maildir_initial_check(stream, Cur) == 0) ++ || nfilesnew > 0L))){ ++ LOCAL->scantime = maildir_scandir (LOCAL->path[Cur], &namescur, &nfilescur, ++ &scan_err, CCLIENT); ++ if (scan_err < 0){ ++ if(namesnew){ ++ for(i = 0L; i < nfilesnew; i++) ++ fs_give((void **)&namesnew[i]); ++ fs_give((void **) &namesnew); ++ } ++ maildir_abort(stream); ++ } ++ } ++ if(LOCAL && (maildir_initial_check(stream, New) == 0) ++ && (nfilescur > 0L)){ ++ while(LOCAL && loop < 10){ ++ if(nfilesnew == 0L) ++ maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); ++ if (scan_err < 0){ ++ if(namesnew){ ++ for(i = 0L; i < nfilesnew; i++) ++ fs_give((void **)&namesnew[i]); ++ fs_give((void **) &namesnew); ++ } ++ maildir_abort(stream); ++ break; ++ } ++ for(i = 0L, rescan = 0, newpos = oldpos = 0L; ++ newpos < nfilescur && i < nfilesnew; i++){ ++ if(maildir_message_in_list(namesnew[i]->d_name, namescur, oldpos, ++ nfilescur - 1L, &newpos)){ ++ oldpos = newpos; ++ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[New], namesnew[i]->d_name); ++ if(unlink(tmp) < 0) ++ scan_err = -1; ++ rescan++; ++ } ++ else ++ newpos = oldpos; ++ } ++ if(scan_err < 0) ++ maildir_abort(stream); ++ if(rescan == 0) ++ break; ++ else{ /* restart */ ++ if(namesnew){ ++ for(i = 0L; i < nfilesnew; i++) ++ fs_give((void **)&namesnew[i]); ++ fs_give((void **) &namesnew); ++ } ++ nfilesnew = 0L; ++ loop++; ++ } ++ } ++ } ++ if(loop == 10) ++ maildir_abort(stream); ++ if(LOCAL){ ++ if(stream->rdonly) ++ stream->recent = 0L; ++ total = namescur || stream->rdonly ++ ? maildir_parse_dir(stream, 0L, Cur, namescur, ++ nfilescur, full) : stream->nmsgs; ++ stream->nmsgs = maildir_parse_dir(stream, total, New, namesnew, ++ nfilesnew, full); ++ } ++ if(namesnew){ ++ for(i = 0L; i < nfilesnew; i++) ++ fs_give((void **)&namesnew[i]); ++ fs_give((void **) &namesnew); ++ } ++ if(namescur){ ++ for(i = 0L; i < nfilescur; i++) ++ fs_give((void **)&namescur[i]); ++ fs_give((void **) &namescur); ++ } ++ MM_NOCRITICAL(stream); ++ } ++ ++ int ++ maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype) ++ { ++ char *tmp; ++ struct stat sbuf; ++ ++ if (access (LOCAL->path[dirtype], R_OK|W_OK|X_OK) != 0){ ++ maildir_abort(stream); ++ return -1; ++ } ++ ++ if (dirtype != New && ++ (stat(LOCAL->path[Cur], &sbuf) < 0 || sbuf.st_ctime == LOCAL->scantime)) ++ return -1; ++ return 0; ++ } ++ ++ ++ /* Return the number of messages in the directory, while filling the ++ * elt structure. ++ */ ++ ++ unsigned long ++ maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, ++ DirNamesType dirtype, struct direct **names, ++ unsigned long nfiles, int full) ++ { ++ char tmp[MAILTMPLEN], file[MAILTMPLEN], newfile[MAILTMPLEN], *mdstr; ++ struct stat sbuf; ++ unsigned long i, new = 0L, l, uid_last; ++ unsigned long recent = stream ? stream->recent : 0L; ++ int d = 0, f = 0, r = 0, s = 0, t = 0; ++ int we_compute, in_list; ++ int silent = stream ? stream->silent : NIL; ++ MESSAGECACHE *elt; ++ ++ if (dirtype == Cur && !stream->rdonly) ++ for (i = 1L; i <= stream->nmsgs;){ ++ elt = mail_elt(stream, i); ++ in_list = elt && elt->private.spare.ptr && nfiles > 0L ++ ? (MDPOS(elt) < nfiles ++ ? same_maildir_file(MDFILE(elt), names[MDPOS(elt)]->d_name) ++ : NIL) ++ || maildir_message_in_list(MDFILE(elt), names, 0L, ++ nfiles - 1L, &MDPOS(elt)) ++ : NIL; ++ if (!in_list){ ++ if (elt->private.spare.ptr) ++ maildir_free_file ((void **) &elt->private.spare.ptr); ++ ++ if (elt->recent) --recent; ++ mail_expunged(stream,i); ++ } ++ else i++; ++ } ++ ++ stream->silent = T; ++ uid_last = 0L; ++ for (we_compute = 0, i = l = 1L; l <= nfiles; l++){ ++ unsigned long pos, uid; ++ if (dirtype == New && !stream->rdonly){ /* move new messages to cur */ ++ pos = l - 1L; ++ snprintf (file, sizeof(file), "%s/%s", LOCAL->path[New], names[pos]->d_name); ++ if(lstat(file,&sbuf) == 0) ++ switch(sbuf.st_mode & S_IFMT){ ++ case S_IFREG: ++ strcpy(tmp, names[pos]->d_name); ++ if((mdstr = strstr(tmp,MDSEP(3))) ++ || (mdstr = strstr(tmp,MDSEP(2)))) ++ *(mdstr+1) = '2'; ++ else ++ strcat(tmp, MDSEP(2)); ++ snprintf(newfile, sizeof(newfile), "%s/%s", LOCAL->path[Cur], tmp); ++ if(rename (file, newfile) != 0){ ++ mm_log("Unable to read new mail!", WARN); ++ continue; ++ } ++ unlink (file); ++ new++; ++ break; ++ case S_IFLNK: /* clean up, clean up, everybody, everywhere */ ++ if(unlink(file) < 0){ ++ if(LOCAL->link == NIL){ ++ mm_log("Unable to remove symbolic link", WARN); ++ LOCAL->link = T; ++ } ++ } ++ continue; ++ break; ++ default: ++ if(LOCAL && LOCAL->link == NIL){ ++ mm_log("Unrecognized file or link in folder", WARN); ++ LOCAL->link = T; ++ } ++ continue; ++ break; ++ } ++ } ++ mail_exists(stream, i + nmsgs); ++ elt = mail_elt(stream, i + nmsgs); ++ pos = (elt && elt->private.spare.ptr) ? MDPOS(elt) : l - 1L; ++ if (dirtype == New) elt->recent = T; ++ maildir_getflag(names[pos]->d_name, &d, &f, &r ,&s, &t); ++ if (elt->private.spare.ptr) ++ maildir_free_file_only ((void **)&elt->private.spare.ptr); ++ else{ ++ maildir_get_file((MAILDIRFILE **)&elt->private.spare.ptr); ++ we_compute++; ++ } ++ MDFILE(elt) = cpystr(names[pos]->d_name); ++ MDPOS(elt) = pos; ++ MDLOC(elt) = dirtype; ++ if (dirtype == Cur){ /* deal with UIDs */ ++ if(elt->private.uid == 0L) ++ elt->private.uid = maildir_get_uid(MDFILE(elt)); ++ if(elt->private.uid <= uid_last){ ++ uid = (we_compute ? uid_last : stream->uid_last) + 1L; ++ if(LOCAL->candouid) ++ maildir_assign_uid(stream, i + nmsgs, uid); ++ else ++ elt->private.uid = uid; ++ } ++ else ++ uid = elt->private.uid; ++ uid_last = uid; ++ if(uid_last > stream->uid_last) ++ stream->uid_last = uid_last; ++ } ++ if(dirtype == New && !stream->rdonly){ ++ maildir_free_file_only((void **)&elt->private.spare.ptr); ++ MDFILE(elt) = cpystr(tmp); ++ MDSIZE(elt) = sbuf.st_size; ++ MDMTIME(elt) = sbuf.st_mtime; ++ MDLOC(elt) = Cur; ++ } ++ if (elt->draft != d || elt->flagged != f || ++ elt->answered != r || elt->seen != s || elt->deleted != t){ ++ elt->draft = d; elt->flagged = f; elt->answered = r; ++ elt->seen = s; elt->deleted = t; ++ if (!we_compute && !stream->rdonly) ++ MM_FLAGS(stream, i+nmsgs); ++ } ++ maildir_get_date(stream, i+nmsgs); ++ elt->valid = T; ++ i++; ++ } ++ stream->silent = silent; ++ if(LOCAL->candouid && dirtype == Cur) ++ maildir_read_uid(stream, NULL, &stream->uid_validity); ++ if (dirtype == New && stream->rdonly) ++ new = nfiles; ++ mail_exists(stream, nmsgs + ((dirtype == New) ? new : nfiles)); ++ mail_recent(stream, recent + ((dirtype == New) ? new : 0L)); ++ ++ return (nmsgs + (dirtype == New ? new : nfiles)); ++ } ++ ++ long maildir_ping (MAILSTREAM *stream) ++ { ++ maildir_parse_folder(stream, 0); ++ if(stream && LOCAL){ ++ if(LOCAL->candouid < 0) ++ LOCAL->candouid++; ++ else if(LOCAL->candouid) ++ maildir_uid_renew_tempfile(stream); ++ else /* try again to get uids */ ++ LOCAL->candouid = maildir_can_assign_uid(stream); ++ } ++ return stream && LOCAL ? LONGT : NIL; ++ } ++ ++ int maildir_select (const struct direct *name) ++ { ++ return (name->d_name[0] != '.'); ++ } ++ ++ /* ++ * Unfortunately, there is no way to sort by arrival in this driver, this ++ * means that opening a folder in this driver using the scandir function ++ * will always make this driver slower than any driver that has a natural ++ * way of sorting by arrival (like a flat file format, "mbox", "mbx", etc). ++ */ ++ int maildir_namesort (const struct direct **d1, const struct direct **d2) ++ { ++ const struct direct *e1 = *(const struct direct **) d1; ++ const struct direct *e2 = *(const struct direct **) d2; ++ ++ return comp_maildir_file((char *) e1->d_name, (char *) e2->d_name); ++ } ++ ++ /* Maildir close */ ++ ++ void maildir_close (MAILSTREAM *stream, long options) ++ { ++ MESSAGECACHE *elt; ++ unsigned long i; ++ int silent = stream ? stream->silent : 0; ++ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); ++ ++ if (!stream) return; ++ ++ for (i = 1L; i <= stream->nmsgs; i++) ++ if((elt = (MESSAGECACHE *) (*mc)(stream,i,CH_ELT)) && elt->private.spare.ptr) ++ maildir_free_file ((void **) &elt->private.spare.ptr); ++ stream->silent = T; ++ if (options & CL_EXPUNGE) maildir_expunge (stream, NIL, NIL); ++ maildir_abort(stream); ++ if (mdfpath) fs_give((void **)&mdfpath); ++ if (mypid) mypid = (pid_t) 0; ++ stream->silent = silent; ++ } ++ ++ void maildir_check (MAILSTREAM *stream) ++ { ++ if (maildir_ping (stream)) mm_log ("Check completed",(long) NIL); ++ } ++ ++ long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs, long flags) ++ { ++ char tmp[MAILTMPLEN]; ++ unsigned long i; ++ MESSAGECACHE *elt; ++ char *s; ++ /* UID call "impossible" */ ++ if (flags & FT_UID || !LOCAL) return NIL; ++ elt = mail_elt (stream, msgno); ++ ++ if (!(flags & FT_PEEK) && !elt->seen){ ++ elt->seen = T; ++ maildir_flagmsg (stream, elt); ++ MM_FLAGS(stream, elt->msgno); ++ } ++ ++ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); ++ if (LOCAL->fd < 0) /* if file closed ? */ ++ LOCAL->fd = open(tmp,O_RDONLY,NIL); ++ ++ if (LOCAL->fd < 0 && (errno == EACCES || errno == ENOENT)){ ++ INIT (bs, mail_string, "", 0); ++ elt->rfc822_size = 0L; ++ return NIL; ++ } ++ ++ s = maildir_text_work(stream, elt, &i, flags); ++ INIT (bs, mail_string, s, i); ++ return LONGT; ++ } ++ ++ char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, ++ unsigned long *length,long flags) ++ { ++ FDDATA d; ++ STRING bs; ++ char *s,tmp[CHUNK]; ++ unsigned long msgno = elt->msgno; ++ static int try = 0; ++ ++ if (length) ++ *length = 0L; ++ LOCAL->buf[0] = '\0'; ++ ++ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); ++ if (LOCAL->fd < 0) /* if file closed ? */ ++ LOCAL->fd = open(tmp,O_RDONLY,NIL); ++ ++ if (LOCAL->fd < 0){ /* flag change? */ ++ if (try < 5){ ++ try++; ++ if (maildir_update_elt_maildirp(stream, msgno) > 0) ++ try = 0; ++ return maildir_text_work(stream, mail_elt(stream, msgno),length, flags); ++ } ++ try = 0; ++ return NULL; ++ } ++ ++ lseek (LOCAL->fd, elt->private.msg.text.offset,L_SET); ++ ++ if (flags & FT_INTERNAL) { /* initial data OK? */ ++ if (elt->private.msg.text.text.size > LOCAL->buflen) { ++ fs_give ((void **) &LOCAL->buf); ++ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ++ elt->private.msg.text.text.size) + 1); ++ } ++ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size); ++ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0'; ++ } ++ else { ++ if (elt->rfc822_size > LOCAL->buflen) { ++ fs_give ((void **) &LOCAL->buf); ++ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1); ++ } ++ d.fd = LOCAL->fd; /* yes, set up file descriptor */ ++ d.pos = elt->private.msg.text.offset; ++ d.chunk = tmp; /* initial buffer chunk */ ++ d.chunksize = CHUNK; ++ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size); ++ for (s = LOCAL->buf; SIZE (&bs);) switch (CHR (&bs)) { ++ case '\r': /* carriage return seen */ ++ *s++ = SNX (&bs); /* copy it and any succeeding LF */ ++ if (SIZE (&bs) && (CHR (&bs) == '\n')) *s++ = SNX (&bs); ++ break; ++ case '\n': ++ *s++ = '\r'; /* insert a CR */ ++ default: ++ *s++ = SNX (&bs); /* copy characters */ ++ } ++ *s = '\0'; /* tie off buffer */ ++ *length = s - (char *) LOCAL->buf; /* calculate length */ ++ } ++ close(LOCAL->fd); LOCAL->fd = -1; ++ return LOCAL->buf; ++ } ++ ++ /* maildir parse, fill the elt structure... well not all of it... */ ++ unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, ++ DirNamesType dirtype) ++ { ++ char *b, *s, *t, c; ++ char tmp[MAILTMPLEN]; ++ struct stat sbuf; ++ unsigned long i, len; ++ int d, f, r, se, dt; ++ MESSAGECACHE *elt; ++ ++ elt = mail_elt (stream,msgno); ++ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), dirtype); ++ if(stat(tmp, &sbuf) == 0) ++ MDSIZE(elt) = sbuf.st_size; ++ ++ maildir_get_date(stream, msgno); ++ maildir_getflag(MDFILE(elt), &d, &f, &r ,&se, &dt); ++ elt->draft = d; elt->flagged = f; elt->answered = r; elt->seen = se; ++ elt->deleted = dt; elt->valid = T; ++ if (LOCAL->fd < 0) /* if file closed ? */ ++ LOCAL->fd = open(tmp,O_RDONLY,NIL); ++ ++ if (LOCAL->fd >= 0){ ++ s = (char *) fs_get (MDSIZE(elt) + 1); ++ read (LOCAL->fd,s,MDSIZE(elt)); ++ s[MDSIZE(elt)] = '\0'; ++ t = s + strlen(s); /* make t point to the end of s */ ++ for (i = 0L, b = s; b < t && !(i && (*b == '\n')); i = (*b++ == '\n')); ++ len = (*b ? ++b : b) - s; ++ elt->private.msg.header.text.size = ++ elt->private.msg.text.offset = len; ++ elt->private.msg.text.text.size = MDSIZE(elt) - len; ++ for (i = 0L, b = s, c = *b; b && ++ ((c < '\016' && ((c == '\012' && ++i) ++ ||(c == '\015' && *(b+1) == '\012' && ++b && (i +=2)))) ++ || b < t); i++, c= *++b); ++ elt->rfc822_size = i; ++ fs_give ((void **) &s); ++ close(LOCAL->fd); LOCAL->fd = -1; ++ } ++ return elt->rfc822_size; ++ } ++ ++ int ++ maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno) ++ { ++ struct direct **names = NIL; ++ unsigned long i, nfiles, pos; ++ int d = 0, f = 0 , r = 0, s = 0, t = 0, in_list, scan_err; ++ MESSAGECACHE *elt; ++ ++ maildir_scandir (LOCAL->path[Cur], &names, &nfiles, &scan_err, CCLIENT); ++ ++ elt = mail_elt (stream,msgno); ++ ++ in_list = nfiles > 0L ++ ? maildir_message_in_list(MDFILE(elt), names, 0L, nfiles - 1L, &pos) ++ : NIL; ++ ++ if (in_list && pos >= 0L && pos < nfiles ++ && !strcmp(MDFILE(elt), names[pos]->d_name)){ ++ in_list = NIL; ++ maildir_abort(stream); ++ } ++ ++ if (in_list && pos >= 0L && pos < nfiles){ ++ maildir_free_file_only((void **)&elt->private.spare.ptr); ++ MDFILE(elt) = cpystr(names[pos]->d_name); ++ maildir_getflag(MDFILE(elt), &d, &f, &r ,&s, &t); ++ if (elt->draft != d || elt->flagged != f || ++ elt->answered != r || elt->seen != s || elt->deleted != t){ ++ elt->draft = d; elt->flagged = f; elt->answered = r; ++ elt->seen = s; elt->deleted = t; ++ MM_FLAGS(stream, msgno); ++ } ++ } ++ for (i = 0L; i < nfiles; i++) ++ fs_give((void **) &names[i]); ++ if (names) ++ fs_give((void **) &names); ++ return in_list ? 1 : -1; ++ } ++ ++ /* Maildir fetch message header */ ++ ++ char *maildir_header (MAILSTREAM *stream,unsigned long msgno, ++ unsigned long *length, long flags) ++ { ++ char tmp[MAILTMPLEN], *s; ++ MESSAGECACHE *elt; ++ static int try = 0; ++ ++ if (length) *length = 0; ++ if (flags & FT_UID || !LOCAL) return ""; /* UID call "impossible" */ ++ elt = mail_elt (stream,msgno); ++ if(elt->private.msg.header.text.size == 0) ++ maildir_parse_message(stream, msgno, MDLOC(elt)); ++ ++ MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); ++ if (LOCAL->fd < 0) ++ LOCAL->fd = open (tmp,O_RDONLY,NIL); ++ ++ if (LOCAL->fd < 0 && errno == EACCES){ ++ mm_log ("Message exists but can not be read. Envelope and body lost!",ERROR); ++ return NULL; ++ } ++ ++ if (LOCAL->fd < 0){ /* flag change? */ ++ if (try < 5){ ++ try++; ++ if (maildir_update_elt_maildirp(stream, msgno) > 0) ++ try = 0; ++ return maildir_header(stream, msgno, length, flags); ++ } ++ try = 0; ++ return NULL; ++ } ++ ++ if (flags & FT_INTERNAL){ ++ if(elt->private.msg.header.text.size > LOCAL->buflen){ ++ fs_give ((void **) &LOCAL->buf); ++ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ++ elt->private.msg.header.text.size) + 1); ++ } ++ read (LOCAL->fd, (void *)LOCAL->buf, elt->private.msg.header.text.size); ++ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0'; ++ } ++ else{ ++ s = (char *) fs_get(elt->private.msg.header.text.size+1); ++ read (LOCAL->fd, (void *)s, elt->private.msg.header.text.size); ++ s[elt->private.msg.header.text.size] = '\0'; ++ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s, ++ elt->private.msg.header.text.size); ++ fs_give ((void **) &s); ++ } ++ elt->private.msg.text.offset = elt->private.msg.header.text.size; ++ elt->private.msg.text.text.size = MDSIZE(elt) - elt->private.msg.text.offset; ++ close(LOCAL->fd); LOCAL->fd = -1; ++ return LOCAL->buf; ++ } ++ ++ /* Maildir find list of subscribed mailboxes ++ * Accepts: mail stream ++ * pattern to search ++ */ ++ ++ void maildir_list (MAILSTREAM *stream,char *ref, char *pat) ++ { ++ char *s,test[MAILTMPLEN],file[MAILTMPLEN]; ++ long i = 0L; ++ ++ if((!pat || !*pat) && maildir_canonicalize (test,ref,"*") ++ && maildir_valid_name(test)){ /* there is a #md/ leading here */ ++ for (i = 3L; test[i] && test[i] != '/'; i++); ++ if ((s = strchr (test+i+1,'/')) != NULL) *++s = '\0'; ++ else test[0] = '\0'; ++ mm_list (stream,'/',test, LATT_NOSELECT); ++ } ++ else if (maildir_canonicalize (test,ref,pat)) { ++ if (test[3] == '/') { /* looking down levels? */ ++ /* yes, found any wildcards? */ ++ if ((s = strpbrk (test,"%*")) != NULL){ ++ /* yes, copy name up to that point */ ++ strncpy (file,test+4,i = s - (test+4)); ++ file[i] = '\0'; /* tie off */ ++ } ++ else strcpy (file,test+4);/* use just that name then */ ++ /* find directory name */ ++ if ((s = strrchr (file, '/')) != NULL){ ++ *s = '\0'; /* found, tie off at that point */ ++ s = file; ++ } ++ /* do the work */ ++ if(IS_COURIER(test)) ++ courier_list_work (stream,s,test,0); ++ else ++ maildir_list_work (stream,s,test,0); ++ } ++ /* always an INBOX */ ++ if (!compare_cstring (test,"#MD/INBOX")) ++ mm_list (stream,NIL,"#MD/INBOX",LATT_NOINFERIORS); ++ if (!compare_cstring (test,"#MC/INBOX")) ++ mm_list (stream,NIL,"#MC/INBOX",LATT_NOINFERIORS); ++ } ++ } ++ ++ void courier_list (MAILSTREAM *stream,char *ref, char *pat) ++ { ++ /* I am too lazy to do anything. Do you care to ask maildir list, please? ++ The real reason why this is a dummy function is because we do not want to ++ see the same folder listed twice. ++ */ ++ } ++ ++ /* For those that want to hide things, we give them a chance to do so */ ++ void *maildir_parameters (long function, void *value) ++ { ++ void *ret = NIL; ++ switch ((int) function) { ++ case SET_MDINBOXPATH: ++ if(strlen((char *) value ) > 49) ++ strcpy(myMdInboxDir, "Maildir"); ++ else ++ strcpy(myMdInboxDir, (char *) value); ++ case GET_MDINBOXPATH: ++ if (myMdInboxDir[0] == '\0') strcpy(myMdInboxDir,"Maildir"); ++ ret = (void *) myMdInboxDir; ++ break; ++ case SET_COURIERSTYLE: ++ CourierStyle = (long) value; ++ case GET_COURIERSTYLE: ++ ret = (void *) CourierStyle; ++ break; ++ case GET_DIRFMTTEST: ++ ret = (void *) maildir_dirfmttest; ++ break; ++ default: ++ break; ++ } ++ return ret; ++ } ++ ++ int maildir_create_folder(char *mailbox) ++ { ++ char tmp[MAILTMPLEN], err[MAILTMPLEN]; ++ DirNamesType i; ++ ++ for (i = Cur; i != EndDir; i++){ ++ MDFLD(tmp, mailbox, i); ++ if (mkdir(tmp, 0700) && errno != EEXIST){ /* try to make new dir */ ++ snprintf (err, sizeof(err), "Can't create %s: %s", tmp, strerror(errno)); ++ mm_log (err,ERROR); ++ return NIL; ++ } ++ } ++ return T; ++ } ++ ++ int maildir_create_work(char *mailbox, int loop) ++ { ++ char *s, c, err[MAILTMPLEN], tmp[MAILTMPLEN], tmp2[MAILTMPLEN], mbx[MAILTMPLEN]; ++ int fnlen, create_dir = 0, courier, mv; ++ struct stat sbuf; ++ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL); ++ ++ courier = IS_COURIER(mailbox); ++ strcpy(mbx, mailbox); ++ mv = maildir_valid(mbx) ? 1 : 0; ++ maildir_file_path(mailbox, tmp, sizeof(tmp)); ++ if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ ++ create_dir++; ++ mailbox[strlen(mailbox) - 1] = '\0'; ++ } ++ ++ if(!loop && courier){ ++ if(mv){ ++ if(create_dir){ ++ if(style == CCLIENT) ++ strcpy (err,"Can not create directory: folder exists. Create subfolder"); ++ else ++ strcpy(err,"Folder and Directory already exist"); ++ } ++ else ++ strcpy (err, "Can't create mailbox: mailbox already exists"); + } + else{ -+ deleteuid++; -+ createuid++; ++ if(create_dir) ++ strcpy(err, "Can not create directory. Cread folder instead"); ++ else ++ err[0] = '\0'; + } -+ } -+ if(deleteuid){ -+ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); -+ unlink(tmp); -+ } -+ if(createuid) -+ maildir_write_uid(stream, (uid_last ? *uid_last : stream->uid_last), -+ uid_validity ? *uid_validity : time(0)); -+ closedir(dir); -+} -+ -+void -+maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, -+ unsigned long uid_validity) -+{ -+ char tmp[MAILTMPLEN]; -+ FILE *fp; -+ -+ if(!stream || stream->rdonly || !LOCAL || !LOCAL->dir) -+ return; -+ -+ snprintf(tmp, sizeof(tmp), "%s/%s.%010lu.%010lu", LOCAL->dir, MDUIDLAST, -+ uid_last, uid_validity); -+ if(fp = fopen(tmp, "w")) -+ fclose(fp); -+} -+ -+unsigned long -+maildir_get_uid(char *name) -+{ -+ char *s; -+ unsigned long rv = 0L; -+ -+ if(!name || (s = strstr(name,MDUIDSEP)) == NULL) -+ return rv; -+ -+ s += strlen(MDUIDSEP); -+ rv = strtoul(s, NULL, 10); -+ return rv; -+} -+ -+ -+void -+maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno) -+{ -+ char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; -+ MESSAGECACHE *elt; -+ -+ elt = mail_elt(stream, msgno); -+ if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) -+ return; -+ -+ snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); -+ t = MDFILE(elt); -+ if(s = strstr(MDFILE(elt), MDUIDSEP)){ -+ *s = '\0'; -+ s += strlen(MDUIDSEP); -+ strtoul(s, &s, 10); -+ snprintf(new, sizeof(new), "%s/%s/%s%s", LOCAL->dir, MDNAME(Cur), t, s); -+ if(rename(old, new) == 0){ -+ maildir_free_file_only ((void **)&elt->private.spare.ptr); -+ s = strrchr(new, '/'); -+ MDFILE(elt) = cpystr(s+1); ++ if(err[0]){ ++ mm_log (err,ERROR); ++ return NIL; + } -+ elt->private.uid = 0L; -+ } -+} -+ -+void -+maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid) -+{ -+ int createuid, deleteuid = 0; -+ char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; -+ MESSAGECACHE *elt; -+ -+ elt = mail_elt(stream, msgno); -+ if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) -+ return; -+ -+ maildir_delete_uid(stream, msgno); -+ snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); -+ t = MDFILE(elt); -+ if((s = strrchr(MDFILE(elt),FLAGSEP)) != NULL){ -+ *s++ = '\0'; -+ snprintf(new, sizeof(new), "%s/%s/%s%s%lu%c%s", -+ LOCAL->dir, MDNAME(Cur), t, MDUIDSEP, uid, FLAGSEP, s); -+ if(rename(old, new) == 0){ -+ maildir_free_file_only ((void **)&elt->private.spare.ptr); -+ s = strrchr(new, '/'); -+ MDFILE(elt) = cpystr(s+1); -+ stream->uid_validity = time(0); -+ } -+ elt->private.uid = uid; -+ } -+} -+ -+void -+maildir_uid_renew_tempfile(MAILSTREAM *stream) -+{ -+ char tmp[MAILTMPLEN]; -+ -+ if(!stream || stream->rdonly -+ || !LOCAL || !LOCAL->candouid || !LOCAL->dir || !LOCAL->uidtempfile) -+ return; -+ -+ if(mypid == (pid_t) 0) -+ mypid = getpid(); -+ -+ snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); -+ if(rename(LOCAL->uidtempfile, tmp) == 0){ -+ fs_give((void **)&LOCAL->uidtempfile); -+ LOCAL->uidtempfile = cpystr(tmp); -+ } -+} -Index: alpine-2.20/imap/src/osdep/unix/maildir.h -=================================================================== ---- /dev/null -+++ alpine-2.20/imap/src/osdep/unix/maildir.h -@@ -0,0 +1,226 @@ -+/* -+ * A few definitions that try to make this module portable to other -+ * platforms (e.g. Cygwin). This module is based on the information from -+ * http://cr.yp.to/proto/maildir.html -+ */ -+ -+/* First we deal with the separator character */ -+#ifndef FLAGSEP -+#define FLAGSEP ':' -+#endif -+#define SIZESEP ',' -+ -+const char sep1[] = {FLAGSEP, '1', ',', '\0'}; /* experimental semantics*/ -+const char sep2[] = {FLAGSEP, '2', ',', '\0'}; /* Flags Information */ -+const char sep3[] = {FLAGSEP, '3', ',', '\0'}; /* Grrrr.... */ -+ -+const char *sep[] = { sep1, sep2, sep3, NULL}; -+ -+#define MDSEP(i) sep[((i) - 1)] -+ -+/* Now we deal with flags. Woohoo! */ -+typedef enum {Draft, Flagged, Passed, Replied, Seen, Trashed, -+ EmptyFlag, EndFlags} MdFlagNamesType; -+const int mdimapflags[] = {Draft, Flagged, Replied, Seen, Trashed, EmptyFlag, EndFlags}; -+const int mdkwdflags[] = {Passed, EmptyFlag, EndFlags}; -+ -+/* this array lists the codes for mdflgnms (maildir flag names) above */ -+const char *mdflags[] = { "D", "F", "P", "R", "S", "T", "", NULL}; -+/* and as characters too */ -+const char cmdflags[] = { 'D', 'F', 'P', 'R', 'S', 'T', '0', '\0'}; -+ -+/* MDFLAG(Seen, elt->seen) */ -+#define MDFLAG(i,j) mdflags[j ? (i) : EmptyFlag] -+/* MDFLAGC(Seen) */ -+#define MDFLAGC(i) cmdflags[(i)] -+ -+/* Now we deal with the directory structure */ -+typedef enum {Cur, Tmp, New, EndDir} DirNamesType; -+char *mdstruct[] = {"cur", "tmp", "new", NULL}; -+#define MDNAME(i) mdstruct[(i)] -+#define MDFLD(tmp, dir, i) sprintf((tmp),"%s/%s", (dir), mdstruct[(i)]) -+#define MSGPATH(tmp, dir, msg,i) sprintf((tmp),"%s/%s/%s", (dir), mdstruct[(i)],(msg)) -+ -+/* Files associated to a maildir directory */ -+ -+#define MDUIDVALIDITY ".uidvalidity" /* support for old maildirs */ -+#define MDDIR ".mdir" /* this folder is a directory */ -+#define MDUIDLAST ".uidlast" /* last assigned uid */ -+#define MDUIDTEMP ".uidtemp" /* We assign uid's no one else */ -+ -+ -+ -+/* Support of Courier Structure */ -+#define CCLIENT 0 -+#define COURIER 1 -+#define IS_CCLIENT(t) \ -+ (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ -+ && ((t)[2] == 'd' || (t)[2] == 'D')\ -+ && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) -+ -+#define IS_COURIER(t) \ -+ (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ -+ && ((t)[2] == 'c' || (t)[2] == 'C')\ -+ && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) -+#define MDPREFIX(s) ((s) ? "#mc/" : "#md/") -+#define MDSEPARATOR(s) ((s) ? '.' : '/') -+ -+/* UID Support */ -+ -+#define MAXTEMPUID (unsigned long) 180L -+const char mduid[] = {',','u','=','\0'}; -+#define MDUIDSEP mduid -+ -+ -+/* Now we deal with messages filenames */ -+char mdlocaldomain[MAILTMPLEN+1] = {'\0'}; -+pid_t mypid = (pid_t) 0; -+static char *mdfpath = NULL; -+static char myMdInboxDir[50] = { '\0' };/* Location of the Maildir INBOX */ -+static long CourierStyle = CCLIENT; -+ -+#define CHUNK 16384 /* from unix.h */ -+ -+typedef struct courier_local { -+ char *name; /* name of directory/folder */ -+ int attribute; /* attributes (children/marked/etc) */ -+} COURIERLOCAL; -+ -+typedef struct courier { -+ char *path; /* Path to collection */ -+ time_t scantime; /* time at which information was generated */ -+ int total; /* total number of elements in data */ -+ COURIERLOCAL **data; -+} COURIER_S; -+ -+/* In gdb this is the *(struct maildir_local *)stream->local structure */ -+typedef struct maildir_local { -+ unsigned int dirty : 1; /* diskcopy needs updating */ -+ unsigned int courier : 1; /* It is Courier style file system */ -+ unsigned int link : 1; /* There is a symbolic link */ -+ int candouid; /* we can assign uids and no one else */ -+ char *uidtempfile; /* path to uid temp file */ -+ int fd; /* fd of open message */ -+ char *dir; /* mail directory name */ -+ char **path; /* path to directories cur, new and tmp */ -+ unsigned char *buf; /* temporary buffer */ -+ unsigned long buflen; /* current size of temporary buffer */ -+ time_t scantime; /* last time directory scanned */ -+} MAILDIRLOCAL; -+ -+/* Convenient access to local data */ -+#define LOCAL ((MAILDIRLOCAL *) stream->local) -+ -+typedef struct maildir_file_info { -+ char *name; /* name of the file */ -+ DirNamesType loc; /* location of this file */ -+ unsigned long pos; /* place in list where this file is listed */ -+ off_t size; /* size in bytes, on disk */ -+ time_t atime; /* last access time */ -+ time_t mtime; /* last modified time */ -+ time_t ctime; /* last changed time */ -+} MAILDIRFILE; -+ -+#define MDFILE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->name) -+#define MDLOC(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->loc) -+#define MDPOS(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->pos) -+#define MDSIZE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->size) -+#define MDATIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->atime) -+#define MDMTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->mtime) -+#define MDCTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->ctime) -+ -+/* Function prototypes */ -+ -+DRIVER *maildir_valid (char *name); -+MAILSTREAM *maildir_open (MAILSTREAM *stream); -+void maildir_close (MAILSTREAM *stream, long options); -+long maildir_ping (MAILSTREAM *stream); -+void maildir_check (MAILSTREAM *stream); -+long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); -+char *maildir_header (MAILSTREAM *stream,unsigned long msgno, -+ unsigned long *length, long flags); -+void maildir_list (MAILSTREAM *stream,char *ref,char *pat); -+void *maildir_parameters (long function,void *value); -+int maildir_create_folder (char *mailbox); -+long maildir_create (MAILSTREAM *stream,char *mailbox); -+void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); /*check */ -+long maildir_expunge (MAILSTREAM *stream, char *sequence, long options); -+long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); -+long maildir_append (MAILSTREAM *stream,char *mailbox, append_t af, void *data); -+long maildir_delete (MAILSTREAM *stream,char *mailbox); -+long maildir_rename (MAILSTREAM *stream,char *old,char *new); -+long maildir_sub (MAILSTREAM *stream,char *mailbox); -+long maildir_unsub (MAILSTREAM *stream,char *mailbox); -+void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat); -+void courier_list (MAILSTREAM *stream,char *ref, char *pat); -+ -+/* utility functions */ -+void courier_realname (char *name, char *realname); -+long maildir_dirfmttest (char *name); -+char *maildir_file (char *dst,char *name); -+int maildir_select (const struct direct *name); -+int maildir_namesort (const struct direct **d1, const struct direct **d2); -+unsigned long antoul (char *seed); -+unsigned long mdfntoul (char *name); -+int courier_dir_select (const struct direct *name); -+int courier_dir_sort (const struct direct **d1, const struct direct **d2); -+long maildir_canonicalize (char *pattern,char *ref,char *pat); -+void maildir_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); -+void courier_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); -+int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); -+int maildir_valid_name (char *name); -+int maildir_valid_dir (char *name); -+int is_valid_maildir (char **name); -+int maildir_message_exists(MAILSTREAM *stream,char *name, char *tmp); -+char *maildir_remove_root(char *name); -+char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, unsigned long *length,long flags); -+unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, -+ DirNamesType dirtype); -+int maildir_eliminate_duplicate (char *name, struct direct ***flist, -+ unsigned long *nfiles); -+int maildir_doscandir (char *name, struct direct ***flist, int flag); -+unsigned long maildir_scandir (char *name, struct direct ***flist, -+ unsigned long *nfiles, int *scand, int flag); -+void maildir_parse_folder (MAILSTREAM *stream, int full); -+void md_domain_name (void); -+char *myrootdir (char *name); -+char *mdirpath (void); -+int maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype); -+unsigned long maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, -+ DirNamesType dirtype, struct direct **names, unsigned long nfiles, int full); -+int same_maildir_file(char *name1, char *name2); -+int comp_maildir_file(char *name1, char *name2); -+int maildir_message_in_list(char *msgname, struct direct **names, -+ unsigned long bottom, unsigned long top, unsigned long *pos); -+void maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t); -+int maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno); -+void maildir_abort (MAILSTREAM *stream); -+int maildir_contains_folder(char *dirname, char *name); -+int maildir_is_dir(char *dirname, char *name); -+int maildir_dir_is_empty(char *mailbox); -+int maildir_create_work (char *mailbox, int loop); -+void maildir_get_file (MAILDIRFILE **mdfile); -+void maildir_free_file (void **mdfile); -+void maildir_free_file_only (void **mdfile); -+int maildir_any_new_msgs(char *mailbox); -+void maildir_get_date(MAILSTREAM *stream, unsigned long msgno); -+void maildir_fast (MAILSTREAM *stream,char *sequence,long flags); -+ -+/* Courier server support */ -+void courier_free_cdir (COURIER_S **cdir); -+COURIER_S *courier_get_cdir (int total); -+int courier_search_list(COURIERLOCAL **data, char *name, int first, int last); -+COURIER_S *courier_list_dir(char *curdir); -+void courier_list_info(COURIER_S **cdirp, char *data, int i); -+ -+/* UID Support */ -+int maildir_can_assign_uid (MAILSTREAM *stream); -+void maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, -+ unsigned long *uid_validity); -+void maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, -+ unsigned long uid_validity); -+unsigned long maildir_get_uid(char *name); -+void maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno); -+void maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid); -+void maildir_uid_renew_tempfile(MAILSTREAM *stream); -+ -Index: alpine-2.20/imap/src/osdep/unix/os_cyg.h -=================================================================== ---- alpine-2.20.orig/imap/src/osdep/unix/os_cyg.h -+++ alpine-2.20/imap/src/osdep/unix/os_cyg.h -@@ -47,6 +47,7 @@ - #define setpgrp setpgid - - #define SYSTEMUID 18 /* Cygwin returns this for SYSTEM */ -+#define FLAGSEP ';' - #define geteuid Geteuid - uid_t Geteuid (void); - -Index: alpine-2.20/pith/conf.c -=================================================================== ---- alpine-2.20.orig/pith/conf.c -+++ alpine-2.20/pith/conf.c -@@ -436,6 +436,9 @@ CONF_TXT_T cf_text_window_position[] = " - - CONF_TXT_T cf_text_newsrc_path[] = "Full path and name of NEWSRC file"; - -+#ifndef _WINDOWS -+CONF_TXT_T cf_text_maildir_location[] = "Location relative to your HOME directory of the directory where your INBOX\n# for the maildir format is located. Default value is \"Maildir\". If your\n# inbox is located at \"~/Maildir\" you do not need to change this value.\n# A common value is also \".maildir\""; -+#endif - - /*---------------------------------------------------------------------- - These are the variables that control a number of pine functions. They -@@ -642,6 +645,10 @@ static struct variable variables[] = { - NULL, cf_text_news_active}, - {"news-spool-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, - NULL, cf_text_news_spooldir}, -+#ifndef _WINDOWS -+{"maildir-location", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, -+ "Maildir Location", cf_text_maildir_location}, -+#endif - {"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, -@@ -2283,6 +2290,12 @@ init_vars(struct pine *ps, void (*cmds_f - mail_parameters(NULL, SET_NEWSSPOOL, - (void *)VAR_NEWS_SPOOL_DIR); - -+#ifndef _WINDOWS -+ set_current_val(&vars[V_MAILDIR_LOCATION], TRUE, TRUE); -+ if(VAR_MAILDIR_LOCATION && VAR_MAILDIR_LOCATION[0]) -+ mail_parameters(NULL, SET_MDINBOXPATH, (void *)VAR_MAILDIR_LOCATION); -+#endif -+ - /* guarantee a save default */ - set_current_val(&vars[V_DEFAULT_SAVE_FOLDER], TRUE, TRUE); - if(!VAR_DEFAULT_SAVE_FOLDER || !VAR_DEFAULT_SAVE_FOLDER[0]) -@@ -2921,6 +2934,10 @@ feature_list(int index) - 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}, -+#ifndef _WINDOWS -+ {"use-courier-folder-list", "Courier Style Folder List", -+ F_COURIER_FOLDER_LIST, h_config_courier_list, PREF_FLDR, 0}, -+#endif - - /* Addr book */ - {"combined-addrbook-display", "Combined Address Book Display", -@@ -7047,7 +7064,7 @@ toggle_feature(struct pine *ps, struct v - int just_flip_value, EditWhich ew) - { - char **vp, *p, **lval, ***alval; -- int og, on_before, was_set; -+ int og, on_before, was_set, i; - char *err; - long l; - -@@ -7100,6 +7117,13 @@ toggle_feature(struct pine *ps, struct v - - break; - -+#ifndef _WINDOWS -+ case F_COURIER_FOLDER_LIST: -+ i = F_ON(f->id ,ps) ? 1 : 0; -+ mail_parameters(NULL,SET_COURIERSTYLE, (void *) &i); -+ break; /* COURIER == 1, CCLIENT == 0, see maildir.h */ -+#endif -+ - case F_COLOR_LINE_IMPORTANT : - case F_DATES_TO_LOCAL : - clear_index_cache(ps->mail_stream, 0); -@@ -7887,6 +7911,10 @@ config_help(int var, int feature) - return(h_config_newmailwidth); - case V_NEWSRC_PATH : - return(h_config_newsrc_path); -+#ifndef _WINDOWS -+ case V_MAILDIR_LOCATION : -+ return(h_config_maildir_location); -+#endif - case V_BROWSER : - return(h_config_browser); - #if defined(DOS) || defined(OS2) -Index: alpine-2.20/pith/conf.h -=================================================================== ---- alpine-2.20.orig/pith/conf.h -+++ alpine-2.20/pith/conf.h -@@ -257,6 +257,10 @@ - #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 -+#ifndef _WINDOWS -+#define VAR_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].current_val.p -+#define GLO_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].global_val.p -+#endif - #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 -Index: alpine-2.20/pith/conftype.h -=================================================================== ---- alpine-2.20.orig/pith/conftype.h -+++ alpine-2.20/pith/conftype.h -@@ -118,6 +118,9 @@ typedef enum { V_PERSONAL_NAME = 0 - , V_NEWSRC_PATH - , V_NEWS_ACTIVE_PATH - , V_NEWS_SPOOL_DIR -+#ifndef _WINDOWS -+ , V_MAILDIR_LOCATION -+#endif - , V_UPLOAD_CMD - , V_UPLOAD_CMD_PREFIX - , V_DOWNLOAD_CMD -@@ -395,6 +398,9 @@ typedef enum { - F_PASS_C1_CONTROL_CHARS, - F_SINGLE_FOLDER_LIST, - F_VERTICAL_FOLDER_LIST, -+#ifndef _WINDOWS -+ F_COURIER_FOLDER_LIST, -+#endif - F_TAB_CHK_RECENT, - F_AUTO_REPLY_TO, - F_VERBOSE_POST, -Index: alpine-2.20/pith/init.c -=================================================================== ---- alpine-2.20.orig/pith/init.c -+++ alpine-2.20/pith/init.c -@@ -408,6 +408,9 @@ get_mail_list(CONTEXT_S *list_cntxt, cha - && stricmp(filename, folder_base)){ - #else - if(strncmp(filename, folder_base, folder_base_len) == 0 -+#ifndef _WINDOWS -+ && filename[folder_base_len] != list_cntxt->dir->delim -+#endif - && strcmp(filename, folder_base)){ - #endif - #endif -Index: alpine-2.20/pith/pattern.c -=================================================================== ---- alpine-2.20.orig/pith/pattern.c -+++ alpine-2.20/pith/pattern.c -@@ -49,7 +49,9 @@ static char rcsid[] = "$Id: pattern.c 12 - #include "../pith/icache.h" - #include "../pith/ablookup.h" - #include "../pith/keyword.h" -- -+#ifndef _WINDOWS -+int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); -+#endif /* _WINDOWS */ - - /* - * Internal prototypes -@@ -5485,6 +5487,15 @@ match_pattern_folder_specific(PATTERN_S - break; - - case '#': -+#ifndef _WINDOWS -+ if(!struncmp(patfolder, "#md/", 4) -+ || !struncmp(patfolder, "#mc/", 4)){ -+ maildir_file_path(patfolder, tmp1, sizeof(tmp1)); -+ if(!strcmp(tmp1, stream->mailbox)) -+ match++; -+ break; -+ } -+#endif - if(!strcmp(patfolder, stream->mailbox)) - match++; - -@@ -7905,7 +7916,7 @@ move_filtered_msgs(MAILSTREAM *stream, M - 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; -+ char *save_ref = NULL, *save_dstfldr = NULL, *save_dstfldr2 = NULL; - #define FILTMSG_MAX 30 - - if(!stream) -@@ -7939,6 +7950,16 @@ move_filtered_msgs(MAILSTREAM *stream, M - if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) - we_cancel = busy_cue(buf, NULL, 0); - -+#ifndef _WINDOWS -+ if(!struncmp(dstfldr, "#md/", 4) || !struncmp(dstfldr, "#mc/", 4)){ -+ char tmp1[MAILTMPLEN]; -+ maildir_file_path(dstfldr, tmp1, sizeof(tmp1)); -+ save_dstfldr2 = dstfldr; -+ save_dstfldr = cpystr(tmp1); -+ dstfldr = save_dstfldr; + } -+#endif -+ - if(!is_absolute_path(dstfldr) - && !(save_context = default_save_context(ps_global->context_list))) - save_context = ps_global->context_list; -@@ -8002,6 +8023,11 @@ move_filtered_msgs(MAILSTREAM *stream, M - if(we_cancel) - cancel_busy_cue(buf[0] ? 0 : -1); - -+ if(save_dstfldr){ -+ fs_give((void **)&save_dstfldr); -+ dstfldr = save_dstfldr2; ++ ++ fnlen = strlen(tmp); ++ if ((s = strrchr(mailbox,MDSEPARATOR(courier))) != NULL){ ++ c = *++s; ++ *s = '\0'; ++ if ((stat(tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && ++ !maildir_create_work (mailbox, ++loop)) ++ return NIL; ++ *s = c; ++ } ++ tmp[fnlen] = '\0'; ++ ++ if (mkdir(tmp,0700) && errno != EEXIST) ++ return NIL; ++ ++ if (create_dir) ++ mailbox[fnlen] = '/'; ++ ++ if (create_dir){ ++ if(style == CCLIENT){ ++ if(!courier){ ++ FILE *fp = NULL; ++ snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, MDDIR); ++ if ((fp = fopen(tmp2,"w")) == NULL){ ++ snprintf (err, sizeof(err), "Problem creating %s: %s", tmp2, strerror(errno)); ++ mm_log (err,ERROR); ++ return NIL; ++ } ++ fclose(fp); ++ } ++ } ++ return T; ++ } ++ else ++ return maildir_create_folder(tmp); ++ } ++ ++ long maildir_create (MAILSTREAM *stream,char *mailbox) ++ { ++ char tmp[MAILTMPLEN], err[MAILTMPLEN]; ++ int rv, create_dir; ++ ++ create_dir = mailbox ? ++ (mailbox[strlen(mailbox) - 1] == ++ MDSEPARATOR(IS_COURIER(mailbox))) : 0; ++ maildir_file_path(mailbox, tmp, sizeof(tmp)); ++ strcpy(tmp, mailbox); ++ rv = maildir_create_work(mailbox, 0); ++ strcpy(mailbox, tmp); ++ if (rv == 0){ ++ snprintf (err, sizeof(err), "Can't create %s %s", ++ (create_dir ? "directory" : "mailbox"), mailbox); ++ mm_log (err,ERROR); ++ } ++ return rv ? LONGT : NIL; ++ } ++ ++ #define MAXTRY 10000 ++ void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) ++ { ++ char oldfile[MAILTMPLEN],newfile[MAILTMPLEN],fn[MAILTMPLEN]; ++ char *s; ++ int ren, try = 0; ++ ++ if (elt->valid){ ++ for (try = 1; try > 0 && try < MAXTRY; try++){ ++ /* build the new filename */ ++ snprintf (oldfile, sizeof(oldfile), "%s/%s",LOCAL->path[Cur], MDFILE(elt)); ++ fn[0] = '\0'; ++ if ((ren = maildir_message_exists(stream, MDFILE(elt), fn)) == 0){ ++ errno = ENOENT; ++ try = MAXTRY; ++ } ++ if (*fn) /* new oldfile! */ ++ snprintf (oldfile,sizeof(oldfile),"%s/%s", LOCAL->path[Cur], fn); ++ if ((s = strrchr (MDFILE(elt), FLAGSEP))) *s = '\0'; ++ snprintf (fn, sizeof(fn), "%s%s%s%s%s%s%s", MDFILE(elt), MDSEP(2), ++ MDFLAG(Draft, elt->draft), MDFLAG(Flagged, elt->flagged), ++ MDFLAG(Replied, elt->answered), MDFLAG(Seen, elt->seen), ++ MDFLAG(Trashed, elt->deleted)); ++ snprintf (newfile, sizeof(newfile), "%s/%s",LOCAL->path[Cur],fn); ++ if (ren != 0 && rename (oldfile,newfile) >= 0) ++ try = -1; ++ } ++ ++ if (try > 0){ ++ snprintf(oldfile, sizeof(oldfile), "Unable to write flags to disk: %s", ++ (errno == ENOENT) ? "message is gone!" : strerror (errno)); ++ mm_log(oldfile,ERROR); ++ return; ++ } ++ #ifdef __CYGWIN__ ++ utime(LOCAL->path[Cur], NIL); /* make sure next scan will catch the change */ ++ #endif ++ maildir_free_file_only ((void **) &elt->private.spare.ptr); ++ MDFILE(elt) = cpystr (fn); ++ } ++ } ++ ++ long maildir_expunge (MAILSTREAM *stream, char *sequence, long options) ++ { ++ long ret; ++ MESSAGECACHE *elt; ++ unsigned long i, n = 0L; ++ unsigned long recent = stream->recent; ++ char tmp[MAILTMPLEN]; ++ ++ mm_critical (stream); /* go critical */ ++ ret = sequence ? ((options & EX_UID) ? ++ mail_uid_sequence (stream,sequence) : ++ mail_sequence (stream,sequence)) : LONGT; ++ if(ret == 0L) ++ return 0L; ++ for (i = 1L; i <= stream->nmsgs;){ ++ elt = mail_elt (stream,i); ++ if (elt->deleted && (sequence ? elt->sequence : T)){ ++ snprintf (tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], MDFILE(elt)); ++ if (unlink (tmp) < 0) {/* try to delete the message */ ++ snprintf (tmp, sizeof(tmp), "Expunge of message %ld failed, aborted: %s",i, ++ strerror (errno)); ++ if (!stream->silent) ++ mm_log (tmp,WARN); ++ break; ++ } ++ if (elt->private.spare.ptr) ++ maildir_free_file ((void **) &elt->private.spare.ptr); ++ if (elt->recent) --recent;/* if recent, note one less recent message */ ++ mail_expunged (stream,i); /* notify upper levels */ ++ n++; /* count up one more expunged message */ ++ } ++ else i++; ++ } ++ if(n){ /* output the news if any expunged */ ++ snprintf (tmp, sizeof(tmp), "Expunged %ld messages", n); ++ if (!stream->silent) ++ mm_log (tmp,(long) NIL); ++ } ++ else ++ if (!stream->silent) ++ mm_log ("No messages deleted, so no update needed",(long) NIL); ++ mm_nocritical (stream); /* release critical */ ++ /* notify upper level of new mailbox size */ ++ mail_exists (stream, stream->nmsgs); ++ mail_recent (stream, recent); ++ return ret; ++ } ++ ++ long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) ++ { ++ STRING st; ++ MESSAGECACHE *elt; ++ unsigned long len; ++ int fd; ++ unsigned long i; ++ struct stat sbuf; ++ char tmp[MAILTMPLEN], flags[MAILTMPLEN], path[MAILTMPLEN], *s; ++ /* copy the messages */ ++ if ((options & CP_UID) ? mail_uid_sequence (stream, sequence) : ++ mail_sequence (stream,sequence)) ++ for (i = 1L; i <= stream->nmsgs; i++) ++ if ((elt = mail_elt (stream,i))->sequence){ ++ MSGPATH(path, LOCAL->dir, MDFILE(elt), MDLOC(elt)); ++ if (((fd = open (path,O_RDONLY,NIL)) < 0) ++ ||((!elt->rfc822_size && ++ ((stat(path, &sbuf) < 0) || !S_ISREG (sbuf.st_mode))))) ++ return NIL; ++ if(!elt->rfc822_size) ++ MDSIZE(elt) = sbuf.st_size; ++ s = (char *) fs_get(MDSIZE(elt) + 1); ++ read (fd,s,MDSIZE(elt)); ++ s[MDSIZE(elt)] = '\0'; ++ close (fd); ++ len = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen, s, MDSIZE(elt)); ++ INIT (&st,mail_string, LOCAL->buf, len); ++ elt->rfc822_size = len; ++ fs_give ((void **)&s); ++ ++ flags[0] = flags[1] = '\0'; ++ if (elt->seen) strcat (flags," \\Seen"); ++ if (elt->draft) strcat (flags," \\Draft"); ++ if (elt->deleted) strcat (flags," \\Deleted"); ++ if (elt->flagged) strcat (flags," \\Flagged"); ++ if (elt->answered) strcat (flags," \\Answered"); ++ flags[0] = '('; /* open list */ ++ strcat (flags,")"); /* close list */ ++ mail_date (tmp,elt); /* generate internal date */ ++ if (!mail_append_full (NIL, mailbox, flags, tmp, &st)) ++ return NIL; ++ if (options & CP_MOVE) elt->deleted = T; ++ } ++ return LONGT; /* return success */ ++ } ++ ++ long maildir_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) ++ { ++ int fd, k, done, fail; ++ STRING *message; ++ char c,*s, *flags, *date; ++ char tmp[MAILTMPLEN],file[MAILTMPLEN],path1[MAILTMPLEN],path2[MAILTMPLEN]; ++ MESSAGECACHE elt; ++ long i, size = 0L, ret = LONGT, f; ++ unsigned long uf, ti; ++ static unsigned int transact = 0; ++ struct stat sbuf; ++ ++ if (!maildir_valid(mailbox)) { ++ snprintf (tmp, sizeof(tmp), "Not a valid Maildir mailbox: %s", mailbox); ++ mm_log (tmp,ERROR); ++ return NIL; ++ } ++ ++ if (!*mdlocaldomain) ++ md_domain_name(); /* get domain name for maildir files in mdlocaldomain now! */ ++ ++ if (mypid == (pid_t) 0) ++ mypid = getpid(); ++ ++ if (!stream){ ++ stream = &maildirproto; ++ ++ for (k = 0; k < NUSERFLAGS && stream->user_flags[k]; ++k) ++ fs_give ((void **) &stream->user_flags[k]); ++ } ++ ++ if (!(*af)(stream, data, &flags, &date, &message)) return NIL; ++ ++ mm_critical (stream); /* go critical */ ++ do { ++ fail = done = 0; /* we have not determined name of message file yet */ ++ if (!SIZE (message)) { /* guard against zero-length */ ++ mm_log ("Append of zero-length message", ERROR); ++ ret = NIL; ++ break; ++ } ++ ++ if (date && !mail_parse_date(&elt,date)){ ++ snprintf (tmp, sizeof(tmp), "Bad date in append: %.80s", date); ++ mm_log (tmp, ERROR); ++ ret = NIL; ++ break; ++ } ++ ++ if(date){ ++ struct tm tm; ++ ++ tm.tm_sec = elt.seconds; ++ tm.tm_min = elt.minutes; ++ tm.tm_hour = elt.hours; ++ tm.tm_mday = elt.day; ++ tm.tm_mon = elt.month - 1; ++ tm.tm_year = BASEYEAR + elt.year - 1900; ++ ++ ti = mktime(&tm); ++ } else ti = time(0); ++ ++ f = mail_parse_flags (stream,flags,&uf); ++ do { ++ /* build file name we will use */ ++ snprintf (file, sizeof(file), "%lu.%d_%09u.%s%s%s%s%s%s", ++ ti, mypid, transact++, mdlocaldomain, (f ? MDSEP(2) : ""), ++ MDFLAG(Draft, f&fDRAFT), MDFLAG(Flagged, f&fFLAGGED), ++ MDFLAG(Replied, f&fANSWERED), MDFLAG(Seen, f&fSEEN)); ++ /* build tmp file name */ ++ if (maildir_file_path(mailbox, tmp, sizeof(tmp))) /* copy in TMP */ ++ MSGPATH(path1, tmp, file, Tmp); ++ /* build final filename to use */ ++ if (maildir_file_path(mailbox, tmp, sizeof(tmp))) ++ MSGPATH(path2, tmp, file, New); /* copy in NEW */ ++ if(stat(path1, &sbuf) < 0 && errno == ENOENT ++ && stat(path2, &sbuf) < 0 && errno == ENOENT) ++ done++; ++ else ++ fail++; ++ if(fail == 1000){ ++ snprintf (tmp, sizeof(tmp), "Failure to create append message name"); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ } while (done == 0); ++ ++ if ((fd = open (path1,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) { ++ snprintf (tmp, sizeof(tmp), "Can't open append mailbox: %s", strerror (errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ for (size = 0,i = SIZE (message),s = (char *) fs_get (i + 1); i; --i) ++ if ((c = SNX (message)) != '\015') s[size++] = c; ++ if ((write (fd, s, size) < 0) || fsync (fd)) { ++ unlink (path1); /* delete message */ ++ snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); ++ mm_log (tmp, ERROR); ++ ret = NIL; ++ } ++ fs_give ((void **) &s); /* flush the buffer */ ++ close (fd); /* close the file */ ++ ++ if (rename (path1,path2) < 0) { ++ snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); ++ mm_log (tmp, ERROR); ++ ret = NIL; ++ } ++ unlink (path1); ++ if(date){ ++ time_t tp[2]; ++ tp[0] = tp[1] = ti; ++ utime (path2,tp); ++ } ++ ++ if (ret) ++ if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL; ++ ++ } while (ret && message); /* write the data */ ++ mm_nocritical (stream); /* release critical */ ++ return ret; ++ } ++ ++ long maildir_delete (MAILSTREAM *stream,char *mailbox) ++ { ++ DIR *dirp; ++ struct direct *d; ++ int i, remove_dir = 0, mddir = 0, rv, error = 0; ++ char tmp[MAILTMPLEN],tmp2[MAILTMPLEN], realname[MAILTMPLEN]; ++ struct stat sbuf; ++ int courier = IS_COURIER(mailbox); ++ ++ if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ ++ remove_dir++; ++ mailbox[strlen(mailbox) -1] = '\0'; ++ } ++ ++ if (!maildir_valid(mailbox)){ ++ maildir_file_path(mailbox, tmp, sizeof(tmp)); ++ if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)){ ++ snprintf(tmp, sizeof(tmp), "Can not remove %s", mailbox); ++ error++; ++ } ++ } ++ ++ if (!error && remove_dir && !maildir_dir_is_empty(mailbox)){ ++ snprintf(tmp, sizeof(tmp), "Can not remove directory %s/: directory not empty", mailbox); ++ error++; ++ } ++ ++ if(error){ ++ mm_log (tmp,ERROR); ++ return NIL; ++ } ++ ++ maildir_close(stream,0); /* even if stream was NULL */ ++ ++ maildir_file_path(mailbox, realname, sizeof(realname)); ++ ++ if (remove_dir){ ++ snprintf(tmp, sizeof(tmp), "%s/%s", realname, MDDIR); ++ if ((rv = stat (tmp,&sbuf)) == 0 && S_ISREG(sbuf.st_mode)) ++ rv = unlink(tmp); ++ else if (errno == ENOENT) ++ rv = 0; ++ if (rv != 0){ ++ snprintf(tmp, sizeof(tmp), "Can not remove %s/%s: %s", tmp2, MDDIR, strerror(errno)); ++ mm_log (tmp,ERROR); ++ return NIL; ++ } ++ if (!maildir_valid(realname) && rmdir(realname) != 0){ ++ snprintf(tmp, sizeof(tmp), "Can not remove %s/: %s", mailbox, strerror(errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ return LONGT; ++ } ++ /* else remove just the folder. Remove all hidden files, except MDDIR */ ++ for (i = Cur; i != EndDir; i++){ ++ MDFLD(tmp, realname, i); ++ ++ if (!(dirp = opendir (tmp))){ ++ snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", mailbox, strerror(errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ ++ while ((d = readdir(dirp)) != NULL){ ++ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..")){ ++ snprintf(tmp2, sizeof(tmp2), "%s/%s", tmp, d->d_name); ++ if (unlink(tmp2) != 0){ ++ snprintf(tmp2, sizeof(tmp2), "Can not remove %s: %s", mailbox, strerror(errno)); ++ mm_log (tmp2, ERROR); ++ return NIL; ++ } ++ } ++ } ++ closedir(dirp); ++ if (rmdir(tmp) != 0){ ++ snprintf(tmp, sizeof(tmp), "Can not remove %s: %s", mailbox, strerror(errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ } ++ /* ++ * ok we have removed all subdirectories of the folder mailbox, Remove the ++ * hidden files. ++ */ ++ ++ if(!(dirp = opendir (realname))){ ++ snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", realname, strerror(errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ ++ while ((d = readdir(dirp)) != NULL){ ++ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") ++ && (!strcmp(d->d_name, MDDIR) ++ || !strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST)) ++ || !strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)))){ ++ if(strcmp(d->d_name, MDDIR) == 0) ++ mddir++; ++ snprintf(tmp, sizeof(tmp), "%s/%s", realname, d->d_name); ++ if (unlink(tmp) != 0) ++ error++; ++ } ++ } ++ closedir(dirp); ++ if (error || ++ (maildir_dir_is_empty(mailbox) && mddir == 0 && rmdir(realname) < 0)){ ++ snprintf(tmp, sizeof(tmp), "Can not remove folder %s: %s", mailbox, strerror(errno)); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ return LONGT; ++ } ++ ++ long maildir_rename (MAILSTREAM *stream, char *old, char *new) ++ { ++ char tmp[MAILTMPLEN], tmpnew[MAILTMPLEN], realold[MAILTMPLEN]; ++ char realnew[MAILTMPLEN]; ++ int courier = IS_COURIER(old) && IS_COURIER(new); ++ int i; ++ long rv = LONGT; ++ COURIER_S *cdir; ++ ++ if((IS_COURIER(old) || IS_COURIER(new)) && !courier){ ++ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s", old, new); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ ++ if (!maildir_valid(old)){ ++ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s: folder not in maildir format",old); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ maildir_file_path(old, realold, sizeof(realold)); ++ if (!maildir_valid_name(new) && new[0] == '#'){ ++ snprintf (tmp, sizeof(tmp), "Cannot rename mailbox %s: folder not in maildir format", new); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ maildir_file_path(new, realnew, sizeof(realnew)); ++ if (access(tmpnew,F_OK) == 0){ /* new mailbox name must not exist */ ++ snprintf (tmp, sizeof(tmp), "Cannot rename to mailbox %s: destination already exists", new); ++ mm_log (tmp, ERROR); ++ return NIL; ++ } ++ ++ if(!courier){ ++ if (rename(realold, realnew)){ /* try to rename the directory */ ++ snprintf(tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, ++ strerror(errno)); ++ mm_log(tmp,ERROR); ++ return NIL; ++ } ++ return LONGT; /* return success */ ++ } ++ ++ cdir = courier_list_dir(old); ++ for (i = 0; cdir && i < cdir->total; i++){ ++ if(strstr(cdir->data[i]->name, old)){ ++ snprintf(tmp, sizeof(tmp), "%s%s", new, cdir->data[i]->name+strlen(old)); ++ maildir_file_path(cdir->data[i]->name, realold, sizeof(realold)); ++ maildir_file_path(tmp, realnew, sizeof(realnew)); ++ if (rename(realold, realnew)){ ++ snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, ++ strerror(errno)); ++ mm_log(tmp,ERROR); ++ rv = NIL; ++ } ++ } ++ } ++ courier_free_cdir(&cdir); ++ return rv; ++ } ++ ++ long maildir_sub(MAILSTREAM *stream,char *mailbox) ++ { ++ return sm_subscribe(mailbox); ++ } ++ ++ long maildir_unsub(MAILSTREAM *stream,char *mailbox) ++ { ++ return sm_unsubscribe(mailbox); ++ } ++ ++ void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat) ++ { ++ void *sdb = NIL; ++ char *s, test[MAILTMPLEN], tmp[MAILTMPLEN]; ++ /* get canonical form of name */ ++ if (maildir_canonicalize (test, ref, pat) && (s = sm_read (tmp, &sdb))) { ++ do if (pmatch_full (s, test, '/')) mm_lsub (stream, '/', s, NIL); ++ while ((s = sm_read (tmp, &sdb)) != NULL); /* until no more subscriptions */ ++ } ++ } ++ ++ long maildir_canonicalize (char *pattern,char *ref,char *pat) ++ { ++ if (ref && *ref) { /* have a reference */ ++ strcpy (pattern,ref); /* copy reference to pattern */ ++ /* # overrides mailbox field in reference */ ++ if (*pat == '#') strcpy (pattern,pat); ++ /* pattern starts, reference ends, with / */ ++ else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/')) ++ strcat (pattern,pat + 1); /* append, omitting one of the period */ ++ ++ else strcat (pattern,pat); /* anything else is just appended */ ++ } ++ else strcpy (pattern,pat); /* just have basic name */ ++ return maildir_valid_name(pattern) ? LONGT : NIL; ++ } ++ ++ void maildir_list_work (MAILSTREAM *stream,char *dir,char *pat,long level) ++ { ++ DIR *dp; ++ struct direct *d; ++ struct stat sbuf; ++ char curdir[MAILTMPLEN],name[MAILTMPLEN], tmp[MAILTMPLEN]; ++ char realpat[MAILTMPLEN]; ++ long i; ++ char *maildirpath = mdirpath(); ++ ++ snprintf(curdir, sizeof(curdir), "%s/%s/", myrootdir(pat), dir ? dir : maildirpath); ++ if ((dp = opendir (curdir)) != NULL){ ++ if (dir) snprintf (name, sizeof(name), "%s%s/",MDPREFIX(CCLIENT),dir); ++ else strcpy (name, pat); ++ ++ if (level == 0 && !strpbrk(pat,"%*")){ ++ if(maildir_valid(pat)){ ++ i = maildir_contains_folder(pat, NULL) ++ ? LATT_HASCHILDREN ++ : (maildir_is_dir(pat, NULL) ++ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS); ++ maildir_file_path(pat, realpat, sizeof(realpat)); ++ i += maildir_any_new_msgs(realpat) ++ ? LATT_MARKED : LATT_UNMARKED; ++ mm_list (stream,'/', pat, i); ++ } ++ else ++ if(pat[strlen(pat) - 1] == '/') ++ mm_list (stream,'/', pat, LATT_NOSELECT); ++ } ++ ++ while ((d = readdir (dp)) != NULL) ++ if(strcmp(d->d_name, ".") && strcmp(d->d_name,"..") ++ && strcmp(d->d_name, MDNAME(Cur)) ++ && strcmp(d->d_name, MDNAME(Tmp)) ++ && strcmp(d->d_name, MDNAME(New))){ ++ ++ if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); ++ else strcpy(tmp, d->d_name); ++ ++ if(pmatch_full (tmp, pat,'/')){ ++ snprintf(tmp, sizeof(tmp), "%s/%s/%s", myrootdir(d->d_name), ++ (dir ? dir : maildirpath), d->d_name); ++ if(stat (tmp,&sbuf) == 0 ++ && ((sbuf.st_mode & S_IFMT) == S_IFDIR)){ ++ if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); ++ else strcpy(tmp, d->d_name); ++ i = maildir_valid(tmp) ++ ? (maildir_contains_folder(dir, d->d_name) ++ ? LATT_HASCHILDREN ++ : (maildir_is_dir(dir, d->d_name) ++ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS)) ++ : LATT_NOSELECT; ++ i += maildir_any_new_msgs(tmp) ++ ? LATT_MARKED : LATT_UNMARKED; ++ mm_list (stream,'/',tmp, i); ++ strcat (tmp, "/"); ++ if(dmatch (tmp, pat,'/') && ++ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL))){ ++ snprintf(tmp, sizeof(tmp), "%s/%s",dir,d->d_name); ++ maildir_list_work (stream,tmp,pat,level+1); ++ } ++ } ++ } ++ } ++ closedir (dp); ++ } ++ } ++ ++ void courier_list_work (MAILSTREAM *stream, char *dir, char *pat, long level) ++ { ++ char c, curdir[MAILTMPLEN], tmp[MAILTMPLEN]; ++ char realname[MAILTMPLEN], realpat[MAILTMPLEN] = {'\0'}; ++ int i, found; ++ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL), j; ++ char *maildirpath = mdirpath(); ++ COURIER_S *cdir; ++ ++ if(!strpbrk(pat,"%*")){ /* a mailbox */ ++ maildir_file_path(pat, curdir, sizeof(curdir)); ++ i = strlen(curdir) - 1; ++ if(curdir[i] == '/') ++ curdir[i] = '\0'; ++ cdir = courier_list_dir(curdir); ++ if(cdir){ ++ found = 0; j = 0L; ++ if(maildir_valid_name(pat)){ ++ for(i = 0; !found && i < cdir->total; i++) ++ if(strstr(curdir, cdir->data[i]->name)){ ++ if(strlen(curdir) < strlen(cdir->data[i]->name)) ++ found += 2; ++ else if(strlen(curdir) == strlen(cdir->data[i]->name)) ++ found -= 1; ++ } ++ if(found > 0) ++ j = LATT_HASCHILDREN; ++ else if(found == 0) ++ j = (style == COURIER) ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; ++ } ++ else ++ j = LATT_NOSELECT; ++ j += maildir_any_new_msgs(curdir) ? LATT_MARKED : LATT_UNMARKED; ++ if (found) ++ mm_list (stream, '.', pat, j); ++ courier_free_cdir(&cdir); ++ } ++ return; ++ } ++ ++ strcpy(tmp,pat + 4); /* a directory */ ++ j = strlen(pat) - 1; ++ maildir_file_path(pat, realpat, sizeof(realpat)); ++ c = pat[j]; ++ pat[j] = '\0'; ++ realname[0] = '\0'; ++ if(dir) ++ maildir_file_path(dir, realname, sizeof(realname)); ++ snprintf(curdir, sizeof(curdir), "%s%s%s/%s", (dir ? "" : myrootdir(pat)), (dir ? "" : "/"), ++ (dir ? realname : maildirpath), (dir ? "" : ".")); ++ snprintf(tmp, sizeof(tmp), "%s%s/.", MDPREFIX(COURIER), dir ? dir : maildirpath); ++ if (level == 0 && tmp && pmatch_full (tmp, realpat, '.')) ++ mm_list (stream,'.', tmp, LATT_NOSELECT); ++ ++ cdir = courier_list_dir(pat); ++ pat[j] = c; ++ for (i = 0; cdir && i < cdir->total; i++) ++ if(pmatch_full (cdir->data[i]->name, pat, '.')){ ++ snprintf(tmp, sizeof(tmp), "%s.", cdir->data[i]->name); ++ courier_list_info(&cdir, tmp, i); ++ mm_list (stream,'.',cdir->data[i]->name, cdir->data[i]->attribute); + } -+ - return(buf[0] != '\0'); - } - -Index: alpine-2.20/pith/pine.hlp -=================================================================== ---- alpine-2.20.orig/pith/pine.hlp -+++ alpine-2.20/pith/pine.hlp -@@ -22060,6 +22060,102 @@ your account's home directory). - <End of help on this topic> - - -+====== h_config_maildir_location ====== -+ -+ -+OPTION: <!--#echo var="VAR_maildir-location"--> -+ -+ -+

    OPTION:

    -+ -+

    -+This option should be used only if you have a Maildir folder which you -+want to use as your INBOX. If this is not your case (or don't know what -+this is), you can safely ignore this option. -+ -+

    -+This option overrides the default directory Pine uses to find the location of -+your INBOX, in case this is in Maildir format. The default value of this -+option is "Maildir", but in some systems, this directory could have been -+renamed (e.g. to ".maildir"). If this is your case use this option to change -+the default. -+ -+

    -+The value of this option is prefixed with the "~/" string to determine the -+full path to your INBOX. -+ -+

    -+You should probably read a few tips that -+teach you how to configure your maildir for optimal performance. This -+version also has support for the -+Courier style file system when a maildir collection is accessed locally. -+ -+

    -+

    -+<End of help on this topic> -+ -+ -+====== h_config_maildir ===== -+ -+ -+Maildir Support -+ -+ -+

    Maildir Support

    -+ -+This version of Alpine has been enhanced with Maildir support. This text is -+intended to be a reference on its support. -+

    -+ -+A Maildir folder is a directory that contains three directories called -+cur, tmp and new. A program that delivers mail (e.g. postfix) will put new -+mail in the new directory. A program that reads mail will look for for old -+messages in the cur directory, while it will look for new mail in the new -+directory. -+

    -+ -+In order to use maildir support it is better to set your inbox-path to the -+value "#md/inbox" (without quotes). This assumes that your mail -+delivery agent is delivering new mail to ~/Maildir/new. If the directory -+where new mail is being delivered is not called "Maildir", you can set the -+name of the subdirectory of home where it is being delivered in the configuration -+variable. Most of the time you will not have to worry about the -+ variable, because it will probably be set by your -+administrator in the pine.conf configuration file. -+

    -+ -+One of the advantages of the Maildir support of this version of Alpine is -+that you do not have to stop using folders in another styles (mbox, mbx, -+etc.). This is desirable since the usage of a specific mail storage system -+is a personal decision. Folders in the maildir format that are part of the -+Mail collection will be recognized without any extra configuration of your -+part. If your mail/ collection is located under the mail/ directory, then -+creating a new maildir folder in this collection is done by pressing "A" -+and entering the string "#driver.md/mail/newfolder". Observe that adding a -+new folder as "newfolder" may not create such folder in maildir format. -+ -+

    -+If you would like to have all folders created in the maildir format by -+default, you do so by adding a Maildir Collection. In order to convert -+your current mail/ collection into a maildir collection, edit the -+collection and change the path variable from "mail/" to -+"#md/mail". In a maildir collection folders of any other format -+are ignored. -+ -+

    Finally, This version also has -+support for the Courier style file system -+when a maildir collection is accessed locally. -+ -+

    -+

    -+<End of help on this topic> -+ -+ - ====== h_config_literal_sig ===== - - -@@ -30086,6 +30182,49 @@ than across the columns as is the defaul -

    - <End of help on this topic> - -+ -+====== h_config_courier_list ===== -+ -+ -+FEATURE: <!--#echo var="FEAT_courier-folder-list"--> -+ -+ -+

    FEATURE:

    -+ -+In a maildir collection, a folder could be used as a directory to store -+folders. In the Courier server if you create a folder, then a directory -+with the same name is created. If you use this patch to access a -+collection created by the Courier server, then the display of such -+collection will look confusing. The best way to access a maildir -+collection created by the Courier server is by using the "#mc/" -+prefix instead of the "#md/" prefix. If you use this alternate -+prefix, then this feature applies to you, otherwise you can safely ignore -+the text that follows. -+

    -+Depending on if you have enabled the option -+ -+a folder may be listed as "folder[.]", or as two entries in the -+list by "folder" and "folder.". -+

    -+If this option is disabled, Pine will list local folders that are in Courier -+style format, as "folder", and those that are also directories as -+"folder[.]". This makes the default display cleaner. -+

    -+If this feature is enabled then creating folders in a maildir collection -+will create a directory with the same name. If this feature is disabled, then -+a folder is considered a directory only if it contains subfolders, so you can -+not create a directory with the same name as an exisiting folder unless -+you create a subfolder of that folder first (e.g. if you have a folder -+called "foo" simply add "foo.bar" directly. This will -+create the directory "foo" and the subfolder "bar" of it). -+

    -+Observe that this feature works only for maildir collections that are accessed -+locally. If a collection is accessed remotely then this feature has no value, -+as the report is created in a server, and Pine only reports what received -+from the server in this case. -+

    -+<End of help on this topic> -+ - - ====== h_config_verbose_post ===== - -Index: alpine-2.20/pith/send.c -=================================================================== ---- alpine-2.20.orig/pith/send.c -+++ alpine-2.20/pith/send.c -@@ -47,6 +47,9 @@ static char rcsid[] = "$Id: send.c 1204 - - #include "../c-client/smtp.h" - #include "../c-client/nntp.h" -+#ifndef _WINDOWS -+int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); -+#endif /* _WINDOWS */ - - - /* this is used in pine_send and pine_simple_send */ -@@ -257,6 +260,13 @@ postponed_stream(MAILSTREAM **streamp, c - - if(exists & FEX_ISFILE){ - context_apply(tmp, p_cntxt, mbox, sizeof(tmp)); -+#ifndef _WINDOWS -+ if (!struncmp(tmp, "#md/",4) || !struncmp(tmp, "#mc/", 4)){ -+ char tmp2[MAILTMPLEN]; -+ maildir_file_path(tmp, tmp2, sizeof(tmp2)); -+ strcpy(tmp, tmp2); -+ } -+#endif - if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){ - /* - * The mbox is relative to the home directory. ++ courier_free_cdir(&cdir); ++ } ++ ++ int ++ same_maildir_file(char *name1, char *name2) ++ { ++ char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN]; ++ char *s; ++ ++ strcpy(tmp1, name1 ? name1 : ""); ++ strcpy(tmp2, name2 ? name2 : ""); ++ if ((s = strrchr(tmp1, FLAGSEP)) != NULL) ++ *s = '\0'; ++ if (((s = strrchr(tmp1, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) ++ *s = '\0'; ++ if ((s = strrchr(tmp2, FLAGSEP)) != NULL) ++ *s = '\0'; ++ if (((s = strrchr(tmp2, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) ++ *s = '\0'; ++ ++ return !strcmp(tmp1, tmp2); ++ } ++ ++ unsigned long antoul(char *seed) ++ { ++ int i, error = 0; ++ unsigned long val = 0L, rv1 = 0L, t; ++ char c, *p; ++ if(!seed) ++ return 0L; ++ t = strtoul(seed, &p, 10); ++ if(p && (*p == '.' || *p == '_')) ++ return t; ++ /* else */ ++ if((p = strchr(seed,'.')) != NULL) ++ *p = '\0'; ++ error = (strlen(seed) > 6); /* too long */ ++ for(i= strlen(seed)-1; error == 0 && i >= 0; i--){ ++ c = seed[i]; ++ if (c >= 'A' && c <= 'Z') val = c - 'A'; ++ else if (c >= 'a' && c <= 'z') val = c - 'a' + 26; ++ else if (c >= '0' && c <= '9') val = c - '0' + 26 + 26; ++ else if (c == '-') val = c - '-' + 26 + 26 + 10; ++ else if (c == '_') val = c - '_' + 26 + 26 + 10 + 1; ++ else error++; ++ rv1 = val + (rv1 << 6); ++ } ++ if(p) ++ *p = '.'; ++ return error ? 0L : rv1; ++ } ++ ++ unsigned long mdfntoul (char *name) ++ { ++ unsigned long t; ++ char *r, last; ++ ++ if((*name == '_') && ((r = strpbrk(name,".,%+")) != NULL)){ /* Grrr!!! */ ++ last = *r; ++ *r = '\0'; ++ t = antoul(r+1); ++ *r = last; ++ } ++ else ++ t = antoul(name); ++ return t; ++ } ++ ++ int comp_maildir_file(char *name1, char *name2) ++ { ++ int uset1 = 1, uset2 = 1, i, j, cmp; ++ unsigned long t1, t2; ++ char *s1, *s2; ++ ++ if (!(name1 && *name1)) ++ return (name2 && *name2) ? (*name2 == FLAGSEP ? 0 : -1) : 0; ++ ++ if (!(name2 && *name2)) ++ return (name1 && *name1) ? (*name1 == FLAGSEP ? 0 : 1) : 0; ++ ++ if((cmp = strcmp(name1,name2)) == 0) ++ return 0; ++ ++ t1 = strtoul(name1, &s1, 10); ++ t2 = strtoul(name2, &s2, 10); ++ ++ if(!s1 || *s1 != '.') ++ uset1 = 0; ++ ++ if(!s2 || *s2 != '.') ++ uset2 = 0; ++ ++ if(uset1 && uset2) /* normal sort order */ ++ return (t1 < t2) ? -1 : (t1 > t2 ? 1 : (cmp < 0 ? -1 : 1)); ++ ++ /* If we make it here we say Grrrr.... first, then we try to figure out ++ * how to sort this mess. ++ * These are the rules. ++ * If there is a number at the beginning it is bigger than anything else. ++ * If there are digits, then the number of digits decides which one is bigger. ++ */ ++ ++ for(i = 0; isdigit(name1[i]); i++); ++ for(j = 0; isdigit(name2[j]); j++); ++ ++ return(uset1 ? 1 ++ : (uset2 ? -1 ++ : (i < j ? -1 : (i > j ? 1 : (cmp < 0 ? -1 : 1))))); ++ } ++ ++ void ++ maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t) ++ { ++ char tmp[MAILTMPLEN], *b; ++ int offset = 0; ++ int tmpd, tmpf, tmpr, tmps, tmpt; ++ ++ if(d) *d = 0; ++ if(f) *f = 0; ++ if(r) *r = 0; ++ if(s) *s = 0; ++ if(t) *t = 0; ++ ++ tmpd = tmpf = tmpr = tmps = tmpt = NIL; /* no flags set by default */ ++ strcpy(tmp,name); ++ while ((b = strrchr(tmp+offset, FLAGSEP)) != NULL){ ++ char flag,last; ++ int k; ++ if (!++b) break; ++ switch (*b){ ++ case '1': ++ case '2': ++ case '3': flag = *b; b += 2; ++ for (k = 0; b[k] && b[k] != FLAGSEP && b[k] != ','; k++); ++ last = b[k]; ++ b[k] = '\0'; ++ if (flag == '2' || flag == '3'){ ++ tmpd = strchr (b, MDFLAGC(Draft)) ? T : NIL; ++ tmpf = strchr (b, MDFLAGC(Flagged)) ? T : NIL; ++ tmpr = strchr (b, MDFLAGC(Replied)) ? T : NIL; ++ tmps = strchr (b, MDFLAGC(Seen)) ? T : NIL; ++ tmpt = strchr (b, MDFLAGC(Trashed)) ? T : NIL; ++ } ++ b[k] = last; ++ b += k; ++ for (; tmp[offset] && tmp[offset] != FLAGSEP; offset++); ++ offset++; ++ break; ++ default: break; /* Should we crash?... Nahhh */ ++ } ++ } ++ if(d) *d = tmpd; ++ if(f) *f = tmpf; ++ if(r) *r = tmpr; ++ if(s) *s = tmps; ++ if(t) *t = tmpt; ++ } ++ ++ int ++ maildir_message_in_list(char *msgname, struct direct **names, ++ unsigned long bottom, unsigned long top, unsigned long *pos) ++ { ++ unsigned long middle = (bottom + top)/2; ++ int test; ++ ++ if (!msgname) ++ return NIL; ++ ++ if (pos) *pos = middle; ++ ++ if (same_maildir_file(msgname, names[middle]->d_name)) ++ return T; ++ ++ if (middle == bottom){ /* 0 <= 0 < 1 */ ++ int rv = NIL; ++ if (same_maildir_file(msgname, names[middle]->d_name)){ ++ rv = T; ++ if (pos) *pos = middle; ++ } ++ else ++ if (same_maildir_file(msgname, names[top]->d_name)){ ++ rv = T; ++ if (pos) *pos = top; ++ } ++ return rv; ++ } ++ ++ test = comp_maildir_file(msgname, names[middle]->d_name); ++ ++ if (top <= bottom) ++ return test ? NIL : T; ++ ++ if (test < 0 ) /* bottom < msgname < middle */ ++ return maildir_message_in_list(msgname, names, bottom, middle, pos); ++ else if (test > 0) /* middle < msgname < top */ ++ return maildir_message_in_list(msgname, names, middle, top, pos); ++ else return T; ++ } ++ ++ void ++ maildir_abort(MAILSTREAM *stream) ++ { ++ if (LOCAL){ ++ DirNamesType i; ++ ++ if(LOCAL->candouid) ++ maildir_read_uid(stream, NULL, &stream->uid_validity); ++ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir); ++ for (i = Cur; i < EndDir; i++) ++ if(LOCAL->path[i]) fs_give ((void **) &LOCAL->path[i]); ++ fs_give ((void **) &LOCAL->path); ++ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); ++ if(LOCAL->uidtempfile){ ++ unlink(LOCAL->uidtempfile); ++ fs_give ((void **) &LOCAL->uidtempfile); ++ } ++ fs_give ((void **) &stream->local); ++ } ++ if (mdfpath) fs_give((void **)&mdfpath); ++ stream->dtb = NIL; ++ } ++ ++ int ++ maildir_contains_folder(char *dirname, char *name) ++ { ++ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN]; ++ int rv = 0; ++ DIR *dir; ++ struct direct *d; ++ ++ maildir_file_path(dirname, tmp2, sizeof(tmp2)); ++ if(name){ ++ strcat(tmp2,"/"); ++ strcat(tmp2, name); ++ } ++ ++ if (!(dir = opendir (tmp2))) ++ return NIL; ++ ++ while ((d = readdir(dir)) != NULL){ ++ if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") ++ && strcmp(d->d_name, MDNAME(Cur)) ++ && strcmp(d->d_name, MDNAME(Tmp)) ++ && strcmp(d->d_name, MDNAME(New))){ ++ ++ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); ++ if(maildir_valid(tmp)){ ++ rv++; ++ break; ++ } ++ } ++ } ++ closedir(dir); ++ return rv; ++ } ++ ++ int ++ maildir_is_dir(char *dirname, char *name) ++ { ++ char tmp[MAILTMPLEN]; ++ struct stat sbuf; ++ ++ maildir_file_path(dirname, tmp, sizeof(tmp)); ++ if(name){ ++ strcat(tmp, "/"); ++ strcat(tmp, name); ++ } ++ strcat(tmp, "/"); ++ strcat(tmp, MDDIR); ++ ++ return ((stat(tmp, &sbuf) == 0) && S_ISREG (sbuf.st_mode)) ? 1 : 0; ++ } ++ ++ int ++ maildir_dir_is_empty(char *mailbox) ++ { ++ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], tmp3[MAILTMPLEN],*s; ++ int rv = 1, courier = IS_COURIER(mailbox); ++ DIR *dir; ++ struct direct *d; ++ struct stat sbuf; ++ ++ maildir_file_path(mailbox, tmp2, sizeof(tmp2)); ++ ++ if(courier){ ++ strcpy(tmp3, tmp2); ++ if(s = strrchr(tmp2, '/')) ++ *s = '\0'; ++ } ++ ++ if (!(dir = opendir (tmp2))) ++ return rv; ++ ++ if(courier){ ++ while((d = readdir(dir)) != NULL){ ++ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); ++ if(!strncmp(tmp, tmp3, strlen(tmp3)) ++ && tmp[strlen(tmp3)] == '.'){ ++ rv = 0; ++ break; ++ } ++ } ++ } ++ else ++ while ((d = readdir(dir)) != NULL){ ++ snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); ++ if (strcmp(d->d_name, ".") ++ && strcmp(d->d_name,"..") ++ && strcmp(d->d_name, MDNAME(Cur)) ++ && strcmp(d->d_name, MDNAME(Tmp)) ++ && strcmp(d->d_name, MDNAME(New)) ++ && strcmp(d->d_name, MDDIR) ++ && strcmp(d->d_name, MDUIDVALIDITY) ++ && !(d->d_name[0] == '.' ++ && stat (tmp,&sbuf) == 0 ++ && S_ISREG(sbuf.st_mode))){ ++ rv = 0; ++ break; ++ } ++ } ++ closedir(dir); ++ return rv; ++ } ++ ++ void ++ maildir_get_file (MAILDIRFILE **mdfile) ++ { ++ MAILDIRFILE *md; ++ ++ md = (MAILDIRFILE *) fs_get(sizeof(MAILDIRFILE)); ++ memset(md, 0, sizeof(MAILDIRFILE)); ++ *mdfile = md; ++ } ++ ++ void ++ maildir_free_file (void **mdfile) ++ { ++ MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; ++ ++ if (md){ ++ if (md->name) fs_give((void **)&md->name); ++ fs_give((void **)&md); ++ } ++ } ++ ++ void ++ maildir_free_file_only (void **mdfile) ++ { ++ MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; ++ ++ if (md && md->name) ++ fs_give((void **)&md->name); ++ } ++ ++ int ++ maildir_any_new_msgs(char *mailbox) ++ { ++ char tmp[MAILTMPLEN]; ++ int rv = NIL; ++ DIR *dir; ++ struct direct *d; ++ ++ MDFLD(tmp, mailbox, New); ++ ++ if (!(dir = opendir (tmp))) ++ return rv; ++ ++ while ((d = readdir(dir)) != NULL){ ++ if (d->d_name[0] == '.') ++ continue; ++ rv = T; ++ break; ++ } ++ closedir(dir); ++ return rv; ++ } ++ ++ ++ void ++ maildir_get_date(MAILSTREAM *stream, unsigned long msgno) ++ { ++ MESSAGECACHE *elt; ++ struct tm *t; ++ time_t ti; ++ int i,k; ++ ++ elt = mail_elt (stream,msgno); ++ if(elt && elt->year != 0) ++ return; ++ if ((ti = mdfntoul(MDFILE(elt))) > 0L && (t = gmtime(&ti))){ ++ i = t->tm_hour * 60 + t->tm_min; ++ k = t->tm_yday; ++ t = localtime(&ti); ++ i = t->tm_hour * 60 + t->tm_min - i; ++ if((k = t->tm_yday - k) != 0) ++ i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60; ++ k = abs (i); ++ elt->hours = t->tm_hour; ++ elt->minutes = t->tm_min; ++ elt->seconds = t->tm_sec; ++ elt->day = t->tm_mday; elt->month = t->tm_mon + 1; ++ elt->year = t->tm_year - (BASEYEAR - 1900); ++ elt->zoccident = (k == i) ? 0 : 1; ++ elt->zhours = k/60; ++ elt->zminutes = k % 60; ++ } ++ } ++ ++ /* Support for Courier Style directories ++ When this code is complete there will be two types of support, which ++ will be configurable. The problem is the following: In Courier style ++ folder structure, a "folder" may have a subfolder called ++ "folder.subfolder", which is not natural in the file system in the ++ sense that I can not stat for "folder.subfolder" wihtout knowing what ++ "subfolder" is. It needs to be guessed. Because of this I need to look ++ in the list of folders if there is a folder with a name ++ "folder.subfolder", before I can say if the folder is dual or not. One ++ can avoid this annoyance if one ignores the problem by declaring that ++ every folder is dual. I will however code as the default the more ++ complicated idea of scaning the containing directory each time it is ++ modified and search for subfolders, and list the entries it found. ++ */ ++ ++ int courier_dir_select (const struct direct *name) ++ { ++ return name->d_name[0] == '.' && (strlen(name->d_name) > 2 ++ || (strlen(name->d_name) == 2 && name->d_name[1] != '.')); ++ } ++ ++ int courier_dir_sort (const struct direct **d1, const struct direct **d2) ++ { ++ const struct direct *e1 = *(const struct direct **) d1; ++ const struct direct *e2 = *(const struct direct **) d2; ++ ++ return strcmp((char *) e1->d_name, (char *) e2->d_name); ++ } ++ ++ void courier_free_cdir (COURIER_S **cdir) ++ { ++ int i; ++ ++ if (!*cdir) ++ return; ++ ++ if ((*cdir)->path) fs_give((void **)&((*cdir)->path)); ++ for (i = 0; i < (*cdir)->total; i++) ++ if((*cdir)->data[i]->name) fs_give((void **)&((*cdir)->data[i]->name)); ++ fs_give((void **)&((*cdir)->data)); ++ fs_give((void **)&(*cdir)); ++ } ++ ++ COURIER_S *courier_get_cdir (int total) ++ { ++ COURIER_S *cdir; ++ ++ cdir = (COURIER_S *)fs_get(sizeof(COURIER_S)); ++ memset(cdir, 0, sizeof(COURIER_S)); ++ cdir->data = (COURIERLOCAL **) fs_get(total*sizeof(COURIERLOCAL *)); ++ memset(cdir->data, 0, sizeof(COURIERLOCAL *)); ++ cdir->total = total; ++ return cdir; ++ } ++ ++ int courier_search_list(COURIERLOCAL **data, char *name, int first, int last) ++ { ++ int try = (first + last)/2; ++ ++ if(!strstr(data[try]->name, name)){ ++ if(first == try) /* first == last || first + 1 == last */ ++ return strstr(data[last]->name, name) ? 1 : 0; ++ if(strcmp(data[try]->name, name) < 0) /*data[try] < name < data[end] */ ++ return courier_search_list(data, name, try, last); ++ else /* data[begin] < name < data[try] */ ++ return courier_search_list(data, name, first, try); ++ } ++ return 1; ++ } ++ ++ /* Lists all directories that are subdirectories of a given directory */ ++ ++ COURIER_S *courier_list_dir(char *curdir) ++ { ++ struct direct **names = NIL; ++ struct stat sbuf; ++ unsigned long ndir; ++ COURIER_S *cdir = NULL; ++ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], pathname[MAILTMPLEN], ++ realname[MAILTMPLEN]; ++ int i, j, scand, td; ++ ++ /* There are two cases, either curdir is ++ #mc/INBOX. #mc/INBOX.foo ++ or ++ #mc/Maildir/. #mc/Maildir/.foo ++ */ ++ strcpy(tmp,curdir + 4); ++ if(!strncmp(ucase(tmp), "INBOX", 5)) ++ strcpy(tmp, "#mc/INBOX."); ++ else{ ++ strcpy(tmp, curdir); ++ for (i = strlen(tmp) - 1; tmp[i] && tmp[i] != '/'; i--); ++ tmp[i+2] = '\0'; /* keep the last "." intact */ ++ } ++ maildir_file_path(tmp, realname, sizeof(realname)); ++ maildir_scandir (realname, &names, &ndir, &scand, COURIER); ++ ++ if (scand > 0){ ++ cdir = courier_get_cdir(ndir); ++ cdir->path = cpystr(realname); ++ for(i = 0, j = 0; i < ndir; i++){ ++ td = realname[strlen(realname) - 1] == '.' ++ && *names[i]->d_name == '.'; ++ snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, names[i]->d_name+1); ++ snprintf(pathname, sizeof(pathname), "%s%s", realname, names[i]->d_name + td); ++ if(stat(pathname, &sbuf) == 0 && S_ISDIR(sbuf.st_mode)){ ++ cdir->data[j] = (COURIERLOCAL *) fs_get(sizeof(COURIERLOCAL)); ++ cdir->data[j++]->name = cpystr(tmp2); ++ } ++ fs_give((void **)&names[i]); ++ } ++ cdir->total = j; ++ if(cdir->total == 0) ++ courier_free_cdir(&cdir); ++ } ++ if(names) ++ fs_give((void **) &names); ++ return cdir; ++ } ++ ++ void ++ courier_list_info(COURIER_S **cdirp, char *data, int i) ++ { ++ long style = *(long *) maildir_parameters(GET_COURIERSTYLE, NIL); ++ COURIER_S *cdir = *cdirp; ++ ++ if(maildir_valid(cdir->data[i]->name)){ ++ if(courier_search_list(cdir->data, data, 0, cdir->total - 1)) ++ cdir->data[i]->attribute = LATT_HASCHILDREN; ++ else ++ cdir->data[i]->attribute = (style == COURIER) ++ ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; ++ } ++ else ++ cdir->data[i]->attribute = LATT_NOSELECT; ++ cdir->data[i]->attribute += maildir_any_new_msgs(cdir->data[i]->name) ++ ? LATT_MARKED : LATT_UNMARKED; ++ } ++ ++ /* UID Support */ ++ /* Yes, I know I procastinated a lot about this, but here it is finally */ ++ ++ /* return code: ++ bigger than zero: this session can assign uids ++ zero: this session will not assign uid ++ smaller than zero: this session temporarily suspends assigning uids ++ */ ++ int ++ maildir_can_assign_uid (MAILSTREAM *stream) ++ { ++ unsigned int rv = 0; ++ int ownuid, existuid; ++ unsigned long t; ++ char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], *p, *s; ++ DIR *dir; ++ struct direct *d; ++ ++ if(!stream || stream->rdonly ++ || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) ++ return 0; ++ ++ if(mypid == (pid_t) 0) ++ mypid = getpid(); ++ ++ snprintf(tmp, sizeof(tmp), "%s.%d", MDUIDTEMP, mypid); ++ ++ ownuid = existuid = 0; ++ s = NULL; ++ while ((d = readdir(dir)) != NULL){ ++ if(strncmp(d->d_name, tmp, strlen(tmp)) == 0){ ++ existuid++; ownuid++; ++ if(ownuid > 1){ ++ snprintf(tmp2, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); ++ unlink(tmp2); ++ if(s){ ++ snprintf(tmp2, sizeof(tmp2), "%s/%s", LOCAL->dir, s); ++ unlink(tmp2); ++ fs_give((void **)&s); ++ } ++ } ++ else ++ s = cpystr(d->d_name); ++ } ++ else if(strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)) == 0) ++ existuid++; ++ } ++ ++ closedir(dir); ++ if(s) ++ fs_give((void **)&s); ++ ++ if(ownuid == 1 && existuid == 1) ++ rv = 1; ++ ++ if(ownuid == 0 && existuid == 0){ /* nobody owns the uid? */ ++ FILE *fp; ++ snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); ++ if(fp = fopen(tmp, "w")){ ++ fclose(fp); ++ if(LOCAL->uidtempfile) ++ fs_give((void **)&LOCAL->uidtempfile); ++ LOCAL->uidtempfile = cpystr(tmp); ++ } ++ rv = 1; ++ } ++ ++ if(ownuid == 0 && existuid > 0) /* someone else owns uid assignment */ ++ return 0; ++ ++ /* if we own the uid, check that we do not own it more than once ++ * or that we share ownership. If any of these situations happens, ++ * give up the ownership until we can recover it ++ */ ++ ++ if(ownuid > 0){ ++ if(ownuid > 1) /* impossible, two lock files for the same session */ ++ return (-1)*ownuid; ++ ++ if(ownuid != existuid){ /* lock files for different sessions */ ++ if(LOCAL->uidtempfile){ ++ unlink(LOCAL->uidtempfile); ++ fs_give((void **)&LOCAL->uidtempfile); ++ } ++ return (-1)*ownuid; ++ } ++ } ++ ++ return rv; ++ } ++ ++ void ++ maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, ++ unsigned long *uid_validity) ++ { ++ int createuid, deleteuid = 0; ++ char tmp[MAILTMPLEN], *s = NULL; ++ DIR *dir; ++ struct direct *d; ++ ++ if(uid_last) *uid_last = 0L; ++ if(uid_last && uid_validity) *uid_validity = time(0); ++ if(!stream || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) ++ return; ++ ++ while ((d = readdir(dir)) != NULL){ ++ if(!strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST))) ++ break; ++ } ++ createuid = d == NULL ? 1 : 0; ++ if(uid_last == NULL) ++ deleteuid++; ++ if(d){ ++ if(uid_last){ ++ s = d->d_name + strlen(MDUIDLAST) + 1; ++ *uid_last = strtoul(s, &s, 10); ++ if(!s || *s != '.'){ ++ deleteuid++; ++ createuid++; ++ *uid_last = 0L; ++ } ++ } ++ if(s && *s == '.'){ ++ if(uid_validity){ ++ s++; ++ *uid_validity = strtoul(s, &s, 10); ++ if(s && *s != '\0'){ ++ *uid_validity = time(0); ++ deleteuid++; ++ createuid++; ++ } ++ } ++ } ++ else{ ++ deleteuid++; ++ createuid++; ++ } ++ } ++ if(deleteuid){ ++ snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); ++ unlink(tmp); ++ } ++ if(createuid) ++ maildir_write_uid(stream, (uid_last ? *uid_last : stream->uid_last), ++ uid_validity ? *uid_validity : time(0)); ++ closedir(dir); ++ } ++ ++ void ++ maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, ++ unsigned long uid_validity) ++ { ++ char tmp[MAILTMPLEN]; ++ FILE *fp; ++ ++ if(!stream || stream->rdonly || !LOCAL || !LOCAL->dir) ++ return; ++ ++ snprintf(tmp, sizeof(tmp), "%s/%s.%010lu.%010lu", LOCAL->dir, MDUIDLAST, ++ uid_last, uid_validity); ++ if(fp = fopen(tmp, "w")) ++ fclose(fp); ++ } ++ ++ unsigned long ++ maildir_get_uid(char *name) ++ { ++ char *s; ++ unsigned long rv = 0L; ++ ++ if(!name || (s = strstr(name,MDUIDSEP)) == NULL) ++ return rv; ++ ++ s += strlen(MDUIDSEP); ++ rv = strtoul(s, NULL, 10); ++ return rv; ++ } ++ ++ ++ void ++ maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno) ++ { ++ char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; ++ MESSAGECACHE *elt; ++ ++ elt = mail_elt(stream, msgno); ++ if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) ++ return; ++ ++ snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); ++ t = MDFILE(elt); ++ if(s = strstr(MDFILE(elt), MDUIDSEP)){ ++ *s = '\0'; ++ s += strlen(MDUIDSEP); ++ strtoul(s, &s, 10); ++ snprintf(new, sizeof(new), "%s/%s/%s%s", LOCAL->dir, MDNAME(Cur), t, s); ++ if(rename(old, new) == 0){ ++ maildir_free_file_only ((void **)&elt->private.spare.ptr); ++ s = strrchr(new, '/'); ++ MDFILE(elt) = cpystr(s+1); ++ } ++ elt->private.uid = 0L; ++ } ++ } ++ ++ void ++ maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid) ++ { ++ int createuid, deleteuid = 0; ++ char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; ++ MESSAGECACHE *elt; ++ ++ elt = mail_elt(stream, msgno); ++ if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) ++ return; ++ ++ maildir_delete_uid(stream, msgno); ++ snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); ++ t = MDFILE(elt); ++ if((s = strrchr(MDFILE(elt),FLAGSEP)) != NULL){ ++ *s++ = '\0'; ++ snprintf(new, sizeof(new), "%s/%s/%s%s%lu%c%s", ++ LOCAL->dir, MDNAME(Cur), t, MDUIDSEP, uid, FLAGSEP, s); ++ if(rename(old, new) == 0){ ++ maildir_free_file_only ((void **)&elt->private.spare.ptr); ++ s = strrchr(new, '/'); ++ MDFILE(elt) = cpystr(s+1); ++ stream->uid_validity = time(0); ++ } ++ elt->private.uid = uid; ++ } ++ } ++ ++ void ++ maildir_uid_renew_tempfile(MAILSTREAM *stream) ++ { ++ char tmp[MAILTMPLEN]; ++ ++ if(!stream || stream->rdonly ++ || !LOCAL || !LOCAL->candouid || !LOCAL->dir || !LOCAL->uidtempfile) ++ return; ++ ++ if(mypid == (pid_t) 0) ++ mypid = getpid(); ++ ++ snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); ++ if(rename(LOCAL->uidtempfile, tmp) == 0){ ++ fs_give((void **)&LOCAL->uidtempfile); ++ LOCAL->uidtempfile = cpystr(tmp); ++ } ++ } +diff -rc alpine-2.21/imap/src/osdep/unix/maildir.h alpine-2.21.maildir/imap/src/osdep/unix/maildir.h +*** alpine-2.21/imap/src/osdep/unix/maildir.h Sun Feb 5 16:15:21 2017 +--- alpine-2.21.maildir/imap/src/osdep/unix/maildir.h Sun Feb 5 16:15:20 2017 +*************** +*** 0 **** +--- 1,226 ---- ++ /* ++ * A few definitions that try to make this module portable to other ++ * platforms (e.g. Cygwin). This module is based on the information from ++ * http://cr.yp.to/proto/maildir.html ++ */ ++ ++ /* First we deal with the separator character */ ++ #ifndef FLAGSEP ++ #define FLAGSEP ':' ++ #endif ++ #define SIZESEP ',' ++ ++ const char sep1[] = {FLAGSEP, '1', ',', '\0'}; /* experimental semantics*/ ++ const char sep2[] = {FLAGSEP, '2', ',', '\0'}; /* Flags Information */ ++ const char sep3[] = {FLAGSEP, '3', ',', '\0'}; /* Grrrr.... */ ++ ++ const char *sep[] = { sep1, sep2, sep3, NULL}; ++ ++ #define MDSEP(i) sep[((i) - 1)] ++ ++ /* Now we deal with flags. Woohoo! */ ++ typedef enum {Draft, Flagged, Passed, Replied, Seen, Trashed, ++ EmptyFlag, EndFlags} MdFlagNamesType; ++ const int mdimapflags[] = {Draft, Flagged, Replied, Seen, Trashed, EmptyFlag, EndFlags}; ++ const int mdkwdflags[] = {Passed, EmptyFlag, EndFlags}; ++ ++ /* this array lists the codes for mdflgnms (maildir flag names) above */ ++ const char *mdflags[] = { "D", "F", "P", "R", "S", "T", "", NULL}; ++ /* and as characters too */ ++ const char cmdflags[] = { 'D', 'F', 'P', 'R', 'S', 'T', '0', '\0'}; ++ ++ /* MDFLAG(Seen, elt->seen) */ ++ #define MDFLAG(i,j) mdflags[j ? (i) : EmptyFlag] ++ /* MDFLAGC(Seen) */ ++ #define MDFLAGC(i) cmdflags[(i)] ++ ++ /* Now we deal with the directory structure */ ++ typedef enum {Cur, Tmp, New, EndDir} DirNamesType; ++ char *mdstruct[] = {"cur", "tmp", "new", NULL}; ++ #define MDNAME(i) mdstruct[(i)] ++ #define MDFLD(tmp, dir, i) sprintf((tmp),"%s/%s", (dir), mdstruct[(i)]) ++ #define MSGPATH(tmp, dir, msg,i) sprintf((tmp),"%s/%s/%s", (dir), mdstruct[(i)],(msg)) ++ ++ /* Files associated to a maildir directory */ ++ ++ #define MDUIDVALIDITY ".uidvalidity" /* support for old maildirs */ ++ #define MDDIR ".mdir" /* this folder is a directory */ ++ #define MDUIDLAST ".uidlast" /* last assigned uid */ ++ #define MDUIDTEMP ".uidtemp" /* We assign uid's no one else */ ++ ++ ++ ++ /* Support of Courier Structure */ ++ #define CCLIENT 0 ++ #define COURIER 1 ++ #define IS_CCLIENT(t) \ ++ (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ ++ && ((t)[2] == 'd' || (t)[2] == 'D')\ ++ && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) ++ ++ #define IS_COURIER(t) \ ++ (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ ++ && ((t)[2] == 'c' || (t)[2] == 'C')\ ++ && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) ++ #define MDPREFIX(s) ((s) ? "#mc/" : "#md/") ++ #define MDSEPARATOR(s) ((s) ? '.' : '/') ++ ++ /* UID Support */ ++ ++ #define MAXTEMPUID (unsigned long) 180L ++ const char mduid[] = {',','u','=','\0'}; ++ #define MDUIDSEP mduid ++ ++ ++ /* Now we deal with messages filenames */ ++ char mdlocaldomain[MAILTMPLEN+1] = {'\0'}; ++ pid_t mypid = (pid_t) 0; ++ static char *mdfpath = NULL; ++ static char myMdInboxDir[50] = { '\0' };/* Location of the Maildir INBOX */ ++ static long CourierStyle = CCLIENT; ++ ++ #define CHUNK 16384 /* from unix.h */ ++ ++ typedef struct courier_local { ++ char *name; /* name of directory/folder */ ++ int attribute; /* attributes (children/marked/etc) */ ++ } COURIERLOCAL; ++ ++ typedef struct courier { ++ char *path; /* Path to collection */ ++ time_t scantime; /* time at which information was generated */ ++ int total; /* total number of elements in data */ ++ COURIERLOCAL **data; ++ } COURIER_S; ++ ++ /* In gdb this is the *(struct maildir_local *)stream->local structure */ ++ typedef struct maildir_local { ++ unsigned int dirty : 1; /* diskcopy needs updating */ ++ unsigned int courier : 1; /* It is Courier style file system */ ++ unsigned int link : 1; /* There is a symbolic link */ ++ int candouid; /* we can assign uids and no one else */ ++ char *uidtempfile; /* path to uid temp file */ ++ int fd; /* fd of open message */ ++ char *dir; /* mail directory name */ ++ char **path; /* path to directories cur, new and tmp */ ++ unsigned char *buf; /* temporary buffer */ ++ unsigned long buflen; /* current size of temporary buffer */ ++ time_t scantime; /* last time directory scanned */ ++ } MAILDIRLOCAL; ++ ++ /* Convenient access to local data */ ++ #define LOCAL ((MAILDIRLOCAL *) stream->local) ++ ++ typedef struct maildir_file_info { ++ char *name; /* name of the file */ ++ DirNamesType loc; /* location of this file */ ++ unsigned long pos; /* place in list where this file is listed */ ++ off_t size; /* size in bytes, on disk */ ++ time_t atime; /* last access time */ ++ time_t mtime; /* last modified time */ ++ time_t ctime; /* last changed time */ ++ } MAILDIRFILE; ++ ++ #define MDFILE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->name) ++ #define MDLOC(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->loc) ++ #define MDPOS(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->pos) ++ #define MDSIZE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->size) ++ #define MDATIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->atime) ++ #define MDMTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->mtime) ++ #define MDCTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->ctime) ++ ++ /* Function prototypes */ ++ ++ DRIVER *maildir_valid (char *name); ++ MAILSTREAM *maildir_open (MAILSTREAM *stream); ++ void maildir_close (MAILSTREAM *stream, long options); ++ long maildir_ping (MAILSTREAM *stream); ++ void maildir_check (MAILSTREAM *stream); ++ long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); ++ char *maildir_header (MAILSTREAM *stream,unsigned long msgno, ++ unsigned long *length, long flags); ++ void maildir_list (MAILSTREAM *stream,char *ref,char *pat); ++ void *maildir_parameters (long function,void *value); ++ int maildir_create_folder (char *mailbox); ++ long maildir_create (MAILSTREAM *stream,char *mailbox); ++ void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); /*check */ ++ long maildir_expunge (MAILSTREAM *stream, char *sequence, long options); ++ long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); ++ long maildir_append (MAILSTREAM *stream,char *mailbox, append_t af, void *data); ++ long maildir_delete (MAILSTREAM *stream,char *mailbox); ++ long maildir_rename (MAILSTREAM *stream,char *old,char *new); ++ long maildir_sub (MAILSTREAM *stream,char *mailbox); ++ long maildir_unsub (MAILSTREAM *stream,char *mailbox); ++ void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat); ++ void courier_list (MAILSTREAM *stream,char *ref, char *pat); ++ ++ /* utility functions */ ++ void courier_realname (char *name, char *realname); ++ long maildir_dirfmttest (char *name); ++ char *maildir_file (char *dst,char *name); ++ int maildir_select (const struct direct *name); ++ int maildir_namesort (const struct direct **d1, const struct direct **d2); ++ unsigned long antoul (char *seed); ++ unsigned long mdfntoul (char *name); ++ int courier_dir_select (const struct direct *name); ++ int courier_dir_sort (const struct direct **d1, const struct direct **d2); ++ long maildir_canonicalize (char *pattern,char *ref,char *pat); ++ void maildir_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); ++ void courier_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); ++ int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); ++ int maildir_valid_name (char *name); ++ int maildir_valid_dir (char *name); ++ int is_valid_maildir (char **name); ++ int maildir_message_exists(MAILSTREAM *stream,char *name, char *tmp); ++ char *maildir_remove_root(char *name); ++ char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, unsigned long *length,long flags); ++ unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, ++ DirNamesType dirtype); ++ int maildir_eliminate_duplicate (char *name, struct direct ***flist, ++ unsigned long *nfiles); ++ int maildir_doscandir (char *name, struct direct ***flist, int flag); ++ unsigned long maildir_scandir (char *name, struct direct ***flist, ++ unsigned long *nfiles, int *scand, int flag); ++ void maildir_parse_folder (MAILSTREAM *stream, int full); ++ void md_domain_name (void); ++ char *myrootdir (char *name); ++ char *mdirpath (void); ++ int maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype); ++ unsigned long maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, ++ DirNamesType dirtype, struct direct **names, unsigned long nfiles, int full); ++ int same_maildir_file(char *name1, char *name2); ++ int comp_maildir_file(char *name1, char *name2); ++ int maildir_message_in_list(char *msgname, struct direct **names, ++ unsigned long bottom, unsigned long top, unsigned long *pos); ++ void maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t); ++ int maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno); ++ void maildir_abort (MAILSTREAM *stream); ++ int maildir_contains_folder(char *dirname, char *name); ++ int maildir_is_dir(char *dirname, char *name); ++ int maildir_dir_is_empty(char *mailbox); ++ int maildir_create_work (char *mailbox, int loop); ++ void maildir_get_file (MAILDIRFILE **mdfile); ++ void maildir_free_file (void **mdfile); ++ void maildir_free_file_only (void **mdfile); ++ int maildir_any_new_msgs(char *mailbox); ++ void maildir_get_date(MAILSTREAM *stream, unsigned long msgno); ++ void maildir_fast (MAILSTREAM *stream,char *sequence,long flags); ++ ++ /* Courier server support */ ++ void courier_free_cdir (COURIER_S **cdir); ++ COURIER_S *courier_get_cdir (int total); ++ int courier_search_list(COURIERLOCAL **data, char *name, int first, int last); ++ COURIER_S *courier_list_dir(char *curdir); ++ void courier_list_info(COURIER_S **cdirp, char *data, int i); ++ ++ /* UID Support */ ++ int maildir_can_assign_uid (MAILSTREAM *stream); ++ void maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, ++ unsigned long *uid_validity); ++ void maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, ++ unsigned long uid_validity); ++ unsigned long maildir_get_uid(char *name); ++ void maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno); ++ void maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid); ++ void maildir_uid_renew_tempfile(MAILSTREAM *stream); ++ +diff -rc alpine-2.21/imap/src/osdep/unix/os_cyg.h alpine-2.21.maildir/imap/src/osdep/unix/os_cyg.h +*** alpine-2.21/imap/src/osdep/unix/os_cyg.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/imap/src/osdep/unix/os_cyg.h Sun Feb 5 16:15:20 2017 +*************** +*** 47,52 **** +--- 47,53 ---- + #define setpgrp setpgid + + #define SYSTEMUID 18 /* Cygwin returns this for SYSTEM */ ++ #define FLAGSEP ';' + #define geteuid Geteuid + uid_t Geteuid (void); + +diff -rc alpine-2.21/pith/conf.c alpine-2.21.maildir/pith/conf.c +*** alpine-2.21/pith/conf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/conf.c Sun Feb 5 16:15:20 2017 +*************** +*** 438,443 **** +--- 438,446 ---- + + CONF_TXT_T cf_text_newsrc_path[] = "Full path and name of NEWSRC file"; + ++ #ifndef _WINDOWS ++ CONF_TXT_T cf_text_maildir_location[] = "Location relative to your HOME directory of the directory where your INBOX\n# for the maildir format is located. Default value is \"Maildir\". If your\n# inbox is located at \"~/Maildir\" you do not need to change this value.\n# A common value is also \".maildir\""; ++ #endif + + /*---------------------------------------------------------------------- + These are the variables that control a number of pine functions. They +*************** +*** 644,649 **** +--- 647,656 ---- + NULL, cf_text_news_active}, + {"news-spool-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_news_spooldir}, ++ #ifndef _WINDOWS ++ {"maildir-location", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, ++ "Maildir Location", cf_text_maildir_location}, ++ #endif + {"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, +*************** +*** 2301,2306 **** +--- 2308,2319 ---- + mail_parameters(NULL, SET_NEWSSPOOL, + (void *)VAR_NEWS_SPOOL_DIR); + ++ #ifndef _WINDOWS ++ set_current_val(&vars[V_MAILDIR_LOCATION], TRUE, TRUE); ++ if(VAR_MAILDIR_LOCATION && VAR_MAILDIR_LOCATION[0]) ++ mail_parameters(NULL, SET_MDINBOXPATH, (void *)VAR_MAILDIR_LOCATION); ++ #endif ++ + /* guarantee a save default */ + set_current_val(&vars[V_DEFAULT_SAVE_FOLDER], TRUE, TRUE); + if(!VAR_DEFAULT_SAVE_FOLDER || !VAR_DEFAULT_SAVE_FOLDER[0]) +*************** +*** 2930,2935 **** +--- 2943,2952 ---- + 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}, ++ #ifndef _WINDOWS ++ {"use-courier-folder-list", "Courier Style Folder List", ++ F_COURIER_FOLDER_LIST, h_config_courier_list, PREF_FLDR, 0}, ++ #endif + + /* Addr book */ + {"combined-addrbook-display", "Combined Address Book Display", +*************** +*** 7036,7042 **** + int just_flip_value, EditWhich ew) + { + char **vp, *p, **lval, ***alval; +! int og, on_before, was_set; + char *err; + long l; + +--- 7053,7059 ---- + int just_flip_value, EditWhich ew) + { + char **vp, *p, **lval, ***alval; +! int og, on_before, was_set, i; + char *err; + long l; + +*************** +*** 7089,7094 **** +--- 7106,7118 ---- + + break; + ++ #ifndef _WINDOWS ++ case F_COURIER_FOLDER_LIST: ++ i = F_ON(f->id ,ps) ? 1 : 0; ++ mail_parameters(NULL,SET_COURIERSTYLE, (void *) &i); ++ break; /* COURIER == 1, CCLIENT == 0, see maildir.h */ ++ #endif ++ + case F_COLOR_LINE_IMPORTANT : + case F_DATES_TO_LOCAL : + clear_index_cache(ps->mail_stream, 0); +*************** +*** 7877,7882 **** +--- 7901,7910 ---- + return(h_config_newmailwidth); + case V_NEWSRC_PATH : + return(h_config_newsrc_path); ++ #ifndef _WINDOWS ++ case V_MAILDIR_LOCATION : ++ return(h_config_maildir_location); ++ #endif + case V_BROWSER : + return(h_config_browser); + case V_HISTORY : +diff -rc alpine-2.21/pith/conf.h alpine-2.21.maildir/pith/conf.h +*** alpine-2.21/pith/conf.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/conf.h Sun Feb 5 16:15:20 2017 +*************** +*** 256,261 **** +--- 256,265 ---- + #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 ++ #ifndef _WINDOWS ++ #define VAR_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].current_val.p ++ #define GLO_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].global_val.p ++ #endif + #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 +diff -rc alpine-2.21/pith/conftype.h alpine-2.21.maildir/pith/conftype.h +*** alpine-2.21/pith/conftype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/conftype.h Sun Feb 5 16:15:20 2017 +*************** +*** 119,124 **** +--- 119,127 ---- + , V_NEWSRC_PATH + , V_NEWS_ACTIVE_PATH + , V_NEWS_SPOOL_DIR ++ #ifndef _WINDOWS ++ , V_MAILDIR_LOCATION ++ #endif + , V_UPLOAD_CMD + , V_UPLOAD_CMD_PREFIX + , V_DOWNLOAD_CMD +*************** +*** 395,400 **** +--- 398,406 ---- + F_PASS_C1_CONTROL_CHARS, + F_SINGLE_FOLDER_LIST, + F_VERTICAL_FOLDER_LIST, ++ #ifndef _WINDOWS ++ F_COURIER_FOLDER_LIST, ++ #endif + F_TAB_CHK_RECENT, + F_AUTO_REPLY_TO, + F_VERBOSE_POST, +diff -rc alpine-2.21/pith/init.c alpine-2.21.maildir/pith/init.c +*** alpine-2.21/pith/init.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/init.c Sun Feb 5 16:15:20 2017 +*************** +*** 408,413 **** +--- 408,416 ---- + && stricmp(filename, folder_base)){ + #else + if(strncmp(filename, folder_base, folder_base_len) == 0 ++ #ifndef _WINDOWS ++ && filename[folder_base_len] != list_cntxt->dir->delim ++ #endif + && strcmp(filename, folder_base)){ + #endif + #endif +diff -rc alpine-2.21/pith/pattern.c alpine-2.21.maildir/pith/pattern.c +*** alpine-2.21/pith/pattern.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/pattern.c Sun Feb 5 16:15:20 2017 +*************** +*** 49,55 **** + #include "../pith/icache.h" + #include "../pith/ablookup.h" + #include "../pith/keyword.h" +! + + /* + * Internal prototypes +--- 49,57 ---- + #include "../pith/icache.h" + #include "../pith/ablookup.h" + #include "../pith/keyword.h" +! #ifndef _WINDOWS +! int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); +! #endif /* _WINDOWS */ + + /* + * Internal prototypes +*************** +*** 5485,5490 **** +--- 5487,5501 ---- + break; + + case '#': ++ #ifndef _WINDOWS ++ if(!struncmp(patfolder, "#md/", 4) ++ || !struncmp(patfolder, "#mc/", 4)){ ++ maildir_file_path(patfolder, tmp1, sizeof(tmp1)); ++ if(!strcmp(tmp1, stream->mailbox)) ++ match++; ++ break; ++ } ++ #endif + if(!strcmp(patfolder, stream->mailbox)) + match++; + +*************** +*** 7905,7911 **** + 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) +--- 7916,7922 ---- + 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, *save_dstfldr = NULL, *save_dstfldr2 = NULL; + #define FILTMSG_MAX 30 + + if(!stream) +*************** +*** 7939,7944 **** +--- 7950,7965 ---- + if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) + we_cancel = busy_cue(buf, NULL, 0); + ++ #ifndef _WINDOWS ++ if(!struncmp(dstfldr, "#md/", 4) || !struncmp(dstfldr, "#mc/", 4)){ ++ char tmp1[MAILTMPLEN]; ++ maildir_file_path(dstfldr, tmp1, sizeof(tmp1)); ++ save_dstfldr2 = dstfldr; ++ save_dstfldr = cpystr(tmp1); ++ dstfldr = save_dstfldr; ++ } ++ #endif ++ + if(!is_absolute_path(dstfldr) + && !(save_context = default_save_context(ps_global->context_list))) + save_context = ps_global->context_list; +*************** +*** 8002,8007 **** +--- 8023,8033 ---- + if(we_cancel) + cancel_busy_cue(buf[0] ? 0 : -1); + ++ if(save_dstfldr){ ++ fs_give((void **)&save_dstfldr); ++ dstfldr = save_dstfldr2; ++ } ++ + return(buf[0] != '\0'); + } + +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.maildir/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/pine.hlp Sun Feb 5 16:15:21 2017 +*************** +*** 22364,22369 **** +--- 22364,22465 ---- + <End of help on this topic> + + ++ ====== h_config_maildir_location ====== ++ ++ ++ OPTION: <!--#echo var="VAR_maildir-location"--> ++ ++ ++

    OPTION:

    ++ ++

    ++ This option should be used only if you have a Maildir folder which you ++ want to use as your INBOX. If this is not your case (or don't know what ++ this is), you can safely ignore this option. ++ ++

    ++ This option overrides the default directory Pine uses to find the location of ++ your INBOX, in case this is in Maildir format. The default value of this ++ option is "Maildir", but in some systems, this directory could have been ++ renamed (e.g. to ".maildir"). If this is your case use this option to change ++ the default. ++ ++

    ++ The value of this option is prefixed with the "~/" string to determine the ++ full path to your INBOX. ++ ++

    ++ You should probably read a few tips that ++ teach you how to configure your maildir for optimal performance. This ++ version also has support for the ++ Courier style file system when a maildir collection is accessed locally. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ ++ ====== h_config_maildir ===== ++ ++ ++ Maildir Support ++ ++ ++

    Maildir Support

    ++ ++ This version of Alpine has been enhanced with Maildir support. This text is ++ intended to be a reference on its support. ++

    ++ ++ A Maildir folder is a directory that contains three directories called ++ cur, tmp and new. A program that delivers mail (e.g. postfix) will put new ++ mail in the new directory. A program that reads mail will look for for old ++ messages in the cur directory, while it will look for new mail in the new ++ directory. ++

    ++ ++ In order to use maildir support it is better to set your inbox-path to the ++ value "#md/inbox" (without quotes). This assumes that your mail ++ delivery agent is delivering new mail to ~/Maildir/new. If the directory ++ where new mail is being delivered is not called "Maildir", you can set the ++ name of the subdirectory of home where it is being delivered in the configuration ++ variable. Most of the time you will not have to worry about the ++ variable, because it will probably be set by your ++ administrator in the pine.conf configuration file. ++

    ++ ++ One of the advantages of the Maildir support of this version of Alpine is ++ that you do not have to stop using folders in another styles (mbox, mbx, ++ etc.). This is desirable since the usage of a specific mail storage system ++ is a personal decision. Folders in the maildir format that are part of the ++ Mail collection will be recognized without any extra configuration of your ++ part. If your mail/ collection is located under the mail/ directory, then ++ creating a new maildir folder in this collection is done by pressing "A" ++ and entering the string "#driver.md/mail/newfolder". Observe that adding a ++ new folder as "newfolder" may not create such folder in maildir format. ++ ++

    ++ If you would like to have all folders created in the maildir format by ++ default, you do so by adding a Maildir Collection. In order to convert ++ your current mail/ collection into a maildir collection, edit the ++ collection and change the path variable from "mail/" to ++ "#md/mail". In a maildir collection folders of any other format ++ are ignored. ++ ++

    Finally, This version also has ++ support for the Courier style file system ++ when a maildir collection is accessed locally. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_literal_sig ===== + + +*************** +*** 30446,30451 **** +--- 30542,30590 ---- +

    + <End of help on this topic> + ++ ++ ====== h_config_courier_list ===== ++ ++ ++ FEATURE: <!--#echo var="FEAT_courier-folder-list"--> ++ ++ ++

    FEATURE:

    ++ ++ In a maildir collection, a folder could be used as a directory to store ++ folders. In the Courier server if you create a folder, then a directory ++ with the same name is created. If you use this patch to access a ++ collection created by the Courier server, then the display of such ++ collection will look confusing. The best way to access a maildir ++ collection created by the Courier server is by using the "#mc/" ++ prefix instead of the "#md/" prefix. If you use this alternate ++ prefix, then this feature applies to you, otherwise you can safely ignore ++ the text that follows. ++

    ++ Depending on if you have enabled the option ++ ++ a folder may be listed as "folder[.]", or as two entries in the ++ list by "folder" and "folder.". ++

    ++ If this option is disabled, Pine will list local folders that are in Courier ++ style format, as "folder", and those that are also directories as ++ "folder[.]". This makes the default display cleaner. ++

    ++ If this feature is enabled then creating folders in a maildir collection ++ will create a directory with the same name. If this feature is disabled, then ++ a folder is considered a directory only if it contains subfolders, so you can ++ not create a directory with the same name as an exisiting folder unless ++ you create a subfolder of that folder first (e.g. if you have a folder ++ called "foo" simply add "foo.bar" directly. This will ++ create the directory "foo" and the subfolder "bar" of it). ++

    ++ Observe that this feature works only for maildir collections that are accessed ++ locally. If a collection is accessed remotely then this feature has no value, ++ as the report is created in a server, and Pine only reports what received ++ from the server in this case. ++

    ++ <End of help on this topic> ++ + + ====== h_config_verbose_post ===== + +diff -rc alpine-2.21/pith/send.c alpine-2.21.maildir/pith/send.c +*** alpine-2.21/pith/send.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.maildir/pith/send.c Sun Feb 5 16:15:21 2017 +*************** +*** 47,52 **** +--- 47,55 ---- + + #include "../c-client/smtp.h" + #include "../c-client/nntp.h" ++ #ifndef _WINDOWS ++ int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); ++ #endif /* _WINDOWS */ + + + /* this is used in pine_send and pine_simple_send */ +*************** +*** 257,262 **** +--- 260,272 ---- + + if(exists & FEX_ISFILE){ + context_apply(tmp, p_cntxt, mbox, sizeof(tmp)); ++ #ifndef _WINDOWS ++ if (!struncmp(tmp, "#md/",4) || !struncmp(tmp, "#mc/", 4)){ ++ char tmp2[MAILTMPLEN]; ++ maildir_file_path(tmp, tmp2, sizeof(tmp2)); ++ strcpy(tmp, tmp2); ++ } ++ #endif + if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){ + /* + * The mbox is relative to the home directory. diff --git a/chappa-rules.patch b/chappa-rules.patch new file mode 100644 index 0000000..90eadd8 --- /dev/null +++ b/chappa-rules.patch @@ -0,0 +1,6242 @@ +diff -rc alpine-2.21/alpine/adrbkcmd.c alpine-2.21.rules/alpine/adrbkcmd.c +*** alpine-2.21/alpine/adrbkcmd.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/adrbkcmd.c Sun Feb 5 16:15:21 2017 +*************** +*** 4128,4133 **** +--- 4128,4135 ---- + * won't do anything, but will cause compose_mail to think there's + * already a role so that it won't try to confirm the default. + */ ++ if (ps_global->role) ++ fs_give((void **)&ps_global->role); + if(role) + role = copy_action(role); + else{ +*************** +*** 4135,4140 **** +--- 4137,4143 ---- + memset((void *)role, 0, sizeof(*role)); + role->nick = cpystr("Default Role"); + } ++ ps_global->role = cpystr(role->nick); + } + + compose_mail(addr, fcc, role, NULL, NULL); +diff -rc alpine-2.21/alpine/alpine.c alpine-2.21.rules/alpine/alpine.c +*** alpine-2.21/alpine/alpine.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/alpine.c Sun Feb 5 16:15:21 2017 +*************** +*** 485,490 **** +--- 485,491 ---- + /* Set up optional for user-defined display filtering */ + pine_state->tools.display_filter = dfilter; + pine_state->tools.display_filter_trigger = dfilter_trigger; ++ pine_state->tools.exec_rule = exec_function_rule; + + #ifdef _WINDOWS + if(ps_global->install_flag){ +*************** +*** 3162,3167 **** +--- 3163,3171 ---- + extern KBESC_T *kbesc; + + dprint((2, "goodnight_gracey:\n")); ++ strncpy(pine_state->cur_folder, pine_state->inbox_name, ++ sizeof(pine_state->cur_folder)); ++ pine_state->cur_folder[sizeof(pine_state->cur_folder) - 1] = '\0'; + + /* We want to do this here before we close up the streams */ + trim_remote_adrbks(); +diff -rc alpine-2.21/alpine/confscroll.c alpine-2.21.rules/alpine/confscroll.c +*** alpine-2.21/alpine/confscroll.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/confscroll.c Sun Feb 5 16:15:21 2017 +*************** +*** 51,56 **** +--- 51,57 ---- + #include "../pith/tempfile.h" + #include "../pith/pattern.h" + #include "../pith/charconv/utf8.h" ++ #include "../pith/rules.h" + + + #define CONFIG_SCREEN_HELP_TITLE _("HELP FOR SETUP CONFIGURATION") +*************** +*** 2436,2441 **** +--- 2437,2445 ---- + * Now go and set the current_val based on user_val changes + * above. Turn off command line settings... + */ ++ set_current_val((*cl)->var, ++ (strcmp((*cl)->var->name,"key-definition-rules") ? TRUE : FALSE), ++ FALSE); + set_current_val((*cl)->var, TRUE, FALSE); + fix_side_effects(ps, (*cl)->var, 0); + +*************** +*** 5200,5205 **** +--- 5204,5233 ---- + var == &ps->vars[V_ABOOK_FORMATS]){ + addrbook_reset(); + } ++ else if(var == &ps->vars[V_INDEX_RULES]){ ++ if(ps_global->rule_list) ++ free_parsed_rule_list(&ps_global->rule_list); ++ create_rule_list(ps->vars); ++ reset_index_format(); ++ clear_index_cache(ps->mail_stream, 0); ++ } ++ else if(var == &ps->vars[V_COMPOSE_RULES] || ++ var == &ps->vars[V_FORWARD_RULES] || ++ var == &ps->vars[V_KEY_RULES] || ++ var == &ps->vars[V_REPLACE_RULES] || ++ var == &ps->vars[V_REPLY_INDENT_RULES] || ++ var == &ps->vars[V_REPLY_LEADIN_RULES] || ++ var == &ps->vars[V_RESUB_RULES] || ++ var == &ps->vars[V_SAVE_RULES] || ++ var == &ps->vars[V_SMTP_RULES] || ++ var == &ps->vars[V_SORT_RULES] || ++ var == &ps->vars[V_STARTUP_RULES] || ++ var == &ps->vars[V_THREAD_DISP_STYLE_RULES] || ++ var == &ps->vars[V_THREAD_INDEX_STYLE_RULES]){ ++ if(ps_global->rule_list) ++ free_parsed_rule_list(&ps_global->rule_list); ++ create_rule_list(ps->vars); ++ } + else if(var == &ps->vars[V_INDEX_FORMAT]){ + reset_index_format(); + clear_index_cache(ps->mail_stream, 0); +diff -rc alpine-2.21/alpine/dispfilt.c alpine-2.21.rules/alpine/dispfilt.c +*** alpine-2.21/alpine/dispfilt.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/dispfilt.c Sun Feb 5 16:15:21 2017 +*************** +*** 461,463 **** +--- 461,523 ---- + + return(passed); + } ++ ++ char * ++ exec_function_rule(char *rawcmd, gf_io_t input_gc, gf_io_t output_pc) ++ { ++ char *status = NULL, *cmd, *tmpfile = NULL; ++ ++ if((cmd = expand_filter_tokens(rawcmd,NULL,&tmpfile,NULL,NULL,NULL,NULL,NULL)) != NULL){ ++ suspend_busy_cue(); ++ ps_global->mangled_screen = 1; ++ if(tmpfile){ ++ PIPE_S *filter_pipe; ++ FILE *fp; ++ gf_io_t gc, pc; ++ STORE_S *tmpf_so; ++ ++ /* write the tmp file */ ++ if((tmpf_so = so_get(FileStar, tmpfile, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){ ++ /* copy input to tmp file */ ++ gf_set_so_writec(&pc, tmpf_so); ++ gf_filter_init(); ++ status = gf_pipe(input_gc, pc); ++ gf_clear_so_writec(tmpf_so); ++ if(so_give(&tmpf_so) != 0 && status == NULL) ++ status = error_description(errno); ++ ++ /* prepare the terminal in case the filter uses it */ ++ if(status == NULL){ ++ if((filter_pipe = open_system_pipe(cmd, NULL, NULL, ++ PIPE_USER|PIPE_PROT|PIPE_NOSHELL|PIPE_SILENT, ++ 0, pipe_callback, NULL)) != NULL){ ++ if(close_system_pipe(&filter_pipe, NULL, pipe_callback) == 0){ ++ /* pull result out of tmp file */ ++ if((fp = our_fopen(tmpfile, "rb")) != NULL){ ++ gf_set_readc(&gc, fp, 0L, FileStar, READ_FROM_LOCALE); ++ gf_filter_init(); ++ status = gf_pipe(gc, output_pc); ++ fclose(fp); ++ } ++ else ++ status = "Can't read result of EXEC command"; ++ } ++ else ++ status = "EXEC command command returned error."; ++ } ++ else ++ status = "Can't open pipe for EXEC command"; ++ } ++ ++ our_unlink(tmpfile); ++ } ++ else ++ status = "Can't open EXEC command tmp file"; ++ } ++ ++ resume_busy_cue(0); ++ fs_give((void **)&cmd); ++ } ++ ++ return(status); ++ } +diff -rc alpine-2.21/alpine/dispfilt.h alpine-2.21.rules/alpine/dispfilt.h +*** alpine-2.21/alpine/dispfilt.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/dispfilt.h Sun Feb 5 16:15:21 2017 +*************** +*** 25,31 **** + char *expand_filter_tokens(char *, ENVELOPE *, char **, char **, char **, int *, int *, int *); + char *filter_session_key(void); + char *filter_data_file(int); +! + + + #endif /* PINE_DISPFILT_INCLUDED */ +--- 25,31 ---- + char *expand_filter_tokens(char *, ENVELOPE *, char **, char **, char **, int *, int *, int *); + char *filter_session_key(void); + char *filter_data_file(int); +! char *exec_function_rule(char *, gf_io_t, gf_io_t); + + + #endif /* PINE_DISPFILT_INCLUDED */ +diff -rc alpine-2.21/alpine/folder.c alpine-2.21.rules/alpine/folder.c +*** alpine-2.21/alpine/folder.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/folder.c Sun Feb 5 16:15:21 2017 +*************** +*** 248,254 **** + dprint((1, "=== folder_screen called ====\n")); + mailcap_free(); /* free resources we won't be using for a while */ + ps->next_screen = SCREEN_FUN_NULL; +! + /* Initialize folder state and dispatches */ + memset(&fs, 0, sizeof(FSTATE_S)); + fs.context = cntxt; +--- 248,254 ---- + dprint((1, "=== folder_screen called ====\n")); + mailcap_free(); /* free resources we won't be using for a while */ + ps->next_screen = SCREEN_FUN_NULL; +! strcpy(ps->screen_name, "folder"); + /* Initialize folder state and dispatches */ + memset(&fs, 0, sizeof(FSTATE_S)); + fs.context = cntxt; +*************** +*** 345,350 **** +--- 345,351 ---- + pine_mail_close(*fs.cache_streamp); + + ps->prev_screen = folder_screen; ++ strcpy(ps->screen_name, "unknown"); + } + + +diff -rc alpine-2.21/alpine/mailcmd.c alpine-2.21.rules/alpine/mailcmd.c +*** alpine-2.21/alpine/mailcmd.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/mailcmd.c Sun Feb 5 16:15:21 2017 +*************** +*** 73,78 **** +--- 73,79 ---- + #include "../pith/tempfile.h" + #include "../pith/search.h" + #include "../pith/margin.h" ++ #include "../pith/rules.h" + #ifdef _WINDOWS + #include "../pico/osdep/mswin.h" + #endif +*************** +*** 2657,2662 **** +--- 2658,2666 ---- + role->nick = cpystr("Default Role"); + } + ++ if(state->role) ++ fs_give((void **)&state->role); ++ state->role = cpystr(role->nick); /* remember the role */ + state->redrawer = NULL; + switch(action){ + case 'c': +*************** +*** 2707,2718 **** + char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section, + SaveDel *dela, SavePreserveOrder *prea) + { +! int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0; + int delindex, preindex, r; + char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN]; + char *buf = tmp_20k_buf; + char shortbuf[200]; +! char *folder; + HelpType help; + SaveDel del = DontAsk; + SavePreserveOrder pre = DontAskPreserve; +--- 2711,2722 ---- + char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section, + SaveDel *dela, SavePreserveOrder *prea) + { +! int rc, ku = -1, n = 0, flags, last_rc = 0, saveable_count = 0, done = 0; + int delindex, preindex, r; + char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN]; + char *buf = tmp_20k_buf; + char shortbuf[200]; +! char *folder, folder2[MAXPATH]; + HelpType help; + SaveDel del = DontAsk; + SavePreserveOrder pre = DontAskPreserve; +*************** +*** 2720,2725 **** +--- 2724,2730 ---- + static HISTORY_S *history = NULL; + CONTEXT_S *tc; + ESCKEY_S ekey[10]; ++ RULE_RESULT *rule; + + if(!cntxt) + alpine_panic("no context ptr in save_prompt"); +*************** +*** 2729,2734 **** +--- 2734,2748 ---- + if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt))) + return(0); /* message expunged! */ + ++ if (rule = get_result_rule(V_SAVE_RULES, FOR_SAVE, env)){ ++ strncpy(folder2,rule->result,sizeof(folder2)-1); ++ folder2[sizeof(folder2)-1] = '\0'; ++ folder = folder2; ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ + /* how many context's can be saved to... */ + for(tc = state->context_list; tc; tc = tc->next) + if(!NEWS_TEST(tc)) +diff -rc alpine-2.21/alpine/mailindx.c alpine-2.21.rules/alpine/mailindx.c +*** alpine-2.21/alpine/mailindx.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/mailindx.c Sun Feb 5 16:15:21 2017 +*************** +*** 229,234 **** +--- 229,236 ---- + state->prev_screen = mail_index_screen; + state->next_screen = SCREEN_FUN_NULL; + ++ setup_threading_display_style(); ++ + if(THRD_AUTO_VIEW() + && sp_viewing_a_thread(state->mail_stream) + && state->view_skipped_index +*************** +*** 240,249 **** +--- 242,255 ---- + + adjust_cur_to_visible(state->mail_stream, state->msgmap); + ++ strcpy(state->screen_name,"index"); ++ + if(THRD_INDX()) + thread_index_screen(state); + else + index_index_screen(state); ++ ++ strcpy(state->screen_name,"unknown"); + } + + +diff -rc alpine-2.21/alpine/mailview.c alpine-2.21.rules/alpine/mailview.c +*** alpine-2.21/alpine/mailview.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/mailview.c Sun Feb 5 16:15:21 2017 +*************** +*** 243,248 **** +--- 243,250 ---- + ps->prev_screen = mail_view_screen; + ps->force_prefer_plain = ps->force_no_prefer_plain = 0; + ++ strcpy(ps->screen_name, "text"); ++ + if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){ + q_status_message(SM_ORDER | SM_DING, 0, 3, + _("Screen too small to view message")); +*************** +*** 479,484 **** +--- 481,488 ---- + } + while(ps->next_screen == SCREEN_FUN_NULL); + ++ strcpy(ps->screen_name, "unknown"); ++ + if (prefix && *prefix) + fs_give((void **)&prefix); + if(we_cancel) +diff -rc alpine-2.21/alpine/osdep/termin.gen.c alpine-2.21.rules/alpine/osdep/termin.gen.c +*** alpine-2.21/alpine/osdep/termin.gen.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/osdep/termin.gen.c Sun Feb 5 16:15:21 2017 +*************** +*** 33,38 **** +--- 33,40 ---- + #include "../../pith/newmail.h" + #include "../../pith/conf.h" + #include "../../pith/busy.h" ++ #include "../../pith/list.h" ++ #include "../../pith/rules.h" + + #include "../../pico/estruct.h" + #include "../../pico/pico.h" +*************** +*** 72,78 **** + * Generic tty input routines + */ + +! + /*---------------------------------------------------------------------- + Read a character from keyboard with timeout + Input: none +--- 74,81 ---- + * Generic tty input routines + */ + +! void process_init_cmds(struct pine *, char **); +! void queue_init_errors(struct pine *); + /*---------------------------------------------------------------------- + Read a character from keyboard with timeout + Input: none +*************** +*** 114,119 **** +--- 117,157 ---- + *utf8str = NULL; + + ucs = read_char(tm); ++ if(!ps_global->initial_cmds){ ++ RULE_RESULT *rule; ++ char **list = NULL, *error = NULL; ++ int commas = 0, k; /* From args.c */ ++ ++ ps_global->pressed_key = cpystr(pretty_command(ucs)); ++ rule = (RULE_RESULT *)get_result_rule(V_KEY_RULES, FOR_KEY, NULL); ++ if(ps_global->pressed_key) ++ fs_give((void **)&ps_global->pressed_key); ++ if (rule){ ++ for(k = 0; rule->result[k]; k++) ++ if(rule->result[k] == ',') commas++; ++ list = parse_list(rule->result, commas+1, 0, &error); ++ if(error) ++ sprintf(tmp_20k_buf, "Error in parsing command list: %s, %s", ++ rule->result, error); ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ if(error){ ++ q_status_message(SM_ORDER | SM_DING, 0, 2, tmp_20k_buf); ++ return (NO_OP_COMMAND); ++ } ++ process_init_cmds(ps_global, list); ++ if(ps_global->init_errs){ ++ queue_init_errors(ps_global); ++ return (NO_OP_COMMAND); ++ } ++ ucs = read_char(tm); ++ ps_global->in_init_seq = 1; /* no output please */ ++ for(k = 0; k < commas; k++) ++ if(list[k]) fs_give((void **)&list[k]); ++ if (list) fs_give((void **)list); ++ } ++ } + if(ucs != NO_OP_COMMAND && ucs != NO_OP_IDLE && ucs != KEY_RESIZE) + zero_new_mail_count(); + +*************** +*** 1158,1163 **** +--- 1196,1202 ---- + if(!*ps_global->initial_cmds && ps_global->free_initial_cmds){ + fs_give((void **) &ps_global->free_initial_cmds); + ps_global->initial_cmds = NULL; ++ firsttime = (char) 1; + } + + return(ret); +diff -rc alpine-2.21/alpine/reply.c alpine-2.21.rules/alpine/reply.c +*** alpine-2.21/alpine/reply.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/reply.c Sun Feb 5 16:15:21 2017 +*************** +*** 62,68 **** + #include "../pith/tempfile.h" + #include "../pith/busy.h" + #include "../pith/ablookup.h" +! + + /* + * Internal Prototypes +--- 62,69 ---- + #include "../pith/tempfile.h" + #include "../pith/busy.h" + #include "../pith/ablookup.h" +! #include "../pith/copyaddr.h" +! #include "../pith/rules.h" + + /* + * Internal Prototypes +*************** +*** 109,119 **** + long msgno, j, totalm, rflags, *seq = NULL; + int i, include_text = 0, times = -1, warned = 0, rv = 0, + flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0; +! int rolemsg = 0, copytomsg = 0; + gf_io_t pc; + PAT_STATE dummy; + REDRAFT_POS_S *redraft_pos = NULL; + ACTION_S *role = NULL, *nrole; + #if defined(DOS) && !defined(_WINDOWS) + char *reserve; + #endif +--- 110,121 ---- + long msgno, j, totalm, rflags, *seq = NULL; + int i, include_text = 0, times = -1, warned = 0, rv = 0, + flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0; +! int rolemsg = 0, copytomsg = 0, do_role_early = 0; + gf_io_t pc; + PAT_STATE dummy; + REDRAFT_POS_S *redraft_pos = NULL; + ACTION_S *role = NULL, *nrole; ++ RULE_RESULT *rule; + #if defined(DOS) && !defined(_WINDOWS) + char *reserve; + #endif +*************** +*** 139,144 **** +--- 141,209 ---- + && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global)) + reply_raw_body = 1; + ++ /* Setup possible role */ ++ if(role_arg) ++ role = copy_action(role_arg); ++ ++ if(!role && F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)){ ++ for(msgno = mn_first_cur(pine_state->msgmap); ++ msgno > 0L; msgno = mn_next_cur(pine_state->msgmap)){ ++ ++ env = pine_mail_fetchstructure(pine_state->mail_stream, ++ mn_m2raw(pine_state->msgmap, msgno), ++ NULL); ++ if(!env) { ++ q_status_message1(SM_ORDER,3,4, ++ _("Error fetching message %s. Can't reply to it."), ++ long2string(msgno)); ++ goto done_early; ++ } ++ ++ if(rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE , env)){ ++ RULELIST *list = get_rulelist_from_code(V_REPLY_INDENT_RULES, ++ ps_global->rule_list); ++ RULE_S *prule = get_rule(list, rule->number); ++ if(condition_contains_token(prule->condition, ROLE_TOKEN)) ++ do_role_early++; ++ if(rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ } ++ } ++ ++ if(do_role_early){ ++ rflags = ROLE_REPLY; ++ if(nonempty_patterns(rflags, &dummy)){ ++ /* setup default role */ ++ nrole = NULL; ++ j = mn_first_cur(pine_state->msgmap); ++ do { ++ role = nrole; ++ nrole = set_role_from_msg(pine_state, rflags, ++ mn_m2raw(pine_state->msgmap, j), ++ NULL); ++ } while(nrole && (!role || nrole == role) ++ && (j=mn_next_cur(pine_state->msgmap)) > 0L); ++ ++ if(!role || nrole == role) ++ role = nrole; ++ else ++ role = NULL; ++ ++ if(confirm_role(rflags, &role)) ++ role = combine_inherited_role(role); ++ else{ /* cancel reply */ ++ role = NULL; ++ cmd_cancelled("Reply"); ++ goto done_early; ++ } ++ } ++ } ++ ++ if (role) ++ ps_global->role = cpystr(role->nick); /* remember the role */ ++ + /* + * We may have to loop through first to figure out what default + * reply-indent-string to offer... +*************** +*** 287,294 **** + outgoing->subject = cpystr("Re: several messages"); + } + } +! else +! outgoing->subject = reply_subject(env->subject, NULL, 0); + } + + /* fill reply header */ +--- 352,369 ---- + outgoing->subject = cpystr("Re: several messages"); + } + } +! else{ +! RULE_RESULT *rule; +! rule = get_result_rule(V_RESUB_RULES,FOR_RESUB|FOR_TRIM , env); +! if (rule){ +! outgoing->subject = reply_subject(rule->result, NULL, 0); +! if (rule->result) +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! else +! outgoing->subject = reply_subject(env->subject, NULL, 0); +! } + } + + /* fill reply header */ +*************** +*** 307,319 **** + if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */ + goto done_early; + +! /* Setup possible role */ +! if (ps_global->reply.role_chosen) +! role = ps_global->reply.role_chosen; +! else if(role_arg) +! role = copy_action(role_arg); +! +! if(!role){ + rflags = ROLE_REPLY; + if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){ + /* setup default role */ +--- 382,388 ---- + if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */ + goto done_early; + +! if(!do_role_early){ + rflags = ROLE_REPLY; + if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){ + /* setup default role */ +*************** +*** 724,729 **** +--- 793,801 ---- + if(prefix) + fs_give((void **)&prefix); + ++ if (ps_global->role) ++ fs_give((void **)&ps_global->role); ++ + if(fcc) + fs_give((void **) &fcc); + +*************** +*** 1598,1606 **** + } + } + +! if(role) + q_status_message1(SM_ORDER, 3, 4, + _("Forwarding using role \"%s\""), role->nick); + + if(role && role->template){ + char *filtered; +--- 1670,1683 ---- + } + } + +! if (ps_global->role) +! fs_give((void **)&ps_global->role); +! +! if(role){ + q_status_message1(SM_ORDER, 3, 4, + _("Forwarding using role \"%s\""), role->nick); ++ ps_global->role = cpystr(role->nick); ++ } + + if(role && role->template){ + char *filtered; +*************** +*** 1832,1837 **** +--- 1909,1915 ---- + #if defined(DOS) && !defined(_WINDOWS) + free((void *)reserve); + #endif ++ outgoing->sparep = env && env->from ? copyaddr(env->from) : NULL; + pine_send(outgoing, &body, "FORWARD MESSAGE", + role, NULL, &reply, redraft_pos, + NULL, NULL, 0); +diff -rc alpine-2.21/alpine/roleconf.c alpine-2.21.rules/alpine/roleconf.c +*** alpine-2.21/alpine/roleconf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/roleconf.c Sun Feb 5 16:15:21 2017 +*************** +*** 7706,7711 **** +--- 7706,7716 ---- + if(apval) + *apval = (role && role->nick) ? cpystr(role->nick) : NULL; + ++ if (ps_global->role) ++ fs_give((void **)&ps_global->role); ++ if (role && role->nick) ++ ps_global->role = cpystr(role->nick); ++ + if((*cl)->value) + fs_give((void **)&((*cl)->value)); + +diff -rc alpine-2.21/alpine/send.c alpine-2.21.rules/alpine/send.c +*** alpine-2.21/alpine/send.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/alpine/send.c Sun Feb 5 16:15:21 2017 +*************** +*** 63,69 **** + #include "../pith/mimetype.h" + #include "../pith/send.h" + #include "../pith/smime.h" +! + + typedef struct body_particulars { + unsigned short type, encoding, had_csp; +--- 63,69 ---- + #include "../pith/mimetype.h" + #include "../pith/send.h" + #include "../pith/smime.h" +! #include "../pith/rules.h" + + typedef struct body_particulars { + unsigned short type, encoding, had_csp; +*************** +*** 240,245 **** +--- 240,250 ---- + role->nick = cpystr("Default Role"); + } + ++ if (ps_global->role) ++ fs_give((void **)&ps_global->role); ++ ++ ps_global->role = cpystr(role->nick); ++ + pine_state->redrawer = NULL; + compose_mail(NULL, NULL, role, NULL, NULL); + free_action(&role); +*************** +*** 449,456 **** + + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; +! if(role) + role = combine_inherited_role(role); + } + break; + +--- 454,465 ---- + + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; +! if (ps_global->role) +! fs_give((void **)&ps_global->role); +! if(role){ + role = combine_inherited_role(role); ++ ps_global->role = cpystr(role->nick); ++ } + } + break; + +*************** +*** 645,653 **** + } + } + +! if(role) + q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""), + role->nick); + + /* + * The type of storage object allocated below is vitally +--- 654,667 ---- + } + } + +! if (ps_global->role) +! fs_give((void **)&ps_global->role); +! +! if(role){ + q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""), + role->nick); ++ ps_global->role = cpystr(role->nick); ++ } + + /* + * The type of storage object allocated below is vitally +*************** +*** 2476,2481 **** +--- 2490,2515 ---- + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); ++ if (!strncmp(pf->name,"Lcc",3) && addr && *addr){ ++ RULE_RESULT *rule; ++ ++ outgoing->date = (unsigned char *) cpystr(addr); ++ ps_global->procid = cpystr("fwd-lcc"); ++ rule = get_result_rule(V_FORWARD_RULES, ++ FOR_COMPOSE|FOR_TRIM, outgoing); ++ if (rule){ ++ addr = cpystr(rule->result); ++ removing_trailing_white_space(addr); ++ (void)removing_extra_stuff(addr); ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ fs_give((void **)&ps_global->procid); ++ if (outgoing->date) ++ fs_give((void **)&outgoing->date); ++ } ++ + rfc822_parse_adrlist(pf->addr, addr, + ps_global->maildomain); + fs_give((void **)&addr); +diff -rc alpine-2.21/pith/Makefile.am alpine-2.21.rules/pith/Makefile.am +*** alpine-2.21/pith/Makefile.am Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/Makefile.am Sun Feb 5 16:15:21 2017 +*************** +*** 26,32 **** + 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 + +--- 26,32 ---- + 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 rules.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 + +diff -rc alpine-2.21/pith/Makefile.in alpine-2.21.rules/pith/Makefile.in +*** alpine-2.21/pith/Makefile.in Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/Makefile.in Sun Feb 5 16:15:21 2017 +*************** +*** 147,153 **** + 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) \ +--- 147,153 ---- + margin.$(OBJEXT) mimedesc.$(OBJEXT) mimetype.$(OBJEXT) \ + msgno.$(OBJEXT) newmail.$(OBJEXT) news.$(OBJEXT) \ + pattern.$(OBJEXT) pipe.$(OBJEXT) readfile.$(OBJEXT) \ +! remote.$(OBJEXT) reply.$(OBJEXT) rfc2231.$(OBJEXT) rules.$(OBJEXT) \ + save.$(OBJEXT) search.$(OBJEXT) sequence.$(OBJEXT) \ + send.$(OBJEXT) sort.$(OBJEXT) state.$(OBJEXT) status.$(OBJEXT) \ + store.$(OBJEXT) stream.$(OBJEXT) string.$(OBJEXT) \ +*************** +*** 442,448 **** + 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 + +--- 442,448 ---- + 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 rules.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 + +*************** +*** 574,579 **** +--- 574,580 ---- + @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@ ++ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rules.Po@am__quote@ + + .c.o: + @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +diff -rc alpine-2.21/pith/adrbklib.c alpine-2.21.rules/pith/adrbklib.c +*** alpine-2.21/pith/adrbklib.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/adrbklib.c Sun Feb 5 16:15:21 2017 +*************** +*** 5136,5143 **** + 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; +--- 5136,5149 ---- + if(as.cur >= as.how_many_personals) + pab->type |= GLOBAL; + +! if(ps_global->mail_stream && +! ps_global->mail_stream->lock && (pab->type & REMOTE_VIA_IMAP)){ +! as.initialized = 0; +! pab->access = NoAccess; +! } +! else{ +! pab->access = adrbk_access(pab); +! } + /* global address books are forced readonly */ + if(pab->type & GLOBAL && pab->access != NoAccess) + pab->access = ReadOnly; +diff -rc alpine-2.21/pith/conf.c alpine-2.21.rules/pith/conf.c +*** alpine-2.21/pith/conf.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/conf.c Sun Feb 5 16:15:21 2017 +*************** +*** 29,34 **** +--- 29,35 ---- + #include "../pith/remote.h" + #include "../pith/keyword.h" + #include "../pith/mailview.h" ++ #include "../pith/rules.h" + #include "../pith/list.h" + #include "../pith/status.h" + #include "../pith/ldap.h" +*************** +*** 222,227 **** +--- 223,258 ---- + + 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_compose_rules[] = "Allows a user to set rules when composing messages."; ++ ++ CONF_TXT_T cf_text_forward_rules[] = "Allows a user to set rules when forwarding messages."; ++ ++ CONF_TXT_T cf_text_reply_rules[] = "Allows a user to set rules when replying messages."; ++ ++ CONF_TXT_T cf_text_index_rules[] = "Allows a user to supersede global index format variable in designated folders."; ++ ++ CONF_TXT_T cf_text_key_def_rules[] = "Allows a user to override keystrokes in certain screens."; ++ ++ CONF_TXT_T cf_text_replace_rules[] = "Allows a user to change the form a specify field in the index-format is \n# displayed."; ++ ++ CONF_TXT_T cf_text_reply_indent_rules[] = "Allows a user to change the form a specify a reply-indent-string\n# based of rules."; ++ ++ CONF_TXT_T cf_text_reply_leadin_rules[] = "Allows a user to replace the reply-leadin message based on different parameters."; ++ ++ CONF_TXT_T cf_text_reply_subject_rules[] = "Allows a user to replace the subject of a message in a customs based way"; ++ ++ CONF_TXT_T cf_text_thread_displaystyle_rule[] = "Allows a user to specify the threading style of specific folders"; ++ ++ CONF_TXT_T cf_text_thread_indexstyle_rule[] = "Allows a user to specify the threading index style of specific folders"; ++ ++ CONF_TXT_T cf_text_save_rules[] = "Allows a user to specify a save folder message for specific senders or folders."; ++ ++ CONF_TXT_T cf_text_smtp_rules[] = "Allows a user to specify a smtp server to be used when sending e-mail,\n# according to the rules specified here."; ++ ++ CONF_TXT_T cf_text_sort_rules[] = "Allows a user to specify the sort default order of a specific folder."; ++ ++ CONF_TXT_T cf_text_startup_rules[] = "Allows a user to specify the position of a highlighted message when opening a \n# folder."; ++ + CONF_TXT_T cf_text_speller[] = "Specifies the program invoked by ^T in the Composer."; + + #ifdef _WINDOWS +*************** +*** 550,555 **** +--- 581,614 ---- + 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}, ++ {"threading-display-style-rule", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Threading Display Style Rule", cf_text_thread_displaystyle_rule}, ++ {"threading-index-style-rule", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Threading Index Style Rule", cf_text_thread_indexstyle_rule}, ++ {"compose-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Compose Rules", cf_text_compose_rules}, ++ {"forward-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Forward Rules", cf_text_forward_rules}, ++ {"index-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, ++ "Index Rules", cf_text_index_rules}, ++ {"key-definition-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, ++ "Key Definition Rules", cf_text_key_def_rules}, ++ {"replace-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, ++ "Replace Rules", cf_text_replace_rules}, ++ {"reply-indent-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Reply Indent Rules", cf_text_reply_indent_rules}, ++ {"reply-leadin-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, ++ "Reply Leadin Rules", cf_text_reply_leadin_rules}, ++ {"reply-subject-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, ++ "Reply Subject Rules", cf_text_reply_subject_rules}, ++ {"save-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Save Rules", cf_text_save_rules}, ++ {"smtp-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Smtp Rules", cf_text_smtp_rules}, ++ {"sort-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Sort Rules", cf_text_sort_rules}, ++ {"startup-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, ++ "Startup Rules", cf_text_startup_rules}, + #ifndef _WINDOWS + {"display-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_disp_char_set}, +*************** +*** 2635,2640 **** +--- 2694,2700 ---- + if(cmds_f) + (*cmds_f)(ps, VAR_INIT_CMD_LIST); + ++ (void)create_rule_list(ps_global->vars); + #ifdef _WINDOWS + mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global)); + #endif /* _WINDOWS */ +*************** +*** 3081,3086 **** +--- 3141,3148 ---- + 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-move-read-msgs-using-rules", "Auto Move Read Messages Using Rules", ++ F_AUTO_READ_MSGS_RULES, h_config_auto_read_msgs_rules, PREF_MISC, 0}, + {"auto-unselect-after-apply", NULL, + F_AUTO_UNSELECT, h_config_auto_unselect, PREF_MISC, 0}, + {"auto-unzoom-after-apply", NULL, +*************** +*** 7699,7704 **** +--- 7761,7794 ---- + return(h_config_ab_sort_rule); + case V_FLD_SORT_RULE : + return(h_config_fld_sort_rule); ++ case V_THREAD_DISP_STYLE_RULES: ++ return(h_config_thread_display_style_rule); ++ case V_THREAD_INDEX_STYLE_RULES: ++ return(h_config_thread_index_style_rule); ++ case V_COMPOSE_RULES: ++ return(h_config_compose_rules); ++ case V_FORWARD_RULES: ++ return(h_config_forward_rules); ++ case V_INDEX_RULES: ++ return(h_config_index_rules); ++ case V_KEY_RULES: ++ return(h_config_key_macro_rules); ++ case V_REPLACE_RULES: ++ return(h_config_replace_rules); ++ case V_REPLY_INDENT_RULES: ++ return(h_config_reply_indent_rules); ++ case V_REPLY_LEADIN_RULES: ++ return(h_config_reply_leadin_rules); ++ case V_RESUB_RULES: ++ return(h_config_resub_rules); ++ case V_SAVE_RULES: ++ return(h_config_save_rules); ++ case V_SMTP_RULES: ++ return(h_config_smtp_rules); ++ case V_SORT_RULES: ++ return(h_config_sort_rules); ++ case V_STARTUP_RULES: ++ return(h_config_startup_rules); + case V_POST_CHAR_SET : + return(h_config_post_char_set); + case V_UNK_CHAR_SET : +diff -rc alpine-2.21/pith/conf.h alpine-2.21.rules/pith/conf.h +*** alpine-2.21/pith/conf.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/conf.h Sun Feb 5 16:15:21 2017 +*************** +*** 148,153 **** +--- 148,193 ---- + #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 ++ #define VAR_COMPOSE_RULES vars[V_COMPOSE_RULES].current_val.l ++ #define GLO_COMPOSE_RULES vars[V_COMPOSE_RULES].global_val.l ++ #define USR_COMPOSE_RULES vars[V_COMPOSE_RULES].user_val.l ++ #define VAR_FORWARD_RULES vars[V_FORWARD_RULES].current_val.l ++ #define GLO_FORWARD_RULES vars[V_FORWARD_RULES].global_val.l ++ #define USR_FORWARD_RULES vars[V_FORWARD_RULES].user_val.l ++ #define VAR_INDEX_RULES vars[V_INDEX_RULES].current_val.l ++ #define GLO_INDEX_RULES vars[V_INDEX_RULES].global_val.l ++ #define USR_INDEX_RULES vars[V_INDEX_RULES].user_val.l ++ #define VAR_KEY_RULES vars[V_KEY_RULES].current_val.l ++ #define GLO_KEY_RULES vars[V_KEY_RULES].global_val.l ++ #define USR_KEY_RULES vars[V_KEY_RULES].user_val.l ++ #define VAR_REPLACE_RULES vars[V_REPLACE_RULES].current_val.l ++ #define GLO_REPLACE_RULES vars[V_REPLACE_RULES].global_val.l ++ #define USR_REPLACE_RULES vars[V_REPLACE_RULES].user_val.l ++ #define VAR_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].current_val.l ++ #define GLO_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].global_val.l ++ #define USR_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].user_val.l ++ #define VAR_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].current_val.l ++ #define GLO_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].global_val.l ++ #define USR_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].user_val.l ++ #define VAR_RESUB_RULES vars[V_RESUB_RULES].current_val.l ++ #define GLO_RESUB_RULES vars[V_RESUB_RULES].global_val.l ++ #define USR_RESUB_RULES vars[V_RESUB_RULES].user_val.l ++ #define VAR_THREAD_DISP_STYLE_RULES vars[V_THREAD_DISP_STYLE_RULES].current_val.l ++ #define GLO_THREAD_DISP_STYLE_RULES vars[V_THREAD_DISP_STYLE_RULES].global_val.l ++ #define VAR_THREAD_INDEX_STYLE_RULES vars[V_THREAD_INDEX_STYLE_RULES].current_val.l ++ #define GLO_THREAD_INDEX_STYLE_RULES vars[V_THREAD_INDEX_STYLE_RULES].global_val.l ++ #define VAR_SAVE_RULES vars[V_SAVE_RULES].current_val.l ++ #define GLO_SAVE_RULES vars[V_SAVE_RULES].global_val.l ++ #define USR_SAVE_RULES vars[V_SAVE_RULES].user_val.l ++ #define VAR_SMTP_RULES vars[V_SMTP_RULES].current_val.l ++ #define GLO_SMTP_RULES vars[V_SMTP_RULES].global_val.l ++ #define USR_SMTP_RULES vars[V_SMTP_RULES].user_val.l ++ #define VAR_SORT_RULES vars[V_SORT_RULES].current_val.l ++ #define GLO_SORT_RULES vars[V_SORT_RULES].global_val.l ++ #define USR_SORT_RULES vars[V_SORT_RULES].user_val.l ++ #define VAR_STARTUP_RULES vars[V_STARTUP_RULES].current_val.l ++ #define GLO_STARTUP_RULES vars[V_STARTUP_RULES].global_val.l ++ #define USR_STARTUP_RULES vars[V_STARTUP_RULES].user_val.l + #ifndef _WINDOWS + #define VAR_CHAR_SET vars[V_CHAR_SET].current_val.p + #define GLO_CHAR_SET vars[V_CHAR_SET].global_val.p +diff -rc alpine-2.21/pith/conftype.h alpine-2.21.rules/pith/conftype.h +*** alpine-2.21/pith/conftype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/conftype.h Sun Feb 5 16:15:21 2017 +*************** +*** 70,75 **** +--- 70,89 ---- + , V_THREAD_MORE_CHAR + , V_THREAD_EXP_CHAR + , V_THREAD_LASTREPLY_CHAR ++ , V_THREAD_DISP_STYLE_RULES ++ , V_THREAD_INDEX_STYLE_RULES ++ , V_COMPOSE_RULES ++ , V_FORWARD_RULES ++ , V_INDEX_RULES ++ , V_KEY_RULES ++ , V_REPLACE_RULES ++ , V_REPLY_INDENT_RULES ++ , V_REPLY_LEADIN_RULES ++ , V_RESUB_RULES ++ , V_SAVE_RULES ++ , V_SMTP_RULES ++ , V_SORT_RULES ++ , V_STARTUP_RULES + #ifndef _WINDOWS + , V_CHAR_SET + , V_OLD_CHAR_SET +*************** +*** 332,337 **** +--- 346,352 ---- + F_FULL_AUTO_EXPUNGE, + F_EXPUNGE_MANUALLY, + F_AUTO_READ_MSGS, ++ F_AUTO_READ_MSGS_RULES, + F_AUTO_FCC_ONLY, + F_READ_IN_NEWSRC_ORDER, + F_SELECT_WO_CONFIRM, +diff -rc alpine-2.21/pith/detoken.c alpine-2.21.rules/pith/detoken.c +*** alpine-2.21/pith/detoken.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/detoken.c Sun Feb 5 16:15:21 2017 +*************** +*** 25,31 **** + #include "../pith/reply.h" + #include "../pith/mailindx.h" + #include "../pith/options.h" +! + + /* + * Hook to read signature from local file +--- 25,31 ---- + #include "../pith/reply.h" + #include "../pith/mailindx.h" + #include "../pith/options.h" +! #include "../pith/rules.h" + + /* + * Hook to read signature from local file +*************** +*** 91,96 **** +--- 91,98 ---- + + if(is_sig){ + /* ++ * First we check if there is a rule about signatures, if there is ++ * use it, otherwise keep going and do the following: + * 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; +*************** +*** 104,117 **** + * 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; +--- 106,130 ---- + * there is no reason to mix them, so we don't provide support to + * do so. + */ +! { RULE_RESULT *rule; +! rule = get_result_rule(V_COMPOSE_RULES, FOR_COMPOSE, env); +! if (rule){ +! sigfile = cpystr(rule->result); +! if (rule->result) +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! } +! if (!sigfile){ +! 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; +*************** +*** 302,308 **** + } + } + } +! else if(pt->what_for & FOR_REPLY_INTRO) + repl = get_reply_data(env, role, pt->ctype, + subbuf, sizeof(subbuf)-1); + +--- 315,321 ---- + } + } + } +! else if(pt->what_for & (FOR_REPLY_INTRO | FOR_RULE)) + repl = get_reply_data(env, role, pt->ctype, + subbuf, sizeof(subbuf)-1); + +diff -rc alpine-2.21/pith/indxtype.h alpine-2.21.rules/pith/indxtype.h +*** alpine-2.21/pith/indxtype.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/indxtype.h Sun Feb 5 16:15:21 2017 +*************** +*** 84,89 **** +--- 84,94 ---- + iCurNews, iArrow, + iMailbox, iAddress, iInit, iCursorPos, + iDay2Digit, iMon2Digit, iYear2Digit, ++ iFolder, iFlag, iCollection, iRole, iProcid, iScreen, iPkey, ++ iNick, iFccFrom, iFccSender, iAltAddress, ++ iAddressTo, iAddressCc, iAddressRecip, iAddressSender, ++ iBcc, iLcc, ++ iFfrom, iFadd, + iSTime, iSTime24, iKSize, + iRoleNick, iNewLine, + iHeader, iText, +*************** +*** 105,119 **** + + + /* 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" + +--- 110,135 ---- + + + /* these are flags for the what_for field in INDEX_PARSE_T */ +! #define FOR_NOTHING 0x00000 +! #define FOR_INDEX 0x00001 +! #define FOR_REPLY_INTRO 0x00002 +! #define FOR_TEMPLATE 0x00004 /* or for signature */ +! #define FOR_FILT 0x00008 +! #define DELIM_USCORE 0x00010 +! #define DELIM_PAREN 0x00020 +! #define DELIM_COLON 0x00040 +! #define FOR_FOLDER 0x00080 /* for rules */ +! #define FOR_RULE 0x00100 /* for rules */ +! #define FOR_TRIM 0x00200 /* for rules */ +! #define FOR_RESUB 0x00400 /* for rules */ +! #define FOR_REPLACE 0x00800 /* for rules */ +! #define FOR_SORT 0x01000 /* for rules */ +! #define FOR_FLAG 0x02000 /* for rules */ +! #define FOR_COMPOSE 0x04000 /* for rules */ +! #define FOR_THREAD 0x08000 /* for rules */ +! #define FOR_STARTUP 0x10000 /* for rules */ +! #define FOR_KEY 0x20000 /* for rules */ +! #define FOR_SAVE 0x40000 /* for rules */ + + #define DEFAULT_REPLY_INTRO "default" + +diff -rc alpine-2.21/pith/mailcmd.c alpine-2.21.rules/pith/mailcmd.c +*** alpine-2.21/pith/mailcmd.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/mailcmd.c Sun Feb 5 16:15:21 2017 +*************** +*** 39,44 **** +--- 39,45 ---- + #include "../pith/ablookup.h" + #include "../pith/search.h" + #include "../pith/charconv/utf8.h" ++ #include "../pith/rules.h" + + #ifdef _WINDOWS + #include "../pico/osdep/mswin.h" +*************** +*** 665,670 **** +--- 666,672 ---- + 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; ++ setup_threading_index_style(); + reset_index_format(); + clear_index_cache(ps_global->mail_stream, 0); + /* MUST sort before restoring msgno! */ +*************** +*** 991,996 **** +--- 993,999 ---- + + clear_index_cache(ps_global->mail_stream, 0); + reset_index_format(); ++ setup_threading_index_style(); + + /* + * Start news reading with messages the user's marked deleted +*************** +*** 1114,1120 **** + + 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) +--- 1117,1126 ---- + + if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){ + +! perfolder_startup_rule = get_perfolder_startup_rule(ps_global->mail_stream, +! V_STARTUP_RULES, newfolder); +! +! 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) +*************** +*** 1136,1259 **** + 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: +! alpine_panic("Unexpected incoming startup case"); +! break; +! +! } + } + else if(IS_NEWS(ps_global->mail_stream)){ + /* +--- 1142,1148 ---- + else + use_this_startup_rule = ps_global->inc_startup_rule; + +! find_startup_position(use_this_startup_rule, m, pc); + } + else if(IS_NEWS(ps_global->mail_stream)){ + /* +*************** +*** 1431,1439 **** + /* 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 */ +--- 1320,1330 ---- + /* Save read messages? */ + if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0] + && sp_flagged(stream, SP_INBOX) +! && (F_ON(F_AUTO_READ_MSGS_RULES, ps_global) || +! (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL)))){ + + if(F_ON(F_AUTO_READ_MSGS,ps_global) ++ || F_ON(F_AUTO_READ_MSGS_RULES, ps_global) + || (pith_opt_read_msg_prompt + && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER))) + /* move inbox's read messages */ +*************** +*** 1716,1721 **** +--- 1607,1615 ---- + char *bufp = NULL; + MESSAGECACHE *mc; + ++ if (F_ON(F_AUTO_READ_MSGS_RULES, ps_global)) ++ return move_read_msgs_using_rules(stream, dstfldr, buf); ++ + if(!is_absolute_path(dstfldr) + && !(save_context = default_save_context(ps_global->context_list))) + save_context = ps_global->context_list; +*************** +*** 1755,1762 **** + 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'; +--- 1649,1657 ---- + snprintf(buf, buflen, "Moving %s read message%s to \"%s\"", + comatose(searched), plural(searched), dstfldr); + we_cancel = busy_cue(buf, NULL, 0); +! ps_global->exiting = 1; +! 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'; +*************** +*** 1794,1800 **** + && ((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; +--- 1689,1697 ---- + && ((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)) +! || (F_ON(F_AUTO_READ_MSGS,ps_global) && +! F_ON(F_AUTO_READ_MSGS_RULES, ps_global)))){ + + for(; f && *archive; archive++){ + char *p; +*************** +*** 2755,2757 **** +--- 2652,2946 ---- + + return(*target ? target : NULL); + } ++ ++ char * ++ move_read_msgs_using_rules(MAILSTREAM *stream, char *dstfldr, char *buf) ++ { ++ CONTEXT_S *save_context = NULL; ++ char **folder_to_save = NULL; ++ int num, we_cancel; ++ long i, j, success; ++ MSGNO_S *msgmap = NULL; ++ unsigned long nmsgs = 0L, stream_nmsgs; ++ ++ if(!is_absolute_path(dstfldr) ++ && !(save_context = default_save_context(ps_global->context_list))) ++ save_context = ps_global->context_list; ++ ++ folder_to_save = (char **)fs_get((stream->nmsgs + 1)*sizeof(char *)); ++ folder_to_save[0] = NULL; ++ mn_init(&msgmap, stream->nmsgs); ++ stream_nmsgs = stream->nmsgs; ++ for (i = 1L; i <= stream_nmsgs ; i++){ ++ set_lflag(stream, msgmap, i, MN_SLCT, 0); ++ folder_to_save[i] = get_lflag(stream, NULL, i, MN_EXLD) ++ ? NULL : get_folder_to_save(stream, i, dstfldr); ++ } ++ for (i = 1L; i <= stream_nmsgs; i++){ ++ num = 0; ++ if (folder_to_save[i]){ ++ mn_init(&msgmap, stream_nmsgs); ++ for (j = i; j <= stream_nmsgs ; j++){ ++ if (folder_to_save[j]){ ++ if (!strcmp(folder_to_save[i], folder_to_save[j])){ ++ set_lflag(stream, msgmap, j, MN_SLCT, 1); ++ num++; ++ if (j != i) ++ fs_give((void **)&folder_to_save[j]); ++ } ++ } ++ } ++ pseudo_selected(stream, msgmap); ++ sprintf(buf, "Moving %s read message%s to \"%.45s\"", ++ comatose(num), plural(num), folder_to_save[i]); ++ we_cancel = busy_cue(buf, NULL, 1); ++ ps_global->exiting = 1; ++ if(success = save(ps_global, stream,save_context, folder_to_save[i], ++ msgmap, SV_DELETE | SV_FIX_DELS)) ++ nmsgs += success; ++ if(we_cancel) ++ cancel_busy_cue(success ? 0 : -1); ++ for (j = i; j <= stream_nmsgs ; j++) ++ set_lflag(stream, msgmap, j, MN_SLCT, 0); ++ fs_give((void **)&folder_to_save[i]); ++ mn_give(&msgmap); ++ } ++ } ++ ps_global->exiting = 0; /* useful if we call from aggregate operations */ ++ sprintf(buf, "Moved automatically %s message%s", ++ comatose(nmsgs), plural(nmsgs)); ++ if (folder_to_save) ++ fs_give((void **)folder_to_save); ++ rule_curpos = 0L; ++ return buf; ++ } ++ ++ char * ++ get_folder_to_save(MAILSTREAM *stream, long i, char *dstfldr) ++ { ++ MESSAGECACHE *mc = NULL; ++ RULE_RESULT *rule; ++ MSGNO_S *msgmap = NULL; ++ char *folder_to_save = NULL, *save_folder = NULL; ++ int n; ++ long msgno; ++ ++ /* The plan is as follows: Select each message of the folder. We ++ * need to set the cursor correctly so that iFlag gets the value ++ * correctly too, otherwise iFlag will get the value of the position ++ * of the cursor. After that we need to look for a rule that applies ++ * to the message and get the saving folder. If we get a saving folder, ++ * and we used the _FLAG_ token, use that folder, if no ++ * _FLAG_ token was used, move only if seen and not deleted, to the ++ * folder specified in the saving rule. If we did not get a saving ++ * folder from the rule, just save in the default folder. ++ */ ++ mn_init(&msgmap, stream->nmsgs); ++ rule_curpos = i; ++ msgno = mn_m2raw(msgmap, i); ++ if (msgno > 0L){ ++ mc = mail_elt(stream, msgno); ++ rule = (RULE_RESULT *) ++ get_result_rule(V_SAVE_RULES, FOR_SAVE, mc->private.msg.env); ++ if (rule){ ++ folder_to_save = cpystr(rule->result); ++ n = rule->number; ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ } ++ ++ if (folder_to_save && *folder_to_save){ ++ RULELIST *list = get_rulelist_from_code(V_SAVE_RULES, ++ ps_global->rule_list); ++ RULE_S *prule = get_rule(list, n); ++ if (condition_contains_token(prule->condition, "_FLAG_") ++ || (mc->valid && mc->seen && !mc->deleted) ++ || (!mc->valid && mc->searched)) ++ save_folder = cpystr(folder_to_save); ++ else ++ save_folder = NULL; ++ } ++ else ++ if (!mc || (mc->seen && !mc->deleted)) ++ save_folder = cpystr(dstfldr); ++ mn_give(&msgmap); ++ rule_curpos = 0L; ++ return save_folder; ++ } ++ ++ unsigned long ++ rules_cursor_pos(MAILSTREAM *stream) ++ { ++ MSGNO_S *msgmap = sp_msgmap(stream); ++ return rule_curpos != 0L ? rule_curpos : mn_m2raw(msgmap,mn_get_cur(msgmap)); ++ } ++ ++ void ++ setup_threading_index_style(void) ++ { ++ RULE_RESULT *rule; ++ NAMEVAL_S *v; ++ int i; ++ ++ rule = get_result_rule(V_THREAD_INDEX_STYLE_RULES, FOR_THREAD, NULL); ++ if (rule || ps_global->VAR_THREAD_INDEX_STYLE){ ++ for(i = 0; v = thread_index_styles(i); i++) ++ if(!strucmp(rule ? rule->result : ps_global->VAR_THREAD_INDEX_STYLE, ++ rule ? (v ? v->name : "" ) : S_OR_L(v))){ ++ ps_global->thread_index_style = v->value; ++ break; ++ } ++ if (rule){ ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ } ++ } ++ ++ unsigned ++ get_perfolder_startup_rule(MAILSTREAM *stream, int rule_type, char *folder) ++ { ++ unsigned startup_rule; ++ char *rule_result; ++ ++ startup_rule = reset_startup_rule(stream); ++ rule_result = get_rule_result(FOR_STARTUP, folder, rule_type); ++ if (rule_result && *rule_result){ ++ int i; ++ NAMEVAL_S *v; ++ ++ for(i = 0; v = incoming_startup_rules(i); i++) ++ if(!strucmp(rule_result, v->name)){ ++ startup_rule = v->value; ++ break; ++ } ++ fs_give((void **)&rule_result); ++ } ++ return startup_rule; ++ } ++ ++ void ++ find_startup_position(int rule, MAILSTREAM *m, long pc) ++ { ++ long n; ++ switch(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: ++ alpine_panic("Unexpected incoming startup case"); ++ break; ++ ++ } ++ } +diff -rc alpine-2.21/pith/mailcmd.h alpine-2.21.rules/pith/mailcmd.h +*** alpine-2.21/pith/mailcmd.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/mailcmd.h Sun Feb 5 16:15:21 2017 +*************** +*** 42,47 **** +--- 42,49 ---- + #define DB_FROMTAB 0x02 /* opening because of TAB command */ + #define DB_INBOXWOCNTXT 0x04 /* interpret inbox as one true inbox */ + ++ static MAILSTREAM *saved_stream; ++ static unsigned long rule_curpos = 0L; + + /* + * generic "is aggregate message command?" test +*************** +*** 63,69 **** +--- 65,77 ---- + 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_msgs_using_rules (MAILSTREAM *, char *, char *); ++ unsigned get_perfolder_startup_rule (MAILSTREAM *, int, char *); ++ void setup_threading_index_style (void); ++ void find_startup_position (int, MAILSTREAM *, long); ++ char *get_folder_to_save (MAILSTREAM *, long, char *); + char *move_read_incoming(MAILSTREAM *, CONTEXT_S *, char *, char **, char *, size_t); ++ unsigned long rules_cursor_pos (MAILSTREAM *); + void cross_delete_crossposts(MAILSTREAM *); + long zoom_index(struct pine *, MAILSTREAM *, MSGNO_S *, int); + int unzoom_index(struct pine *, MAILSTREAM *, MSGNO_S *); +diff -rc alpine-2.21/pith/mailindx.c alpine-2.21.rules/pith/mailindx.c +*** alpine-2.21/pith/mailindx.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/mailindx.c Sun Feb 5 16:15:21 2017 +*************** +*** 40,45 **** +--- 40,46 ---- + #include "../pith/send.h" + #include "../pith/options.h" + #include "../pith/ablookup.h" ++ #include "../pith/rules.h" + #ifdef _WINDOWS + #include "../pico/osdep/mswin.h" + #endif +*************** +*** 377,382 **** +--- 378,390 ---- + PAT_STATE pstate; + PAT_S *pat; + int we_set_it = 0; ++ char *rule; ++ ++ if(rule = get_rule_result(FOR_INDEX, ps_global->cur_folder, V_INDEX_RULES)){ ++ init_index_format(rule, &ps_global->index_disp_format); ++ fs_give((void **)&rule); ++ return; ++ } + + if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ + for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ +*************** +*** 450,464 **** + 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}, + {"SIZETHREAD", iSizeThread, FOR_INDEX}, + {"SIZENARROW", iSizeNarrow, FOR_INDEX}, + {"KSIZE", iKSize, FOR_INDEX}, +! {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"FULLSTATUS", iFStatus, FOR_INDEX}, + {"IMAPSTATUS", iIStatus, FOR_INDEX}, +--- 458,472 ---- + static INDEX_PARSE_T itokens[] = { + {"STATUS", iStatus, FOR_INDEX}, + {"MSGNO", iMessNo, FOR_INDEX}, +! {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"FROMORTO", iFromTo, FOR_INDEX}, + {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX}, + {"SIZE", iSize, FOR_INDEX}, + {"SIZECOMMA", iSizeComma, FOR_INDEX}, + {"SIZETHREAD", iSizeThread, FOR_INDEX}, + {"SIZENARROW", iSizeNarrow, FOR_INDEX}, + {"KSIZE", iKSize, FOR_INDEX}, +! {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE|FOR_TRIM}, + {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"FULLSTATUS", iFStatus, FOR_INDEX}, + {"IMAPSTATUS", iIStatus, FOR_INDEX}, +*************** +*** 469,524 **** + {"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}, + {"SMARTTIME24", iSTime24, 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}, +--- 477,536 ---- + {"SUBJECTTEXT", iSubjectText, FOR_INDEX}, + {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX}, + {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX}, +! {"OPENINGTEXT", iOpeningText, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_TRIM}, +! {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_TRIM}, +! {"KEY", iKey, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_COMPOSE}, +! {"KEYINIT", iKeyInit, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_COMPOSE}, + {"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|FOR_RULE|FOR_SAVE}, +! {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTTIME24", iSTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, +! {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_COMPOSE}, +! {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_COMPOSE}, +! {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_SAVE|FOR_SAVE}, +! {"ADDRESSTO", iAddressTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"ADDRESSCC", iAddressCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"ADDRESSRECIPS", iAddressRecip, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"ADDRESSSENDER", iAddressSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"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}, +*************** +*** 527,582 **** + {"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} + }; + +--- 539,609 ---- + {"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|FOR_RULE|FOR_SAVE}, +! {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, +! {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE}, + {"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|FOR_RULE|FOR_SAVE}, +! {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb, +! FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURPREFDATETIME", iCurPrefDateTime, +! FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit, +! FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, +! {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"HEADER", iHeader, FOR_INDEX}, + {"TEXT", iText, FOR_INDEX}, + {"ARROW", iArrow, FOR_INDEX}, + {"NEWLINE", iNewLine, FOR_REPLY_INTRO}, + {"CURSORPOS", iCursorPos, FOR_TEMPLATE}, ++ {"NICK", iNick, FOR_RULE|FOR_SAVE}, ++ {"FCCFROM", iFccFrom, FOR_RULE|FOR_SAVE}, ++ {"FCCSENDER", iFccSender, FOR_RULE|FOR_SAVE}, ++ {"ALTADDRESS", iAltAddress, FOR_RULE|FOR_SAVE}, ++ {"FOLDER", iFolder, FOR_RULE|FOR_SAVE|FOR_FOLDER}, ++ {"ROLE", iRole, FOR_RULE|FOR_RESUB|FOR_TRIM|FOR_TEMPLATE}, ++ {"PROCID", iProcid, FOR_RULE|FOR_RESUB|FOR_FLAG|FOR_COMPOSE|FOR_TRIM|FOR_TEMPLATE}, ++ {"PKEY", iPkey, FOR_RULE|FOR_KEY}, ++ {"SCREEN", iScreen, FOR_RULE|FOR_KEY}, ++ {"FLAG", iFlag, FOR_RULE|FOR_SAVE|FOR_FLAG}, ++ {"COLLECTION", iCollection, FOR_RULE|FOR_SAVE|FOR_COMPOSE|FOR_FOLDER}, ++ {"BCC", iBcc, FOR_COMPOSE|FOR_RULE}, ++ {"LCC", iLcc, FOR_COMPOSE|FOR_RULE}, ++ {"FORWARDFROM", iFfrom, FOR_COMPOSE|FOR_RULE}, ++ {"FORWARDADDRESS", iFadd, FOR_COMPOSE|FOR_RULE}, + {NULL, iNothing, FOR_NOTHING} + }; + +*************** +*** 2483,2488 **** +--- 2510,2533 ---- + from_str(cdesc->ctype, idata, str, sizeof(str), ice); + break; + ++ case iAddressTo: ++ case iAddressCc: ++ case iAddressRecip: ++ {ENVELOPE *env; ++ int we_clear; ++ env = rules_fetchenvelope(idata, &we_clear); ++ sprintf(str, "%-*.*s", ifield->width, ifield->width, ++ detoken_src((cdesc->ctype == iAddressTo ++ ? "_ADDRESSTO_" ++ : (cdesc->ctype == iAddressCc ++ ? "_ADRESSCC_" ++ : "_ADRESSRECIPS_")), FOR_INDEX, ++ env, NULL, NULL, NULL)); ++ if(we_clear) ++ mail_free_envelope(&env); ++ } ++ break; ++ + case iTo: + if(((field = ((addr = fetch_to(idata)) + ? "To" +*************** +*** 3833,3839 **** +--- 3878,3894 ---- + + if(p > buf){ + size_t l; ++ ENVELOPE *env; ++ char *rule_result; + ++ if(rule_result = find_value((delete_quotes ++ ? "_OPENINGTEXTNQ_" : "_OPENINGTEXT_"), ++ buf, PROCESS_SP, idata, 4)){ ++ collspaces(rule_result); ++ strncpy(buf, rule_result, sizeof(buf)); ++ buf[sizeof(buf) - 1] = '\0'; ++ fs_give((void **) &rule_result); ++ } + l = strlen(buf); + l += 100; + firsttext = fs_get((l+1) * sizeof(char)); +*************** +*** 5407,5416 **** + { + 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; +--- 5462,5471 ---- + { + char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL; + char *p, *border, *q = NULL, *free_subj = NULL; +! char *sp, *rule_result; + size_t len; + int width = -1; +! int depth = 0, mult = 2, collapsed, i, we_clear = 0; + int save; + int do_subj = 0, truncated_tree = 0; + PINETHRD_S *thd, *thdorig; +*************** +*** 5466,5471 **** +--- 5521,5534 ---- + * to free it at the end of this routine. + */ + ++ if (rule_result = find_value("_SUBJECT_", origsubj, PROCESS_SP, idata, 4)){ ++ if(origsubj) ++ fs_give((void **)&origsubj); ++ we_clear++; ++ origsubj = cpystr(rule_result); ++ fs_give((void **)&rule_result); ++ } ++ + if(shorten) + shorten_subject(origsubj); + +*************** +*** 5908,5913 **** +--- 5971,5979 ---- + + if(free_subj) + fs_give((void **) &free_subj); ++ ++ if (we_clear && origsubj) ++ fs_give((void **)&origsubj); + } + + +*************** +*** 6276,6291 **** + ? "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", (int)(strsize-1-4), (int)(strsize-1-4), +! newsgroups); + break; + } + +--- 6342,6374 ---- + ? "To" + : (addr = fetch_cc(idata)) + ? "Cc" +! : NULL))){ +! char *rule_result; +! rule_result = find_value("_FROM_", NULL, 0, idata, 1); +! if (!rule_result) +! set_index_addr(idata, field, addr, "To: ", +! strsize-1, fptr); +! else{ +! sprintf(str, "%-*.*s", strsize-1, strsize-1, +! rule_result); +! fs_give((void **)&rule_result); +! } + ++ break; ++ } + if(ctype == iFromTo && + (newsgroups = fetch_newsgroups(idata)) && + *newsgroups){ +! char *rule_result; +! rule_result = find_value("_FROM_", NULL, 0, idata, 1); +! if (!rule_result) +! sprintf(str, "To: %-*.*s", strsize-1-4, +! strsize-1-4, newsgroups); +! else{ +! sprintf(str, "%-*.*s", strsize-1, strsize-1, +! rule_result); +! fs_give((void **)&rule_result); +! } + break; + } + +*************** +*** 6298,6304 **** + break; + + case iFrom: +! set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr); + break; + + case iAddress: +--- 6381,6395 ---- + break; + + case iFrom: +! { char *rule_result; +! rule_result = find_value("_FROM_", NULL, 0, idata, 4); +! if (!rule_result) +! set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr); +! else{ +! sprintf(str, "%-*.*s", strsize-1, strsize-1, rule_result); +! fs_give((void **)&rule_result); +! } +! } + break; + + case iAddress: +*************** +*** 6596,6598 **** +--- 6687,6750 ---- + } + } + } ++ ++ void ++ setup_threading_display_style(void) ++ { ++ RULE_RESULT *rule; ++ NAMEVAL_S *v; ++ int i; ++ ++ rule = get_result_rule(V_THREAD_DISP_STYLE_RULES, FOR_THREAD, NULL); ++ if (rule || ps_global->VAR_THREAD_DISP_STYLE){ ++ for(i = 0; v = thread_disp_styles(i); i++) ++ if(!strucmp(rule ? rule->result : ps_global->VAR_THREAD_DISP_STYLE, ++ rule ? (v ? v->name : "" ) : S_OR_L(v))){ ++ ps_global->thread_disp_style = v->value; ++ break; ++ } ++ if (rule){ ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ } ++ } ++ ++ char * ++ find_value(char *token, char *use_this, int flag, INDEXDATA_S *idata, int nfcn) ++ { ++ int n = 0, i, rule_context, we_clear; ++ char *rule_result = NULL, **list; ++ ENVELOPE *env; ++ RULELIST *rule; ++ RULE_S *prule; ++ ++ env = rules_fetchenvelope(idata, &we_clear); ++ if(env && env->sparep) ++ fs_give((void **)&env->sparep); ++ if(we_clear) ++ mail_free_envelope(&env); ++ if(rule = get_rulelist_from_code(V_REPLACE_RULES, ps_global->rule_list)){ ++ list = functions_for_token(token); ++ while(rule_result == NULL && (prule = get_rule(rule,n++))){ ++ rule_context = 0; ++ if (prule->action->token && !strcmp(prule->action->token, token)){ ++ for (i = 0; i < nfcn; i++) ++ if(list[i+1] && !strcmp(prule->action->function, list[i+1])) ++ rule_context |= context_for_function(list[i+1]); ++ if (rule_context){ ++ env = rules_fetchenvelope(idata, &we_clear); ++ if(use_this) ++ env->sparep = get_sparep_for_rule(use_this, flag); ++ rule_result = process_rule(prule, rule_context, env); ++ if(env->sparep) ++ free_sparep_for_rule(&env->sparep); ++ if(we_clear) ++ mail_free_envelope(&env); ++ } ++ } ++ } ++ } ++ return rule_result; ++ } +diff -rc alpine-2.21/pith/mailindx.h alpine-2.21.rules/pith/mailindx.h +*** alpine-2.21/pith/mailindx.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/mailindx.h Sun Feb 5 16:15:21 2017 +*************** +*** 30,35 **** +--- 30,38 ---- + + + /* exported prototypes */ ++ SortOrder translate (char *, int); ++ char *find_value (char *, char *, int, INDEXDATA_S *, int); ++ void setup_threading_display_style (void); + int msgline_hidden(MAILSTREAM *, MSGNO_S *, long, int); + void adjust_cur_to_visible(MAILSTREAM *, MSGNO_S *); + unsigned long line_hash(char *); +diff -rc alpine-2.21/pith/makefile.wnt alpine-2.21.rules/pith/makefile.wnt +*** alpine-2.21/pith/makefile.wnt Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/makefile.wnt Sun Feb 5 16:15:21 2017 +*************** +*** 45,51 **** + 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 + +--- 45,51 ---- + 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 rules.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 + +*************** +*** 54,60 **** + 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 + +--- 54,60 ---- + 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 rules.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 + +diff -rc alpine-2.21/pith/pine.hlp alpine-2.21.rules/pith/pine.hlp +*** alpine-2.21/pith/pine.hlp Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/pine.hlp Sun Feb 5 16:15:21 2017 +*************** +*** 3855,3860 **** +--- 3855,3861 ---- +

  • FEATURE: +
  • FEATURE: +
  • FEATURE: ++
  • FEATURE: +
  • FEATURE: +
  • FEATURE: +
  • FEATURE: +*************** +*** 19045,19050 **** +--- 19046,19052 ---- + "" option, + in the "" option, + in signature files, ++ in the "new-rules" option, + in template files used in + "roles", and in the folder name + that is the target of a Filter Rule. +*************** +*** 19057,19063 **** +

    +

    + +!

    Tokens Available for all Cases (except Filter Rules)

    + +
    +
    SUBJECT
    +--- 19059,19065 ---- +

    +

    + +!

    Tokens Available for all Cases (except Filter Rules or in some cases for new-rules)

    + +
    +
    SUBJECT
    +*************** +*** 19091,19096 **** +--- 19093,19114 ---- + For example, "mailbox@domain". +
  • + ++
    ADDRESSTO
    ++
    ++ This is similar to the "TO" token, only it is always the ++ email address of all people listed in the TO: field of the messages. Addresses ++ are separated by a blank space. Example, "mailbox@domain" when ++ the e-mail message contains only one person in the To: field, or ++ "peter@flintstones.com president@world.com". ++
    ++ ++
    ADDRESSSENDER
    ++
    ++ This is similar to the "sender" token, only it is always the ++ email address of all person listed in the Sender: field of the message. ++ Example: "mailbox@domain". ++
    ++ +
    MAILBOX
    +
    + This is the same as the "ADDRESS" except that the +*************** +*** 19138,19143 **** +--- 19156,19170 ---- + message's "Cc:" header field. +
    + ++
    ADDRESSCC
    ++
    ++ This is similar to the "CC" token, only it is always the ++ email address of all people listed in the Cc: field of the messages. Addresses ++ are separated by a blank space. Example: "mailbox@domain" when ++ the e-mail message contains only one person in the Cc: field, or ++ "peter@flintstones.com president@world.com". ++
    ++ +
    RECIPS
    +
    + This token represents the personal names (or email addresses if the names +*************** +*** 19146,19151 **** +--- 19173,19186 ---- + the message's "Cc:" header field. +
    + ++
    ADDRESSRECIPS
    ++
    ++ This token represent the e-mail addresses of the people in the To: and ++ Cc: fields, exactly in that order separated by a space. It is almost obtained ++ by concatenating the ADDRESSTO and ADDRESSCC tokens. ++
    ++ ++ +
    NEWSANDRECIPS
    +
    + This token represents the newsgroups from the +*************** +*** 20270,20275 **** +--- 20305,20414 ---- + + +

    ++

    Tokens Available Only for New-Rules

    ++ ++
    ++
    FCCFROM
    ++
    ++ The Fcc: folder assigned to the email address in the From: field in the ++ addressbook. ++
    ++
    ++ ++
    ++
    FCCSENDER
    ++
    ++ The Fcc: folder assigned to the email address in the Sender: field in the ++ addressbook. ++
    ++
    ++ ++
    ++
    ALTADDRESS
    ++
    ++ The value of your ++ ++ variable. At this time, no expansion of regular expressions is supported. ++
    ++
    ++ ++
    ++
    NICK
    ++
    ++ Nickname of the person in the From field in your addressbook. ++
    ++
    ++ ++
    ++
    FOLDER
    ++
    ++ Name of the folder where the rule will be applied. ++
    ++
    ++ ++
    ++
    COLLECTION
    ++
    ++ Name of the collection list where the rule will be applied. ++
    ++
    ++ ++
    ++
    ROLE
    ++
    ++ Name of the Role used to reply a message. ++
    ++
    ++ ++
    ++
    BCC
    ++
    ++ Not implemented yet, but it will be implemented in future versions. It will ++ be used for compose ++ reply ++ forward ++ rules. ++
    ++
    ++ ++
    ++
    LCC
    ++
    ++ This is the value of the Lcc: field at the moment that you start the composition. ++
    ++
    ++ ++
    ++
    FORWARDFROM
    ++
    ++ This corresponds to the personal name (or address if there's no personal ++ name) of the person who sent the message that you are forwarding. ++
    ++
    ++ ++
    ++
    FORWARDADDRESS
    ++
    ++ This is the address of the person that sent the message that you ++ are forwarding. ++
    ++
    ++ ++ ++ ++ ++
    ++
    FLAG
    ++
    ++ A string containing the value of all the flags associated to a specific ++ message. The possible values of allowed flags are "*" for Important, "N" ++ for recent or new, "U" for unseen or unread, "R" for seen or read, "A" for ++ answered and "D" for deleted. See an example of its use in the ++ new rules explanation and example help. ++
    ++
    ++ ++

    +

    Token Available Only for Templates and Signatures

    + +
    +*************** +*** 23397,23402 **** +--- 23536,24432 ---- + <End of help on this topic> + + ++ ====== h_config_procid ===== ++ ++ ++ Token: PROCID ++ ++ ++

    TOKEN: PROCID explained

    ++ ++

    ++ The PROCID token is a way in which the user and the program can differentiate ++ between different parts of a program. It allows the user to tell the ++ program when to use a specific rule, and only use it at that specific ++ moment. ++ ++

    The normal way in which this is done is by adding a new configuration ++ variable. The idea behind the PROCID token is that instead of adding a new ++ configuration variable (which means the user has to go through more ++ configuration variables just to tune the program to his liking), we reuse ++ an old variable and let the user look inside that variable for the desired ++ behavior, which is actually set by setting the PROCID token. ++ ++

    ++ Consider the following examples for forward-rules: ++ ++

    ++ _ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} ++ ++

    ++ and ++ ++

    ++ _ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} ++ ++

    ++ both are triggered by the same condition. Since both are configured in the ++ same variable, only one of them will be executed all the time (whichever ++ is first). Therefore in order to differentiate, we add a _PROCID_ token. ++ So, for example, the first example above will be executed only when we are ++ determining the subject. In this case, the following rule will accomplish ++ this task ++ ++

    ++ _PROCID_ == {fwd-subject} && _ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} ++ ++

    ++ In this case, this rule will be tested fully only when we are determining ++ the subject line of a forwarded message, not otherwise. ++ ++

    ++ It is wise to add the _PROCID_ token as the first condition in a rule, so ++ that other conditions will not be tested in a long list of rules. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_compose_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_compose-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    At this time, this option is used to generate values for signature ++ files that is not possible to do with the use of ++ roles. ++ ++

    For example, you can have a rule like:
    ++ _TO_ >> {Peter Flintstones} => _SIGNATURE_{~/.petersignature} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_forward_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_forward-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option has several uses. This feature uses the PROCID function ++ to identify different features of forwarding. You can read more about PROCID ++ by following this link. ++ ++

    If you want to edit the subject of a forwarded message, use the ++ PROCID fwd-subject. For example you could have a rule like ++ ++

    ++ _ROLE_ == {admin} && _SUBJECT_ !> {[tag] } => _COPY_{[tag] _SUBJECT_} ++ ++

    Another way in which this option can be used, is to trim the values of ++ some fields. For this application the PROCID is fwd-lcc. For ++ example it can be used in the following way: ++ ++

    ++ _ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} ++ ++

    Other functions that can be used in this option are _EXEC_ and _REXTRIM_. ++ ++

    You can also use the _EXEC_ function. The documentation for this function ++ is in the ++ ++ help text. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_index_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_index-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to supersede the value of the option for specific folders. In ++ this form you can have different index-formats for different folders. For ++ example an entry here may be: ++ ++

    ++ _FOLDER_ == {INBOX} => _INDEX_{IMAPSTATUS DATE FROM(33%) SIZE SUBJECT(67%)} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_pretty_command ===== ++ ++ ++ Pretty-Command Explained ++ ++ ++

    Pretty Command Explained

    ++ ++

    This text explains how to encode keys so that they will be recognized ++ by Alpine in the _PKEY_ token. Most direct keystrokes are recognized in the ++ same way. For example, the key ~ is recognized by the same character. The ++ issue is how control, or functions keys are recognized. The internal code ++ is most times easy to find out. If the key you want to use is not already ++ recognized by Alpine simply press it. Alpine will print its code. For example, ++ the return key is not recognized in this screen, so if you press it, you ++ will see the following message. ++ ++

    [Command "RETURN" not defined for this screen. Use ? for help] ++ ++

    from here you can guess that the code for the return command is ++ RETURN. You can try other commands, like Control-C, the TAB key, F4, etc. ++ to see their codes. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_key_macro_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_key-definition-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option can be used to define macros, that is, to define a key that ++ when pressed executes a group of predetermined keystrokes. Since Alpine is ++ a menu driven program, sometimes the same key may have different meanings ++ in different screens, so a global redefinition of a key although possible ++ is not advisable. ++ ++

    Always use the _SCREEN_ token as defined below.. You have been ++ warned! ++ ++

    In each screen, every time you press a recognized key, a command is ++ activated. In order to understand this feature, think of commands instead ++ of keystrokes. For example, you can think of the sort by thread command. ++ This command is associated to the keystrokes $ and h. You may want to ++ associate this command to a specific keystroke, like ~, so every time you ++ press the ~ key, Alpine understand the $ and h keystrokes, which activates ++ the sort by thread command. ++ ++

    Therefore, in order to use this option you must think of three ++ components. The screen where you will use the macro, the keystroke you ++ want to use and the set of keystrokes used by Alpine to accomplish the task ++ you want to accomplish. We will talk about these three components in what ++ follows. ++ ++

    First you must decide in which screen the macro will be used. This ++ feature is currently only available for the screen where your messages ++ are listed in index form (MESSAGE INDEX), ++ the screen where your message is displayed ++ (MESSAGE TEXT) and the screen where the list of ++ folders is displayed (FOLDER LIST). The ++ internal names of these screens for this patch are "index", ++ "text" and ++ "folder" respectively. Please note that the internal names are ++ all in lowercase and are case sensitive. ++ ++

    In order to define the screen, you use the _SCREEN_ token, so for ++ example, you can write _SCREEN_ == {index}. ++ ++

    Second you must think of which key you will use to activate the macro. ++ Here you can use any key of your choice. The token you use to designate a ++ key is the _PKEY_ token (PKEY stands for "pressed key"). For ++ example you could use _PKEY_ == {~}, to designate the "~" ++ key to activate the command. Some keystrokes (like control, or ++ function keys) are encoded in special ways. You should read the ++ full explanation on how to find ++ out the encoding for each keystroke. ++ ++

    Last, you must think of the list of keys you will use to accomplish ++ the task you want Alpine to perform. Say for example you want to have the ++ folder sorted by thread. That means you want Aline to execute the keys ++ "$" and "h". You use the _COMMAND_ function to specify ++ this. The syntax in this case is _COMMAND_{$,h}. ++ ++

    Observe that in the above example the different inputs are separated ++ by commas. This is the standard way in which the ++ command works from ++ the command line. Due to restrictions in the way Alpine works, a comma is a ++ special character, which when added to a configuration option like this ++ will cause the configuration to split into several lines in the ++ configuration screen. This has the effect of producing several ++ configuration options, all of which are incorrect. This is undesirable ++ because what you want is to have it all in one line. In order to force the ++ configuration into one line you must quote the comma. The best way to ++ accomplish this is by quoting the full definition of the rule. For ++ example. ++ ++

    ++ "_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{$,h}" ++ ++

    Another way to accomplish the same effect is by quoting the command and ++ not using quotes for the full command, nor commas to separate the ++ keystrokes in the command, for example ++ ++

    ++ _SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{"$h"} ++ ++

    For more information on how to define the argument of the _COMMAND_ ++ token see the help of ++ . ++ ++

    Because the $ command can also be used as the first character in the ++ definition of an environemnt variable, no expansion of environment variables ++ is done when parsing this variable. The $ character does not need quoting ++ and quoting it will make Alpine fail to produce the correct result. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_replace_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_replace-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine print different values for specific ++ tokens in the . For example you ++ can replace strings like "To: newsgroup" by your name. ++ ++

    Here are examples of possible rules: ++ ++

    _FOLDER_ != {sent-mail} && _NICK_ != {} => _FROM_ := _REPLACE_{_FROM_ (_NICK_)} ++ ++

    or if you receive messages with tags that contain arbitrary numbers, and ++ you want them removed from the index (but not from the subject), use a rule ++ like the following ++ ++

    _FOLDER_ == {INBOX} => _SUBJECT_ := _REXTRIM_{\[some-tag-here #[0-9].*\]} ++ ++

    You can also use this configuration option to remove specific strings of ++ the index display screen, so that you can trim unnecessary information in ++ your index, like the reply leadin string in the OPENINGTEXTNQ token of the index.
    ++ ++

    _FOLDER_ == {some-folder} => _OPENINGTEXTNQ_ := _REXTRIM_{On.*wrote: } ++ ++

    You can also use the _EXEC_ function. The documentation for this function ++ is in the ++ ++ help text. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_reply_leadin_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_reply-leadin-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine generate a different ++ string dependent either on ++ the person you are replying to, or the folder where the message is being ++ replied is in, or both. ++ ++

    Here there are examples of how this can be used. One can use the definition ++ below to post to newsgroups and the pine-info mailing list, say: ++

    ++ _FOLDER_ << {pine-info;_NEWS_} => _REPLY_{*** _FROM_ _ADDRESS_("_FROM_" "" "(_ADDRESS_) ")wrote in_NEWS_("" " the" "") _FOLDER_ _NEWS_("" "list " "")_SMARTDATE_("Today" "today" "on _LONGDATE_"):} ++ ++

    Here there is an example that one can use to change the reply indent string ++ to reply people that speak spanish. ++

    ++ _FROM_{Condorito;Quico} => _REPLY_{*** _FROM_ (_ADDRESS_) escribió _SMARTDATE_("Today" "hoy" "en _LONGDATE_"):} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_resub_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_reply-subject-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine generate a different subject when ++ replying rather than the one Alpine would generate automatically. ++ ++

    Here there are a couple of examples about how to use this ++ configuration option: ++ ++

    In order to have messages with empty subject to be replied with the message ++ "your message" use the rule
    ++

    _SUBJECT_ == {} => _RESUB_{Re: your message}
    ++ ++

    If you want to trim some parts of the subject when you reply use the ++ rule
    ++

    _SUBJECT_ >> {[one];two} => _SUBJECT_ := _TRIM_{[;];two}
    ++ ++

    this rule removes the brackets "[" and "]" whenever the string "[one]" ++ appears in it, it also removes the word "two" from it. ++ ++

    Another example where you may want to use this rule is when you ++ correspond with people that change the reply string from "Re:" ++ to "AW:" or "Sv:". In this case a rule like
    ++

    _SUBJECT_ >> {Sv: ;AW: } => _SUBJECT_ := _TRIM_{Sv: ;AW: }
    ++

    ++ would eliminate undesired strings in replies. ++ ++

    Another interesting use of this option is the use of the _EXEC_ function. ++ This function takes as an argument a program or a script. This program ++ must take as the input a file, and write its output to that file. For example, ++ below is a sample of a script that removes the letter "a" of a file. ++ ++

    ++ #!/bin/sh
    ++ sed 's/a//g' $1 > /tmp/mytest
    ++ mv /tmp/mytest $1
    ++ 
    ++ ++

    ++ As you can see this script took "$1" as input file, the sed program ++ wrote its output to /tmp/mytest, and then the move program moved the file ++ /tmp/mytest to the input file "$1". This is the kind of behavior ++ that your program is expected to have. ++ ++

    ++ The content of the input file ("$1" above) is the value of a token ++ like _SUBJECT_. In order to indicate this, we use the notation ++ ++

    ++ _SUBJECT_ := _EXEC_{/path/to/script} ++ ++

    for the action. So for example ++ ++

    ++ _FOLDER_ := {sent-mail} => _SUBJECT_ := _EXEC_{/path/to/script} ++ ++

    is a valid rule. ++ ++

    You can also use this configuration option to customize reply subjects ++ according to the sender of the message. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ====== h_config_sort_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_sort-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine sort different folders in different orders ++ and thus override the value already set in the ++ configuration option. ++ ++

    Here's an example of the way it can be used. In this case all incoming ++ folders are mailing lists, except for INBOX, so we sort INBOX by arrival ++ (which is the default type of sort), but we want all the rest of mailing ++ lists and newsgroups to be sorted by thread. ++ ++

    ++ _COLLECTION_ >> {Incoming-Folders;News} && _FOLDER_ != {INBOX} => _SORT_{tHread} ++ ++

    Another example could be
    ++ _FOLDER_ == {Mailing List} => _SORT_{Reverse tHread} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ++ ====== h_config_save_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_save-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to specify which folder should be used to save a ++ message depending either on the folder the message is in, who the message ++ is from, or text that the message contains in specific headers (Cc:, ++ Subject:, etc). ++ ++

    If this option is set and the ++ configuration ++ option is also enabled then these definitions will be used to move messages ++ from your INBOX when exiting Alpine. ++ ++

    Here there are some examples
    ++ _FLAG_ >> {D} -> Trash
    ++ _FROM_ == {U2} -> Bono
    ++ _FOLDER_ == {comp.mail.pine} -> pine-stuff
    ++ _NICK_ != {} -> _NICK_/_NICK_
    ++ _DATEISO_ >> {02-10;02-11} -> archive-oct-nov-2002 ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ++ ====== h_config_reply_indent_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_reply-indent-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to specify which reply-indent-string is to be used ++ when replying to an e-mail. If none of the rules are successful, the result in ++ the variable ++ is used. ++ ++

    The associated function to this configuration option is called "RESTR" (for ++ REply STRing). Some examples of its use are:
    ++ _FROM_ == {Your Boss} => _RESTR_{"> "}
    ++ _FROM_ == {My Wife} => _RESTR_{":* "}
    ++ _FROM_ == {Perter Flintstone;Wilma Flintstone} => _RESTR_{"_INIT_ > "}
    ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ++ ====== h_config_smtp_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_smtp-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to specify which SMTP server should be used when ++ sending a message, if this rule is not defined, or the execution of the rule ++ results in no server selected, then Alpine will look for ++ the value from the role that is being used to compose the message. If no smtp ++ server is defined in that role or you are not using a role, then Alpine will get ++ the name of the server from the ++ "" configuration ++ option according to the rules used in that variable. ++ ++

    The function associated to this configuration option is _SMTP_, an example ++ of the use of this function is
    ++ _ADDRESSTO_ == {peter@bedrock.com} => _SMTP_{smtp.bedrock.com} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ++ ====== h_config_startup_rules ===== ++ ++ ++ OPTION: <!--#echo var="VAR_startup-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used when a folder is being opened. You can use it to specify its and override ++ Alpine's global value set for all folders. ++ ++

    An example of the usage of this option is:
    ++ _FOLDER_ == {Lynx;pine-info;_NEWS_} => _STARTUP_{first-unseen} ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    <End of help on this topic> ++ ++ ++ ++ ====== h_config_new_rules ===== ++ ++ ++ OPTION: New Rules Explained ++ ++ ++

    OPTION: New Rules Explained

    ++ ++ This is a quite powerful option. Here you can define rules that override ++ the values of any other option you have set in Alpine. ++ ++

    ++ For example, you can set your folders to be sorted in a certain way when ++ you open them (say by Arrival). You may want, however, your newsgroups to ++ be sorted by thread. The set of "rules" options allows you to ++ configure this and many other options, including the index-format for ++ specific folders, the way the subject is displayed in the index screen or ++ the reply-leadin-string, to name a few. ++ ++

    ++ Every rule has three parts: a condition, a separator and an action. The ++ action is what will happen if the condition of the rule is satisfied. ++ ++

    ++ Here is an example: ++ ++

    ++ _FROM_ == {Fred Flintstone} => _SAVE_{Fred} ++ ++

    ++ Here the separator is "=>". Whatever is to the left of the separator ++ is the condition (that is _FROM_ == {Fred Flintstone}) and to the right is ++ the action (_SAVE_{Fred}). The condition means that the rule will be ++ applied only if the message that you are reading is from "Fred ++ Flintstone", and the action will be that you will be offered to save ++ it in the folder "Fred", whenever you press the letter ++ "S" to save a message. ++ ++

    ++ The separator is always "=>", with one exception to be seen ++ later. But for the most part this will be the only one you will ever need. ++ ++

    ++ Now let us see how to do it. There are 13 functions already defined for ++ you. These are: _EXEC_, _INDEX_, _REPLACE_, _REPLY_, _RESUB_, _SAVE_, ++ _SIGNATURE_, _SORT_, _STARTUP_, _TRIM_, _REXTRIM_, _THREADSTYLE and ++ _THREADINDEX_. The parameter of a function has to be enclosed between ++ "{" and "}", so for example you can specify ++ _SAVE_{saved-messages} as a valid sentence. ++ ++

    ++ Later in the document you will find examples. Here is a short ++ description of what each function does: ++ ++

    ++

      ++
    • _EXEC_ : This function takes as an argument a program. This program ++ gets as the input a file and must rewrite its output to that file, which ++ is then taken as the value to replace from the contents of that file. You ++ can use this function with ++ , ++ and ++ . ++ See the help of those options for examples of how to use this function ++ and configure these rules. ++
       
      ++
    • _INDEX_ : This function takes as an argument an index-format, and ++ makes that the index-format for the specified folder. ++
       
      ++
    • _REPLACE_ : This function replaces the subject/from of the given e-mail by ++ another subject/from only when displaying the index. ++
       
      ++
    • _REPLY_ : This function takes as an argument a definition of a ++ reply-leadin-string and makes this the reply-leading-string of the ++ specified folder or person. ++
       
      ++
    • _RESTR_ : This function takes as an argument the value of the ++ reply-indent-string to be used to answer the message being replied to. ++
       
      ++
    • _RESUB_ : This function replaces the subject of the given e-mail by ++ another subject only when replying to a message. ++
       
      ++
    • _SAVE_ : The save function takes as an argument the name of a ++ possibly non existing folder, whenever you want to save a message, that ++ folder will be offered for you to save. ++
       
      ++
    • _SIGNATURE_ : This function takes as an argument a signature file and ++ uses that file as the signature for the message you are about to ++ compose/reply/forward. ++
       
      ++
    • _SMTP_ : This function takes as an argument the definition of a ++ SMTP server. ++
       
      ++
    • _SORT_ : This function takes as an argument a Sort Style, and sorts a ++ specified folder in that sort order. ++
       
      ++
    • _TRIM_ : This function takes as an argument a list of strings that ++ you want removed from another string. At this time this only works for ++ _FROM_ and _SUBJECT_. ++
       
      ++
    • _REXTRIM_ : Same as _TRIM_ but its argument is one and ++ only one extended regular expression. ++
       
      ++
    • _STARTUP_ : This function takes as an argument an ++ incoming-startup-rule, and open an specified folder using that rule. ++
       
      ++
    • _THREADSTYLE_ : This function takes as an argument a ++ threading-display-style and uses it to display threads in a folder. ++
       
      ++
    • _THREADINDEX_ : This function takes as an argument a ++ threading-index-style and uses it to display threads in a folder. ++
    ++ ++

    ++ You must me wondering how to define the person/folder over who to apply ++ the action. This is done in the condition. When you specify a rule, the ++ rule is only executed if the condition is satisfied. In another words for ++ the rule: ++ ++

    ++ _FROM_ == {Fred Flintstone} => _SAVE_{Fred} ++ ++

    it will only be applied if the from is "Fred Flintstone". If ++ the From is "Wilma Flintstone" the rule will be skipped. ++ ++

    In order to test a condition you can use the following tokens (in ++ alphabetical order): _ADDRESS_, _CC_, _FOLDER_, _FROM_,_NICK_, _ROLE, ++ _SENDER_, _SUBJECT_ and _TO_. The token will always be tested against what ++ it is between "{" and "}" in the condition, this part ++ of the condition is called the "condition set". The definition ++ of each token can be found here. ++ ++

    A special testing token called _PROCID_ can be used to differentiate ++ inside a rule, between two rules that are triggered by the same condition. ++ A full explanation of the _PROCID_ token can be found in ++ this link. ++ ++

    There are two more tokens related to the option ++ key-definition-rules. Those tokens ++ are only specific to that option, and hence are not explained here. ++ ++

    You can also test in different ways, you can use the following ++ "test operands": <<, !<, >>, !>, == and !=. ++ All of them are two characters long. Here is the meaning of them: ++ ++

    ++

      ++
    • << : It tests if the value of the token is contained in ++ the condition set. Here for example if the condition set were equal to ++ "Freddy", then the condition: _NICK_ << {Freddy}, would be true if ++ the value of _NICK_ were "Fred", "red" or "Freddy". You are just looking ++ for substrings here. ++
    • >> : It tests if the value of the token contains the value of ++ the condition set. Here for example if the condittion set were equal to ++ "Fred", then the condition: _FROM_ >> {Fred}, would be true if ++ the value of _FROM_ were "Fred Flintstone" or "Fred P. Flintstone" or "Freddy". ++
    • == : It tests if the value of the token is exactly equal to the value ++ of the set condition. For example _NICK_ == {Fred} will be false if the value ++ of _NICK_ is "Freddy" or "red". ++
    • !< : This is true only when << is false and vice versa. ++
    • !> : This is true only when >> is false and vice versa. ++
    • != : This is true only when == is false and vice versa. ++
    ++ ++

    ++ Now let us say that you want the same action to be applied to more than ++ one person or folder, say you want "folder1" and "folder2" to be sorted by ++ Ordered Subject upon entering. Then you can list them all of them in the ++ condition part separting them by a ";". Here is the way to do it. ++ ++

    ++ _FOLDER_ << {folder1; folder2} => _SORT_{OrderedSubj} ++ ++

    ++ Here is the first subtlety about these definitions. Notice that the ++ following rule: ++ ++

    ++ _FOLDER_ == {folder1; folder2} => _SORT_{Reverse OrderedSubj} ++ ++

    works only for "folder1" but not for "folder2". This is because the ++ comparison of the name of the folder is done with whatever is in between ++ "{", ";" or "}", so in the above rule you would be testing
    ++ "folder2" == " folder2". The extra space makes the difference. ++ The reason why the first rule does not fail is because ++ "folder2" << " folder2" is actually ++ true. If something ever fails this may be something to look into. ++ ++

    ++ Here are a few examples of what we have talked about before. ++ ++

    ++ _NICK_ == {lisa;kika} => _SAVE_{_NICK_/_NICK_}
    ++ This means that if the nick is lisa, it will ++ save the message in the folder "lisa/lisa", and if the nick ++ is "kika", it will save the message in the folder "kika/kika" ++ ++

    ++ _FOLDER_ == {Lynx} -> lynx
    ++ This, is an abbreviation of the following rule:
    ++ _FOLDER_ == {Lynx} => _SAVE_{lynx}
    ++ (note the change in separator from "=>" to "->"). In the future ++ I will use that abbreviation. ++ ++

    _FOLDER_ << {comp.mail.pine; pine-info; pine-alpha} -> pine
    ++ Any message in the folders "comp.mail.pine", "pine-info" or "pine-alpha" ++ will be saved to the folder "pine". ++ ++

    _FROM_ << {Pine Master} -> pine
    ++ Any message whose From field contains ++ "Pine Master" will be saved in the folder pine. ++ ++

    _FOLDER_ << {Lynx; pine-info; comp.mail.pine} => ++ _INDEX_{IMAPSTATUS MSGNO DATE FROMORTO(33%) SUBJECT(66%)}
    Use a ++ different index-format for the folders "Lynx", "pine-info" and ++ "comp.mail.pine", where the size is not present. ++ ++

    _FOLDER_ == {Lynx;pine-info} => _REPLY_{*** _FROM_ (_ADDRESS_) ++ wrote in the _FOLDER_ list _SMARTDATE_("Today" "today" "on ++ _LONGDATE_"):}
    If a message is in one of the incoming folders "Lynx" ++ or "pine-info", create a reply-leadin-string that acknowledges that. Note ++ the absence of "," in the function _SMARTDATE_. For example answering to a ++ message in the pine-info list would look like: ++ ++

    ++ *** Steve Hubert (hubert@cac.washington.edu) wrote in the pine-info list today: ++ ++

    ++ However replying for a message in the Lynx list would look: ++ ++

    ++ *** mattack@area.com (mattack@area.com) wrote in the Lynx list today: ++ ++

    ++ If you write in more than one language you can use this feature to create ++ Reply-leadin-strings in different languages. ++ ++

    Note that at least for people you can create particular ++ reply-leadin-string using the role features, but it does not work as this ++ one does. This seems to be the right way to do it. ++ ++

    _FOLDER_ << {Lynx; comp.mail.pine; pine_info; pine-alpha} => ++ _SORT_{OrderedSubj}
    This means upon opening, sort the folders "Lynx", ++ "comp.mail.pine", etc in ordered subject. All the others use the default ++ sort order. You can not sort in reverse in this form. The possible ++ arguments of this function are listed in the definition of the ++ default-sort-rule (Arrival, scorE, siZe, etc). ++ ++

    The last examples use the function _TRIM_ which has a special form. ++ This function can only be used in the index list. ++ ++

    _FOLDER_ << {Lynx} => _SUBJECT_ := _TRIM_{lynx-dev }
    In ++ the folder "Lynx" eliminate from the subject the string "lynx-dev " (with ++ the space at the end). For example a message whose subject is "Re: ++ lynx-dev unvisited Visited Links", would be shown in the index with ++ subject: "Re: unvisited Visited Links", making the subject shorter and ++ giving the same information. ++ ++

    _FROM_ >> {Name (Comment)} => _FROM_ := ++ _TRIM_{ (Comment)}
    Remove the part " (Comment)" ++ from the _FROM_, so when displaying in the index the real From "Name" ++ will appear. ++ ++

    _SUBJECT_ == {} => _RESUB_{Re: your mail without subject} ++ If there is no subject in the message, use the subject "Re: your mail ++ wiyhout subject" as a subject for the reply message. ++ ++

    You can add more complexity to your rules by checking more than one ++ conditions before a rule is executed. More than one condition can be ++ checked by separating different conditions by the && (and) separator, ++ or using the || (or) separator. For example we could have a rule that ++ saves all ++ messages in inbox from Rubye, to the Personal folder, as ++ ++

    _FOLDER_ == {INBOX} && _FROM_ >> {Rubye} => _SAVE_{Personal} ++ ++

    We could also have a rule that is triggered by an "or" ++ condition by, sat for messages from Andres or messages in the index ++ to trigger a specific reply leadin string. ++ ++

    _FOLDER_ == {INBOX} || _FROM_ >> {Andres} => _REPLY_{You wrote:} ++ ++

    Observe that the construction ++ ++

    _TOKEN_ == {value1} || _TOKEN_ == {value2} ++ ++

    can be shortened to ++ ++

    _TOKEN_ == {value1;value2} ++ ++

    Round parentheses can be used to group some conditions, for example ++ ++

    (_FROM_ >> {Andres} && _FOLDER_ == {INBOX}) || _FROM_ >> {Rubye} ++ ++ ++

    You can also list your index by nick, in the following way:
    ++ _NICK_ != {} => _FROM_ := _REPLACE_{_NICK_} ++ ++

    ++ If you want to open the folder "pine-info" in the first non-read message ++ use the rule:
    ++ _FOLDER_ == {pine-info} => _STARTUP_{first-unseen} ++ ++

    ++ If you want to move your deleted messages to a folder, called "Trash", use ++ the following rule:
    ++ _FLAG_ >> {D} -> Trash ++ ++

    ++ The reason why the above test is not "_FLAG_ == {D}" is because that would mean ++ that this is the only flag set in the message. It's better to test by containment in this case. ++ ++

    If you want to use a specific signature when you are in a specific collection ++ use the following rule:
    ++ _COLLECTION_ == {Mail} => _SIGNATURE_{/full/path/to/.signature} ++ ++

    Finally about the question of which rule will be executed. Only the ++ first rule that matches will be executed. It is important to notice though ++ that "saving" rules do not compete with "sorting" rules. So the first ++ "saving" rule that matches will be executed in the case of saving and so ++ on. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_char_set ===== + + +*************** +*** 27005,27010 **** +--- 28035,28110 ---- + <End of help on this topic> + + ++ ====== h_config_thread_display_style_rule ===== ++ ++ ++ OPTION: Threading-Display-Style-Rule ++ ++ ++

    OPTION: Threading-Display-Style-Rule

    ++ ++ This option is very similar to ++ , but it is a rule which specifies the ++ display styles for a thread that you want displayed in a specific ++ folder or collection. ++

    ++ The token to be used in this function is _THREADSTYLE_. Here there is ++ an example of its use ++

    ++ _FOLDER_ == {pine-info} => _THREADSTYLE_{mutt-like} ++

    ++ The values that can be given for the _THREADSTYLE_ function are the ++ values of the threading-display-style function, which can be found ++ listed in the threading-display-style ++ configuration option. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ ++ ====== h_config_thread_index_style_rule ===== ++ ++ ++ OPTION: Threading-Index-Style-Rule ++ ++ ++

    OPTION: Threading-Index-Style-Rule

    ++ ++ This option is very similar to ++ , but it is a rule which specifies the ++ index styles for a thread that you want displayed in a specific ++ folder or collection. ++

    ++ The token to be used in this function is _THREADINDEX_. Here there is ++ an example of its use ++

    ++ _FOLDER_ == {pine-info} => _THREADINDEX_{regular-index-with-expanded-threads} ++

    ++ The values that can be given for the _THREADINDEX_ function are the ++ values of the threading-index-display function, which can be found ++ listed in the ++ configuration option. ++ ++

    This configuration option is just one of many that allow you to ++ override the value of some global configurations within Alpine. There is a ++ help text explaining how to define all of them, which you can read by ++ following this link. ++ ++

    ++

    ++ <End of help on this topic> ++ ++ + ====== h_config_pruning_rule ===== + + +*************** +*** 30595,30600 **** +--- 31695,31723 ---- + them as deleted in the INBOX. Messages in the INBOX marked with an + "N" (meaning New, or unseen) are not affected. +

    ++

    ++ <End of help on this topic> ++ ++ ++ ====== h_config_auto_read_msgs_rules ===== ++ ++ ++ FEATURE: auto-move-read-msgs-using-rules ++ ++ ++

    FEATURE: auto-move-read-msgs-using-rules

    ++ This feature controls an aspect of Alpine's behavior upon quitting. If set, ++ and the ++ "" ++ option is also set, then Alpine will automatically transfer all read ++ messages to the designated folder using the rules that you have defined in ++ your ++ "" and mark ++ them as deleted in the INBOX. Messages in the INBOX marked with an ++ "N" (meaning New, or unseen) are not affected. ++

    +

    +diff -rc alpine-2.21/pith/reply.c alpine-2.21.rules/pith/reply.c +*** alpine-2.21/pith/reply.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/reply.c Sun Feb 5 16:15:22 2017 +*************** +*** 47,52 **** +--- 47,54 ---- + #include "../pith/mailcmd.h" + #include "../pith/margin.h" + #include "../pith/smime.h" ++ #include "../pith/copyaddr.h" ++ #include "../pith/rules.h" + + + /* +*************** +*** 864,871 **** + 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 */ +--- 866,892 ---- + reply_quote_str(ENVELOPE *env) + { + char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1]; ++ char reply_string[MAX_PREFIX+1]; + +! { RULE_RESULT *rule; +! rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE , env); +! if (rule){ +! strncpy(reply_string,rule->result,sizeof(reply_string)); +! reply_string[sizeof(reply_string)-1] = '\0'; +! if (rule->result) +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! else +! if ((ps_global->VAR_REPLY_STRING) && (ps_global->VAR_REPLY_STRING[0])){ +! strncpy(reply_string,ps_global->VAR_REPLY_STRING, sizeof(reply_string)-1); +! reply_string[sizeof(reply_string)-1] = '\0'; +! } +! else +! strncpy(reply_string,"> ",sizeof("> ")); +! } +! +! strncpy(buf, reply_string, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + + /* set up the prefix to quote included text */ +*************** +*** 917,926 **** + 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))); + } + + +--- 938,966 ---- + int + reply_quote_str_contains_tokens(void) + { +! char *reply_string; +! +! reply_string = (char *) malloc( 80*sizeof(char)); +! { RULE_RESULT *rule; +! rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE, NULL); +! if (rule){ +! reply_string = cpystr(rule->result); +! if (rule->result) +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! else +! if ((ps_global->VAR_REPLY_STRING) && (ps_global->VAR_REPLY_STRING[0])){ +! strncpy(reply_string,ps_global->VAR_REPLY_STRING, sizeof(reply_string)-1); +! reply_string[sizeof(reply_string)-1] = '\0'; +! } +! else +! reply_string = cpystr("> "); +! } +! return(reply_string && reply_string[0] && +! (strstr(reply_string, from_token) || +! strstr(reply_string, nick_token) || +! strstr(reply_string, init_token))); + } + + +*************** +*** 1385,1390 **** +--- 1425,1434 ---- + buf[0] = '\0'; + + switch(type){ ++ case iFfrom: ++ addr = env && env->sparep ? env->sparep : NULL; ++ break; ++ + case iFrom: + addr = env ? env->from : NULL; + break; +*************** +*** 1797,1818 **** + + 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); +--- 1841,2034 ---- + + break; + ++ case iProcid: ++ if(ps_global->procid){ ++ strncpy(buf, ps_global->procid, maxlen); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ ++ case iRole: ++ if (ps_global->role){ ++ strncpy(buf, ps_global->role, maxlen); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ ++ case iRoleNick: ++ if(role && role->nick){ ++ strncpy(buf, role->nick, maxlen); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ ++ case iPkey: ++ if(ps_global->pressed_key){ ++ strcpy(buf, ps_global->pressed_key); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ ++ case iScreen: ++ if(ps_global->screen_name){ ++ strncpy(buf, ps_global->screen_name, maxlen); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ ++ case iFfrom: + case iFrom: + case iTo: + case iCc: + case iSender: + case iRecips: + case iInit: ++ if (env) + get_addr_data(env, type, buf, maxlen); + break; + +! case iFolder: +! if(ps_global->cur_folder){ +! strncpy(buf,ps_global->cur_folder, maxlen); +! buf[maxlen] = '\0'; +! } +! break; +! +! case iCollection: +! if(ps_global->context_current->nickname){ +! strncpy(buf,ps_global->context_current->nickname, maxlen); +! buf[maxlen] = '\0'; +! } +! break; +! +! case iFlag: +! {MAILSTREAM *stream = ps_global->mail_stream; +! MSGNO_S *msgmap = NULL; +! long msgno; +! MESSAGECACHE *mc; +! strncpy(buf, "_FLAG_", maxlen); /* default value */ +! if (stream){ +! msgmap = sp_msgmap(stream); +! msgno = mn_m2raw(msgmap, rules_cursor_pos(stream)); +! if (msgno > 0L) mc = stream ? mail_elt(stream, msgno) : NULL; +! if (mc) +! sprintf(buf,"%s%s%s%s",mc->flagged ? "*" : "", +! mc->recent ? (mc->seen ? "R" : "N") : (mc->seen) ? "R" : "U", +! mc->answered ? "A" : "", +! mc->deleted ? "D" : "" ); +! } +! buf[maxlen] = '\0'; +! } +! break; +! +! case iAltAddress: +! if(ps_global->VAR_ALT_ADDRS != NULL +! && ps_global->VAR_ALT_ADDRS[0] != NULL){ +! size_t len; +! int i, j; +! +! for(i = 0, len = 0; len < maxlen && ps_global->VAR_ALT_ADDRS[i]; i++){ +! for(j = 0; len < maxlen && ps_global->VAR_ALT_ADDRS[i][j] != '\0'; j++){ +! if(ps_global->VAR_ALT_ADDRS[i][j] == ';') +! buf[len++] = '\\'; +! buf[len++] = ps_global->VAR_ALT_ADDRS[i][j]; +! } +! if(len < maxlen){ +! if(ps_global->VAR_ALT_ADDRS[i+1] != NULL) +! buf[len++] = ';'; +! else +! buf[len++] = '\0'; +! } +! } +! buf[maxlen] = '\0'; +! } +! break; +! +! case iNick: +! case iFccFrom: +! case iFccSender: +! if (env){ +! ADDRESS *tmp_adr; +! +! switch(type){ +! case iNick: +! tmp_adr = env->from ? copyaddr(env->from) +! : env->sender ? copyaddr(env->sender) : NULL; +! break; +! case iFccFrom: +! tmp_adr = env->from ? copyaddr(env->from) : NULL; +! break; +! case iFccSender: +! tmp_adr = env->sender ? copyaddr(env->sender) : NULL; +! break; +! default: alpine_panic("Unhandled Rules case (01)"); +! } +! if(type == iNick) +! get_nickname_from_addr(tmp_adr, buf, maxlen); +! else +! get_fcc_from_addr(tmp_adr, buf, maxlen); +! mail_free_address(&tmp_adr); + } + break; + ++ case iAddressSender: ++ case iAddressCc: ++ case iAddressRecip: ++ case iAddressTo: ++ case iFadd: ++ { ++ int plen = 0; /* partial length */ ++ ADDRESS *sparep2 = (type == iAddressTo || type == iAddressRecip) ++ ? ((env && env->to) ++ ? copyaddrlist(env->to) ++ : NULL) ++ : (type == iAddressCc) ++ ? ((env && env->cc) ++ ? copyaddrlist(env->cc) ++ : NULL) ++ : (type == iAddressSender) ++ ? ((env && env->sender) ++ ? copyaddr(env->sender) ++ : NULL) ++ : ((env && env->sparep) ++ ? copyaddr((ADDRESS *)env->sparep) ++ : NULL); ++ ADDRESS *sparep; ++ ++ if (type == iAddressRecip){ ++ ADDRESS *last_to = NULL; ++ ++ for(last_to = sparep2;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 ? copyaddrlist(env->cc) : NULL); ++ ++ } ++ sparep = sparep2; ++ for(; sparep ; sparep = sparep->next) ++ if(sparep && sparep->mailbox && sparep->mailbox[0] && ++ (plen ? plen + 1 : plen) + strlen(sparep->mailbox) <= maxlen){ ++ if (plen == 0) ++ strcpy(buf, sparep->mailbox); ++ else{ ++ strcat(buf, " "); ++ strcat(buf, sparep->mailbox); ++ } ++ if(sparep->host && ++ sparep->host[0] && ++ sparep->host[0] != '.' && ++ strlen(buf) + strlen(sparep->host) + 1 <= maxlen){ ++ strcat(buf, "@"); ++ strcat(buf, sparep->host); ++ } ++ plen = strlen(buf); ++ } ++ mail_free_address(&sparep2); ++ } ++ ++ break; ++ + case iNewLine: + if(maxlen >= strlen(NEWLINE)){ + strncpy(buf, NEWLINE, maxlen); +*************** +*** 1840,1845 **** +--- 2056,2066 ---- + + break; + ++ case iLcc: /* fake it, there are not enough spare pointers */ ++ if (env && env->date) ++ sprintf(buf,"%s",env->date); ++ break; ++ + case iNews: + case iCurNews: + get_news_data(env, type, buf, maxlen); +*************** +*** 1889,1894 **** +--- 2110,2123 ---- + + break; + ++ case iOpeningText: ++ case iOpeningTextNQ: ++ if(env && env->sparep){ ++ strncpy(buf, ((SPAREP_S *)env->sparep)->value, maxlen); ++ buf[maxlen] = '\0'; ++ } ++ break; ++ + case iSubject: + case iShortSubject: + if(env && env->subject){ +*************** +*** 1951,1957 **** + 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)){ +--- 2180,2197 ---- + if(!env) + return; + +! { RULE_RESULT *rule; +! rule = get_result_rule(V_REPLY_LEADIN_RULES, FOR_REPLY_INTRO, env); +! if(rule){ +! strncpy(buf, rule->result, MAX_DELIM); +! if (rule->result) +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! else +! 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)){ +*************** +*** 2210,2215 **** +--- 2450,2456 ---- + { + size_t l; + char *p, buftmp[MAILTMPLEN]; ++ RULE_RESULT *rule; + + if(!env) + return(NULL); +*************** +*** 2217,2225 **** + 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) +--- 2458,2476 ---- + dprint((9, "checking subject: \"%s\"\n", + env->subject ? env->subject : "NULL")); + +! buftmp[0] = '\0'; +! ps_global->procid = cpystr("fwd-subject"); +! if (rule = get_result_rule(V_FORWARD_RULES,FOR_COMPOSE, env)){ +! snprintf(buftmp, sizeof(buftmp), "%s", rule->result); +! fs_give((void **)&rule->result); +! fs_give((void **)&rule); +! } +! else if(env->subject) +! snprintf(buftmp, sizeof(buftmp), "%s", env->subject); +! buftmp[sizeof(buftmp)-1] = '\0'; +! fs_give((void **)&ps_global->procid); +! +! if(buftmp[0]){ /* add (fwd)? */ + /* 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) +diff -rc alpine-2.21/pith/rules.c alpine-2.21.rules/pith/rules.c +*** alpine-2.21/pith/rules.c Sun Feb 5 16:15:22 2017 +--- alpine-2.21.rules/pith/rules.c Sun Feb 5 16:15:22 2017 +*************** +*** 0 **** +--- 1,1416 ---- ++ /* This module was written by ++ * ++ * Eduardo Chappa (chappa@washington.edu) ++ * http://patches.freeiz.com/alpine/ ++ * ++ * Original Version: November 1999 ++ * Last Modified : September 14, 2013 ++ * ++ * Send bug reports about this module to the address above. ++ */ ++ ++ #include "../pith/headers.h" ++ #include "../pith/state.h" ++ #include "../pith/conf.h" ++ #include "../pith/copyaddr.h" ++ #include "../pith/mailindx.h" ++ #include "../pith/rules.h" ++ ++ #define CSEP_C ('\001') ++ #define CSEP_S ("\001") ++ ++ /* Internal Prototypes */ ++ ++ int test_condition (CONDITION_S *, int, ENVELOPE *); ++ int test_in (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int test_ni (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int test_not_in (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int test_not_ni (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int test_eq (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int test_not_eq (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++ int isolate_condition (char *, char **, int *); ++ int sanity_check_condition (char *); ++ char *test_rule (RULELIST *, int, ENVELOPE *, int *); ++ char *trim (RULEACTION_S *, int, ENVELOPE *); ++ char *rextrim (RULEACTION_S *, int, ENVELOPE *); ++ char *raw_value (RULEACTION_S *, int, ENVELOPE *); ++ char *extended_value (RULEACTION_S *, int, ENVELOPE *); ++ char *exec_fcn (RULEACTION_S *, int, ENVELOPE *); ++ char *expand (char *, void *); ++ char *get_name_token (char *); ++ char *advance_to_char (char *, char, int, int *); ++ char **functions_for_token (char *); ++ char *canonicalize_condition (char *, int *); ++ void free_token_value (TOKEN_VALUE **); ++ void free_condition (CONDITION_S **); ++ void free_condition_value (CONDVALUE_S **); ++ void free_ruleaction (RULEACTION_S **); ++ void free_rule (RULE_S **); ++ void free_rule_list (RULELIST **); ++ void free_alloc_rule (void **, int); ++ void *alloc_mem (size_t); ++ void add_rule (int, int); ++ void set_rule_list (struct variable *); ++ void parse_patterns_into_action(TOKEN_VALUE **); ++ void free_parsed_value(TOKEN_VALUE **value); ++ RULE_S *parse_rule (char *, int); ++ RULELIST *get_rule_list (char **, int, int); ++ TOKEN_VALUE *parse_group_data (char *,int *); ++ TOKEN_VALUE *copy_parsed_value (TOKEN_VALUE *, int, ENVELOPE *); ++ CONDVALUE_S *fill_condition_value (char *); ++ CONDITION_S *fill_condition (char *); ++ CONDITION_S *parse_condition (char *, int *); ++ PRULELIST_S *add_prule (PRULELIST_S *, PRULELIST_S *); ++ RULEACTION_S *parse_action (char *, int); ++ ++ REL_TOKEN rel_rules_test[] = { ++ {EQ_REL, Equal, test_eq}, ++ {IN_REL, Subset, test_in}, ++ {NI_REL, Includes, test_ni}, ++ {NOT_EQ_REL, NotEqual, test_not_eq}, ++ {NOT_IN_REL, NotSubset, test_not_in}, ++ {NOT_NI_REL, NotIncludes, test_not_ni}, ++ {NULL, EndTypes, NULL} ++ }; ++ ++ #define NREL (sizeof(rel_rules_test)/sizeof(rel_rules_test[0]) - 1) ++ ++ RULE_FCN rule_fcns[] = { ++ {COPY_FCN, extended_value, FOR_SAVE|FOR_COMPOSE}, ++ {SAVE_FCN, extended_value, FOR_SAVE}, ++ {EXEC_FCN, exec_fcn, FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++ {REPLY_FCN, extended_value, FOR_REPLY_INTRO}, ++ {TRIM_FCN, trim, FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++ {REPLACE_FCN, extended_value, FOR_REPLACE}, ++ {SORT_FCN, raw_value, FOR_SORT}, ++ {INDEX_FCN, raw_value, FOR_INDEX}, ++ {COMMAND_FCN, raw_value, FOR_KEY}, ++ {REPLYSTR_FCN, raw_value, FOR_COMPOSE}, ++ {SIGNATURE_FCN, raw_value, FOR_COMPOSE}, ++ {RESUB_FCN, extended_value, FOR_RESUB}, ++ {STARTUP_FCN, raw_value, FOR_STARTUP}, ++ {REXTRIM_FCN, rextrim, FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++ {THRDSTYLE_FCN, raw_value, FOR_THREAD}, ++ {THRDINDEX_FCN, raw_value, FOR_THREAD}, ++ {SMTP_FCN, raw_value, FOR_COMPOSE}, ++ {NULL, 0, FOR_NOTHING} ++ }; ++ ++ char* token_rules[] = { ++ FROM_TOKEN, ++ NICK_TOKEN, ++ FCCF_TOKEN, ++ FCCS_TOKEN, ++ OTEXT_TOKEN, ++ OTEXTNQ_TOKEN, ++ ROLE_TOKEN, ++ FOLDER_TOKEN, ++ SUBJ_TOKEN, ++ PROCID_TOKEN, ++ THDDSPSTY_TOKEN, ++ THDNDXSTY_TOKEN, ++ FLAG_TOKEN, ++ COLLECT_TOKEN, ++ THDDSPSTY_TOKEN, ++ ADDR_TOKEN, ++ TO_TOKEN, ++ ADDTO_TOKEN, ++ ADDCC_TOKEN, ++ ADDRECIP_TOKEN, ++ SCREEN_TOKEN, ++ KEY_TOKEN, ++ SEND_TOKEN, ++ CC_TOKEN, ++ LCC_TOKEN, ++ BCC_TOKEN, ++ FFROM_TOKEN, ++ FADDRESS_TOKEN, ++ NULL ++ }; ++ ++ #define NTOKENS (sizeof(token_rules)/sizeof(token_rules[0]) - 1) ++ #define NFCN (sizeof(rule_fcns)/sizeof(rule_fcns[0]) - 1) ++ ++ char *subj_fcn[] = {SUBJ_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++ char *from_fcn[] = {FROM_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++ char *otext_fcn[] = {OTEXT_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++ char *otextnq_fcn[] = {OTEXTNQ_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++ ++ char *adto_fcn[] = {ADDTO_TOKEN, EXEC_FCN, NULL, NULL, NULL}; ++ ++ char **fcns_for_index[] = {subj_fcn, from_fcn, otext_fcn, otextnq_fcn}; ++ ++ #define NFCNFI (sizeof(fcns_for_index)/sizeof(fcns_for_index[0])) /*for idx*/ ++ #define NFPT (sizeof(fcns_for_index[0])) /* functions pert token */ ++ ++ SPAREP_S * ++ get_sparep_for_rule(char *value, int flag) ++ { ++ SPAREP_S *rv; ++ rv = (SPAREP_S *) alloc_mem(sizeof(SPAREP_S)); ++ rv->flag = flag; ++ rv->value = value ? cpystr(value) : NULL; ++ return rv; ++ } ++ ++ void free_sparep_for_rule(void **sparep) ++ { ++ SPAREP_S *spare = (SPAREP_S *) *sparep; ++ if(!spare) return; ++ if(spare->value) ++ fs_give((void **)&spare->value); ++ fs_give((void **)sparep); ++ } ++ ++ ++ int context_for_function(char *name) ++ { ++ int i, j; ++ for (i = 0; i < NFCN && strcmp(rule_fcns[i].name, name); i++); ++ return i == NFCN ? 0 : rule_fcns[i].what_for; ++ ++ } ++ ++ char **functions_for_token(char *name) ++ { ++ int i; ++ for (i = 0; i < NFCNFI && strcmp(fcns_for_index[i][0], name); i++); ++ return i == NFCNFI ? NULL : fcns_for_index[i]; ++ } ++ ++ void ++ free_alloc_rule (void **voidtext, int code) ++ { ++ switch(code){ ++ case FREEREGEX : regfree((regex_t *)*voidtext); ++ break; ++ default: break; ++ } ++ } ++ ++ ++ ++ void free_token_value(TOKEN_VALUE **token) ++ { ++ if(token && *token){ ++ if ((*token)->testxt) ++ fs_give((void **)&(*token)->testxt); ++ free_alloc_rule (&(*token)->voidtxt, (*token)->codefcn); ++ if((*token)->next) ++ free_token_value(&(*token)->next); ++ fs_give((void **)token); ++ } ++ } ++ ++ void ++ free_condition_value(CONDVALUE_S **cvalue) ++ { ++ if(cvalue && *cvalue){ ++ if ((*cvalue)->tname) ++ fs_give((void **)&(*cvalue)->tname); ++ if ((*cvalue)->value) ++ free_token_value(&(*cvalue)->value); ++ fs_give((void **)cvalue); ++ } ++ } ++ ++ void free_condition(CONDITION_S **condition) ++ { ++ if(condition && *condition){ ++ if((*condition)->cndtype == Condition) ++ free_condition_value((CONDVALUE_S **)&(*condition)->cndrule); ++ else if((*condition)->cndtype == ParOpen || (*condition)->cndtype == ParClose) ++ fs_give(&(*condition)->cndrule); ++ if((*condition)->next) ++ free_condition(&((*condition)->next)); ++ fs_give((void **)condition); ++ } ++ } ++ ++ void free_ruleaction(RULEACTION_S **raction) ++ { ++ if(raction && *raction){ ++ if ((*raction)->token) ++ fs_give((void **)&((*raction)->token)); ++ if ((*raction)->function) ++ fs_give((void **)&((*raction)->function)); ++ if ((*raction)->value) ++ free_token_value(&(*raction)->value); ++ fs_give((void **)raction); ++ } ++ } ++ ++ void free_rule(RULE_S **rule) ++ { ++ if(rule && *rule){ ++ free_condition(&((*rule)->condition)); ++ free_ruleaction(&((*rule)->action)); ++ fs_give((void **)rule); ++ } ++ } ++ ++ void free_rule_list(RULELIST **rule) ++ { ++ if(!*rule) ++ return; ++ ++ if((*rule)->next) ++ free_rule_list(&((*rule)->next)); ++ ++ if((*rule)->prule) ++ free_rule(&((*rule)->prule)); ++ ++ fs_give((void **)rule); ++ } ++ ++ void ++ free_parsed_rule_list(PRULELIST_S **rule) ++ { ++ if(!*rule) ++ return; ++ ++ if((*rule)->next) ++ free_parsed_rule_list(&((*rule)->next)); ++ ++ if((*rule)->rlist) ++ free_rule_list(&((*rule)->rlist)); ++ ++ fs_give((void **)rule); ++ } ++ ++ void * ++ alloc_mem (size_t amount) ++ { ++ void *genmem; ++ memset(genmem = fs_get(amount), 0, amount); ++ return genmem; ++ } ++ ++ ++ void ++ parse_patterns_into_action(TOKEN_VALUE **tokenp) ++ { ++ if(!*tokenp) ++ return; ++ ++ if((*tokenp)->testxt){ ++ regex_t preg; ++ ++ (*tokenp)->voidtxt = NULL; ++ (*tokenp)->voidtxt = fs_get(sizeof(regex_t)); ++ if (regcomp((regex_t *)(*tokenp)->voidtxt, ++ (*tokenp)->testxt, REG_EXTENDED) != 0){ ++ regfree((regex_t *)(*tokenp)->voidtxt); ++ (*tokenp)->voidtxt = NULL; ++ } ++ } ++ if((*tokenp)->voidtxt) ++ (*tokenp)->codefcn = FREEREGEX; ++ if((*tokenp)->next) ++ parse_patterns_into_action(&(*tokenp)->next); ++ } ++ ++ ++ int ++ isolate_condition (char *data, char **cvalue, int *len) ++ { ++ char *p = data; ++ int done = 0, error = 0, next_condition = 0, l; ++ ++ if(*p == '"' && p[strlen(p) - 1] == '"'){ ++ p[strlen(p) - 1] = '\0'; ++ p++; ++ } ++ *cvalue = NULL; ++ while (*p && !done){ ++ switch (*p){ ++ case '_': *cvalue = advance_to_char(p,'}', STRICTLY, NULL); ++ if(*cvalue){ ++ strcat(*cvalue,"}"); ++ p += strlen(*cvalue); ++ } ++ else ++ error++; ++ done++; ++ case ' ': p++; ++ break; ++ case '&': ++ case '|': if (*(p+1) == *p){ /* looking for && or ||*/ ++ p += 2; ++ next_condition++; ++ } ++ else{ ++ error++; ++ done++; ++ } ++ break; ++ case '=': /* looking for => or -> */ ++ case '-': if (*(p+1) != '>' || next_condition) ++ error++; ++ done++; ++ break; ++ default : done++; ++ error++; ++ break; ++ } ++ } ++ *len = p - data; ++ return error ? -1 : (*cvalue ? 1 : 0); ++ } ++ ++ TOKEN_VALUE * ++ parse_group_data (char *data, int *error) ++ { ++ TOKEN_VALUE *rvalue; ++ char *p, *d; ++ int offset, err = 0, freeme = 0; ++ ++ if(error) ++ *error = 0; ++ ++ if (!data) ++ return (TOKEN_VALUE *) NULL; ++ ++ if(*data == '_'){ ++ d = detoken_src(data, FOR_RULE, NULL, NULL, NULL, NULL); ++ freeme++; ++ } ++ else ++ d = data; ++ ++ rvalue = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ if (p = advance_to_char(d,';', STRICTLY, &offset)){ ++ rvalue->testxt = p; ++ rvalue->next = parse_group_data(d + strlen(p) + 1 + offset, error); ++ } ++ else if (p = advance_to_char(d,'}', STRICTLY, NULL)) ++ rvalue->testxt = p; ++ else if (d && *d == '}') ++ rvalue->testxt = cpystr(""); ++ else{ ++ err++; ++ free_token_value(&rvalue); ++ } ++ if (error) ++ *error += err; ++ if(freeme != 0 && d != NULL) ++ fs_give((void **)&d); ++ return(rvalue); ++ } ++ ++ CONDVALUE_S * ++ fill_condition_value(char *data) ++ { ++ CONDVALUE_S *condition; ++ int i, done, error = 0; ++ char *group; ++ ++ for (i = 0, done = 0; done == 0 && token_rules[i] != NULL; i++) ++ done = strncmp(data,token_rules[i], strlen(token_rules[i])) ? 0 : 1; ++ if (done){ ++ condition = alloc_mem(sizeof(CONDVALUE_S)); ++ condition->tname = cpystr(token_rules[--i]); ++ data += strlen(token_rules[i]); ++ } ++ else if (*data == '_') { ++ char *itokname; ++ for (i = 0, done = 0; done == 0 && (itokname = itoken(i)->name) != NULL; i++) ++ done = strncmp(data+1, itokname, strlen(itokname)) ++ ? 0 : data[strlen(itokname) + 1] == '_'; ++ if (done){ ++ condition = (CONDVALUE_S *) alloc_mem(sizeof(CONDVALUE_S)); ++ condition->tname = fs_get(strlen(itokname) + 3); ++ sprintf(condition->tname, "_%s_", itokname); ++ data += strlen(itokname) + 2; ++ } ++ else ++ return NULL; ++ } ++ else ++ return NULL; ++ ++ for (; *data && *data == ' '; data++); ++ if (*data){ ++ for (i = 0, done = 0; done == 0 && rel_rules_test[i].value != NULL; i++) ++ done = strncmp(data, rel_rules_test[i].value, 2) ? 0 : 1; ++ if (done) ++ condition->ttype = rel_rules_test[--i].ttype; ++ else{ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ } ++ else{ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ ++ data += 2; ++ for (; *data && *data == ' '; data++); ++ if (*data++ != '{'){ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ group = advance_to_char(data,'}', STRICTLY, &error); ++ if (group || (!group && error < 0)){ ++ condition->value = parse_group_data(data, &error); ++ if(group && error) ++ free_condition_value(&condition); ++ if(group) ++ fs_give((void **) &group); ++ } ++ else ++ free_condition_value(&condition); ++ return condition; ++ } ++ ++ char * ++ canonicalize_condition(char *data, int *eoc) ++ { ++ char *p = data, *s, *t, c; ++ char *q = fs_get((5*strlen(data)+1)*sizeof(char)); ++ char tmp[10]; ++ int level, done, error, i; ++ ++ if(eoc) *eoc = -1; /* assume error */ ++ *q = '\0'; ++ if(*p == '"'){ ++ if(p[strlen(p) - 1] == '"') ++ p[strlen(p) - 1] = '\0'; ++ p++; ++ } ++ for(level = done = error = 0; *p && !done && !error; ){ ++ switch(*p){ ++ case ' ' : p++; break; ++ case '(' : strcat(q, CSEP_S); strcat(q, "("); ++ sprintf(tmp, "%d ", level++); ++ strcat(q, tmp); ++ p++; ++ break; ++ case ')' : strcat(q, CSEP_S); strcat(q, ")"); ++ sprintf(tmp, "%d ", --level); ++ strcat(q, tmp); ++ p++; ++ if(level < 0) error++; ++ break; ++ case '_' : for(s = p+1; *s >= 'A' && *s <= 'Z'; s++); ++ for(i = 0; token_rules[i] != NULL; i++) ++ if(!strncmp(token_rules[i], p, s-p)) ++ break; ++ if(token_rules[i] == NULL) ++ error++; ++ else if(*s++ == '_'){ ++ for(; *s == ' '; s++); ++ if(*s && *(s+1)){ ++ for(i = 0; rel_rules_test[i].value != NULL; i++) ++ if(!strncmp(rel_rules_test[i].value, s, 2)) ++ break; ++ if (rel_rules_test[i].value == NULL) ++ error++; ++ else{ ++ s += 2; ++ for(; *s == ' '; s++); ++ if(*s == '{'){ ++ if(*(s+1) != '}') ++ t = advance_to_char(s+1,'}', STRICTLY, NULL); ++ else ++ t = cpystr(""); ++ if(t != NULL){ ++ for(i = 0; t[i] != '\0' && t[i] != CSEP_C; i++); ++ if(t[i] == CSEP_C) error++; ++ if(error == 0){ ++ strcat(q, CSEP_S); strcat(q, "C["); ++ s += strlen(t) + 1; /* get past '{' */ ++ *s = '\0'; ++ strcat(q, p); ++ strcat(q, "}] "); ++ *s++ = '}'; ++ p = s; ++ } ++ fs_give((void **) &t); ++ } ++ else error++; ++ } ++ else ++ error++; ++ } ++ } ++ } ++ else error++; ++ break; ++ case '|': ++ case '&': if(*(p+1) = *p){ ++ strcat(q, CSEP_S); strcat(q, *p == '|' ? "OR " : "AND "); ++ p += 2; ++ } else error++; ++ break; ++ case '-': ++ case '=': if (*(p+1) == '>'){ ++ if(eoc) *eoc = p - data; ++ done++; ++ } ++ else ++ error++; ++ break; ++ default : error++; ++ break; ++ } ++ } ++ if(error || level > 0) /*simplistic approach by now */ ++ fs_give((void **)&q); ++ else ++ q[strlen(q)-1] = '\0'; ++ return q; ++ } ++ ++ /* for a canonical condition, return if it is constructed according ++ * to logical rules such as AND or OR between conditions, etc. We assume ++ * we already canonicalized data, or else this will not work. ++ */ ++ int ++ sanity_check_condition(char *data) ++ { ++ int i, error; ++ char *s, *t, *d; ++ ++ if(data == NULL || *data == '\0') /* no data in, no data out */ ++ return 0; ++ ++ d = fs_get((strlen(data)+1)*sizeof(char)); ++ for(s = data,i = 0; (t = strchr(s, CSEP_C))!= NULL && (d[i] = *(t+1)); s = t+1, i++); ++ d[i] = '\0'; ++ for(i = 0, error = 0; d[i] != '\0' && error == 0; i++){ ++ switch(d[i]){ ++ case 'C': if((d[i+1] != '\0' && (d[i+1] == '(' || d[i+1] == 'C')) ++ || (i == 0 && d[1] != 'A' && d[1] != 'O' && d[1] != '\0')) ++ error++; ++ break; ++ case ')': if(i == 0 || d[i+1] != '\0' && (d[i+1] == 'C' || d[i+1] == '(')) ++ error++; ++ break; ++ case '(': if(d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O') ++ error++; ++ break; ++ case 'O': ++ case 'A': if(i == 0 || d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O') ++ error++; ++ break; ++ default : error++; ++ } ++ } ++ if(d) fs_give((void **)&d); ++ return error ? 0 : 1; ++ } ++ ++ /* given a parsed data that satisfies sanity checks, parse it ++ * into a condition we can check later on. ++ */ ++ CONDITION_S * ++ fill_condition(char *data) ++ { ++ char *s, *t, *u; ++ CONDITION_S *rv = NULL; ++ CONDVALUE_S *cvalue; ++ int *i; ++ ++ if(data == NULL || *data == '\0' || (s = strchr(data, CSEP_C)) == NULL) ++ return NULL; ++ ++ rv = (CONDITION_S *) alloc_mem(sizeof(CONDITION_S)); ++ switch(*++s){ ++ case ')': ++ case '(': i = fs_get(sizeof(int)); ++ *i = atoi(s+1); ++ rv->cndrule = (void *) i; ++ rv->cndtype = *s == '(' ? ParOpen : ParClose; ++ break; ++ ++ case 'C': if((u = strchr(s+2, CSEP_C)) != NULL){ ++ *u = '\0'; ++ t = strrchr(s, ']'); ++ t = '\0'; ++ *u = CSEP_C; ++ } else ++ s[strlen(s) - 1] = '\0'; ++ rv->cndrule = (void *) fill_condition_value(s+2); ++ rv->cndtype = Condition; ++ break; ++ ++ case 'A': ++ case 'O': rv->cndtype = *s == 'A' ? And : Or; ++ break; ++ ++ default : fs_give((void **)&rv); ++ break; ++ } ++ rv->next = fill_condition(strchr(s, CSEP_C)); ++ ++ return rv; ++ } ++ ++ /* eoc = end of condition, equal to -1 on error */ ++ CONDITION_S * ++ parse_condition (char *data, int *eoc) ++ { ++ CONDITION_S *condition = NULL; ++ char *pvalue; ++ ++ if((pvalue = canonicalize_condition(data, eoc)) != NULL ++ && sanity_check_condition(pvalue) > 0) ++ condition = fill_condition(pvalue); ++ ++ if(pvalue) ++ fs_give((void **)&pvalue); ++ ++ if (condition == NULL && eoc) ++ *eoc = -1; ++ ++ return condition; ++ } ++ ++ RULEACTION_S * ++ parse_action (char *data, int context) ++ { ++ int i, done, is_save; ++ RULEACTION_S *raction = NULL; ++ char *function, *p = data; ++ ++ if (p == NULL || *p == '\0') ++ return NULL; ++ ++ is_save = *p == '-'; ++ p += 2; ++ for (; *p == ' '; p++); ++ ++ if (is_save){ /* got "->", a save-rule separator */ ++ raction = (RULEACTION_S *) alloc_mem(sizeof(RULEACTION_S)); ++ raction->function = cpystr("_SAVE_"); ++ raction->value = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ raction->context |= FOR_SAVE; ++ raction->exec = extended_value; ++ raction->value->testxt = cpystr(p); ++ return raction; ++ } ++ for (i = 0, done = 0; !done && (i < NFCN); i++) ++ done = (strstr(p,rule_fcns[i].name) == p); ++ p += done ? strlen(rule_fcns[--i].name) + 1 : 0; ++ if(!*p || (rule_fcns[i].what_for && !(rule_fcns[i].what_for & context))) ++ return NULL; ++ if (done){ ++ raction = alloc_mem(sizeof(RULEACTION_S)); ++ /* We assign raction->token to be subject. This is not necessary for ++ most rules. It is done only for rules that need it and will not ++ make any difference in rules that do not need it. It will hopefully ++ reduce complexity in the language ++ */ ++ raction->token = cpystr(SUBJ_TOKEN); ++ raction->function = cpystr(rule_fcns[i].name); ++ raction->context = rule_fcns[i].what_for; ++ raction->exec = rule_fcns[i].execute; ++ raction->value = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ raction->value->testxt = advance_to_char(p,'}', STRICTLY, NULL); ++ if(!raction->value->testxt) ++ free_ruleaction(&raction); ++ return raction; ++ } ++ ++ done = (((function = strstr(p, "_TRIM_")) != NULL) ++ ? 1 : ((function = strstr(p, "_COPY_")) != NULL) ++ ? 2 : ((function = strstr(p, "_EXEC_")) != NULL) ++ ? 3 : ((function = strstr(p, "_REXTRIM_")) != NULL) ++ ? 4 : ((function = strstr(p, "_REPLACE_")) != NULL) ++ ? 5 : 0); ++ ++ if(!function) ++ return (RULEACTION_S *) NULL; ++ ++ *function = '\0'; ++ raction = (RULEACTION_S *) alloc_mem(sizeof(RULEACTION_S)); ++ raction->token = get_name_token(p); ++ *function = '_'; ++ p += strlen(raction->token) + 1; ++ for (; *p && *p == ' '; p++); ++ if (!strncmp(p, ":=", 2)) ++ p += 2; ++ else{ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ for (; *p && *p == ' '; p++); ++ if (p != function){ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ p += done <= 3 ? 6 : 9; /* 6 = strlen("_EXEC_"), 9 = strlen("_REPLACE_") */ ++ if (*p != '{'){ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ *p = '\0'; ++ for(i = 0; i < NFCN && strcmp(function, rule_fcns[i].name);i++); ++ raction->function = cpystr(function); ++ raction->is_trim = strcmp(function,"_TRIM_") ? 0 : 1; ++ raction->is_rextrim = strcmp(function,"_REXTRIM_") ? 0 : 1; ++ raction->is_replace = strcmp(function,"_REPLACE_") ? 0 : 1; ++ raction->context = rule_fcns[i].what_for; ++ raction->exec = rule_fcns[i].execute; ++ *p++ = '{'; ++ if((raction->value = parse_group_data(p, NULL)) == NULL ++ || raction->value->testxt == NULL) ++ free_ruleaction(&raction); ++ if(raction && raction->is_rextrim) ++ parse_patterns_into_action(&raction->value); ++ return raction; ++ } ++ ++ RULE_S * ++ parse_rule (char *data, int context) ++ { ++ RULE_S *prule; /*parsed rule */ ++ int len = 0; ++ ++ if (!(prule = (RULE_S *) alloc_mem(sizeof(RULE_S))) || ++ !(prule->condition = parse_condition(data, &len)) || ++ !(prule->action = parse_action(data+len, context))) ++ free_rule(&prule); ++ ++ return prule; ++ } ++ ++ RULELIST * ++ get_rule_list(char **list, int context, int i) ++ { ++ RULE_S *rule; ++ RULELIST *trulelist = NULL; ++ ++ if (list[i] && *list[i]){ ++ if(rule = parse_rule(list[i], context)){ ++ trulelist = (RULELIST *)alloc_mem(sizeof(RULELIST)); ++ trulelist->prule = rule; ++ trulelist->next = get_rule_list(list, context, i+1); ++ } ++ else ++ trulelist = get_rule_list(list, context, i+1); ++ } ++ return trulelist; ++ } ++ ++ PRULELIST_S * ++ add_prule(PRULELIST_S *rule_list, PRULELIST_S *rule) ++ { ++ if (!rule_list) ++ rule_list = (PRULELIST_S *) alloc_mem(sizeof(PRULELIST_S)); ++ ++ if(rule_list->next) ++ rule_list->next = add_prule(rule_list->next, rule); ++ else{ ++ if (rule_list->rlist) ++ rule_list->next = rule; ++ else ++ rule_list = rule; ++ } ++ return rule_list; ++ } ++ ++ void ++ add_rule(int code, int context) ++ { ++ char **list = ps_global->vars[code].current_val.l; ++ PRULELIST_S *prulelist, *trulelist, *orulelist; ++ ++ if (list && *list && **list){ ++ trulelist = (PRULELIST_S *)alloc_mem(sizeof(PRULELIST_S)); ++ trulelist->varnum = code; ++ if (trulelist->rlist = get_rule_list(list, context, 0)) ++ ps_global->rule_list = add_prule(ps_global->rule_list, trulelist); ++ else ++ free_parsed_rule_list(&trulelist); ++ } ++ } ++ ++ /* see create_rule_list below */ ++ void ++ set_rule_list(struct variable *vars) ++ { ++ set_current_val(&vars[V_THREAD_DISP_STYLE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_THREAD_INDEX_STYLE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_COMPOSE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_FORWARD_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_INDEX_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_KEY_RULES], FALSE, TRUE); ++ set_current_val(&vars[V_REPLACE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_REPLY_INDENT_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_REPLY_LEADIN_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_RESUB_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SAVE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SMTP_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SORT_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_STARTUP_RULES], TRUE, TRUE); ++ } ++ ++ /* see set_rule_list above */ ++ void ++ create_rule_list(struct variable *vars) ++ { ++ set_rule_list(vars); ++ add_rule(V_THREAD_DISP_STYLE_RULES, FOR_THREAD); ++ add_rule(V_THREAD_INDEX_STYLE_RULES, FOR_THREAD); ++ add_rule(V_COMPOSE_RULES, FOR_COMPOSE); ++ add_rule(V_FORWARD_RULES, FOR_COMPOSE); ++ add_rule(V_INDEX_RULES, FOR_INDEX); ++ add_rule(V_KEY_RULES, FOR_KEY); ++ add_rule(V_REPLACE_RULES, FOR_REPLACE); ++ add_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE); ++ add_rule(V_REPLY_LEADIN_RULES, FOR_REPLY_INTRO); ++ add_rule(V_RESUB_RULES, FOR_RESUB|FOR_TRIM); ++ add_rule(V_SAVE_RULES, FOR_SAVE); ++ add_rule(V_SMTP_RULES, FOR_COMPOSE); ++ add_rule(V_SORT_RULES, FOR_SORT); ++ add_rule(V_STARTUP_RULES, FOR_STARTUP); ++ } ++ ++ int ++ condition_contains_token(CONDITION_S *condition, char *token) ++ { ++ while(condition && condition->cndtype != Condition) ++ condition = condition->next; ++ ++ return condition ++ ? (!strcmp(COND(condition)->tname, token) ++ ? 1 ++ : condition_contains_token(condition->next, token)) ++ : 0; ++ } ++ ++ RULELIST * ++ get_rulelist_from_code(int code, PRULELIST_S *list) ++ { ++ return list ? (list->varnum == code ? list->rlist ++ : get_rulelist_from_code(code, list->next)) ++ : (RULELIST *) NULL; ++ } ++ ++ char * ++ test_rule(RULELIST *rlist, int ctxt, ENVELOPE *env, int *n) ++ { ++ char *result; ++ ++ if(!rlist) ++ return NULL; ++ ++ if (result = process_rule(rlist->prule, ctxt, env)) ++ return result; ++ else{ ++ (*n)++; ++ return test_rule(rlist->next, ctxt, env, n); ++ } ++ } ++ ++ RULE_S * ++ get_rule (RULELIST *rule, int n) ++ { ++ return rule ? (n ? get_rule(rule->next, n-1) : rule->prule) ++ : NULL; ++ } ++ ++ /* get_result_rule: ++ * Parameters: list: the list of rules to be passed to the function to check ++ * rule_context: context of the rule ++ * env : envelope used to check the rule, if needed. ++ * ++ * Returns: The value of the first rule that is satisfied in the list, or ++ * NULL if not. This function should be called in the following ++ * way (notice that memory is freed by caller). ++ * ++ * You should use this function to obtain the result of a rule. You can ++ * also call directly "process_rule", but I advice to use this function if ++ * there's no difference on which function to call. ++ ++ RULE_RESULT *rule; ++ ++ rule = (RULE_RESULT *) ++ get_result_rule(V_SOME_RULE, context, envelope); ++ ++ if (rule){ ++ assign the value of rule->result; ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ */ ++ ++ RULE_RESULT * ++ get_result_rule(int code, int rule_context, ENVELOPE *env) ++ { ++ char *rule_result; ++ RULE_RESULT *rule = NULL; ++ RULELIST *rlist; ++ int n = 0; ++ ++ if(!(rule_context & FOR_RULE)) ++ rule_context |= FOR_RULE; ++ rlist = get_rulelist_from_code(code, ps_global->rule_list); ++ if (rlist){ ++ rule_result = test_rule(rlist, rule_context, env, &n); ++ if (rule_result && *rule_result){ ++ rule = (RULE_RESULT *) fs_get (sizeof(RULE_RESULT)); ++ rule->result = rule_result; ++ rule->number = n; ++ } ++ } ++ return rule; ++ } ++ ++ char *get_rule_result(int rule_context, char *newfolder, int code) ++ { ++ char *rule_result = NULL; ++ ENVELOPE *news_envelope; ++ RULE_RESULT *rule; ++ ++ if (IS_NEWS(ps_global->mail_stream)){ ++ news_envelope = mail_newenvelope(); ++ news_envelope->newsgroups = cpystr(newfolder); ++ } ++ else ++ news_envelope = NULL; ++ ++ rule = get_result_rule(code, rule_context, news_envelope); ++ ++ if (news_envelope) ++ mail_free_envelope (&news_envelope); ++ ++ if (rule){ ++ rule_result = cpystr(rule->result); ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ return rule_result; ++ } ++ ++ /* process_rule: ++ Parameters: prule, a processed rule, ready to be tested ++ rule_context: context of the rule, and ++ env: An envelope if needed. ++ ++ Returns : The value of the processed rule_data if the processing was ++ successful and matches context and possibly the envelope, or ++ NULL if there's no match ++ */ ++ ++ char * ++ process_rule (RULE_S *prule, int rule_context, ENVELOPE *env) ++ { ++ if(!prule) ++ return NULL; ++ ++ if(!(rule_context & FOR_RULE)) ++ rule_context |= FOR_RULE; ++ ++ return test_condition(prule->condition, rule_context, env) ++ ? (prule->action->exec)(prule->action, rule_context, env) ++ : NULL; ++ } ++ ++ TOKEN_VALUE * ++ copy_parsed_value(TOKEN_VALUE *value, int ctxt, ENVELOPE *env) ++ { ++ TOKEN_VALUE *tval = NULL; ++ ++ if(!value) ++ return NULL; ++ ++ if(value->testxt){ ++ tval = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ tval->testxt = detoken_src(value->testxt, ctxt, env, NULL, NULL, NULL); ++ tval->voidtxt = value->voidtxt; ++ tval->codefcn = value->codefcn; ++ } ++ if(value->next) ++ tval->next = copy_parsed_value(value->next, ctxt, env); ++ ++ return tval; ++ } ++ ++ void ++ free_parsed_value(TOKEN_VALUE **value) ++ { ++ TOKEN_VALUE *tval = NULL; ++ ++ if(!*value) ++ return; ++ ++ if((*value)->testxt) ++ fs_give((void **)&(*value)->testxt); ++ ++ if((*value)->next) ++ free_parsed_value(&(*value)->next); ++ ++ fs_give((void **)value); ++ } ++ ++ int ++ test_condition_work(CONDITION_S *bc, CONDITION_S *ec, int rcntxt, ENVELOPE *env) ++ { ++ int rv,level; ++ TOKEN_VALUE *group; ++ CONDITION_S *cend; ++ ++ switch(bc->cndtype){ ++ case Condition: group = copy_parsed_value(COND(bc)->value, rcntxt, env); ++ rv = (*rel_rules_test[COND(bc)->ttype].execute)(bc, group, env, rcntxt); ++ free_parsed_value(&group); ++ if(bc == ec) ++ return rv; ++ if(bc->next == NULL) ++ return rv; ++ else ++ switch(bc->next->cndtype){ ++ case And: return rv ? test_condition_work(bc->next->next, ec, rcntxt, env) : 0; ++ break; ++ case Or : return rv ? 1 : test_condition_work(bc->next->next, ec, rcntxt, env); ++ break; ++ case ')': return rv; ++ default : rv = 0; break; /* fail, we should not be here */ ++ } ++ break; ++ ++ case ParOpen: level = ((int *)bc->cndrule)[0]; ++ for(cend = bc; cend->next && (cend->next->cndtype != ParClose ++ || ((int *)cend->next->cndrule)[0] != level); ++ cend = cend->next); ++ rv = test_condition_work(bc->next, cend, rcntxt, env); ++ cend = cend->next; /* here we are at ')' */ ++ if(cend->next == NULL) ++ return rv; ++ else{ ++ switch(cend->next->cndtype){ ++ case And: return rv ? test_condition_work(cend->next->next, ec, rcntxt, env) : 0; ++ break; ++ case Or : return rv ? 1 : test_condition_work(cend->next->next, ec, rcntxt, env); ++ break; ++ default : rv = 0; break; /* fail, we should not be here */ ++ } ++ } ++ break; ++ default: rv = 0; break; /* fail, we should not be here */ ++ } ++ return rv; /* we never ever get here */ ++ } ++ ++ ++ int ++ test_condition(CONDITION_S *condition, int rcntxt, ENVELOPE *env) ++ { ++ return test_condition_work(condition, NULL, rcntxt, env); ++ } ++ ++ /* returns the name of the token it found or NULL if there is no token, the ++ * real value of the token is obtained by calling the detoken_src function. ++ */ ++ ++ char * ++ get_name_token (char *condition) ++ { ++ char *p = NULL, *q, *s; ++ ++ if ((q = strchr(condition,'_')) && (s = strchr(q+1,'_'))){ ++ char c = *++s; ++ *s = '\0'; ++ p = cpystr(q); ++ *s = c; ++ } ++ return p; ++ } ++ ++ /* This function tests if a string contained in the variable "group" is ++ * in the "condition" ++ */ ++ int test_in (CONDITION_S *condition, TOKEN_VALUE *group, ENVELOPE *env, ++ int context) ++ { ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ while (rv == 0 && test_group){ ++ if(!*test || strstr(test_group->testxt, test)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++ } ++ ++ int test_ni (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++ { ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ if(!test_group) ++ rv++; ++ while (rv == 0 && test_group){ ++ if(!*test_group->testxt || strstr(test, test_group->testxt)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++ } ++ ++ int test_not_in (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++ { ++ return !test_in(condition, group, env, context); ++ } ++ ++ int test_not_ni (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++ { ++ return !test_ni(condition, group, env, context); ++ } ++ ++ int test_eq (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++ { ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ while (rv == 0 && test_group){ ++ if((!*test && !*test_group->testxt) || !strcmp(test_group->testxt, test)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++ } ++ ++ int test_not_eq (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++ { ++ return !test_eq(condition, group, env, context); ++ } ++ ++ char * ++ do_trim (char *test, TOKEN_VALUE *tval) ++ { ++ char *begin_text; ++ int offset = 0; ++ ++ if (!tval) ++ return test; ++ ++ while(begin_text = strstr(test+offset,tval->testxt)){ ++ memmove(begin_text, begin_text+strlen(tval->testxt), strlen(begin_text) - strlen(tval->testxt)); ++ offset = begin_text - test; ++ } ++ ++ return do_trim(test, tval->next); ++ } ++ ++ char * ++ trim (RULEACTION_S *action, int context, ENVELOPE *env) ++ { ++ char *begin_text, *test; ++ RULEACTION_S *taction = action; ++ int offset; ++ ++ if (taction->context & context){ ++ if (test = detoken_src(taction->token, context, env, NULL, NULL, NULL)) ++ test = do_trim(test, taction->value); ++ return test; ++ } ++ return NULL; ++ } ++ ++ ++ char * ++ do_rextrim (char *test, TOKEN_VALUE *tval) ++ { ++ char *begin_text, *trim_text; ++ int offset = 0; ++ ++ if (!tval) ++ return test; ++ ++ trim_text = expand(test, tval->voidtxt); ++ while(trim_text && (begin_text = strstr(test+offset,trim_text))){ ++ strcpy(begin_text, begin_text+strlen(trim_text)); ++ offset = begin_text - test; ++ } ++ ++ return do_rextrim(test, tval->next); ++ } ++ ++ char * ++ rextrim (RULEACTION_S *action, int context, ENVELOPE *env) ++ { ++ char *test = NULL; ++ RULEACTION_S *taction = action; ++ ++ if (taction->context & context && ++ (test = detoken_src(taction->token, context, env, NULL, NULL, NULL))) ++ test = do_rextrim(test, taction->value); ++ return test; ++ } ++ ++ char * ++ raw_value (RULEACTION_S *action, int context, ENVELOPE *env) ++ { ++ return (action->context & context) ? cpystr(action->value->testxt) : NULL; ++ } ++ ++ char * ++ extended_value (RULEACTION_S *action, int ctxt, ENVELOPE *env) ++ { ++ return (action->context & ctxt) ++ ? detoken_src(action->value->testxt, ctxt, env, NULL, NULL, NULL) ++ : NULL; ++ } ++ ++ /* advances given_string until it finds given_char, memory freed by caller */ ++ char * ++ advance_to_char(char *given_string, char given_char, int flag, int *error) ++ { ++ char *b, *s, c; ++ int i, err = 0, quoted ; ++ ++ if (error) ++ *error = 0; ++ ++ if (!given_string || !*given_string) ++ return NULL; ++ ++ b = s = cpystr(given_string); ++ for(i = 0, quoted = 0, c = *s; c ; c = *++s){ ++ if(c == '\\'){ ++ quoted++; ++ continue; ++ } ++ if(quoted){ ++ quoted = 0; ++ if (c == given_char){ ++ err += flag & STRICTLY ? 0 : 1; ++ err++; ++ break; ++ } ++ b[i++] = '\\'; ++ } ++ if(c == given_char){ ++ err += flag & STRICTLY ? 0 : 1; ++ break; ++ } ++ b[i++] = c; ++ } ++ b[i] = '\0'; ++ if (b && (strlen(b) == strlen(given_string)) && (flag & STRICTLY)){ ++ fs_give((void **)&b); ++ return NULL; /* character not found */ ++ } ++ ++ if(b && !*b){ ++ fs_give((void **)&b); ++ err = -1; ++ } ++ ++ if (error) ++ *error = err; ++ ++ return b; ++ } ++ ++ /* Regular Expressions Support */ ++ char * ++ expand (char *string, void *pattern) ++ { ++ char c, *ret_string = NULL; ++ regmatch_t pmatch; ++ ++ if((regex_t *)pattern == NULL) ++ return NULL; ++ ++ if(regexec((regex_t *)pattern, string , 1, &pmatch, 0) == 0 ++ && pmatch.rm_so < pmatch.rm_eo){ ++ c = string[pmatch.rm_eo]; ++ string[pmatch.rm_eo] = '\0'; ++ ret_string = cpystr(string+pmatch.rm_so); ++ string[pmatch.rm_eo] = c; ++ } ++ return ret_string; ++ } ++ ++ ++ char * ++ exec_fcn (RULEACTION_S *action, int ctxt, ENVELOPE *env) ++ { ++ STORE_S *output_so; ++ gf_io_t gc, pc; ++ char *status, *rv, *cmd, *test; ++ ++ if(!(action->context & ctxt)) ++ return NULL; ++ ++ if((test = detoken_src(action->token, ctxt, env, NULL, NULL, NULL)) != NULL) ++ gf_set_readc(&gc, test, (unsigned long)strlen(test), CharStar, 0); ++ ++ if((output_so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL) ++ gf_set_so_writec(&pc, output_so); ++ ++ cmd = (char *)fs_get((strlen(action->value->testxt) + strlen("_TMPFILE_") + 2)*sizeof(char)); ++ sprintf(cmd,"%s _TMPFILE_", action->value->testxt); ++ status = (*ps_global->tools.exec_rule)(cmd, gc, pc); ++ ++ so_seek(output_so, 0L, 0); ++ rv = cpystr(output_so->dp); ++ gf_clear_so_writec(output_so); ++ so_give(&output_so); ++ if(test) ++ fs_give((void **)&test); ++ ++ return status ? NULL : rv; ++ } ++ ++ ENVELOPE * ++ rules_fetchenvelope(INDEXDATA_S *idata, int *we_clear) ++ { ++ ENVELOPE *env; ++ ++ if (idata->no_fetch){ ++ if(we_clear) ++ *we_clear = 1; ++ env = mail_newenvelope(); ++ env->from = copyaddrlist(idata->from); ++ env->to = copyaddrlist(idata->to); ++ env->cc = copyaddrlist(idata->cc); ++ env->sender = copyaddrlist(idata->sender); ++ env->subject = cpystr(idata->subject); ++ env->date = cpystr((unsigned char *) idata->date); ++ env->newsgroups = cpystr(idata->newsgroups); ++ return env; ++ } ++ if(we_clear) ++ *we_clear = 0; ++ env = pine_mail_fetchenvelope(idata->stream, idata->rawno); ++ return env; ++ } +diff -rc alpine-2.21/pith/rules.h alpine-2.21.rules/pith/rules.h +*** alpine-2.21/pith/rules.h Sun Feb 5 16:15:22 2017 +--- alpine-2.21.rules/pith/rules.h Sun Feb 5 16:15:22 2017 +*************** +*** 0 **** +--- 1,151 ---- ++ /* Included file rules.h */ ++ ++ #ifndef PITH_RULES_INCLUDED ++ #define PITH_RULES_INCLUDED ++ ++ #include "../pith/conftype.h" ++ #include "../pith/detoken.h" ++ #include "../pith/indxtype.h" ++ #include "../pith/rulestype.h" ++ ++ /* Exported prototypes */ ++ ++ void create_rule_list (struct variable *); ++ SPAREP_S *get_sparep_for_rule(char *, int); ++ void free_sparep_for_rule(void **); ++ void free_parsed_rule_list (PRULELIST_S **); ++ RULE_RESULT *get_result_rule (int, int, ENVELOPE *); ++ char *get_rule_result (int , char *, int); ++ char *process_rule (RULE_S *, int, ENVELOPE *); ++ char **functions_for_token (char *); ++ RULELIST *get_rulelist_from_code (int, PRULELIST_S *); ++ RULE_S *get_rule (RULELIST *, int); ++ int condition_contains_token (CONDITION_S *, char *); ++ int context_for_function (char *); ++ ENVELOPE *rules_fetchenvelope(INDEXDATA_S *idata, int *we_clear); ++ ++ /* Separators: ++ * ++ * A separator is a string that separates the rule condition with the rule ++ * action. Below is the list of separators ++ * ++ */ ++ ++ #define SAVE_TO_SEP "->" ++ #define APPLY_SEP "=>" ++ ++ /*------- Definitions of tokens -------*/ ++ /*------ Keep the list alphabetically sorted, thanks -------*/ ++ ++ #define ADDR_TOKEN "_ADDRESS_" ++ #define ADDCC_TOKEN "_ADDRESSCC_" ++ #define ADDRECIP_TOKEN "_ADDRESSRECIPS_" ++ #define ADDTO_TOKEN "_ADDRESSTO_" ++ #define BCC_TOKEN "_BCC_" ++ #define CC_TOKEN "_CC_" ++ #define COLLECT_TOKEN "_COLLECTION_" ++ #define FCCF_TOKEN "_FCCFROM_" ++ #define FCCS_TOKEN "_FCCSENDER_" ++ #define FLAG_TOKEN "_FLAG_" ++ #define FOLDER_TOKEN "_FOLDER_" ++ #define FADDRESS_TOKEN "_FORWARDADDRESS_" ++ #define FFROM_TOKEN "_FORWARDFROM_" ++ #define FROM_TOKEN "_FROM_" ++ #define KEY_TOKEN "_PKEY_" ++ #define LCC_TOKEN "_LCC_" ++ #define NICK_TOKEN "_NICK_" ++ #define OTEXT_TOKEN "_OPENINGTEXT_" ++ #define OTEXTNQ_TOKEN "_OPENINGTEXTNQ_" ++ #define PROCID_TOKEN "_PROCID_" ++ #define ROLE_TOKEN "_ROLE_" ++ #define SCREEN_TOKEN "_SCREEN_" ++ #define SEND_TOKEN "_SENDER_" ++ #define SUBJ_TOKEN "_SUBJECT_" ++ #define THDDSPSTY_TOKEN "_THREADSTYLE_" ++ #define THDNDXSTY_TOKEN "_THREADINDEX_" ++ #define TO_TOKEN "_TO_" ++ ++ /*------ Definitions of relational operands -------------*/ ++ ++ typedef struct { ++ char *value; ++ TestType ttype; ++ int (*execute)(); ++ } REL_TOKEN; ++ ++ /* Relational Operands */ ++ #define AND_REL "&&" /* For putting more than one condition */ ++ #define IN_REL "<<" /* For belonging relation */ ++ #define NI_REL ">>" /* For contain relation */ ++ #define NOT_IN_REL "!<" /* Negation of IN_REL */ ++ #define NOT_NI_REL "!>" /* Negation of NI_REL */ ++ #define EQ_REL "==" /* Test of equality */ ++ #define NOT_EQ_REL "!=" /* Test of inequality */ ++ #define OPEN_SET "{" /* Braces to open a set */ ++ #define CLOSE_SET "}" /* Braces to close a set*/ ++ ++ /*--- Context in which these variables can be used ---*/ ++ ++ typedef struct use_context { ++ char *name; ++ int what_for; ++ } USE_IN_CONTEXT; ++ ++ ++ static USE_IN_CONTEXT tokens_use[] = { ++ {NICK_TOKEN, FOR_SAVE}, ++ {FROM_TOKEN, FOR_SAVE}, ++ {OTEXT_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {OTEXTNQ_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {ROLE_TOKEN, FOR_COMPOSE}, ++ {FOLDER_TOKEN, FOR_SAVE|FOR_FOLDER|FOR_THREAD|FOR_COMPOSE}, ++ {SUBJ_TOKEN, FOR_SAVE|FOR_FOLDER|FOR_COMPOSE}, ++ {FLAG_TOKEN, FOR_SAVE|FOR_FLAG}, ++ {COLLECT_TOKEN, FOR_SAVE|FOR_COMPOSE|FOR_FOLDER|FOR_THREAD}, ++ {THDDSPSTY_TOKEN, FOR_THREAD}, ++ {THDNDXSTY_TOKEN, FOR_THREAD}, ++ {ADDR_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {TO_TOKEN, FOR_SAVE}, ++ {ADDTO_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {ADDCC_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {ADDRECIP_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {SCREEN_TOKEN, FOR_KEY}, ++ {KEY_TOKEN, FOR_KEY}, ++ {SEND_TOKEN, FOR_SAVE}, ++ {CC_TOKEN, FOR_SAVE}, ++ {BCC_TOKEN, FOR_COMPOSE}, ++ {LCC_TOKEN, FOR_COMPOSE}, ++ {FFROM_TOKEN, FOR_COMPOSE}, ++ {FADDRESS_TOKEN, FOR_COMPOSE}, ++ {NULL, FOR_NOTHING} ++ }; ++ ++ ++ typedef struct { ++ char *name; ++ char* (*execute)(); ++ int what_for; ++ } RULE_FCN; ++ ++ #define COMMAND_FCN "_COMMAND_" ++ #define COPY_FCN "_COPY_" ++ #define EXEC_FCN "_EXEC_" ++ #define INDEX_FCN "_INDEX_" ++ #define REPLACE_FCN "_REPLACE_" ++ #define REPLYSTR_FCN "_RESTR_" ++ #define REPLY_FCN "_REPLY_" ++ #define RESUB_FCN "_RESUB_" ++ #define REXTRIM_FCN "_REXTRIM_" ++ #define SAVE_FCN "_SAVE_" ++ #define SIGNATURE_FCN "_SIGNATURE_" ++ #define SMTP_FCN "_SMTP_" ++ #define SORT_FCN "_SORT_" ++ #define STARTUP_FCN "_STARTUP_" ++ #define THRDSTYLE_FCN "_THREADSTYLE_" ++ #define THRDINDEX_FCN "_THREADINDEX_" ++ #define TRIM_FCN "_TRIM_" ++ ++ #define STRICTLY 0x1 ++ #define RELAXED 0x2 ++ ++ #endif /* PITH_RULES_INCLUDED */ +diff -rc alpine-2.21/pith/rulestype.h alpine-2.21.rules/pith/rulestype.h +*** alpine-2.21/pith/rulestype.h Sun Feb 5 16:15:22 2017 +--- alpine-2.21.rules/pith/rulestype.h Sun Feb 5 16:15:22 2017 +*************** +*** 0 **** +--- 1,85 ---- ++ #ifndef PITH_RULESTYPE_INCLUDED ++ #define PITH_RULESTYPE_INCLUDED ++ ++ typedef struct rule { ++ char *result; /* The result of the rule */ ++ int number; /* The number of the rule that succeded, -1 if not */ ++ } RULE_RESULT; ++ ++ typedef struct { ++ char *value; ++ int type; ++ } RULE_ACTION; ++ ++ ++ #define TOKEN_VALUE struct tokenvalue_s ++ #define CONDITION_S struct condition_s ++ #define RULEACTION_S struct ruleaction_s ++ #define RULE_S struct rule_s ++ #define RULELIST struct rulelist_s ++ #define PRULELIST_S struct parsedrulelist_s ++ ++ #define FREEREGEX 1 ++ ++ TOKEN_VALUE { ++ char *testxt; ++ void *voidtxt; ++ int codefcn; ++ TOKEN_VALUE *next; ++ }; ++ ++ typedef enum {Equal, Subset, Includes, NotEqual, NotSubset, NotIncludes, EndTypes} TestType; ++ ++ typedef enum {And, Or, ParOpen, ParClose, Condition} CondType; ++ ++ typedef struct condvalue_s { ++ char *tname; /* tname ttype {value} */ ++ TestType ttype; /* type of rule */ ++ TOKEN_VALUE *value; /* value to check against */ ++ } CONDVALUE_S; ++ ++ CONDITION_S { ++ void *cndrule; /* text in condition */ ++ CondType cndtype; /* type of object */ ++ CONDITION_S *next; /* next condition to test */ ++ }; ++ ++ #define COND(C) ((CONDVALUE_S *)((C)->cndrule)) ++ ++ RULEACTION_S { ++ char *token; /* token := function{value} or token = null */ ++ char *function; /* token := function{value} or simply function{value}*/ ++ TOKEN_VALUE *value; /* token := function{value} or simply function{value}*/ ++ int context; /* context in which this rule can be used */ ++ char* (*exec)(); ++ unsigned int is_trim:1; ++ unsigned int is_rextrim:1; ++ unsigned int is_replace:1; ++ }; ++ ++ RULE_S { ++ CONDITION_S *condition; ++ RULEACTION_S *action; ++ }; ++ ++ RULELIST { ++ RULE_S *prule; ++ RULELIST *next; ++ }; ++ ++ PRULELIST_S { ++ int varnum; /* number associated to the variable */ ++ RULELIST *rlist; ++ PRULELIST_S *next; ++ }; ++ ++ #define USE_RAW_SP 0x001 ++ #define PROCESS_SP 0x010 ++ ++ typedef struct sparep { ++ int flag; ++ char *value; ++ } SPAREP_S; ++ ++ ++ #endif /* PITH_RULESTYPE_INCLUDED */ +diff -rc alpine-2.21/pith/save.c alpine-2.21.rules/pith/save.c +*** alpine-2.21/pith/save.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/save.c Sun Feb 5 16:15:22 2017 +*************** +*** 954,960 **** + *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) +--- 954,960 ---- + *date = '\0'; + + rv = save_fetch_append(stream, mn_m2raw(msgmap, i), +! NULL, save_stream, folder, context, + mc ? mc->rfc822_size : 0L, flags, date, so); + + if(flags) +diff -rc alpine-2.21/pith/send.c alpine-2.21.rules/pith/send.c +*** alpine-2.21/pith/send.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/send.c Sun Feb 5 16:15:22 2017 +*************** +*** 44,49 **** +--- 44,50 ---- + #include "../pith/ablookup.h" + #include "../pith/sort.h" + #include "../pith/smime.h" ++ #include "../pith/rules.h" + + #include "../c-client/smtp.h" + #include "../c-client/nntp.h" +*************** +*** 1742,1750 **** + 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; +--- 1743,1751 ---- + char error_buf[200], *error_mess = NULL, *postcmd; + ADDRESS *a; + ENVELOPE *fake_env = NULL; +! int addr_error_count, we_cancel = 0, choice, num_rules = 0, added_rules = -1; + long smtp_opts = 0L; +! char *verbose_file = NULL, **smtp_list; + BODY *bp = NULL; + PINEFIELD *pf; + BODY *origBody = body; +*************** +*** 1899,1918 **** + * 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]; +--- 1900,1948 ---- + * 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, ++ * First we check for rules and make a list using the rules. + */ +! if(ps_global->VAR_SMTP_RULES && ps_global->VAR_SMTP_RULES[0] +! && ps_global->VAR_SMTP_RULES[0][0]) +! while (ps_global->VAR_SMTP_RULES[num_rules]) num_rules++; +! +! if(num_rules){ +! int i, j; +! +! added_rules = 0; +! smtp_list = (char **) fs_get ((num_rules + 1)*sizeof(char*)); +! for (i = 0, j = 0; i < num_rules; i++){ +! RULELIST *rule = get_rulelist_from_code(V_SMTP_RULES, +! ps_global->rule_list); +! RULE_S *prule = get_rule(rule, i); +! if(prule){ +! char *rule_result = process_rule(prule, FOR_COMPOSE, header->env); +! if(rule_result && *rule_result){ +! smtp_list[j++] = cpystr(rule_result); +! added_rules++; +! } +! } +! } +! } +! +! if (added_rules < 0){ +! smtp_list = (char **) fs_get (sizeof(char*)); +! added_rules = 0; + } +! smtp_list[added_rules] = NULL; +! +! choice = smtp_list && smtp_list[0] && smtp_list[0][0] ? 3 : +! (alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0] ? 2 : +! (ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0] +! && ps_global->VAR_SMTP_SERVER[0][0] ? 1 : -1)); +! +! if(choice > 0){ +! /*---------- SMTP ----------*/ +! dprint((4, "call_mailer: via TCP (%s)\n",smtp_list[0])); +! TIME_STAMP("smtp-open start (tcp)", 1); +! sending_stream = smtp_open(choice == 3 ? smtp_list +! : (choice == 2 ? alt_smtp_servers +! : 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]; +diff -rc alpine-2.21/pith/sort.c alpine-2.21.rules/pith/sort.c +*** alpine-2.21/pith/sort.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/sort.c Sun Feb 5 16:15:22 2017 +*************** +*** 30,36 **** + #include "../pith/signal.h" + #include "../pith/busy.h" + #include "../pith/icache.h" +! + + /* + * global place to store mail_sort and mail_thread results +--- 30,36 ---- + #include "../pith/signal.h" + #include "../pith/busy.h" + #include "../pith/icache.h" +! #include "../pith/rules.h" + + /* + * global place to store mail_sort and mail_thread results +*************** +*** 686,692 **** + PAT_S *pat; + SortOrder the_sort_order; + int sort_is_rev; +! + /* set default order */ + the_sort_order = ps_global->def_sort; + sort_is_rev = the_sort_order == SortThread +--- 686,706 ---- + PAT_S *pat; + SortOrder the_sort_order; + int sort_is_rev; +! char *rule_result; +! SortOrder new_sort = EndofList; +! int is_rev; +! +! rule_result = get_rule_result(FOR_SORT, ps_global->cur_folder, V_SORT_RULES); +! if (rule_result && *rule_result){ +! new_sort = (SortOrder) translate(rule_result, 1); +! is_rev = (SortOrder) translate(rule_result, 0) == EndofList ? 0 : 1; +! fs_give((void **)&rule_result); +! } +! if (new_sort != EndofList){ +! the_sort_order = new_sort; +! sort_is_rev = is_rev; +! } +! else{ + /* set default order */ + the_sort_order = ps_global->def_sort; + sort_is_rev = the_sort_order == SortThread +*************** +*** 704,713 **** + : pat->action->revsort; + } + } +! + if(the_sort_order == SortThread && !(flags & SRT_MAN)) + ps_global->thread_cur_sort = ps_global->thread_def_sort; + + sort_folder(ps_global->mail_stream, ps_global->msgmap, + the_sort_order, sort_is_rev, flags, 1); + } +--- 718,763 ---- + : pat->action->revsort; + } + } +! } + if(the_sort_order == SortThread && !(flags & SRT_MAN)) + ps_global->thread_cur_sort = ps_global->thread_def_sort; + + sort_folder(ps_global->mail_stream, ps_global->msgmap, + the_sort_order, sort_is_rev, flags, 1); + } ++ ++ SortOrder translate(char *order, int is_rev) ++ { ++ int rev = 0; ++ if (!strncmp(order,"tHread", 6) ++ || (rev = !strncmp(order,"Reverse tHread", 14))) ++ return is_rev || rev ? SortThread : EndofList; ++ if (!strncmp(order,"OrderedSubj", 11) ++ || (rev = !strncmp(order,"Reverse OrderedSubj", 19))) ++ return is_rev || rev ? SortSubject2 : EndofList; ++ if (!strncmp(order,"Subject", 7) ++ || (rev = !strncmp(order,"Reverse SortSubject", 15))) ++ return is_rev || rev ? SortSubject : EndofList; ++ if (!strncmp(order,"Arrival", 7) ++ || (rev = !strncmp(order,"Reverse Arrival", 15))) ++ return is_rev || rev ? SortArrival : EndofList; ++ if (!strncmp(order,"From", 4) ++ || (rev = !strncmp(order,"Reverse From", 12))) ++ return is_rev || rev ? SortFrom : EndofList; ++ if (!strncmp(order,"To", 2) ++ || (rev = !strncmp(order,"Reverse To", 10))) ++ return is_rev || rev ? SortTo : EndofList; ++ if (!strncmp(order,"Cc", 2) ++ || (rev = !strncmp(order,"Reverse Cc", 10))) ++ return is_rev || rev ? SortCc : EndofList; ++ if (!strncmp(order,"Date", 4) ++ || (rev = !strncmp(order,"Reverse Date", 12))) ++ return is_rev || rev ? SortDate : EndofList; ++ if (!strncmp(order,"siZe", 4) ++ || (rev = !strncmp(order,"Reverse siZe", 12))) ++ return is_rev || rev ? SortSize : EndofList; ++ if (!strncmp(order,"scorE", 5) ++ || (rev = !strncmp(order,"Reverse scorE", 13))) ++ return is_rev || rev ? SortScore : EndofList; ++ return EndofList; ++ } +diff -rc alpine-2.21/pith/sort.h alpine-2.21.rules/pith/sort.h +*** alpine-2.21/pith/sort.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/sort.h Sun Feb 5 16:15:22 2017 +*************** +*** 45,50 **** + void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned, int); + int decode_sort(char *, SortOrder *, int *, int); + void reset_sort_order(unsigned); +! + + #endif /* PITH_SORT_INCLUDED */ +--- 45,50 ---- + void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned, int); + int decode_sort(char *, SortOrder *, int *, int); + void reset_sort_order(unsigned); +! SortOrder translate(char *, int); + + #endif /* PITH_SORT_INCLUDED */ +diff -rc alpine-2.21/pith/state.c alpine-2.21.rules/pith/state.c +*** alpine-2.21/pith/state.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/state.c Sun Feb 5 16:15:22 2017 +*************** +*** 33,39 **** + #include "../pith/remote.h" + #include "../pith/list.h" + #include "../pith/smime.h" +! + + /* + * Globals referenced throughout pine... +--- 33,39 ---- + #include "../pith/remote.h" + #include "../pith/list.h" + #include "../pith/smime.h" +! #include "../pith/rules.h" + + /* + * Globals referenced throughout pine... +*************** +*** 239,244 **** +--- 239,247 ---- + if((*pps)->msgmap) + msgno_give(&(*pps)->msgmap); + ++ if((*pps)->rule_list) ++ free_parsed_rule_list(&(*pps)->rule_list); ++ + free_vars(*pps); + + fs_give((void **) pps); +diff -rc alpine-2.21/pith/state.h alpine-2.21.rules/pith/state.h +*** alpine-2.21/pith/state.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/state.h Sun Feb 5 16:15:22 2017 +*************** +*** 33,39 **** + #include "../pith/stream.h" + #include "../pith/color.h" + #include "../pith/user.h" +! + + /* + * Printing control structure +--- 33,39 ---- + #include "../pith/stream.h" + #include "../pith/color.h" + #include "../pith/user.h" +! #include "../pith/rulestype.h" + + /* + * Printing control structure +*************** +*** 105,110 **** +--- 105,115 ---- + MAILSTREAM *mail_stream; /* ptr to current folder stream */ + MSGNO_S *msgmap; /* ptr to current message map */ + ++ char screen_name[10]; /* name of current screen */ ++ char *role; /* role used when composing */ ++ char *procid; /* procedure id when needed */ ++ int exiting; ++ + unsigned read_predicted:1; + + char cur_folder[MAXPATH+1]; +*************** +*** 349,354 **** +--- 354,360 ---- + struct { + char *(*display_filter)(char *, STORE_S *, gf_io_t, FILTLIST_S *); + char *(*display_filter_trigger)(BODY *, char *, size_t); ++ char *(*exec_rule)(char *, gf_io_t, gf_io_t); + } tools; + + KEYWORD_S *keywords; +*************** +*** 359,364 **** +--- 365,373 ---- + char last_error[500]; + INIT_ERR_S *init_errs; + ++ PRULELIST_S *rule_list; ++ char *pressed_key; ++ + PRINT_S *print; + + #ifdef SMIME +diff -rc alpine-2.21/pith/string.c alpine-2.21.rules/pith/string.c +*** alpine-2.21/pith/string.c Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/string.c Sun Feb 5 16:15:22 2017 +*************** +*** 20,25 **** +--- 20,26 ---- + string.c + Misc extra and useful string functions + - rplstr replace a substring with another string ++ - collspaces consecutive spaces are reduced to one space. + - sqzspaces Squeeze out the extra blanks in a string + - sqznewlines Squeeze out \n and \r. + - removing_trailing_white_space +*************** +*** 131,136 **** +--- 132,162 ---- + return(x3); + } + ++ /*---------------------------------------------------------------------- ++ collapse blank space ++ ----------------------------------------------------------------------*/ ++ void ++ collspaces(char *string) ++ { ++ char *p = string; ++ int only_one_space = 0; ++ ++ if(!string) ++ return; ++ ++ for(;isspace(*p); p++); ++ ++ while(*string = *p++) ++ if(!isspace((unsigned char)*string)){ ++ only_one_space = 0; ++ string++; ++ } ++ else if(!only_one_space){ ++ string++; ++ only_one_space++; ++ } ++ *string = '\0'; ++ } + + + /*---------------------------------------------------------------------- +*************** +*** 2983,2985 **** +--- 3009,3043 ---- + if(i < len) rn[i] = '\0'; + rn[len-1] = '\0'; + } ++ ++ ++ void ++ removing_extra_stuff(string) ++ char *string; ++ { ++ char *p = NULL; ++ int change = 0, length = 0; ++ ++ ++ if(!string) ++ return; ++ ++ for(; *string; string++, length++) ++ p = ((unsigned char)*string != ',') ? NULL : (!p) ? string : p; ++ ++ if(p) ++ *p = '\0'; ++ ++ string -= length; ++ for (; *string; string++){ ++ if (change){ ++ *string = ' '; ++ change = 0; ++ } ++ if ((((unsigned char)*string == ' ') || ++ ((unsigned char)*string == ',')) && ++ ((unsigned char)*(string + 1) == ',')) ++ change++; ++ } ++ } ++ +diff -rc alpine-2.21/pith/string.h alpine-2.21.rules/pith/string.h +*** alpine-2.21/pith/string.h Sun Feb 5 16:02:36 2017 +--- alpine-2.21.rules/pith/string.h Sun Feb 5 16:15:22 2017 +*************** +*** 87,92 **** +--- 87,93 ---- + + /* exported protoypes */ + char *rplstr(char *, size_t, int, char *); ++ void collspaces(char *); + void sqzspaces(char *); + void sqznewlines(char *); + void removing_leading_white_space(char *); +*************** +*** 94,99 **** +--- 95,101 ---- + void replace_tabs_by_space(char *); + void removing_leading_and_trailing_white_space(char *); + int removing_double_quotes(char *); ++ void removing_extra_stuff (char *); + char *skip_white_space(char *); + char *skip_to_white_space(char *); + char *removing_quotes(char *); diff --git a/chappa-unixnullbug.patch b/chappa-unixnullbug.patch deleted file mode 100644 index b914394..0000000 --- a/chappa-unixnullbug.patch +++ /dev/null @@ -1,35 +0,0 @@ ---- - imap/src/c-client/mail.c | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) - -Index: alpine-2.20/imap/src/c-client/mail.c -=================================================================== ---- alpine-2.20.orig/imap/src/c-client/mail.c -+++ alpine-2.20/imap/src/c-client/mail.c -@@ -3400,13 +3400,13 @@ unsigned long mail_filter (char *text,un - long flags) - { - STRINGLIST *hdrs; -- int notfound; -+ int notfound, fix = text[len - 1] == '\0'; - unsigned long i; - char c,*s,*e,*t,tmp[MAILTMPLEN]; - char *src = text; - char *dst = src; - char *end = text + len; -- text[len] = '\012'; /* guard against running off buffer */ -+ text[fix ? len - 1 : len] = '\012'; /* guard against running off buffer */ - while (src < end) { /* process header */ - /* slurp header line name */ - for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp; -@@ -3447,6 +3447,10 @@ unsigned long mail_filter (char *text,un - } - } - *dst = '\0'; /* tie off destination */ -+ if(fix){ -+ text[len] = '\012'; -+ text[len-1] = '\0'; -+ } - return dst - text; - } - diff --git a/lint.diff b/lint.diff deleted file mode 100644 index a5597c6..0000000 --- a/lint.diff +++ /dev/null @@ -1,69 +0,0 @@ -From: Jan Engelhardt -Date: 2015-03-25 18:25:22.663262139 +0100 - -rpmlint: -W: alpine implicit-pointer-decl dmail.c:160 -W: alpine implicit-pointer-decl mlock.c:114 -W: alpine implicit-pointer-decl tmail.c:168 -E: alpine no-return-in-nonvoid-function imap.c:2790 - -gcc: -dmail.c:160:5: warning: implicit declaration of function 'isdigit' [-Wimplicit-function-declaration] -mlock.c:114:5: warning: implicit declaration of function 'time' [-Wimplicit-function-declaration] -tmail.c:168:7: warning: implicit declaration of function 'isdigit' [-Wimplicit-function-declaration] -imap.c:2790:1: warning: control reaches end of non-void function [-Wreturn-type] ---- - alpine/imap.c | 1 + - imap/src/dmail/dmail.c | 1 + - imap/src/mlock/mlock.c | 1 + - imap/src/tmail/tmail.c | 1 + - 4 files changed, 4 insertions(+) - -Index: alpine-2.20/alpine/imap.c -=================================================================== ---- alpine-2.20.orig/alpine/imap.c -+++ alpine-2.20/alpine/imap.c -@@ -2786,6 +2786,7 @@ if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING, - return(want_to(_("Preserve password on DISK for next login"), - 'y', 'x', NO_HELP, WT_NORM) - == 'y'); -+return 0; - #endif /* PASSFILE */ - } - -Index: alpine-2.20/imap/src/dmail/dmail.c -=================================================================== ---- alpine-2.20.orig/imap/src/dmail/dmail.c -+++ alpine-2.20/imap/src/dmail/dmail.c -@@ -24,6 +24,7 @@ - * - */ - -+#include - #include - #include - #include -Index: alpine-2.20/imap/src/mlock/mlock.c -=================================================================== ---- alpine-2.20.orig/imap/src/mlock/mlock.c -+++ alpine-2.20/imap/src/mlock/mlock.c -@@ -32,6 +32,7 @@ - #include - #include - #include -+#include - #include - #include - #include -Index: alpine-2.20/imap/src/tmail/tmail.c -=================================================================== ---- alpine-2.20.orig/imap/src/tmail/tmail.c -+++ alpine-2.20/imap/src/tmail/tmail.c -@@ -23,6 +23,7 @@ - * - */ - -+#include - #include - #include - #include diff --git a/pico-stripwhitespace.diff b/pico-stripwhitespace.diff deleted file mode 100644 index f5732ed..0000000 --- a/pico-stripwhitespace.diff +++ /dev/null @@ -1,33 +0,0 @@ ---- pine4.61/pico/pico.c 2004/08/12 15:24:12 1.1 -+++ pine4.61/pico/pico.c 2004/08/12 15:24:42 -@@ -898,30 +898,6 @@ - } - - /* -- * Remove all trailing white space from the text -- */ --int --stripwhitespace(void) --{ -- int i; -- LINE *cur_line = lforw(curbp->b_linep); -- -- do{ -- /* we gotta test for the sigdash case here */ -- if(!(cur_line->l_used == 3 && -- lgetc(cur_line, 0).c == '-' && -- lgetc(cur_line, 1).c == '-' && -- lgetc(cur_line, 2).c == ' ')) -- for(i = cur_line->l_used - 1; i >= 0; i--) -- if(ucs4_isspace(lgetc(cur_line, i).c)) -- cur_line->l_used--; -- else -- break; -- }while((cur_line = lforw(cur_line)) != curbp->b_linep); -- return 0; --} -- --/* - * Abort. - * Beep the beeper. Kill off any keyboard macro, etc., that is in progress. - * Sometimes called as a routine, to do general aborting of stuff. diff --git a/pine-nonvoid-function.patch b/pine-nonvoid-function.patch deleted file mode 100644 index 615ab2f..0000000 --- a/pine-nonvoid-function.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- - pith/filter.c | 1 + - 1 file changed, 1 insertion(+) - -Index: alpine-2.10/pith/filter.c -=================================================================== ---- alpine-2.10.orig/pith/filter.c -+++ alpine-2.10/pith/filter.c -@@ -7710,6 +7710,7 @@ html_element_comment(FILTER_S *f, char * - } - } - } -+ return(1); /* Is this correct? */ - } - -