jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

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:
Msrc/fetchaddr.c | 13++++++++++---
Msrc/jaro | 78++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/zlibs/addressbook | 266+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/zlibs/email | 11++++++++---
Msrc/zlibs/filters | 54+++++++++++++++++++++++++++++-------------------------
Msrc/zlibs/helpers | 39++++++++++++++++++++++++++++++---------
Msrc/zlibs/maildirs | 6+++---
Msrc/zlibs/search | 1+
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"