commit e983cc675a9caf9cac14fb93a22b07aa5e4ca7ff
parent 5615a88c32b8d7ca131d133a51f8177f4bcc3cfa
Author: Jaromil <jaromil@dyne.org>
Date:   Thu,  8 Jan 2015 19:12:44 +0100
several improvements to fetchaddr and email parsing and filtering
Diffstat:
8 files changed, 300 insertions(+), 168 deletions(-)
diff --git a/src/fetchaddr.c b/src/fetchaddr.c
@@ -56,6 +56,8 @@ void chop(struct header *cur)
     cur->value[--cur->len] = '\0';
 }
 
+char print_email_only = 0;
+
 int writeout(struct header *h, const char *datefmt, 
              unsigned char create_real_name)
 {
@@ -96,9 +98,12 @@ int writeout(struct header *h, const char *datefmt,
 
           strftime(timebuf, sizeof(timebuf), datefmt, localtime(&timep));
 
-          printf("%s,%s\n", p->mailbox,
-                 p->personal && *p->personal ? p->personal : " ");
-
+          if(print_email_only == 1) {
+            printf("%s\n", p->mailbox);
+          } else {
+            printf("%s,%s\n", p->mailbox,
+                   p->personal && *p->personal ? p->personal : " ");
+          }
           rv = 1;
         }
     }
@@ -137,6 +142,8 @@ int main(int argc, char* argv[])
 #endif
       } else if (!strcmp (argv[i], "-a")) {
         create_real_name = 1;
+      } else if (!strcmp (argv[i], "-e")) {
+        print_email_only = 1;
       } else {
         fprintf (stderr, "%s: `%s' wrong parameter\n", argv[0], argv[i]);
       }
diff --git a/src/jaro b/src/jaro
@@ -20,8 +20,8 @@
 # this source code; if not, write to:
 # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-VERSION=2.1
-DATE=May/2014
+VERSION=3.0
+DATE=Jan/2015
 JAROMAILEXEC=$0
 typeset -a OLDARGS
 for arg in ${argv}; do OLDARGS+=($arg); done
@@ -35,16 +35,22 @@ DRYRUN=0
 CLEANEXIT=1
 CALLMUTT=1
 
+# default permission on files
+umask 066
+
 # global variables for binaries called
 typeset -h rm mkdir mutt
 
+# load zsh regex module
+zmodload zsh/regex
+# zmodload zsh/mapfile
+
 
 # date stamp
 datestamp=`date '+%d%b%y'`
 ##########################
 
-# # # # SQL
-# command
+# SQL command
 SQL=sqlite3
 
 
@@ -52,6 +58,9 @@ PARAM=()
 typeset -A global_opts
 typeset -A opts
 
+typeset -h global_quit
+global_quit=0
+
 # global variable for account selection
 typeset -h account account_type
 # account=default
@@ -66,6 +75,10 @@ typeset -h host port type
 # global variables for addressbook
 typeset -h hostname addressbook addressbook_tmp
 
+# global variables for email parsers
+typeset -A e_addr
+typeset -h e_parsed
+
 # global array for maildirs (filled by list_maildirs)
 typeset -al maildirs
 
@@ -132,26 +145,6 @@ _tmp_create() {
     return 0
 }
 
-# Cleanup anything sensitive before exiting.
-_endgame() {
-    # Clear temporary files
-    for f in $JAROTMPFILES; do
-        func "endgame() cleaning tempfile $f"
-        rm -f "$f"
-    done
-    unset JAROTMPFILES
-}
-# Trap functions for the _endgame event
-TRAPINT()  { _endgame INT   }
-TRAPEXIT() { _endgame EXIT  }
-TRAPHUP()  { _endgame HUP   }
-TRAPQUIT() { _endgame QUIT  }
-TRAPABRT() { _endgame ABORT }
-TRAPKILL() { _endgame KILL  }
-TRAPPIPE() { _endgame PIPE  }
-TRAPTERM() { _endgame TERM  }
-TRAPSTOP() { _endgame STOP  }
-
 # standard output message routines
 # it's always useful to wrap them, in case we change behaviour later
 notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi }
@@ -241,12 +234,12 @@ if [ -d $WORKDIR/zlibs ]; then
     if [ "$WORKDIR" = "../src" ]; then
     for z in `find $WORKDIR/zlibs -type f`; do
         func "Loading zlib: ${z}"
-        . ${z}
+        source ${z}
     done
     else
     for z in `find $WORKDIR/zlibs -type f | grep -v '.zwc$'`; do
         func "Loading zlib: ${z}"
-        . ${z}
+        source ${z}
     done
     fi
     act "full set of auxiliary functions loaded"
@@ -262,6 +255,36 @@ ACCOUNTS="$MAILDIRS/Accounts"
 KEYRING="$MAILDIRS/Keyring"
 
 
+# Cleanup anything sensitive before exiting.
+_endgame() {
+    func "endgame() $1"
+    [[ "$1" = "EXIT" ]] || {
+        global_quit=1
+        sleep 2
+    }
+    # Clear temporary files
+    for f in $JAROTMPFILES; do
+        func "endgame() cleaning tempfile $f"
+        rm -f "$f"
+    done
+    JAROTMPFILES=()
+
+    [[ -e "$MAILDIRS"/cache/notmuch.lock ]] && {
+        func "unlocking notmuch/Xapian search cache"
+        unlock "$MAILDIRS"/cache/notmuch
+    }
+}
+# Trap functions for the _endgame event
+TRAPINT()  { _endgame INT   }
+TRAPEXIT() { _endgame EXIT  }
+TRAPHUP()  { _endgame HUP   }
+TRAPQUIT() { _endgame QUIT  }
+TRAPABRT() { _endgame ABORT }
+TRAPKILL() { _endgame KILL  }
+TRAPPIPE() { _endgame PIPE  }
+TRAPTERM() { _endgame TERM  }
+TRAPSTOP() { _endgame STOP  }
+
 case $OS in
     GNU)        
         # backward compatibility tests for old paths in JaroMail <1.3
@@ -652,6 +675,9 @@ main()
     { option_is_set -R } && { muttflags+=" -R " }
     { option_is_set -f } && { FORCE=1 }
 
+    # clean up options from param
+    PARAM=(${PARAM// -? /})
+
     case "$subcommand" in
     compose) compose ${PARAM} ;;
     queue)   queue ${PARAM} ;;
diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook
@@ -61,29 +61,25 @@ insert_address() {
         func "address already present in $list"
         return 1
     }
-     
+
     print "From: $_name <$_email>" | \
         abook --datafile "$ADDRESSBOOK" \
-        --add-email-quiet
-
-    return 0
-}
+        --add-email-quiet > /dev/null
 
-remove_address() {
-    error "remove_address() TODO in abook branch"
     return 0
 }
 
 search_addressbook() {
     func "search \"$@\" in $list"
-    abook --datafile "$ADDRESSBOOK" --mutt-query "$@"
+    abook --datafile "$ADDRESSBOOK" --mutt-query "${@:l}"
 }
 
 
 lookup_email() {
-    func "lookup address $1 in $list"
+    _addr=${1:l}
+    func "lookup address $_addr in $list"
     abook --datafile "$ADDRESSBOOK" \
-        --mutt-query "$1" > /dev/null
+        --mutt-query "$_addr" > /dev/null
     return $?
 }
 
@@ -124,8 +120,9 @@ sender_isknown() {
 { print $0 }
 /^$/ { exit }' | ${WORKDIR}/bin/fetchaddr -x From -a`"
 
-    email="${head[(ws:,:)1]}"
-    [[ "$email" = "" ]] && { return 1 }
+    email="${head[(ws:,:)1]:l}"
+    isemail $email
+    [[ $? = 0 ]] || { return 1 }
 
     abook --datafile $MAILDIRS/$list.abook \
         --mutt-query "$email" > /dev/null
@@ -134,77 +131,71 @@ sender_isknown() {
 
 learn() {
     func "learning ${PARAM[1]} in stdin piped mails"
-    [[ $DRYRUN == 1 ]] && {
-        func "dryrun parsing ${PARAM[1]} in stdin piped mails" }
+    [[ $DRYRUN == 1 ]] && func "running in dryrun mode, no entries added to addressbook"
 
     what=sender
     [[ "$1" = "" ]] || { what="$1" }
     func "learning from $what"
 
-    # read in only headers from stdin (till empty line)
-    buffer=`awk '{ print $0 } /^$/ { exit }'`
-
     case ${what} in
 
         sender|from) # simple: one address only on From:
-            head="`print $buffer | ${WORKDIR}/bin/fetchaddr -x From -a`"
-            # (Q) eliminates quotes, then word split
-            email="${(Q)head[(ws:,:)1]}"
-            name="${(Q)head[(ws:,:)2]}"
-            print "$head"
-            [[ $DRYRUN == 1 ]] || {
-                insert_address "$email" "$name"
-                [[ $? = 0 ]] && { act "new: $_name <${_email}>" }
+
+            # now e_name e_mail and e_parsed are filled in
+            awk '{ print $0 } /^$/ { exit }' | e_parse From
+            
+            # no need to cycle, From is always only one field
+            [[ $DRYRUN == 0 ]] && {
+                _e="${(k)e_addr}"
+                _n="${(v)e_addr}"
+                insert_address "$_e" "$_n"
+                [[ $? = 0 ]] && { act "$list <- $_n <$_e>" }
             }
             return 0
             ;;
 
         all)
-            head="`print $buffer | ${WORKDIR}/bin/fetchaddr -a`"
-            for h in ${(f)head}; do
-                # (Q) eliminates quotes, then word split
-                email="${(Q)h[(ws:,:)1]}"
-                name="${(Q)h[(ws:,:)2]}"
 
-                print "$h"
+            awk '{ print $0 } /^$/ { exit }' | e_parse
 
-                [[ $DRYRUN == 1 ]] || {
-                    insert_address "$email" "$name"
-                    [[ $? = 0 ]] && { act "new: $_name <${_email}>" }
-                }
-            done
+            [[ $DRYRUN == 0 ]] && {
+                # complex: more addresses in To: and Cc:
+                for _e in ${(k)e_addr}; do
+                    _n="${e_addr[$_e]}"
+                    insert_address "$_e" "$_n"
+                    [[ $? = 0 ]] && { act "$list <- $_n <${_e}>" }
+                done
+            }
             return 0
             ;;
+        
+        recipient|to)
 
-        recipient|to) # complex: more addresses in To: and Cc:
-            head="`print $buffer | ${WORKDIR}/bin/fetchaddr -x To -a`"
-            for h in ${(f)head}; do
-                # (Q) eliminates quotes, then word split
-                email="${(Q)h[(ws:,:)1]}"
-                name="${(Q)h[(ws:,:)2]}"
-                print "$h"
-
-                [[ $DRYRUN == 1 ]] || {
-                    insert_address "$email" "$name"
-                    [[ $? = 0 ]] && { act "new: $_name <${_email}>" }
-                }
-            done
+            awk '{ print $0 } /^$/ { exit }' | e_parse To
+
+            [[ $DRYRUN == 0 ]] && {
+                # complex: more addresses in To: and Cc:
+                for _e in ${(k)e_addr}; do
+                    _n="${e_addr[$_e]}"
+                    insert_address "$_e" "$_n"
+                    [[ $? = 0 ]] && { act "$list <- $_n <${_e}>" }
+                done
+            }
 
-            head="`print $buffer | ${WORKDIR}/bin/fetchaddr -x Cc -a`"
-            for h in ${(f)head}; do
-                # (Q) eliminates quotes, then word split
-                email="${(Q)h[(ws:,:)1]}"
-                name="${(Q)h[(ws:,:)2]}"
-                print "$h"
+            e_parse Cc
+
+            [[ $DRYRUN == 0 ]] && {
+                # complex: more addresses in To: and Cc:
+                for _e in ${(k)e_addr}; do
+                    _n="${e_addr[$_e]}"
+                    insert_address "$_e" "$_n"
+                    [[ $? = 0 ]] && { act "$list <- $_n <${_e}>" }
+                done
+            }
 
-                [[ $DRYRUN == 1 ]] || {
-                    insert_address "$email" "$name"
-                    [[ $? = 0 ]] && { act "new: $_name <${_email}>" }
-                }
-            done
             return 0
             ;;
-
+        
         *)
             error "Unknown learning function: $what" ;;
     esac
@@ -226,35 +217,45 @@ forget() {
 # extract all addresses found in a list of email files from stdin
 extract_mails() {
     _mails=`cat`
-    # we switch dryrun temporarily off to use learn()
-    # without modifying the addressbook
-    _dryrun=$DRYRUN
-    DRYRUN=1
+
+    _tot=`print $_mails | wc -l`
+    act "$_tot emails to parse"
+
+    [[ $_tot -gt 100 ]] && {
+        act "operation will take a while, showing progress"
+        _prog=0
+        c=0
+    }
 
     # learn from senders, recipients or all
     _action="$1"
 
-    typeset -a learned
+
+    # -U eliminates duplicates
+    typeset -aU _res
+
     for m in ${(f)_mails}; do
-        _l=`hdr $m | learn $_action`
-        # handles results on multiple lines (recipients, all)
-        for ii in ${(f)_l}; do
-            learned+=("$ii")
-        done
-    done
 
-    DRYRUN=$_dryrun
-    # eliminates duplicates
-    typeset -A result
-    for i in ${learned}; do
-        _e=${i[(ws:,:)1]}
-        [[ "${result[$_e]}" = "" ]] && {
-            _n=${i[(ws:,:)2]}
-            result+=("$_e" "$_n")
-            print - "$_n <$_e>"
+        # e_parse fills in e_addr(map) and e_parsed(newline term str)
+        hdr $m | e_parse $_action
+        for _e in ${(k)e_addr}; do
+            _res+=("${(v)e_addr[$_e]} <$_e>")
+        done
+        
+        [[ $_tot -gt 100 ]] && {
+            c=$(( $c + 1 ))
+            [[ $c -gt 99 ]] && {
+                _prog=$(( $_prog + $c ))
+                act "$_prog / $_tot processed so far"
+                c=1
+            }
         }
     done
-    notice "${#result} addresses extracted"
+    # print out results
+    for r in $_res; do
+        print - $r
+    done
+    notice "${#_res} unique addresses extracted"
 }
 
 # extract all addresses found into a maildir
@@ -375,6 +376,9 @@ extract() {
     # default is whitelist
 
     arg=${PARAM[1]}
+
+    func "extract() arg: $arg (param: $PARAM)"
+
     # no arg means print all entries from adressbook
     [[ "$arg" = "" ]] && {
         notice "Extracting all addresses in $list"
@@ -411,7 +415,7 @@ extract() {
             _addrs=`gpg --list-keys --with-colons | awk -F: '{print $10}'`
             for i in ${(f)_addrs}; do
                 _parsed=`print "From: $i" | ${WORKDIR}/bin/fetchaddr -a -x from`
-                _e="${_parsed[(ws:,:)1]}"
+                _e="${_parsed[(ws:,:)1]:l}"
                 isemail "$_e"
                 [[ $? = 0 ]] || continue
                 # check if the email is not already parsed
@@ -438,7 +442,7 @@ extract() {
         [[ "$_magic" =~ "PGP public key" ]] && {
             notice "Extracting addresses from sigs on GPG key $arg"
             _gpg="gpg --no-default-keyring --keyring $MAILDIRS/cache/pubkey.gpg --batch --with-colons"
-            rm -f $MAILDIRS/cache/pubkey.gpg
+            ${=rm} $MAILDIRS/cache/pubkey.gpg
             ${=_gpg} --import "$arg"
             # first make sure all unknown keys are imported
             _addrs=`${=_gpg} --list-sigs | awk -F: '{print $5 " " $10}'`
@@ -452,7 +456,7 @@ extract() {
             _addrs=`${=_gpg} --list-sigs | awk -F: '{print $10}'`
             for i in ${(f)_addrs}; do
                 _parsed=`print "From: $i" | ${WORKDIR}/bin/fetchaddr -a -x from`
-                _e="${_parsed[(ws:,:)1]}"
+                _e="${_parsed[(ws:,:)1]:l}"
                 isemail "$_e"
                 [[ $? = 0 ]] || continue
                 # check if the email is not already parsed
@@ -486,25 +490,27 @@ extract() {
 
 # import address lists from stdin
 import() {
-    
-    arg=${PARAM[1]}
-    # remove options and trim
-    arg=`trim ${arg//-?/}`
 
+    # case insensitive match
+    unsetopt CASE_MATCH
+
+    _arg=${PARAM[1]}
+
+    func "import() arg: $_arg (param: $PARAM)"
     # a map to eliminate duplicates
     typeset -AU result
 
-    [[ "$arg" = "" ]] || {
-        notice "Import address list from stdin into addressbook"
+    [[ "$_arg" = "" ]] && {
+        notice "Import address list from stdin into addressbook $list"
         _stdin=`cat`
-        print - $_stdin
         _new=0
+        act "imported new entries will be printed on stdout"
         for i in ${(f)_stdin}; do
             # skip comments starting with #
             [[ "$i[1]" = "#" ]] && continue
             
             _parsed=`print - "From: $i" | ${WORKDIR}/bin/fetchaddr -a -x from`
-            _e="${_parsed[(ws:,:)1]}"
+            _e="${_parsed[(ws:,:)1]:l}"
             
             # check if is really an email
             isemail "$_e"
@@ -529,10 +535,9 @@ import() {
                 continue
             }
 
-            act "import new entry: $_n <$_e>"            
-            [[ $DRYRUN = 0 ]] && {
-                insert_address "$_e" "$_n"
-            }
+            print - "$_n <$_e>"            
+            [[ $DRYRUN = 0 ]] && insert_address "$_e" "$_n"
+
             _new=$(( $_new + 1 ))
         done
         notice "Valid unique entries parsed: ${#result}"
@@ -540,9 +545,72 @@ import() {
         return $?
     }
 
+    [[ -r "$_arg" ]] && {
+        notice "Import address list from stdin into group file $arg"
+        act "parsing entries in group file"
+        _group=`cat $arg`
+        for i in ${(f)_group}; do
+            # skip comments starting with #
+            [[ "$i[1]" = "#" ]] && continue
+            
+            _parsed=`print - "From: $i" | ${WORKDIR}/bin/fetchaddr -a -x from`
+            _e="${_parsed[(ws:,:)1]:l}"
+            
+            # check if is really an email
+            isemail "$_e"
+            [[ $? = 0 ]] || {
+                func "not an email: $_e"
+                continue
+            }
+
+            # check if the email is a duplicate
+            [[ "${result[$_e]}" = "" ]] || {
+                func "duplicate email: $_e"
+                continue
+            }
+            
+            _n="${_parsed[(ws:,:)2]}"
+            result+=("$_e" "$_n")            
+        done
+
+        # imported all group file contents in results
+        notice "${#result} entries parsed in $arg"
+        act "reading entries from stdin, printing out new entries"
+        
+        _stdin=`cat`
+        _new=0
+        for i in ${(f)_stdin}; do
+            # skip comments starting with #
+            [[ "$i[1]" = "#" ]] && continue
+            
+            _parsed=`print - "From: $i" | ${WORKDIR}/bin/fetchaddr -a -x from`
+            _e="${_parsed[(ws:,:)1]:l}"
+            
+            # check if is really an email
+            isemail "$_e"
+            [[ $? = 0 ]] || {
+                func "not an email: $_e"
+                continue
+            }
+
+            # check if the email is a duplicate
+            [[ "${result[$_e]}" = "" ]] || {
+                func "duplicate email: $_e"
+                continue
+            }
+
+            _n="${_parsed[(ws:,:)2]}"
+            result+=("$_e" "$_n")
+
+            print - "$_n <$_e>"            
+            _new=$(( $_new + 1 ))
+        done
+        # TODO: overwrite group file with all entries
+        notice "Valid unique entries parsed: ${#result}"
+        act "new addresses found: ${_new}"
+
+    }
 
-    # stdin
-    notice "Importing addressbook from stdin list of addresses"
     return 0
 }
 
diff --git a/src/zlibs/email b/src/zlibs/email
@@ -265,7 +265,7 @@ send() {
 
     # list mails to send
     queue_outbox=`${=find} "${MAILDIRS}/outbox" -type f`
-    queue_outbox_num=`${=find} "${MAILDIRS}/outbox" -type f|wc -l`
+    queue_outbox_num=`print $queue_outbox | wc -l`
     { test "$queue_outbox_num" = "0" } && {
         act "Outbox is empty, no mails to send."
         return 0 }
@@ -304,6 +304,12 @@ send() {
 
     for qbody in ${(f)queue_outbox}; do
 
+        # clean interrupt
+        [[ $global_quit = 1 ]] && {
+            error "User break requested, interrupting operation"
+            break
+        }
+
         # check if this is an anonymous mail
         hdr "$qbody" | grep -i '^from: anon' > /dev/null
         if [[ $? = 0 ]]; then
@@ -396,8 +402,7 @@ EOF
         else
             notice "Mail sent succesfully"
             # whitelist those to whom we send mails
-            cat "$qbody" | \
-                "$WORKDIR/bin/jaro" -q learn recipient > /dev/null
+            hdr "$qbody" | learn recipient
             cat "$qbody" | deliver sent
             [[ $? = 0 ]] && { rm "$qbody" }
         fi
diff --git a/src/zlibs/filters b/src/zlibs/filters
@@ -198,8 +198,8 @@ filter_maildir() {
         return 1
     }
 
-    numm=`${=find} "$MAILDIRS/$mdinput" -maxdepth 2 -type f|wc -l`
     mails=`${=find} "$MAILDIRS/$mdinput" -maxdepth 2 -type f`
+    numm=`print $mails | wc -l`
 
     [[ $numm = 0 ]] && {
         error "Nothing to filter inside maildir $mdinput"
@@ -210,6 +210,13 @@ filter_maildir() {
     c=0
 
     for m in ${(f)mails}; do
+
+        # clean interrupt
+        [[ $global_quit = 1 ]] && {
+            error "User break requested, interrupting operation"
+            break
+        }
+
         match=0
         c=$(($c + 1))
 
@@ -217,24 +224,24 @@ filter_maildir() {
         hdr "$m" | sender_isknown
         [[ $? = 0 ]] && {
             [[ "$mdinput" = "zz.blacklist" ]] && {
-                act "$c\t\t/ $numm"
+                act "$c\t/ $numm"
                 continue
             }
             cat "$m" | deliver zz.blacklist
             [[ $? = 0 ]] && { rm "$m" }
-            act "$c\t\t/ $numm\t\t-> zz.blacklist"
+            act "$c\t/ $numm\t-> zz.blacklist"
             continue
         }
         
         hdr "$m" | awk '/Sender.*mailman-bounce/ { exit 1 }'
         [[ $? = 0 ]] || {
             [[ "$mdinput" = "zz.bounces" ]] && {
-                act "$c\t\t/ $numm"
+                act "$c\t/ $numm"
                 continue
             }
             cat "$m" | deliver zz.bounces
             [[ $? = 0 ]] && { rm "$m" }
-            act "$c\t\t/ $numm\t\t-> zz.bounces"
+            act "$c\t/ $numm\t-> zz.bounces"
             continue
         }
         
@@ -245,20 +252,19 @@ filter_maildir() {
 
             # run all filter regexps on the from: field
             [[ "$ffrom" = "" ]] || {
-                femail="${ffrom[(ws:,:)1]}"
+                femail="${ffrom[(ws:,:)1]:l}"
                 for exp in ${(k)filter_from}; do
 
                     if [[ "$femail" =~ "$exp" ]]; then
                         # if destination maildir is same as input, skip
                         [[ "${filter_from[$exp]}" = "$mdinput" ]] && {
-                            act "$c\t\t/ $numm"
+                            act "$c\t/ $numm"
                             match=1
                             break 
                         }
-                        act "$c\t\t/ $numm\t\t-> ${filter_from[$exp]}"
                         cat "$m" | deliver ${filter_from[$exp]}
                         if [[ $? = 0 ]]; then
-                            func "from filter match: $exp"
+                            act "$c\t/ $numm\t-> ${filter_from[$exp]}"
                             match=1
                             rm "$m"
                             break
@@ -277,8 +283,8 @@ filter_maildir() {
 
             typeset -alU ftos
             # recompile the array of destination addresses
-            ftos=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x cc -a | cut -d, -f1`)
-            ftos+=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x to -a | cut -d, -f1`)
+            ftos=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x cc -e`)
+            ftos+=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x to -e`)
 
             # run all filter regexps on the to: and cc: fields
             { test "$ftos" = "" } || {
@@ -288,15 +294,13 @@ filter_maildir() {
                         if [[ "$ft" =~ "$exp" ]]; then
                             # if destination maildir is same as input, skip
                             [[ "${filter_to[$exp]}" = "$mdinput" ]] && {
-                                act "$c\t\t/ $numm"
-                                func "same dir"
+                                act "$c\t/ $numm"
                                 match=1
                                 break
                             }
-                            act "$c\t\t/ $numm\t\t-> ${filter_to[$exp]}"
                             cat "$m" | deliver ${filter_to[$exp]}
                             if [[ $? = 0 ]]; then
-                                func "to filter match: $exp"
+                                act "$c\t/ $numm\t-> ${filter_to[$exp]}"
                                 match=1
                                 rm "$m"
                                 break
@@ -322,24 +326,24 @@ filter_maildir() {
         hdr "$m" | sender_isknown
         [[ $? = 0 ]] && {
             [[ "$mdinput" = "known" ]] && {
-                act "$c\t\t/ $numm"
+                act "$c\t/ $numm"
                 continue
             }
             cat "$m" | deliver known
             [[ $? = 0 ]] && { rm "$m" }
-            act "$c\t\t/ $numm\t\t-> known"
+            act "$c\t/ $numm\t-> known"
             continue
         }
 
         hdr "$m" | awk '/X-Spam-Flag.*YES/ { exit 1 }'
         { test $? = 0 } || {
             [[ "$mdinput" = "zz.spam" ]] && {
-                act "$c\t\t/ $numm"
+                act "$c\t/ $numm"
                 continue
             }
             cat "$m" | deliver zz.spam
             [[ $? = 0 ]] && { rm "$m" }
-            act "$c\t\t/ $numm\t\t-> zz.spam"
+            act "$c\t/ $numm\t-> zz.spam"
             continue
         }
 
@@ -349,14 +353,14 @@ filter_maildir() {
             # check if destination address is in filter_own array
             if [[ ${filter_own[(r)$f]} == ${f} ]] ; then
                 [[ "$mdinput" = "priv" ]] && {
-                    act "$c\t\t/ $numm"
+                    act "$c\t/ $numm"
                     match=1
                     break
                 }
                 cat "$m" | deliver priv
                 [[ $? = 0 ]] && {
                     rm "$m";
-                    act "$c\t\t/ $numm\t\t-> priv"
+                    act "$c\t/ $numm\t-> priv"
                     match=1
                     break
                 }
@@ -368,7 +372,7 @@ filter_maildir() {
         hdr "$m" | ismailinglist
         [[ $? = 0 ]] && {
             [[ "$mdinput" = "unsorted.ml" ]] && {
-                act "$c\t\t/ $numm"
+                act "$c\t/ $numm"
                 continue
             }
             cat "$m" | deliver unsorted.ml
@@ -378,9 +382,9 @@ filter_maildir() {
         
         # if here then file to unsorted
         if [ "$mdinput" = "unsorted" ]; then
-            act "$c\t\t/ $numm"
+            act "$c\t/ $numm"
         else
-            act "$c\t\t/ $numm\t\t-> unsorted"
+            act "$c\t/ $numm\t-> unsorted"
             cat "$m" | deliver unsorted
             [[ $? = 0 ]] && { rm "$m" }
         fi
@@ -402,7 +406,7 @@ update_mutt() {
     func "lock: $dotlock"
     ${=mkdir} "$MUTTDIR"
     ${=mkdir} "$MUTTDIR"/cache
-    rm -f "$MUTTDIR"/rc
+    ${=rm}    "$MUTTDIR"/rc
 
     gpgkey=""
     # detect the default gpg key to always encrypt also to self
diff --git a/src/zlibs/helpers b/src/zlibs/helpers
@@ -41,14 +41,9 @@ awk '{ for (i=1;i<=NF;i++)
 }
 
 isemail() {
-    [[ "$1" = "" ]] && {
-        func "isemail() called on an empty string"
-        return 1
-    }
-    print "$1" | \
-        grep -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" \
-        > /dev/null
-   return $?
+    [[ "$1" -regex-match "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" ]] && return 0
+    
+    return 1
 }
 
 ismailinglist() {
@@ -152,10 +147,36 @@ autostart() {
     return 1
 }
 
+e_parse() {
+    _arg=""
+    # optional second argument limits parsing to header fields
+    [[ "$1" = "" ]] || [[ "$1" = "all" ]] || _arg="-x $1"
+
+    # use RFC822 parser in fetchaddr
+    e_parsed=`${WORKDIR}/bin/fetchaddr ${=_arg} -a`
+
+    e_addr=()
+    for _p in ${(f)e_parsed}; do
+        _e="${(Q)_p[(ws:,:)1]:l}"
+        # check if an email address was found
+        isemail "$_e" && {
+            _n="${(Q)_p[(ws:,:)2]}"
+            # reformat output using comma as separator
+            e_addr+=("$_e" "$_n")
+            func "parsed: $_n <$_e>"
+        }
+    done
+
+    # no emails found
+    [[ ${#e_addr} = 0 ]] && return 1
+
+    return 0
+}
+
 
 # short utility to print only mail headers
 hdr() {
-    { test -r "$1" } || {
+   [[ -r "$1" ]] || {
         error "hdr() called on non existing file: $1"
         return 1 }
     awk '{ print $0 }
diff --git a/src/zlibs/maildirs b/src/zlibs/maildirs
@@ -256,13 +256,13 @@ deliver() {
     }
 
     _email=`cat`
-    print "$_email" | ismailinglist
+    print - "$_email" | ismailinglist
     [[ $? = 0 ]] && {
-        print "$_email" | nm insert --folder="$dest" +filters +mailinglist
+        print - "$_email" | nm insert --folder="$dest" +filters +mailinglist
         return $?
     }
 
     # anything else +filters
-    print "$_email" | nm insert --folder="$dest" +filters
+    print - "$_email" | nm insert --folder="$dest" +filters
     return $?
 }
diff --git a/src/zlibs/search b/src/zlibs/search
@@ -41,6 +41,7 @@ nm() {
 nm_setup() {
     nm_dir="$MAILDIRS"/cache/notmuch
     mkdir -p $nm_dir
+    lock $nm_dir
 
     # setup the default tags for all new messages
     deftags="$1"