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 85e0bcbd6aca359178032a6217be873304244cf1
parent 247f352b68b8435abad913ce0315ac0f56b7114e
Author: Jaromil <jaromil@dyne.org>
Date:   Mon, 24 Feb 2014 12:04:16 +0100

new build for GNU systems relying on system-wide apps

now using system wide mutt, mairix and gpg

Diffstat:
Mbuild/build-gnu.sh | 395+++++++++++++++++++++++++++++++++++++------------------------------------------
Asrc/Makefile | 3+++
Rsrc/mairix/dfasyn/COPYING -> src/dfasyn/COPYING | 0
Rsrc/mairix/dfasyn/INSTALL -> src/dfasyn/INSTALL | 0
Rsrc/mairix/dfasyn/Makefile -> src/dfasyn/Makefile | 0
Rsrc/mairix/dfasyn/NEWS -> src/dfasyn/NEWS | 0
Rsrc/mairix/dfasyn/README -> src/dfasyn/README | 0
Rsrc/mairix/dfasyn/abbrevs.c -> src/dfasyn/abbrevs.c | 0
Rsrc/mairix/dfasyn/blocks.c -> src/dfasyn/blocks.c | 0
Rsrc/mairix/dfasyn/charclass.c -> src/dfasyn/charclass.c | 0
Rsrc/mairix/dfasyn/compdfa.c -> src/dfasyn/compdfa.c | 0
Rsrc/mairix/dfasyn/configure -> src/dfasyn/configure | 0
Rsrc/mairix/dfasyn/dfasyn.1 -> src/dfasyn/dfasyn.1 | 0
Rsrc/mairix/dfasyn/dfasyn.5 -> src/dfasyn/dfasyn.5 | 0
Rsrc/mairix/dfasyn/dfasyn.c -> src/dfasyn/dfasyn.c | 0
Rsrc/mairix/dfasyn/dfasyn.h -> src/dfasyn/dfasyn.h | 0
Rsrc/mairix/dfasyn/dfasyn.texi -> src/dfasyn/dfasyn.texi | 0
Rsrc/mairix/dfasyn/evaluator.c -> src/dfasyn/evaluator.c | 0
Rsrc/mairix/dfasyn/expr.c -> src/dfasyn/expr.c | 0
Rsrc/mairix/dfasyn/n2d.c -> src/dfasyn/n2d.c | 0
Rsrc/mairix/dfasyn/n2d.h -> src/dfasyn/n2d.h | 0
Rsrc/mairix/dfasyn/parse.y -> src/dfasyn/parse.y | 0
Rsrc/mairix/dfasyn/scan.l -> src/dfasyn/scan.l | 0
Rsrc/mairix/dfasyn/states.c -> src/dfasyn/states.c | 0
Rsrc/mairix/dfasyn/stimulus.c -> src/dfasyn/stimulus.c | 0
Rsrc/mairix/dfasyn/tabcompr.c -> src/dfasyn/tabcompr.c | 0
Rsrc/mairix/dfasyn/tokens.c -> src/dfasyn/tokens.c | 0
Msrc/dotlock.c | 2+-
Asrc/mairix.h | 403+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/memmac.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mutt/gpg | 24++++++++++++------------
Asrc/nvp.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/nvp.h | 38++++++++++++++++++++++++++++++++++++++
Asrc/nvp.nfa | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/nvptypes.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/pgpewrap.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/rfc2047.c | 2+-
Dsrc/rfc822.c | 768-------------------------------------------------------------------------------
Asrc/rfc822_mairix.c | 1536+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/rfc822_mutt.c | 768+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/rfc822.h -> src/rfc822_mutt.h | 0
Msrc/zlibs/locking | 6+++---
42 files changed, 3741 insertions(+), 996 deletions(-)

diff --git a/build/build-gnu.sh b/build/build-gnu.sh @@ -4,12 +4,12 @@ distro=unknown builddir=`pwd` # cc="${builddir}/cc-static.zsh" -cc="gcc -O3 -fomit-frame-pointer -ffast-math" +cc="gcc -O3" pushd .. -which apt-get && distro=debian -which yum && distro=fedora +which apt-get > /dev/null && distro=debian +which yum > /dev/null && distro=fedora target=all { test -z $1 } || { target="$1" } @@ -17,218 +17,191 @@ target=all mkdir -p build/gnu -case $distro in - debian) - - { test "$target" = "deps" } || { - test "$target" = "all" } && { - echo "Checking software to install..." - which zsh || sudo apt-get install zsh - which msmtp || sudo apt-get install msmtp +{ test "$target" = "deps" } || { + test "$target" = "all" } && { + deps=() + case $distro in + debian) + print "Building on Debian" + print "Checking software to install" + which procmail >/dev/null || deps+=(procmail) + which fetchmail >/dev/null || deps+=(fetchmail) + which msmtp >/dev/null || deps+=(msmtp) + which mutt >/dev/null || deps+=(mutt) + which mairix >/dev/null || deps+=(mairix) + which pinentry >/dev/null || deps+=(pinentry) + which abook >/dev/null || deps+=(abook) + which wipe >/dev/null || deps+=(wipe) + + print "Checking build dependencies" + which gcc >/dev/null || deps+=(gcc) + which bison >/dev/null || deps+=(bison) + which flex >/dev/null || deps+=(flex) + which make >/dev/null || deps+=(make) + which autoconf >/dev/null || deps+=(autoconf) + which automake >/dev/null || deps+=(automake) + which sqlite3 >/dev/null || deps+=(sqlite3) +# which gpgme-config || sudo apt-get install libgpgme11-dev + + { test -r /usr/share/doc/libgnome-keyring-dev/copyright } || { + deps+=(libglib2.0-dev libgnome-keyring-dev) } + + + { test ${#deps} -gt 0 } && { + print "Installing missing components" + sudo apt-get install ${=deps} } + + ;; + + fedora) + + print "Building on Fedora" + print "Checking software to install..." + which zsh || sudo yum install zsh + which mutt || sudo yum install mutt + which procmail || sudo yum install procmail + which msmtp || sudo yum install msmtp + which pinentry || sudo yum install pinentry + which fetchmail || sudo yum install fetchmail + which wipe || sudo yum install wipe + which abook || sudo yum install abook + + print "Checking build dependencies" + which gcc || sudo yum install gcc + which bison || sudo yum install bison + which flex || sudo yum install flex + rpm -q glib2-devel || sudo yum install glib2-devel + rpm -q libgnome-keyring-devel || sudo yum install libgnome-keyring-devel + rpm -q bzip2-devel || sudo yum install bzip2-devel + rpm -q zlib-devel || sudo yum install zlib-devel + + ;; - echo "Checking build dependencies" - which gcc || sudo apt-get install gcc - which bison || sudo apt-get install bison - which flex || sudo apt-get install flex - which make || sudo apt-get install make - which autoconf || sudo apt-get install autoconf - which automake || sudo apt-get install automake - which sqlite3 || sudo apt-get install sqlite3 - - { test -r /usr/include/bzlib.h } || { - sudo apt-get install libbz2-dev } - { test -r /usr/share/doc/libgnome-keyring-dev/copyright } || { - sudo apt-get install libglib2.0-dev libgnome-keyring-dev } - { test -r /usr/lib/pkgconfig/tokyocabinet.pc } || { - sudo apt-get install libtokyocabinet-dev } - { test -r /usr/share/doc/libslang2-dev/copyright } || { - sudo apt-get install libslang2-dev } - { test -r /usr/share/doc/libssl-dev/copyright } || { - sudo apt-get install libssl-dev } - { test -r /usr/share/doc/libgnutls-dev/copyright } || { - sudo apt-get install libgnutls-dev } - - which gpgme-config || sudo apt-get install libgpgme11-dev - echo "All dependencies installed" - } - - { test "$target" = "dotlock" } || { - test "$target" = "all" } && { - pushd src - echo -n "Compiling the file lock utility... " - ${=cc} -o dotlock dotlock.c - popd - cp src/dotlock build/gnu/dotlock - } - - { test "$target" = "fetchaddr" } || { - test "$target" = "all" } && { - pushd src - echo -n "Compiling the address parser... " - ${=cc} -c fetchaddr.c helpers.c rfc2047.c rfc822.c; - ${=cc} -o fetchaddr fetchaddr.o helpers.o rfc2047.o rfc822.o -lbz2 - popd - cp src/fetchaddr build/gnu/ - } - - { test "$target" = "mairix" } || { - test "$target" = "all" } && { - echo "Compiling the search engine..." - pushd src/mairix - CC="$cc" ./configure > /dev/null - make > make.log - popd - cp src/mairix/mairix build/gnu/ - } - - { test "$target" = "fetchdate" } || { - test "$target" = "all" } && { - echo -n "Compiling the date parser... " - pushd src - ${=cc} -I mairix -c fetchdate.c - ${=cc} -DHAS_STDINT_H -DHAS_INTTYPES_H -DUSE_GZIP_MBOX \ - -o fetchdate fetchdate.o \ - mairix/datescan.o mairix/db.o mairix/dotlock.o \ - mairix/expandstr.o mairix/glob.o mairix/md5.o \ - mairix/nvpscan.o mairix/rfc822.o mairix/stats.o \ - mairix/writer.o mairix/dates.o mairix/dirscan.o \ - mairix/dumper.o mairix/fromcheck.o mairix/hash.o mairix/mbox.o \ - mairix/nvp.o mairix/reader.o mairix/search.o mairix/tok.o \ - -lz -lbz2 - popd - cp src/fetchdate build/gnu/ - } + *) + print "Error: no distro recognized, build by hand." + ;; + esac + + print "All dependencies installed" +} + +# { test "$target" = "dotlock" } || { +# test "$target" = "all" } && { +# pushd src +# print -n "Compiling the file lock utility... " +# ${=cc} -Wno-unused-result -o dotlock dotlock.c +# popd +# cp src/dotlock build/gnu/dotlock +# print OK +# } + +{ test "$target" = "pgpewrap" } || { + test "$target" = "all" } && { + pushd src + print -n "Compiling the pgp address wrapper... " + ${=cc} -c pgpewrap.c + ${=cc} -o pgpewrap pgpewrap.o + popd + cp src/pgpewrap build/gnu/ + print OK +} + +# { test "$target" = "mairix" } || { +# test "$target" = "all" } && { +# print "Compiling the parser library..." +# pushd src +# parser_sources=(datescan db dotlock expandstr glob md5 nvpscan rfc822 stats tok) +# parser_sources+=(writer dates dirscan dumper fromcheck hash mbox nvp reader search) +# for s in $parser_sources; do +# ${=cc} -I /usr/include -c $s.c +# done +# popd +# print OK +# } + + +{ test "$target" = "fetchaddr" } || { + test "$target" = "all" } && { + pushd src + print -n "Compiling the address parser... " + ${=cc} -c helpers.c + ${=cc} -c rfc2047.c + ${=cc} -c rfc822_mutt.c; + ${=cc} -o fetchaddr fetchaddr.o helpers.o rfc2047.o rfc822_mutt.o + popd + cp src/fetchaddr build/gnu/ + print OK +} + +{ test "$target" = "dfasyn" } || { + test "$target" = "all" } && { + print "Compiling the generator for deterministic finite state automata... " + pushd src/dfasyn + make + popd +} + +{ test "$target" = "fetchdate" } || { + test "$target" = "all" } && { + print "Compiling the date parser... " + pushd src + # then the C files made by dfasyn + ./dfasyn/dfasyn -o nvpscan.c -ho nvpscan.h -r nvpscan.report -u nvp.nfa + # then the utilities + ${=cc} -c rfc822_mairix.c + ${=cc} -c nvp.c nvpscan.c + ${=cc} -I . -c fetchdate.c + ${=cc} -o fetchdate rfc822_mairix.o nvpscan.o nvp.o fetchdate.o + popd + cp src/fetchdate build/gnu/ + print OK +} - { test "$target" = "gnome-keyring" } || { - test "$target" = "all" } && { - echo "Compiling gnome-keyring" - pushd src/gnome-keyring - ${=cc} jaro-gnome-keyring.c -o jaro-gnome-keyring \ - `pkg-config --cflags --libs glib-2.0 gnome-keyring-1` - popd - cp src/gnome-keyring/jaro-gnome-keyring build/gnu/ - } - - # build mutt only if specified - { test "$target" = "mutt" } && { - echo "Compiling Mutt (MUA)" - pushd src/mutt-1.5.21 - { test -r configure } || { autoreconf -i } - CC="$cc" LDFLAGS="-lm" ./configure \ - --with-ssl --with-gnutls --enable-imap --disable-debug --with-slang --disable-gpgme \ - --enable-hcache --with-regex --with-tokyocabinet --with-mixmaster --enable-pgp - make > make.log - popd - cp src/mutt-1.5.21/mutt build/gnu/mutt-jaro - cp src/mutt-1.5.21/pgpewrap build/gnu/pgpewrap - } - - # build mixmaster only if specified - { test "$target" = "mixmaster" } && { - echo "Compiling Mixmaster (anonymous remailer)" - pushd src/mixmaster-3.0/Src - mixmaster_sources=(main menustats mix rem rem1 rem2 chain chain1 chain2 nym) - mixmaster_sources+=(pgp pgpdb pgpdata pgpget pgpcreat pool mail rfc822 mime keymgt) - mixmaster_sources+=(compress stats crypto random rndseed util buffers maildir parsedate.tab) - bison parsedate.y - for s in ${=mixmaster_sources}; do ${=cc} -c ${s}.c; done - ${=cc} -o mixmaster *.o -lssl - popd - cp src/mixmaster-3.0/Src/mixmaster build/gnu - } - - - - echo "Done compiling." - echo "Now run ./install.sh and Jaro Mail will be ready in ~/Mail" - echo "or \"./install.sh path\" to install it somewhere else." - ;; - +{ test "$target" = "gnome-keyring" } || { + test "$target" = "all" } && { + print "Compiling gnome-keyring" + pushd src/gnome-keyring + ${=cc} jaro-gnome-keyring.c -o jaro-gnome-keyring \ + `pkg-config --cflags --libs glib-2.0 gnome-keyring-1` + popd + cp src/gnome-keyring/jaro-gnome-keyring build/gnu/ +} + +# build mutt only if specified +{ test "$target" = "mutt" } && { + print "Compiling Mutt (MUA)" + pushd src/mutt-1.5.21 + { test -r configure } || { autoreconf -i } + CC="$cc" LDFLAGS="-lm" ./configure \ + --with-ssl --with-gnutls --enable-imap --disable-debug --with-slang --disable-gpgme \ + --enable-hcache --with-regex --with-tokyocabinet --with-mixmaster --enable-pgp + make > make.log + popd + cp src/mutt-1.5.21/mutt build/gnu/mutt-jaro + cp src/mutt-1.5.21/pgpewrap build/gnu/pgpewrap +} + +# build mixmaster only if specified +{ test "$target" = "mixmaster" } && { + print "Compiling Mixmaster (anonymous remailer)" + pushd src/mixmaster-3.0/Src + mixmaster_sources=(main menustats mix rem rem1 rem2 chain chain1 chain2 nym) + mixmaster_sources+=(pgp pgpdb pgpdata pgpget pgpcreat pool mail rfc822 mime keymgt) + mixmaster_sources+=(compress stats crypto random rndseed util buffers maildir parsedate.tab) + bison parsedate.y + for s in ${=mixmaster_sources}; do ${=cc} -c ${s}.c; done + ${=cc} -o mixmaster *.o -lssl + popd + cp src/mixmaster-3.0/Src/mixmaster build/gnu +} + + + +print "Done compiling." +print "Now run ./install.sh and Jaro Mail will be ready in ~/Mail" +print "or \"./install.sh path\" to install it somewhere else." - fedora) - - - echo "Checking software to install..." - which zsh || sudo yum install zsh - which mutt || sudo yum install mutt - which procmail || sudo yum install procmail - which msmtp || sudo yum install msmtp - which pinentry || sudo yum install pinentry - which fetchmail || sudo yum install fetchmail - which wipe || sudo yum install wipe - which abook || sudo yum install abook - - echo "Checking build dependencies" - which gcc || sudo yum install gcc - which bison || sudo yum install bison - which flex || sudo yum install flex - rpm -q glib2-devel || sudo yum install glib2-devel - rpm -q libgnome-keyring-devel || sudo yum install libgnome-keyring-devel - rpm -q bzip2-devel || sudo yum install bzip2-devel - rpm -q zlib-devel || sudo yum install zlib-devel - - echo "All dependencies installed" - cd src - echo -n "Compiling the address parser... " - - - echo "fetchaddr" - gcc $cflags -c fetchaddr.c helpers.c rfc2047.c rfc822.c; \ - gcc $cflags -o fetchaddr fetchaddr.o helpers.o rfc2047.o rfc822.o - - echo "dotlock" - gcc $cflags -c dotlock.c - gcc $cflags -o dotlock dotlock.o - cd - > /dev/null - - echo "Compiling the search engine..." - cd src/mairix - ./configure - make clean > /dev/null - make > /dev/null - cd - > /dev/null - - - echo -n "Compiling the date parser... " - cd src - gcc $cflags -I mairix -c fetchdate.c - gcc $cflags -DHAS_STDINT_H -DHAS_INTTYPES_H -DUSE_GZIP_MBOX \ - -o fetchdate fetchdate.o \ - mairix/datescan.o mairix/db.o mairix/dotlock.o \ - mairix/expandstr.o mairix/glob.o mairix/md5.o \ - mairix/nvpscan.o mairix/rfc822.o mairix/stats.o \ - mairix/writer.o mairix/dates.o mairix/dirscan.o \ - mairix/dumper.o mairix/fromcheck.o mairix/hash.o mairix/mbox.o \ - mairix/nvp.o mairix/reader.o mairix/search.o mairix/tok.o \ - -lz -lbz2 - echo "fetchdate" - cd - > /dev/null - - cp src/mairix/mairix build/gnu/ - cp src/fetchaddr build/gnu/ - cp src/fetchdate build/gnu/ - cp src/dotlock build/gnu/ - - echo "Compiling gnome-keyring" - cd src/gnome-keyring - gcc jaro-gnome-keyring.c \ - `pkg-config --cflags --libs glib-2.0 gnome-keyring-1` \ - $cflags -o jaro-gnome-keyring - cd - > /dev/null - cp src/gnome-keyring/jaro-gnome-keyring build/gnu/ - strip build/gnu/* - echo "Done compiling." - echo "Now run ./install.sh and Jaro Mail will be ready in ~/Mail" - echo "or \"./install.sh path\" to install it somewhere else." - ;; - - - - *) - echo "Error: no distro recognized, build by hand." - ;; -esac popd diff --git a/src/Makefile b/src/Makefile @@ -0,0 +1,3 @@ + +all: + @echo "To build use the build-gnu.sh script in ../build" diff --git a/src/mairix/dfasyn/COPYING b/src/dfasyn/COPYING diff --git a/src/mairix/dfasyn/INSTALL b/src/dfasyn/INSTALL diff --git a/src/mairix/dfasyn/Makefile b/src/dfasyn/Makefile diff --git a/src/mairix/dfasyn/NEWS b/src/dfasyn/NEWS diff --git a/src/mairix/dfasyn/README b/src/dfasyn/README diff --git a/src/mairix/dfasyn/abbrevs.c b/src/dfasyn/abbrevs.c diff --git a/src/mairix/dfasyn/blocks.c b/src/dfasyn/blocks.c diff --git a/src/mairix/dfasyn/charclass.c b/src/dfasyn/charclass.c diff --git a/src/mairix/dfasyn/compdfa.c b/src/dfasyn/compdfa.c diff --git a/src/mairix/dfasyn/configure b/src/dfasyn/configure diff --git a/src/mairix/dfasyn/dfasyn.1 b/src/dfasyn/dfasyn.1 diff --git a/src/mairix/dfasyn/dfasyn.5 b/src/dfasyn/dfasyn.5 diff --git a/src/mairix/dfasyn/dfasyn.c b/src/dfasyn/dfasyn.c diff --git a/src/mairix/dfasyn/dfasyn.h b/src/dfasyn/dfasyn.h diff --git a/src/mairix/dfasyn/dfasyn.texi b/src/dfasyn/dfasyn.texi diff --git a/src/mairix/dfasyn/evaluator.c b/src/dfasyn/evaluator.c diff --git a/src/mairix/dfasyn/expr.c b/src/dfasyn/expr.c diff --git a/src/mairix/dfasyn/n2d.c b/src/dfasyn/n2d.c diff --git a/src/mairix/dfasyn/n2d.h b/src/dfasyn/n2d.h diff --git a/src/mairix/dfasyn/parse.y b/src/dfasyn/parse.y diff --git a/src/mairix/dfasyn/scan.l b/src/dfasyn/scan.l diff --git a/src/mairix/dfasyn/states.c b/src/dfasyn/states.c diff --git a/src/mairix/dfasyn/stimulus.c b/src/dfasyn/stimulus.c diff --git a/src/mairix/dfasyn/tabcompr.c b/src/dfasyn/tabcompr.c diff --git a/src/mairix/dfasyn/tokens.c b/src/dfasyn/tokens.c diff --git a/src/dotlock.c b/src/dotlock.c @@ -18,7 +18,7 @@ */ /* - * Completely stolen from the Mutt mailreader + * Borrowed from the Mutt mailreader */ #include <stdio.h> diff --git a/src/mairix.h b/src/mairix.h @@ -0,0 +1,403 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2002,2003,2004,2005,2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + + +#ifndef MAIRIX_H +#define MAIRIX_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "memmac.h" + +struct msgpath {/*{{{*/ + /* The 'selector' for this union is the corresponding entry of type 'enum + * message_type' */ + union { + struct { + char *path; + size_t size; /* size of the message in bytes */ + time_t mtime; /* mtime of message file on disc */ + } mpf; /* message per file */ + struct { + int file_index; /* index into table of mbox files */ + int msg_index; /* index of message within the file */ + } mbox; /* for messages in mbox format folders */ + } src; + + /* Now fields that are common to both types of message. */ + time_t date; /* representation of Date: header in message */ + int tid; /* thread-id */ + + /* Message flags. */ + unsigned int seen:1; + unsigned int replied:1; + unsigned int flagged:1; + + /* + other stuff eventually */ +}; +/*}}}*/ + +enum message_type {/*{{{*/ + MTY_DEAD, /* msg no longer exists, i.e. don't report in searches, + prune it on a '-p' run. */ + MTY_FILE, /* msg <-> file in 1-1 correspondence e.g. maildir, MH */ + MTY_MBOX /* multiple msgs per file : MBOX format file */ +}; +/*}}}*/ +struct msgpath_array {/*{{{*/ + enum message_type *type; + struct msgpath *paths; + int n; + int max; +}; +/*}}}*/ + +struct matches {/*{{{*/ + unsigned char *msginfo; + int n; /* bytes in use */ + int max; /* bytes allocated */ + unsigned long highest; +}; +/*}}}*/ +struct token {/*{{{*/ + char *text; + unsigned long hashval; + /* to store delta-compressed info of which msgpaths match the token */ + struct matches match0; +}; +/*}}}*/ +struct token2 {/*{{{*/ + char *text; + unsigned long hashval; + /* to store delta-compressed info of which msgpaths match the token */ + struct matches match0; + struct matches match1; +}; +/*}}}*/ +struct toktable {/*{{{*/ + struct token **tokens; + int n; /* # in use */ + int size; /* # allocated */ + unsigned int mask; /* for masking down hash values */ + int hwm; /* number to have before expanding */ +}; +/*}}}*/ +struct toktable2 {/*{{{*/ + struct token2 **tokens; + int n; /* # in use */ + int size; /* # allocated */ + unsigned int mask; /* for masking down hash values */ + int hwm; /* number to have before expanding */ +}; +/*}}}*/ + +enum content_type {/*{{{*/ + CT_TEXT_PLAIN, + CT_TEXT_HTML, + CT_TEXT_OTHER, + CT_MESSAGE_RFC822, + CT_OTHER +}; +/*}}}*/ +struct rfc822; +struct attachment {/*{{{*/ + struct attachment *next; + struct attachment *prev; + enum content_type ct; + char *filename; + union attachment_body { + struct normal_attachment_body { + int len; + char *bytes; + } normal; + struct rfc822 *rfc822; + } data; +}; +/*}}}*/ +struct headers {/*{{{*/ + char *to; + char *cc; + char *from; + char *subject; + + /* The following are needed to support threading */ + char *message_id; + char *in_reply_to; + char *references; + + struct { + unsigned int seen:1; + unsigned int replied:1; + unsigned int flagged:1; + } flags; + + time_t date; +}; +/*}}}*/ +struct rfc822 {/*{{{*/ + struct headers hdrs; + struct attachment atts; +}; +/*}}}*/ + +typedef char checksum_t[16]; + +struct message_list {/*{{{*/ + struct message_list *next; + off_t start; + size_t len; +}; +/*}}}*/ +struct mbox {/*{{{*/ + /* If path==NULL, this indicates that the mbox is dead, i.e. no longer + * exists. */ + char *path; + /* As read in from database (i.e. current last time mairix scan was run.) */ + time_t file_mtime; + size_t file_size; + /* As found in the filesystem now. */ + time_t current_mtime; + size_t current_size; + /* After reconciling a loaded database with what's on the disc, this entry + stores how many of the msgs that used to be there last time are still + present at the head of the file. Thus, all messages beyond that are + treated as dead, and scanning starts at that point to find 'new' messages + (whch may actually be old ones that have moved, but they're treated as + new.) */ + int n_old_msgs_valid; + + /* Hold list of new messages and their number. Number is temporary - + * eventually just list walking in case >=2 have to be reattached. */ + struct message_list *new_msgs; + int n_new_msgs; + + int n_so_far; /* Used during database load. */ + + int n_msgs; /* Number of entries in 'start' and 'len' */ + int max_msgs; /* Allocated size of 'start' and 'len' */ + /* File offset to the start of each message (first line of real header, not to mbox 'From ' line) */ + off_t *start; + /* Length of each message */ + size_t *len; + /* Checksums on whole messages. */ + checksum_t *check_all; + +}; +/*}}}*/ +struct database {/*{{{*/ + /* Used to hold an entire mapping between an array of filenames, each + containing a single message, and the sets of tokens that occur in various + parts of those messages */ + + enum message_type *type; + struct msgpath *msgs; /* Paths to messages */ + int n_msgs; /* Number in use */ + int max_msgs; /* Space allocated */ + + struct mbox *mboxen; + int n_mboxen; /* number in use. */ + int max_mboxen; /* space allocated */ + + /* Seed for hashing in the token tables. Randomly created for + * each new database - avoid DoS attacks through carefully + * crafted messages. */ + unsigned int hash_key; + + /* Token tables */ + struct toktable *to; + struct toktable *cc; + struct toktable *from; + struct toktable *subject; + struct toktable *body; + struct toktable *attachment_name; + + /* Encoding chain 0 stores all msgids appearing in the following message headers: + * Message-Id, In-Reply-To, References. Used for thread reconciliation. + * Encoding chain 1 stores just the Message-Id. Used for search by message ID. + */ + struct toktable2 *msg_ids; +}; +/*}}}*/ + +enum folder_type {/*{{{*/ + FT_MAILDIR, + FT_MH, + FT_MBOX, + FT_RAW, + FT_EXCERPT +}; +/*}}}*/ + +struct string_list {/*{{{*/ + struct string_list *next; + struct string_list *prev; + char *data; +}; +/*}}}*/ + +struct msg_src { + enum {MS_FILE, MS_MBOX} type; + char *filename; + off_t start; + size_t len; +}; + +/* Outcomes of checking a filename/dirname to see whether to keep on looking + * at filenames within this dir. */ +enum traverse_check { + TRAV_PROCESS, /* Continue looking at this entry */ + TRAV_IGNORE, /* Ignore just this dir entry */ + TRAV_FINISH /* Ignore this dir entry and don't bother looking at the rest of the directory */ +}; + +struct traverse_methods { + int (*filter)(const char *, const struct stat *); + enum traverse_check (*scrutinize)(int, const char *); +}; + +extern struct traverse_methods maildir_traverse_methods; +extern struct traverse_methods mh_traverse_methods; +extern struct traverse_methods mbox_traverse_methods; + +extern int verbose; /* cmd line -v switch */ +extern int do_hardlinks; /* cmd line -H switch */ +extern int do_movefiles; /* cmd line -M switch */ + +/* Lame fix for systems where NAME_MAX isn't defined after including the above + * set of .h files (Solaris, FreeBSD so far). Probably grossly oversized but + * it'll do. */ + +#if !defined(NAME_MAX) +#define NAME_MAX 4096 +#endif + +/* In glob.c */ +struct globber; +struct globber_array; + +struct globber *make_globber(const char *wildstring); +void free_globber(struct globber *old); +int is_glob_match(struct globber *g, const char *s); +struct globber_array *colon_sep_string_to_globber_array(const char *in); +int is_globber_array_match(struct globber_array *ga, const char *s); +void free_globber_array(struct globber_array *in); + +/* In hash.c */ +unsigned int hashfn( unsigned char *k, unsigned int length, unsigned int initval); + +/* In dirscan.c */ +struct msgpath_array *new_msgpath_array(void); +int valid_mh_filename_p(const char *x); +void free_msgpath_array(struct msgpath_array *x); +void string_list_to_array(struct string_list *list, int *n, char ***arr); +void split_on_colons(const char *str, int *n, char ***arr); +void build_message_list(char *folder_base, char *folders, enum folder_type ft, + struct msgpath_array *msgs, struct globber_array *omit_globs); + +/* In rfc822.c */ +struct rfc822 *make_rfc822(char *filename); +void free_rfc822(struct rfc822 *msg); +enum data_to_rfc822_error { + DTR8_OK, + DTR8_MISSING_END, /* missing endpoint marker. */ + DTR8_MULTIPART_SANS_BOUNDARY, /* multipart with no boundary string defined */ + DTR8_BAD_HEADERS, /* corrupt headers */ + DTR8_BAD_ATTACHMENT /* corrupt attachment (e.g. no body part) */ +}; +struct rfc822 *data_to_rfc822(struct msg_src *src, char *data, int length, enum data_to_rfc822_error *error); +void create_ro_mapping(const char *filename, unsigned char **data, int *len); +void free_ro_mapping(unsigned char *data, int len); +char *format_msg_src(struct msg_src *src); + +/* In tok.c */ +struct toktable *new_toktable(void); +struct toktable2 *new_toktable2(void); +void free_token(struct token *x); +void free_token2(struct token2 *x); +void free_toktable(struct toktable *x); +void free_toktable2(struct toktable2 *x); +void add_token_in_file(int file_index, unsigned int hash_key, char *tok_text, struct toktable *table); +void check_and_enlarge_encoding(struct matches *m); +void insert_index_on_encoding(struct matches *m, int idx); +void add_token2_in_file(int file_index, unsigned int hash_key, char *tok_text, struct toktable2 *table, int add_to_chain1); + +/* In db.c */ +#define CREATE_RANDOM_DATABASE_HASH 0 +struct database *new_database(unsigned int hash_key); +struct database *new_database_from_file(char *db_filename, int do_integrity_checks); +void free_database(struct database *db); +void maybe_grow_message_arrays(struct database *db); +void tokenise_message(int file_index, struct database *db, struct rfc822 *msg); +int update_database(struct database *db, struct msgpath *sorted_paths, int n_paths, int do_fast_index); +void check_database_integrity(struct database *db); +int cull_dead_messages(struct database *db, int do_integrity_checks); + +/* In mbox.c */ +void build_mbox_lists(struct database *db, const char *folder_base, + const char *mboxen_paths, struct globber_array *omit_globs); +int add_mbox_messages(struct database *db); +void compute_checksum(const char *data, size_t len, checksum_t *csum); +void cull_dead_mboxen(struct database *db); +unsigned int encode_mbox_indices(unsigned int mb, unsigned int msg); +void decode_mbox_indices(unsigned int index, unsigned int *mb, unsigned int *msg); +int verify_mbox_size_constraints(struct database *db); +void glob_and_expand_paths(const char *folder_base, char **paths_in, int n_in, char ***paths_out, int *n_out, const struct traverse_methods *methods, struct globber_array *omit_globs); + +/* In glob.c */ +struct globber; + +struct globber *make_globber(const char *wildstring); +void free_globber(struct globber *old); +int is_glob_match(struct globber *g, const char *s); + +/* In writer.c */ +void write_database(struct database *db, char *filename, int do_integrity_checks); + +/* In search.c */ +int search_top(int do_threads, int do_augment, char *database_path, char *complete_mfolder, char **argv, enum folder_type ft, int verbose); + +/* In stats.c */ +void get_db_stats(struct database *db); + +/* In dates.c */ +int scan_date_string(char *in, time_t *start, int *has_start, time_t *end, int *has_end); + +/* In dumper.c */ +void dump_database(char *filename); + +/* In strexpand.c */ +char *expand_string(const char *p); + +/* In dotlock.c */ +void lock_database(char *path, int forced_unlock); +void unlock_database(void); +void unlock_and_exit(int code); + +/* In mairix.c */ +void report_error(const char *str, const char *filename); + +#endif /* MAIRIX_H */ diff --git a/src/memmac.h b/src/memmac.h @@ -0,0 +1,72 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2002-2004 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + + +#ifndef MEMMAC_H +#define MEMMAC_H + +/*{{{ Safe alloc helpers (GCC extensions) */ +extern void out_of_mem(char *file, int line, size_t size); + +#undef TEST_OOM + +#ifdef TEST_OOM +extern int total_bytes; +#endif + +static __inline__ void* safe_malloc(char *file, int line, size_t s)/*{{{*/ +{ + void *x = malloc(s); +#ifdef TEST_OOM + total_bytes += s; + if (total_bytes > 131072) x = NULL; +#endif + if (!x) out_of_mem(file, line, s); + return x; +} +/*}}}*/ +static __inline__ void* safe_realloc(char *file, int line, void *old_ptr, size_t s)/*{{{*/ +{ + void *x = realloc(old_ptr, s); + if (!x) out_of_mem(file, line, s); + return x; +} +/*}}}*/ +#ifndef TEST +#define Malloc(s) safe_malloc(__FILE__, __LINE__, s) +#define Realloc(xx,s) safe_realloc(__FILE__, __LINE__,xx,s) +#else +#define Malloc(s) malloc(s) +#define Realloc(xx,s) realloc(xx,s) +#endif +/*}}}*/ + +/*{{{ Memory macros*/ +#define new_string(s) strcpy((char *) Malloc(1+strlen(s)), (s)) +#define extend_string(x,s) (strcat(Realloc(x, (strlen(x)+strlen(s)+1)), s)) +#define new(T) (T *) Malloc(sizeof(T)) +#define new_array(T, n) (T *) Malloc(sizeof(T) * (n)) +#define grow_array(T, n, oldX) (T *) ((oldX) ? Realloc(oldX, (sizeof(T) * (n))) : Malloc(sizeof(T) * (n))) +#define EMPTY(x) {&(x), &(x)} +/*}}}*/ + +#endif /* MEMMAC_H */ diff --git a/src/mutt/gpg b/src/mutt/gpg @@ -31,40 +31,40 @@ # breaking PGP/MIME. # decode application/pgp -set pgp_decode_command="gpg-jaro --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f" +set pgp_decode_command="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f" # verify a pgp/mime signature -set pgp_verify_command="gpg-jaro --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f" +set pgp_verify_command="gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f" # decrypt a pgp/mime attachment -set pgp_decrypt_command="gpg-jaro --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f" +set pgp_decrypt_command="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f" # create a pgp/mime signed attachment -set pgp_sign_command="gpg-jaro --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f" +set pgp_sign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f" # create a application/pgp signed (old-style) message -set pgp_clearsign_command="gpg-jaro --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f" +set pgp_clearsign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f" # create a pgp/mime encrypted attachment -set pgp_encrypt_only_command="pgpewrap gpg-jaro --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f" +set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f" # create a pgp/mime encrypted and signed attachment -set pgp_encrypt_sign_command="pgpewrap gpg-jaro %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f" +set pgp_encrypt_sign_command="pgpewrap gpg %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f" # import a key into the public key ring -set pgp_import_command="gpg-jaro --no-verbose --import %f" +set pgp_import_command="gpg --no-verbose --import %f" # export a key from the public key ring -set pgp_export_command="gpg-jaro --no-verbose --export --armor %r" +set pgp_export_command="gpg --no-verbose --export --armor %r" # verify a key -set pgp_verify_key_command="gpg-jaro --verbose --batch --fingerprint --check-sigs %r" +set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs %r" # read in the public key ring -set pgp_list_pubring_command="gpg-jaro --no-verbose --batch --quiet --with-colons --list-keys %r" +set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons --list-keys %r" # read in the secret key ring -set pgp_list_secring_command="gpg-jaro --no-verbose --batch --quiet --with-colons --list-secret-keys %r" +set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --list-secret-keys %r" # fetch keys # set pgp_getkeys_command="pkspxycwrap %r" diff --git a/src/nvp.c b/src/nvp.c @@ -0,0 +1,416 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2006,2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + +#ifdef VERBOSE_TEST +#define TEST 1 +#endif + +/* Parse name/value pairs from mail headers into a lookup table. */ +#include <stdio.h> +#include <ctype.h> +#include "mairix.h" +#include "nvptypes.h" +#include "nvpscan.h" +#include "nvp.h" + +enum nvp_type {/*{{{*/ + NVP_NAME, + NVP_MAJORMINOR, + NVP_NAMEVALUE +}; +/*}}}*/ +struct nvp_entry {/*{{{*/ + struct nvp_entry *next; + struct nvp_entry *prev; + enum nvp_type type; + char *lhs; + char *rhs; +}; +/*}}}*/ +struct nvp {/*{{{*/ + struct nvp_entry *first, *last; +}; +/*}}}*/ +static void append(struct nvp *nvp, struct nvp_entry *ne)/*{{{*/ +{ + ne->next = NULL; + ne->prev = nvp->last; + if (nvp->last) nvp->last->next = ne; + else nvp->first = ne; + nvp->last = ne; +} +/*}}}*/ +static void append_name(struct nvp *nvp, char *name)/*{{{*/ +{ + struct nvp_entry *ne; + ne = new(struct nvp_entry); + ne->type = NVP_NAME; + ne->lhs = new_string(name); + append(nvp, ne); +} +/*}}}*/ +static void append_majorminor(struct nvp *nvp, char *major, char *minor)/*{{{*/ +{ + struct nvp_entry *ne; + ne = new(struct nvp_entry); + ne->type = NVP_MAJORMINOR; + ne->lhs = new_string(major); + ne->rhs = new_string(minor); + append(nvp, ne); + +} +/*}}}*/ +static void append_namevalue(struct nvp *nvp, char *name, char *value)/*{{{*/ +{ + struct nvp_entry *ne; + ne = new(struct nvp_entry); + ne->type = NVP_NAMEVALUE; + ne->lhs = new_string(name); + ne->rhs = new_string(value); + append(nvp, ne); +} +/*}}}*/ +static void combine_namevalue(struct nvp *nvp, char *name, char *value)/*{{{*/ +{ + struct nvp_entry *n; + for (n=nvp->first; n; n=n->next) { + if (n->type == NVP_NAMEVALUE) { + if (!strcmp(n->lhs, name)) { + char *new_rhs; + new_rhs = new_array(char, strlen(n->rhs) + strlen(value) + 1); + strcpy(new_rhs, n->rhs); + strcat(new_rhs, value); + free(n->rhs); + n->rhs = new_rhs; + return; + } + } + } + /* No match : it's the first one */ + append_namevalue(nvp, name, value); +} +/*}}}*/ +static void release_nvp(struct nvp *nvp)/*{{{*/ +{ + struct nvp_entry *e, *ne; + for (e=nvp->first; e; e=ne) { + ne = e->next; + switch (e->type) { + case NVP_NAME: + free(e->lhs); + break; + case NVP_MAJORMINOR: + case NVP_NAMEVALUE: + free(e->lhs); + free(e->rhs); + break; + } + free(e); + } + free(nvp); +} +/*}}}*/ +struct nvp *make_nvp(struct msg_src *src, char *s, const char *pfx)/*{{{*/ +{ + int current_state; + unsigned int tok; + char *q; + unsigned char qq; + char name[256]; + char minor[256]; + char value[256]; + enum nvp_action last_action, current_action; + struct nvp *result; + size_t pfxlen; + char *nn, *mm, *vv; + + pfxlen = strlen(pfx); + if (strncasecmp(pfx, s, pfxlen)) + return NULL; + s += pfxlen; + + result = new(struct nvp); + result->first = result->last = NULL; + + current_state = nvp_in; + + q = s; + nn = name; + mm = minor; + vv = value; + last_action = GOT_NOTHING; + do { + qq = *(unsigned char *) q; + if (qq) { + tok = nvp_char2tok[qq]; + } else { + tok = nvp_EOS; + } + current_state = nvp_next_state(current_state, tok); +#ifdef VERBOSE_TEST + fprintf(stderr, "Char %02x (%c) tok=%d new_current_state=%d\n", + qq, ((qq>=32) && (qq<=126)) ? qq : '.', + tok, current_state); +#endif + + if (current_state < 0) { +#ifdef TEST + fprintf(stderr, "'%s' could not be parsed\n", s); +#else + fprintf(stderr, "Header '%s%s' in %s could not be parsed\n", + pfx, s, format_msg_src(src)); +#endif + release_nvp(result); + return NULL; + } + + switch (nvp_copier[current_state]) { + case COPY_TO_NAME: +#ifdef VERBOSE_TEST + fprintf(stderr, " COPY_TO_NAME\n"); +#endif + *nn++ = *q; + break; + case COPY_TO_MINOR: +#ifdef VERBOSE_TEST + fprintf(stderr, " COPY_TO_MINOR\n"); +#endif + *mm++ = *q; + break; + case COPY_TO_VALUE: +#ifdef VERBOSE_TEST + fprintf(stderr, " COPY_TO_VALUE\n"); +#endif + *vv++ = *q; + break; + case COPY_NOWHERE: + break; + } + + current_action = nvp_action[current_state]; + switch (current_action) { + case GOT_NAME: + case GOT_NAME_TRAILING_SPACE: + case GOT_MAJORMINOR: + case GOT_NAMEVALUE: + case GOT_NAMEVALUE_CONT: +#ifdef VERBOSE_TEST + fprintf(stderr, " Setting last action to %d\n", current_action); +#endif + last_action = current_action; + break; + case GOT_TERMINATOR: +#ifdef VERBOSE_TEST + fprintf(stderr, " Hit terminator; last_action=%d\n", last_action); +#endif + switch (last_action) { + case GOT_NAME: + *nn = 0; + append_name(result, name); + break; + case GOT_NAME_TRAILING_SPACE: + while (isspace(*--nn)) {} + *++nn = 0; + append_name(result, name); + break; + case GOT_MAJORMINOR: + *nn = 0; + *mm = 0; + append_majorminor(result, name, minor); + break; + case GOT_NAMEVALUE: + *nn = 0; + *vv = 0; + append_namevalue(result, name, value); + break; + case GOT_NAMEVALUE_CONT: + *nn = 0; + *vv = 0; + combine_namevalue(result, name, value); + break; + default: + break; + } + nn = name; + mm = minor; + vv = value; + break; + case GOT_NOTHING: + break; + } + + q++; + } while (tok != nvp_EOS); + + return result; +} +/*}}}*/ +void free_nvp(struct nvp *nvp)/*{{{*/ +{ + struct nvp_entry *ne, *nne; + for (ne = nvp->first; ne; ne=nne) { + nne = ne->next; + switch (ne->type) { + case NVP_NAME: + free(ne->lhs); + break; + case NVP_MAJORMINOR: + case NVP_NAMEVALUE: + free(ne->lhs); + free(ne->rhs); + break; + } + free(ne); + } + free(nvp); +} +/*}}}*/ +const char *nvp_lookup(struct nvp *nvp, const char *name)/*{{{*/ +{ + struct nvp_entry *ne; + for (ne = nvp->first; ne; ne=ne->next) { + if (ne->type == NVP_NAMEVALUE) { + if (!strcmp(ne->lhs, name)) { + return ne->rhs; + } + } + } + return NULL; +} +/*}}}*/ +const char *nvp_lookupcase(struct nvp *nvp, const char *name)/*{{{*/ +{ + struct nvp_entry *ne; + for (ne = nvp->first; ne; ne=ne->next) { + if (ne->type == NVP_NAMEVALUE) { + if (!strcasecmp(ne->lhs, name)) { + return ne->rhs; + } + } + } + return NULL; +} +/*}}}*/ + +void nvp_dump(struct nvp *nvp, FILE *out)/*{{{*/ +{ + struct nvp_entry *ne; + fprintf(out, "----\n"); + for (ne = nvp->first; ne; ne=ne->next) { + switch (ne->type) { + case NVP_NAME: + fprintf(out, "NAME: %s\n", ne->lhs); + break; + case NVP_MAJORMINOR: + fprintf(out, "MAJORMINOR: %s/%s\n", ne->lhs, ne->rhs); + break; + case NVP_NAMEVALUE: + fprintf(out, "NAMEVALUE: %s=%s\n", ne->lhs, ne->rhs); + break; + } + } +} +/*}}}*/ + +/* In these cases, we only look at the first entry */ +const char *nvp_major(struct nvp *nvp)/*{{{*/ +{ + struct nvp_entry *ne; + ne = nvp->first; + if (ne) { + if (ne->type == NVP_MAJORMINOR) { + return ne->lhs; + } else { + return NULL; + } + } else { + return NULL; + } +} +/*}}}*/ +const char *nvp_minor(struct nvp *nvp)/*{{{*/ +{ + struct nvp_entry *ne; + ne = nvp->first; + if (ne) { + if (ne->type == NVP_MAJORMINOR) { + return ne->rhs; + } else { + return NULL; + } + } else { + return NULL; + } +} +/*}}}*/ +const char *nvp_first(struct nvp *nvp)/*{{{*/ +{ + struct nvp_entry *ne; + ne = nvp->first; + if (ne) { + if (ne->type == NVP_NAME) { + return ne->lhs; + } else { + return NULL; + } + } else { + return NULL; + } +} +/*}}}*/ + +#ifdef TEST + +static void do_test(char *s) +{ + struct nvp *n; + n = make_nvp(NULL, s, ""); + if (n) { + nvp_dump(n, stderr); + free_nvp(n); + } +} + + +int main (int argc, char **argv) { + struct nvp *n; +#if 0 + do_test("attachment; filename=\"foo.c\"; prot=ro"); + do_test("attachment; filename= \"foo bar.c\" ;prot=ro"); + do_test("attachment ; filename= \"foo bar.c\" ;prot= ro"); + do_test("attachment ; filename= \"foo bar.c\" ;prot= ro"); + do_test("attachment ; filename= \"foo ; bar.c\" ;prot= ro"); + do_test("attachment ; x*0=\"hi \"; x*1=\"there\""); +#endif + + do_test("application/vnd.ms-excel; name=\"thequiz.xls\""); +#if 0 + do_test("inline; filename*0=\"aaaa bbbb cccc dddd eeee ffff gggg hhhh iiii jjjj\t kkkkllll\""); + do_test(" text/plain ; name= \"foo bar.c\" ;prot= ro/rw; read/write; read= foo bar"); +#endif + return 0; +} +#endif + + + + diff --git a/src/nvp.h b/src/nvp.h @@ -0,0 +1,38 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2006,2010 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + +#ifndef NVP_H +#define NVP_H + +struct nvp; +struct msg_src; +extern struct nvp *make_nvp(struct msg_src *, char *, const char *); +extern void free_nvp(struct nvp *); +extern void nvp_dump(struct nvp *nvp, FILE *out); +extern const char *nvp_major(struct nvp *n); +extern const char *nvp_minor(struct nvp *n); +extern const char *nvp_first(struct nvp *n); +extern const char *nvp_lookup(struct nvp *n, const char *name); +extern const char *nvp_lookupcase(struct nvp *n, const char *name); + +#endif + diff --git a/src/nvp.nfa b/src/nvp.nfa @@ -0,0 +1,197 @@ +######################################################################### +# +# mairix - message index builder and finder for maildir folders. +# +# Copyright (C) Richard P. Curnow 2006,2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ======================================================================= + +Tokens EOS +Abbrev VALUE = [\041-~]~[\\";] +Abbrev QVALUE = VALUE | [\011\040;] | <escape:in->out> +Abbrev NAME1 = [0-9a-zA-Z_\-] +Abbrev MINOR = NAME1 | [\.\-+] +Abbrev OWS = <optwhite:in->out> + +%{ +#include "nvptypes.h" +%} + +Block escape { + State in + [\\] ; [\\] -> out + [\\] ; ["] -> out +} + +Block optwhite { + State in + -> out + # I have seen headers with ^M in them... + [ \t\r] -> in +} + +Block name { + # This needs to cope with embedded spaces, e.g. for mailers that write '7 + # bit' instead of '7bit' + State in + NAME1 -> name1 + + State name1 + = COPY_TO_NAME + = GOT_NAME + NAME1 -> name1 + [ \t] -> name2 + -> out + + State name2 + = COPY_TO_NAME + = GOT_NAME_TRAILING_SPACE + [ \t] -> name2 + NAME1 -> name1 + -> out + + State out +} + +Block value { + State in + VALUE -> v1 + State v1 + = COPY_TO_VALUE + -> out + VALUE -> v1 +} + +Block qvalue { + State in + ["] -> qv0 + + State qv0 + QVALUE -> qv1 + + State qv1 + = COPY_TO_VALUE + QVALUE -> qv1 + -> qv2 + + State qv2 + ["] -> out +} + +Block digits { + State in + [0-9] -> out + [0-9] -> in +} + +Block namevalue { + State in + OWS ; <name:in->out> ; OWS ; [=] -> rhs_normal + OWS ; <name:in->out> ; [*] ; <digits:in->out> ; OWS ; [=] -> rhs_continue + + State rhs_normal + OWS ; <qvalue:in->out> ; OWS -> out_normal + OWS ; <value:in->out> ; OWS -> out_normal + OWS ; ; EOS -> out_normal + + State rhs_continue + OWS ; <qvalue:in->out> ; OWS -> out_continue + OWS ; <value:in->out> ; OWS -> out_continue + + State out_normal = GOT_NAMEVALUE + -> out + State out_continue = GOT_NAMEVALUE_CONT + -> out +} + +Block major { + State in + NAME1 -> name1 + + State name1 + NAME1 -> name1 + -> out +} + +Block minor { + State in + MINOR -> minor1 + + State minor1 + = COPY_TO_MINOR + MINOR -> minor1 + -> out +} + +Block majorminor { + State in + <major:in->out> -> foo + + State foo + [/] -> bar + + State bar + <minor:in->out> -> out + + State out = GOT_MAJORMINOR +} + +Block component { + State in + <namevalue:in->out> -> out + <name:in->out> -> out + <majorminor:in->out> -> out +} + +Block main { + State in Entry in + OWS ; <component:in->out> ; OWS ; EOS -> out2 + OWS ; <component:in->out> ; OWS ; [;] ; OWS ; EOS -> out2 + OWS ; <component:in->out> ; OWS ; [;] -> in2 + + State in2 + = GOT_TERMINATOR + -> in + + State out2 + = GOT_TERMINATOR + -> out +} + +Defattr 0 +Prefix nvp + +Group action { + Attr GOT_NAMEVALUE + Attr GOT_NAMEVALUE_CONT + Attr GOT_NAME + Attr GOT_NAME_TRAILING_SPACE + Attr GOT_MAJORMINOR + Attr GOT_TERMINATOR + Defattr GOT_NOTHING + Type "enum nvp_action" +} + +Group copier { + Attr COPY_TO_NAME + Attr COPY_TO_MINOR + Attr COPY_TO_VALUE + Defattr COPY_NOWHERE + Type "enum nvp_copier" +} + +# vim:et:sts=4:sw=4:ht=8 + diff --git a/src/nvptypes.h b/src/nvptypes.h @@ -0,0 +1,43 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2006,2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + +#ifndef NVPTYPES_H +#define NVPTYPES_H + +enum nvp_action { + GOT_NAMEVALUE, + GOT_NAMEVALUE_CONT, + GOT_NAME, + GOT_NAME_TRAILING_SPACE, + GOT_MAJORMINOR, + GOT_TERMINATOR, + GOT_NOTHING +}; + +enum nvp_copier { + COPY_TO_NAME, + COPY_TO_MINOR, + COPY_TO_VALUE, + COPY_NOWHERE +}; + +#endif diff --git a/src/pgpewrap.c b/src/pgpewrap.c @@ -0,0 +1,64 @@ +/* + * C version by Wessel Dankers <wsl@fruit.eu.org> + * + * This code is in the public domain. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +void print_usage(const char *progname) { + fprintf(stderr, "Command line usage: %s [flags] -- prefix [recipients]\n", progname); + exit(1); +} + +int main(int argc, char **argv) { + char **opts, **opt, *pfx; + int i; + + if (argc <= 1) { + print_usage(argv[0]); + } + + opts = malloc((2 * argc + 1) * sizeof (* opts)); /* __MEM_CHECKED__ */ + if(!opts) { + perror(argv[0]); + exit(2); + } + + if (argc < 2) + { + fprintf (stderr, + "Command line usage: %s [flags] -- prefix [recipients]\n", + argv[0]); + return 1; + } + + opt = opts; + *opt++ = argv[1]; + pfx = NULL; + + for(i = 2; i < argc; ) { + if(!strcmp(argv[i], "--")) { + i += 2; + if(i > argc) { + print_usage(argv[0]); + } + pfx = argv[i-1]; + } + if(pfx) + *opt++ = pfx; + *opt++ = argv[i++]; + } + *opt = NULL; + + execvp(opts[0], opts); + perror(argv[0]); + return 2; +} diff --git a/src/rfc2047.c b/src/rfc2047.c @@ -26,7 +26,7 @@ #include <limits.h> #endif -#include "rfc822.h" +#include "rfc822_mutt.h" #include "rfc2047.h" #include "helpers.h" diff --git a/src/rfc822.c b/src/rfc822.c @@ -1,768 +0,0 @@ -/* - * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA. - */ - -/* $Id: rfc822.c,v 1.5 2005-10-29 14:48:11 roland Exp $ */ - -#include <string.h> -#include <ctype.h> -#include <stdlib.h> -#include <stdio.h> - -#include "helpers.h" -#include "rfc822.h" - -const char RFC822Specials[] = "@.,:;<>[]\\\"()"; -#define is_special(x) strchr(RFC822Specials,x) - -int RFC822Error = 0; - -/* these must defined in the same order as the numerated errors given in rfc822.h */ -const char *RFC822Errors[] = { - "out of memory", - "mismatched parenthesis", - "mismatched quotes", - "bad route in <>", - "bad address in <>", - "bad address spec" -}; - -void rfc822_dequote_comment (char *s) -{ - char *w = s; - - for (; *s; s++) - { - if (*s == '\\') - { - if (!*++s) - break; /* error? */ - *w++ = *s; - } - else if (*s != '\"') - { - if (w != s) - *w = *s; - w++; - } - } - *w = 0; -} - -void rfc822_free_address (ADDRESS **p) -{ - ADDRESS *t; - - while (*p) - { - t = *p; - *p = (*p)->next; -#ifdef EXACT_ADDRESS - FREE (&t->val); -#endif - FREE (&t->personal); - FREE (&t->mailbox); - FREE (&t); - } -} - -static const char * -parse_comment (const char *s, - char *comment, size_t *commentlen, size_t commentmax) -{ - int level = 1; - - while (*s && level) - { - if (*s == '(') - level++; - else if (*s == ')') - { - if (--level == 0) - { - s++; - break; - } - } - else if (*s == '\\') - { - if (!*++s) - break; - } - if (*commentlen < commentmax) - comment[(*commentlen)++] = *s; - s++; - } - if (level) - { - RFC822Error = ERR_MISMATCH_PAREN; - return NULL; - } - return s; -} - -static const char * -parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax) -{ - if (*tokenlen < tokenmax) - token[(*tokenlen)++] = '"'; - while (*s) - { - if (*tokenlen < tokenmax) - token[(*tokenlen)++] = *s; - if (*s == '"') - return (s + 1); - if (*s == '\\') - { - if (!*++s) - break; - } - s++; - } - RFC822Error = ERR_MISMATCH_QUOTE; - return NULL; -} - -static const char * -next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax) -{ - if (*s == '(') - return (parse_comment (s + 1, token, tokenlen, tokenmax)); - if (*s == '"') - return (parse_quote (s + 1, token, tokenlen, tokenmax)); - if (is_special (*s)) - { - if (*tokenlen < tokenmax) - token[(*tokenlen)++] = *s; - return (s + 1); - } - while (*s) - { - if (isspace (*s) || is_special (*s)) - break; - if (*tokenlen < tokenmax) - token[(*tokenlen)++] = *s; - s++; - } - return s; -} - -static const char * -parse_mailboxdomain (const char *s, const char *nonspecial, - char *mailbox, size_t *mailboxlen, size_t mailboxmax, - char *comment, size_t *commentlen, size_t commentmax) -{ - const char *ps; - - while (*s) - { - SKIPWS (s); - if (strchr (nonspecial, *s) == NULL && is_special (*s)) - return s; - - if (*s == '(') - { - if (*commentlen && *commentlen < commentmax) - comment[(*commentlen)++] = ' '; - ps = next_token (s, comment, commentlen, commentmax); - } - else - ps = next_token (s, mailbox, mailboxlen, mailboxmax); - if (!ps) - return NULL; - s = ps; - } - - return s; -} - -static const char * -parse_address (const char *s, - char *token, size_t *tokenlen, size_t tokenmax, - char *comment, size_t *commentlen, size_t commentmax, - ADDRESS *addr) -{ - s = parse_mailboxdomain (s, ".\"(\\", - token, tokenlen, tokenmax, - comment, commentlen, commentmax); - if (!s) - return NULL; - - if (*s == '@') - { - if (*tokenlen < tokenmax) - token[(*tokenlen)++] = '@'; - s = parse_mailboxdomain (s + 1, ".([]\\", - token, tokenlen, tokenmax, - comment, commentlen, commentmax); - if (!s) - return NULL; - } - - token[*tokenlen] = 0; - addr->mailbox = safe_strdup (token); - - if (*commentlen && !addr->personal) - { - comment[*commentlen] = 0; - addr->personal = safe_strdup (comment); - } - - return s; -} - -static const char * -parse_route_addr (const char *s, - char *comment, size_t *commentlen, size_t commentmax, - ADDRESS *addr) -{ - char token[STRING]; - size_t tokenlen = 0; - - SKIPWS (s); - - /* find the end of the route */ - if (*s == '@') - { - while (s && *s == '@') - { - if (tokenlen < sizeof (token) - 1) - token[tokenlen++] = '@'; - s = parse_mailboxdomain (s + 1, ".\\[](", token, - &tokenlen, sizeof (token) - 1, - comment, commentlen, commentmax); - } - if (!s || *s != ':') - { - RFC822Error = ERR_BAD_ROUTE; - return NULL; /* invalid route */ - } - - if (tokenlen < sizeof (token) - 1) - token[tokenlen++] = ':'; - s++; - } - - if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL) - return NULL; - - if (*s != '>' || !addr->mailbox) - { - RFC822Error = ERR_BAD_ROUTE_ADDR; - return NULL; - } - - s++; - return s; -} - -static const char * -parse_addr_spec (const char *s, - char *comment, size_t *commentlen, size_t commentmax, - ADDRESS *addr) -{ - char token[STRING]; - size_t tokenlen = 0; - - s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr); - if (s && *s && *s != ',' && *s != ';') - { - RFC822Error = ERR_BAD_ADDR_SPEC; - return NULL; - } - return s; -} - -static void -add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase, - char *comment, size_t *commentlen, size_t commentmax) -{ - ADDRESS *cur = rfc822_new_address (); - - if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) - return; - - if (*last) - (*last)->next = cur; - else - *top = cur; - *last = cur; -} - -ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s) -{ - const char *begin, *ps; - char comment[STRING], phrase[STRING]; - size_t phraselen = 0, commentlen = 0; - ADDRESS *cur, *last = NULL; - - RFC822Error = 0; - - last = top; - while (last && last->next) - last = last->next; - - SKIPWS (s); - begin = s; - while (*s) - { - if (*s == ',') - { - if (phraselen) - { - phrase[phraselen] = 0; - add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); - } - else if (commentlen && last && !last->personal) - { - comment[commentlen] = 0; - last->personal = safe_strdup (comment); - } - -#ifdef EXACT_ADDRESS - if (last && !last->val) - last->val = mutt_substrdup (begin, s); -#endif - commentlen = 0; - phraselen = 0; - s++; - begin = s; - SKIPWS (begin); - } - else if (*s == '(') - { - if (commentlen && commentlen < sizeof (comment) - 1) - comment[commentlen++] = ' '; - if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL) - { - rfc822_free_address (&top); - return NULL; - } - s = ps; - } - else if (*s == ':') - { - cur = rfc822_new_address (); - phrase[phraselen] = 0; - cur->mailbox = safe_strdup (phrase); - cur->group = 1; - - if (last) - last->next = cur; - else - top = cur; - last = cur; - -#ifdef EXACT_ADDRESS - last->val = mutt_substrdup (begin, s); -#endif - - phraselen = 0; - commentlen = 0; - s++; - begin = s; - SKIPWS (begin); - } - else if (*s == ';') - { - if (phraselen) - { - phrase[phraselen] = 0; - add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); - } - else if (commentlen && !last->personal) - { - comment[commentlen] = 0; - last->personal = safe_strdup (comment); - } -#ifdef EXACT_ADDRESS - if (last && !last->val) - last->val = mutt_substrdup (begin, s); -#endif - - /* add group terminator */ - cur = rfc822_new_address (); - if (last) - { - last->next = cur; - last = cur; - } - - phraselen = 0; - commentlen = 0; - s++; - begin = s; - SKIPWS (begin); - } - else if (*s == '<') - { - phrase[phraselen] = 0; - cur = rfc822_new_address (); - if (phraselen) - { - if (cur->personal) - FREE (&cur->personal); - /* if we get something like "Michael R. Elkins" remove the quotes */ - rfc822_dequote_comment (phrase); - cur->personal = safe_strdup (phrase); - } - if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL) - { - rfc822_free_address (&top); - rfc822_free_address (&cur); - return NULL; - } - - if (last) - last->next = cur; - else - top = cur; - last = cur; - - phraselen = 0; - commentlen = 0; - s = ps; - } - else - { - if (phraselen && phraselen < sizeof (phrase) - 1) - phrase[phraselen++] = ' '; - if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) - { - rfc822_free_address (&top); - return NULL; - } - s = ps; - } - SKIPWS (s); - } - - if (phraselen) - { - phrase[phraselen] = 0; - comment[commentlen] = 0; - add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); - } - else if (commentlen && last && !last->personal) - { - comment[commentlen] = 0; - last->personal = safe_strdup (comment); - } -#ifdef EXACT_ADDRESS - if (last) - last->val = mutt_substrdup (begin, s); -#endif - - return top; -} - -void rfc822_qualify (ADDRESS *addr, const char *host) -{ - char *p; - - for (; addr; addr = addr->next) - if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) - { - p = safe_malloc (strlen (addr->mailbox) + strlen (host) + 2); - sprintf (p, "%s@%s", addr->mailbox, host); - safe_free (&addr->mailbox); - addr->mailbox = p; - } -} - -void -rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials) -{ - if (strpbrk (value, specials)) - { - char tmp[256], *pc = tmp; - size_t tmplen = sizeof (tmp) - 3; - - *pc++ = '"'; - for (; *value && tmplen > 1; value++) - { - if (*value == '\\' || *value == '"') - { - *pc++ = '\\'; - tmplen--; - } - *pc++ = *value; - tmplen--; - } - *pc++ = '"'; - *pc = 0; - strfcpy (buf, tmp, buflen); - } - else - strfcpy (buf, value, buflen); -} - -void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr) -{ - size_t len; - char *pbuf = buf; - char *pc; - - if (!addr) - return; - - buflen--; /* save room for the terminal nul */ - -#ifdef EXACT_ADDRESS - if (addr->val) - { - if (!buflen) - goto done; - strfcpy (pbuf, addr->val, buflen); - len = strlen (pbuf); - pbuf += len; - buflen -= len; - if (addr->group) - { - if (!buflen) - goto done; - *pbuf++ = ':'; - buflen--; - *pbuf = 0; - } - return; - } -#endif - - if (addr->personal) - { - if (strpbrk (addr->personal, RFC822Specials)) - { - if (!buflen) - goto done; - *pbuf++ = '"'; - buflen--; - for (pc = addr->personal; *pc && buflen > 0; pc++) - { - if (*pc == '"' || *pc == '\\') - { - if (!buflen) - goto done; - *pbuf++ = '\\'; - buflen--; - } - if (!buflen) - goto done; - *pbuf++ = *pc; - buflen--; - } - if (!buflen) - goto done; - *pbuf++ = '"'; - buflen--; - } - else - { - if (!buflen) - goto done; - strfcpy (pbuf, addr->personal, buflen); - len = strlen (pbuf); - pbuf += len; - buflen -= len; - } - - if (!buflen) - goto done; - *pbuf++ = ' '; - buflen--; - } - - if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) - { - if (!buflen) - goto done; - *pbuf++ = '<'; - buflen--; - } - - if (addr->mailbox) - { - if (!buflen) - goto done; - strfcpy (pbuf, addr->mailbox, buflen); - len = strlen (pbuf); - pbuf += len; - buflen -= len; - - if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) - { - if (!buflen) - goto done; - *pbuf++ = '>'; - buflen--; - } - - if (addr->group) - { - if (!buflen) - goto done; - *pbuf++ = ':'; - buflen--; - if (!buflen) - goto done; - *pbuf++ = ' '; - buflen--; - } - } - else - { - if (!buflen) - goto done; - *pbuf++ = ';'; - buflen--; - } -done: - /* no need to check for length here since we already save space at the - beginning of this routine */ - *pbuf = 0; -} - -/* note: it is assumed that `buf' is nul terminated! */ -void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr) -{ - char *pbuf = buf; - size_t len = strlen (buf); - - buflen--; /* save room for the terminal nul */ - - if (len > 0) - { - if (len > buflen) - return; /* safety check for bogus arguments */ - - pbuf += len; - buflen -= len; - if (!buflen) - goto done; - *pbuf++ = ','; - buflen--; - if (!buflen) - goto done; - *pbuf++ = ' '; - buflen--; - } - - for (; addr && buflen > 0; addr = addr->next) - { - /* use buflen+1 here because we already saved space for the trailing - nul char, and the subroutine can make use of it */ - rfc822_write_address_single (pbuf, buflen + 1, addr); - - /* this should be safe since we always have at least 1 char passed into - the above call, which means `pbuf' should always be nul terminated */ - len = strlen (pbuf); - pbuf += len; - buflen -= len; - - /* if there is another address, and its not a group mailbox name or - group terminator, add a comma to separate the addresses */ - if (addr->next && addr->next->mailbox && !addr->group) - { - if (!buflen) - goto done; - *pbuf++ = ','; - buflen--; - if (!buflen) - goto done; - *pbuf++ = ' '; - buflen--; - } - } -done: - *pbuf = 0; -} - -/* this should be rfc822_cpy_adr */ -ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr) -{ - ADDRESS *p = rfc822_new_address (); - -#ifdef EXACT_ADDRESS - p->val = safe_strdup (addr->val); -#endif - p->personal = safe_strdup (addr->personal); - p->mailbox = safe_strdup (addr->mailbox); - p->group = addr->group; - return p; -} - -/* this should be rfc822_cpy_adrlist */ -ADDRESS *rfc822_cpy_adr (ADDRESS *addr) -{ - ADDRESS *top = NULL, *last = NULL; - - for (; addr; addr = addr->next) - { - if (last) - { - last->next = rfc822_cpy_adr_real (addr); - last = last->next; - } - else - top = last = rfc822_cpy_adr_real (addr); - } - return top; -} - -/* append list 'b' to list 'a' and return the last element in the new list */ -ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b) -{ - ADDRESS *tmp = *a; - - while (tmp && tmp->next) - tmp = tmp->next; - if (!b) - return tmp; - if (tmp) - tmp->next = rfc822_cpy_adr (b); - else - tmp = *a = rfc822_cpy_adr (b); - while (tmp && tmp->next) - tmp = tmp->next; - return tmp; -} - -#ifdef TESTING -void safe_free (void *ptr) -{ - void **p = (void **)ptr; - if (*p) - { - free (*p); - *p = 0; - } -} - -int main (int argc, char **argv) -{ - ADDRESS *list; - char buf[256]; - char *str = "michael, Michael Elkins <me@cs.hmc.edu>, testing a really complex address: this example <@contains.a.source.route@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)"; - - list = rfc822_parse_adrlist (NULL, str); - buf[0] = 0; - rfc822_write_address (buf, sizeof (buf), list); - rfc822_free_address (&list); - puts (buf); - exit (0); -} -#endif diff --git a/src/rfc822_mairix.c b/src/rfc822_mairix.c @@ -0,0 +1,1536 @@ +/* + mairix - message index builder and finder for maildir folders. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 2002,2003,2004,2005,2006,2007,2010 + * rfc2047 decode: + * Copyright (C) Mikael Ylikoski 2002 + * gzip mbox support: + * Copyright (C) Ico Doornekamp 2005 + * Copyright (C) Felipe Gustavo de Almeida 2005 + * bzip2 mbox support: + * Copyright (C) Paramjit Oberoi 2005 + * caching uncompressed mbox data: + * Copyright (C) Chris Mason 2006 + * memory leak fixes: + * Copyright (C) Samuel Tardieu 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + ********************************************************************** + */ + +#include "mairix.h" +#include "nvp.h" + +#include <assert.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#ifdef USE_GZIP_MBOX +# include <zlib.h> +#endif +#ifdef USE_BZIP_MBOX +# include <bzlib.h> +#endif + +struct DLL {/*{{{*/ + struct DLL *next; + struct DLL *prev; +}; +/*}}}*/ +static void enqueue(void *head, void *x)/*{{{*/ +{ + /* Declare this way so it can be used with any kind of double linked list + * having next & prev pointers in its first two words. */ + struct DLL *h = (struct DLL *) head; + struct DLL *xx = (struct DLL *) x; + xx->next = h; + xx->prev = h->prev; + h->prev->next = xx; + h->prev = xx; + return; +} +/*}}}*/ + +enum encoding_type {/*{{{*/ + ENC_UNKNOWN, + ENC_NONE, + ENC_BINARY, + ENC_7BIT, + ENC_8BIT, + ENC_QUOTED_PRINTABLE, + ENC_BASE64, + ENC_UUENCODE +}; +/*}}}*/ +struct content_type_header {/*{{{*/ + const char *major; /* e.g. text */ + const char *minor; /* e.g. plain */ + const char *boundary; /* for multipart */ + /* charset? */ +}; +/*}}}*/ +struct line {/*{{{*/ + struct line *next; + struct line *prev; + char *text; +}; +/*}}}*/ + +static void init_headers(struct headers *hdrs)/*{{{*/ +{ + hdrs->to = NULL; + hdrs->cc = NULL; + hdrs->from = NULL; + hdrs->subject = NULL; + hdrs->message_id = NULL; + hdrs->in_reply_to = NULL; + hdrs->references = NULL; + hdrs->date = 0; + hdrs->flags.seen = 0; + hdrs->flags.replied = 0; + hdrs->flags.flagged = 0; +}; +/*}}}*/ +static void splice_header_lines(struct line *header)/*{{{*/ +{ + /* Deal with newline then tab in header */ + struct line *x, *next; + for (x=header->next; x!=header; x=next) { +#if 0 + printf("next header, x->text=%08lx\n", x->text); + printf("header=<%s>\n", x->text); +#endif + next = x->next; + if (isspace(x->text[0] & 0xff)) { + /* Glue to previous line */ + char *p, *newbuf, *oldbuf; + struct line *y; + for (p=x->text; *p; p++) { + if (!isspace(*(unsigned char *)p)) break; + } + p--; /* point to final space */ + y = x->prev; +#if 0 + printf("y=%08lx p=%08lx\n", y->text, p); +#endif + newbuf = new_array(char, strlen(y->text) + strlen(p) + 1); + strcpy(newbuf, y->text); + strcat(newbuf, p); + oldbuf = y->text; + y->text = newbuf; + free(oldbuf); + y->next = x->next; + x->next->prev = y; + free(x->text); + free(x); + } + } + return; +} +/*}}}*/ +static int audit_header(struct line *header)/*{{{*/ +{ + /* Check for obvious broken-ness + * 1st line has no leading spaces, single word then colon + * following lines have leading spaces or single word followed by colon + * */ + struct line *x; + int first=1; + int count=1; + for (x=header->next; x!=header; x=x->next) { + int has_leading_space=0; + int is_blank; + int has_word_colon=0; + + if (1 || first) { + /* Ignore any UUCP or mbox style From line at the start */ + if (!strncmp("From ", x->text, 5)) { + continue; + } + /* Ignore escaped From line at the start */ + if (!strncmp(">From ", x->text, 6)) { + continue; + } + } + + is_blank = !(x->text[0]); + if (!is_blank) { + char *p; + int saw_char = 0; + has_leading_space = isspace(x->text[0] & 0xff); + has_word_colon = 0; /* default */ + p = x->text; + while(*p) { + if(*p == ':') { + has_word_colon = saw_char; + break; + } else if (isspace(*(unsigned char *) p)) { + has_word_colon = 0; + break; + } else { + saw_char = 1; + } + p++; + } + } + + if (( first && (is_blank || has_leading_space || !has_word_colon)) || + (!first && (is_blank || !(has_leading_space || has_word_colon)))) { +#if 0 + fprintf(stderr, "Header line %d <%s> fails because:", count, x->text); + if (first && is_blank) { fprintf(stderr, " [first && is_blank]"); } + if (first && has_leading_space) { fprintf(stderr, " [first && has_leading_space]"); } + if (first && !has_word_colon) { fprintf(stderr, " [first && !has_word_colon]"); } + if (!first && is_blank) { fprintf(stderr, " [!first && is_blank]"); } + if (!first && !(has_leading_space||has_word_colon)) { fprintf(stderr, " [!first && !has_leading_space||has_word_colon]"); } + fprintf(stderr, "\n"); +#endif + /* Header fails the audit */ + return 0; + } + first = 0; + count++; + } + /* If we get here the header must have been OK */ + return 1; +}/*}}}*/ +static int match_string(const char *ref, const char *candidate)/*{{{*/ +{ + int len = strlen(ref); + return !strncasecmp(ref, candidate, len); +} +/*}}}*/ + +static char equal_table[] = {/*{{{*/ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00-0f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10-1f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20-2f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 30-3f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40-4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50-5f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60-6f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70-7f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80-8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90-9f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a0-af */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b0-bf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c0-cf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d0-df */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e0-ef */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* f0-ff */ +}; +/*}}}*/ +static int base64_table[] = {/*{{{*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2f */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, /* 30-3f */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4f */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5f */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6f */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0-af */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0-df */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* f0-ff */ +}; +/*}}}*/ +static int hex_to_val(char x) {/*{{{*/ + switch (x) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return (x - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return 10 + (x - 'a'); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return 10 + (x - 'A'); + break; + default: + return 0; + } +} +/*}}}*/ +static void decode_header_value(char *text){/*{{{*/ + /* rfc2047 decode, written by Mikael Ylikoski */ + + char *s, *a, *b, *e, *p, *q; + + for (p = q = s = text; (s = strstr(s, "=?")); s = e + 2) { + if (p == q) + p = q = s; + else + while (q != s) + *p++ = *q++; + s += 2; + a = strchr(s, '?'); + if (!a) break; + a++; + b = strchr(a, '?'); + if (!b) break; + b++; + e = strstr(b, "?="); + if (!e) break; + /* have found an encoded-word */ + if (b - a != 2) + continue; /* unknown encoding */ + if (*a == 'q' || *a == 'Q') { + int val; + q = b; + while (q < e) { + if (*q == '_') { + *p++ = 0x20; + q++; + } else if (*q == '=') { + q++; + val = hex_to_val(*q++) << 4; + val += hex_to_val(*q++); + *p++ = val; + } else + *p++ = *q++; + } + } else if (*a == 'b' || *a == 'B') { + int reg, nc, eq; /* register, #characters in reg, #equals */ + int dc; /* decoded character */ + eq = reg = nc = 0; + for (q = b; q < e; q++) { + unsigned char cq = *(unsigned char *)q; + dc = base64_table[cq]; + eq += equal_table[cq]; + + if (dc >= 0) { + reg <<= 6; + reg += dc; + nc++; + if (nc == 4) { + *p++ = ((reg >> 16) & 0xff); + if (eq < 2) *p++ = ((reg >> 8) & 0xff); + if (eq < 1) *p++ = reg & 0xff; + nc = reg = 0; + if (eq) break; + } + } + } + } else { + continue; /* unknown encoding */ + } + q = e + 2; + } + if (p == q) return; + while (*q != '\0') + *p++ = *q++; + *p = '\0'; +} +/*}}}*/ +static char *copy_header_value(char *text){/*{{{*/ + char *p; + for (p = text; *p && (*p != ':'); p++) ; + if (!*p) return NULL; + p++; + p = new_string(p); + decode_header_value(p); + return p; +} +/*}}}*/ +static void copy_or_concat_header_value(char **previous, char *text){/*{{{*/ + char *p = copy_header_value(text); + if (*previous) + { + *previous = extend_string(*previous, ", "); + *previous = extend_string(*previous, p); + free(p); + } + else + *previous = p; +} +/*}}}*/ +static enum encoding_type decode_encoding_type(const char *e)/*{{{*/ +{ + enum encoding_type result; + const char *p; + if (!e) { + result = ENC_NONE; + } else { + for (p=e; *p && isspace(*(unsigned char *)p); p++) ; + if ( match_string("7bit", p) + || match_string("7-bit", p) + || match_string("7 bit", p)) { + result = ENC_7BIT; + } else if (match_string("8bit", p) + || match_string("8-bit", p) + || match_string("8 bit", p)) { + result = ENC_8BIT; + } else if (match_string("quoted-printable", p)) { + result = ENC_QUOTED_PRINTABLE; + } else if (match_string("base64", p)) { + result = ENC_BASE64; + } else if (match_string("binary", p)) { + result = ENC_BINARY; + } else if (match_string("x-uuencode", p)) { + result = ENC_UUENCODE; + } else { + fprintf(stderr, "Warning: unknown encoding type: '%s'\n", e); + result = ENC_UNKNOWN; + } + } + return result; +} +/*}}}*/ +static void parse_content_type(struct nvp *ct_nvp, struct content_type_header *result)/*{{{*/ +{ + result->major = NULL; + result->minor = NULL; + result->boundary = NULL; + + result->major = nvp_major(ct_nvp); + if (result->major) { + result->minor = nvp_minor(ct_nvp); + } else { + result->minor = NULL; + result->major = nvp_first(ct_nvp); + } + + result->boundary = nvp_lookupcase(ct_nvp, "boundary"); +} + +/*}}}*/ +static char *looking_at_ws_then_newline(char *start)/*{{{*/ +{ + char *result; + result = start; + do { + if (*result == '\n') return result; + else if (!isspace(*(unsigned char *) result)) return NULL; + else result++; + } while (1); + + /* Can't get here */ + assert(0); +} +/*}}}*/ + +static char *unencode_data(struct msg_src *src, char *input, int input_len, const char *enc, int *output_len)/*{{{*/ +{ + enum encoding_type encoding; + char *result, *end_result; + char *end_input; + + encoding = decode_encoding_type(enc); + end_input = input + input_len; + + /* All mime encodings result in expanded data, so this is guaranteed to + * safely oversize the output array */ + result = new_array(char, input_len + 1); + + /* Now decode */ + switch (encoding) { + case ENC_7BIT:/*{{{*/ + case ENC_8BIT: + case ENC_BINARY: + case ENC_NONE: + { + memcpy(result, input, input_len); + end_result = result + input_len; + } + break; +/*}}}*/ + case ENC_QUOTED_PRINTABLE:/*{{{*/ + { + char *p, *q; + p = result; + for (p=result, q=input; + q<end_input; ) { + + if (*q == '=') { + /* followed by optional whitespace then \n? discard them. */ + char *r; + int val; + q++; + r = looking_at_ws_then_newline(q); + if (r) { + q = r + 1; /* Point into next line */ + continue; + } + /* not that case. */ + val = hex_to_val(*q++) << 4; + val += hex_to_val(*q++); + *p++ = val; + + } else { + /* Normal character */ + *p++ = *q++; + } + } + end_result = p; + } + break; +/*}}}*/ + case ENC_BASE64:/*{{{*/ + { + char *p, *q; + int reg, nc, eq; /* register, #characters in reg, #equals */ + int dc; /* decoded character */ + eq = reg = nc = 0; + for (q=input, p=result; q<end_input; q++) { + unsigned char cq = * (unsigned char *)q; + /* Might want a 256 entry array instead of this sub-optimal mess + * eventually. */ + dc = base64_table[cq]; + eq += equal_table[cq]; + + if (dc >= 0) { + reg <<= 6; + reg += dc; + nc++; + if (nc == 4) { + *p++ = ((reg >> 16) & 0xff); + if (eq < 2) *p++ = ((reg >> 8) & 0xff); + if (eq < 1) *p++ = reg & 0xff; + nc = reg = 0; + if (eq) goto done_base_64; + } + } + } + done_base_64: + end_result = p; + } + break; + /*}}}*/ + case ENC_UUENCODE:/*{{{*/ + { + char *p, *q; + /* Find 'begin ' */ + for (q = input; q < end_input - 6 && memcmp(q, "begin ", 6); q++) + ; + q += 6; + /* skip to EOL */ + while (q < end_input && *q != '\n') + q++; + p = result; + while (q < end_input) { /* process line */ +#define DEC(c) (((c) - ' ') & 077) + int len = DEC(*q++); + if (len == 0) + break; + for (; len > 0; q += 4, len -= 3) { + if (len >= 3) { + *p++ = DEC(q[0]) << 2 | DEC(q[1]) >> 4; + *p++ = DEC(q[1]) << 4 | DEC(q[2]) >> 2; + *p++ = DEC(q[2]) << 6 | DEC(q[3]); + } else { + if (len >= 1) + *p++ = DEC(q[0]) << 2 | DEC(q[1]) >> 4; + if (len >= 2) + *p++ = DEC(q[1]) << 4 | DEC(q[2]) >> 2; + } + } + while (q < end_input && *q != '\n') + q++; + } + end_result = p; + } + break; + /*}}}*/ + case ENC_UNKNOWN:/*{{{*/ + fprintf(stderr, "Unknown encoding type in %s\n", format_msg_src(src)); + /* fall through - ignore this data */ + /*}}}*/ + default:/*{{{*/ + end_result = result; + break; + /*}}}*/ + } + *output_len = end_result - result; + result[*output_len] = '\0'; /* for convenience with text/plain etc to make it printable */ + return result; +} +/*}}}*/ +char *format_msg_src(struct msg_src *src)/*{{{*/ +{ + static char *buffer = NULL; + static int buffer_len = 0; + char *result; + int len; + switch (src->type) { + case MS_FILE: + result = src->filename; + break; + case MS_MBOX: + len = strlen(src->filename); + len += 32; + if (!buffer || (len > buffer_len)) { + free(buffer); + buffer = new_array(char, len); + buffer_len = len; + } + sprintf(buffer, "%s[%d,%d)", src->filename, + (int) src->start, (int) (src->start + src->len)); + result = buffer; + break; + default: + result = NULL; + break; + } + return result; +} +/*}}}*/ +static int split_and_splice_header(struct msg_src *src, char *data, struct line *header, char **body_start)/*{{{*/ +{ + char *sol, *eol; + int blank_line; + header->next = header->prev = header; + sol = data; + do { + if (!*sol) break; + blank_line = 1; /* until proven otherwise */ + eol = sol; + while (*eol && (*eol != '\n')) { + if (!isspace(*(unsigned char *) eol)) blank_line = 0; + eol++; + } + if (*eol == '\n') { + if (!blank_line) { + int line_length = eol - sol; + char *line_text = new_array(char, 1 + line_length); + struct line *new_header; + + strncpy(line_text, sol, line_length); + line_text[line_length] = '\0'; + new_header = new(struct line); + new_header->text = line_text; + enqueue(header, new_header); + } + sol = eol + 1; /* Start of next line */ + } else { /* must be null char */ + fprintf(stderr, "Got null character whilst processing header of %s\n", + format_msg_src(src)); + return -1; /* & leak memory */ + } + } while (!blank_line); + + *body_start = sol; + + if (audit_header(header)) { + splice_header_lines(header); + return 0; + } else { +#if 0 + /* Caller generates message */ + fprintf(stderr, "Message had bad rfc822 headers, ignoring\n"); +#endif + return -1; + } +} +/*}}}*/ + +/* Forward prototypes */ +static void do_multipart(struct msg_src *src, char *input, int input_len, + const char *boundary, struct attachment *atts, + enum data_to_rfc822_error *error); + +/*{{{ do_body() */ +static void do_body(struct msg_src *src, + char *body_start, int body_len, + struct nvp *ct_nvp, struct nvp *cte_nvp, + struct nvp *cd_nvp, + struct attachment *atts, + enum data_to_rfc822_error *error) +{ + char *decoded_body; + int decoded_body_len; + const char *content_transfer_encoding; + content_transfer_encoding = NULL; + if (cte_nvp) { + content_transfer_encoding = nvp_first(cte_nvp); + if (!content_transfer_encoding) { + fprintf(stderr, "Giving up on %s, content_transfer_encoding header not parseable\n", + format_msg_src(src)); + return; + } + } + + decoded_body = unencode_data(src, body_start, body_len, content_transfer_encoding, &decoded_body_len); + + if (ct_nvp) { + struct content_type_header ct; + parse_content_type(ct_nvp, &ct); + if (ct.major && !strcasecmp(ct.major, "multipart")) { + do_multipart(src, decoded_body, decoded_body_len, ct.boundary, atts, error); + /* Don't need decoded body any longer - copies have been taken if + * required when handling multipart attachments. */ + free(decoded_body); + if (error && (*error == DTR8_MISSING_END)) return; + } else { + /* unipart */ + struct attachment *new_att; + const char *disposition; + new_att = new(struct attachment); + disposition = cd_nvp ? nvp_first(cd_nvp) : NULL; + if (disposition && !strcasecmp(disposition, "attachment")) { + const char *lookup; + lookup = nvp_lookupcase(cd_nvp, "filename"); + if (lookup) { + new_att->filename = new_string(lookup); + } else { + /* Some messages have name=... in content-type: instead of + * filename=... in content-disposition. */ + lookup = nvp_lookup(ct_nvp, "name"); + if (lookup) { + new_att->filename = new_string(lookup); + } else { + new_att->filename = NULL; + } + } + } else { + new_att->filename = NULL; + } + if (ct.major && !strcasecmp(ct.major, "text")) { + if (ct.minor && !strcasecmp(ct.minor, "plain")) { + new_att->ct = CT_TEXT_PLAIN; + } else if (ct.minor && !strcasecmp(ct.minor, "html")) { + new_att->ct = CT_TEXT_HTML; + } else { + new_att->ct = CT_TEXT_OTHER; + } + } else if (ct.major && !strcasecmp(ct.major, "message") && + ct.minor && !strcasecmp(ct.minor, "rfc822")) { + new_att->ct = CT_MESSAGE_RFC822; + } else { + new_att->ct = CT_OTHER; + } + + if (new_att->ct == CT_MESSAGE_RFC822) { + new_att->data.rfc822 = data_to_rfc822(src, decoded_body, decoded_body_len, error); + free(decoded_body); /* data no longer needed */ + } else { + new_att->data.normal.len = decoded_body_len; + new_att->data.normal.bytes = decoded_body; + } + enqueue(atts, new_att); + } + } else { + /* Treat as text/plain {{{*/ + struct attachment *new_att; + new_att = new(struct attachment); + new_att->filename = NULL; + new_att->ct = CT_TEXT_PLAIN; + new_att->data.normal.len = decoded_body_len; + /* Add null termination on the end */ + new_att->data.normal.bytes = new_array(char, decoded_body_len + 1); + memcpy(new_att->data.normal.bytes, decoded_body, decoded_body_len + 1); + free(decoded_body); + enqueue(atts, new_att);/*}}}*/ + } +} +/*}}}*/ +/*{{{ do_attachment() */ +static void do_attachment(struct msg_src *src, + char *start, char *after_end, + struct attachment *atts) +{ + /* decode attachment and add to attachment list */ + struct line header, *x, *nx; + char *body_start; + int body_len; + + struct nvp *ct_nvp, *cte_nvp, *cd_nvp, *nvp; + + if (split_and_splice_header(src, start, &header, &body_start) < 0) { + fprintf(stderr, "Giving up on attachment with bad header in %s\n", + format_msg_src(src)); + return; + } + + /* Extract key headers */ + ct_nvp = cte_nvp = cd_nvp = NULL; + for (x=header.next; x!=&header; x=x->next) { + if ((nvp = make_nvp(src, x->text, "content-type:"))) { + ct_nvp = nvp; + } else if ((nvp = make_nvp(src, x->text, "content-transfer-encoding:"))) { + cte_nvp = nvp; + } else if ((nvp = make_nvp(src, x->text, "content-disposition:"))) { + cd_nvp = nvp; + } + } + +#if 0 + if (ct_nvp) { + fprintf(stderr, "======\n"); + fprintf(stderr, "Dump of content-type hdr\n"); + nvp_dump(ct_nvp, stderr); + free(ct_nvp); + } + + if (cte_nvp) { + fprintf(stderr, "======\n"); + fprintf(stderr, "Dump of content-transfer-encoding hdr\n"); + nvp_dump(cte_nvp, stderr); + free(cte_nvp); + } +#endif + + if (body_start > after_end) { + /* This is a (maliciously?) b0rken attachment, e.g. maybe empty */ + if (verbose) { + fprintf(stderr, "Message %s contains an invalid attachment, length=%d bytes\n", + format_msg_src(src), (int)(after_end - start)); + } + } else { + body_len = after_end - body_start; + /* Ignore errors in nested body parts. */ + do_body(src, body_start, body_len, ct_nvp, cte_nvp, cd_nvp, atts, NULL); + } + + /* Free header memory */ + for (x=header.next; x!=&header; x=nx) { + nx = x->next; + free(x->text); + free(x); + } + + if (ct_nvp) free_nvp(ct_nvp); + if (cte_nvp) free_nvp(cte_nvp); + if (cd_nvp) free_nvp(cd_nvp); +} +/*}}}*/ +/*{{{ do_multipart() */ +static void do_multipart(struct msg_src *src, + char *input, int input_len, + const char *boundary, + struct attachment *atts, + enum data_to_rfc822_error *error) +{ + char *b0, *b1, *be, *bx; + char *line_after_b0, *start_b1_search_from; + int boundary_len; + int looking_at_end_boundary; + + if (!boundary) { + fprintf(stderr, "Can't process multipart message %s with no boundary string\n", + format_msg_src(src)); + if (error) *error = DTR8_MULTIPART_SANS_BOUNDARY; + return; + } + + boundary_len = strlen(boundary); + + b0 = NULL; + line_after_b0 = input; + be = input + input_len; + + do { + int boundary_ok; + start_b1_search_from = line_after_b0; + do { + /* reject boundaries that aren't a whole line */ + b1 = NULL; + for (bx = start_b1_search_from; bx < be - (boundary_len + 4); bx++) { + if (bx[0] == '-' && bx[1] == '-' && + !strncmp(bx+2, boundary, boundary_len)) { + b1 = bx; + break; + } + } + if (!b1) { + if (error) + *error = DTR8_MISSING_END; + return; + } + + looking_at_end_boundary = (b1[boundary_len+2] == '-' && + b1[boundary_len+3] == '-'); + boundary_ok = 1; + if ((b1 > input) && (*(b1-1) != '\n')) + boundary_ok = 0; + if (!looking_at_end_boundary && (b1 + boundary_len + 2 < input + input_len) && (*(b1 + boundary_len + 2) != '\n')) + boundary_ok = 0; + if (!boundary_ok) { + char *eol = strchr(b1, '\n'); + if (!eol) { + fprintf(stderr, "Oops, didn't find another normal boundary in %s\n", + format_msg_src(src)); + return; + } + start_b1_search_from = 1 + eol; + } + } while (!boundary_ok); + + /* b1 is now looking at a good boundary, which might be the final one */ + + if (b0) { + /* don't treat preamble as an attachment */ + do_attachment(src, line_after_b0, b1, atts); + } + + b0 = b1; + line_after_b0 = strchr(b0, '\n'); + if (line_after_b0 == 0) + line_after_b0 = b0 + strlen(b0); + else + ++line_after_b0; + } while (b1 < be && !looking_at_end_boundary); +} +/*}}}*/ +static time_t parse_rfc822_date(char *date_string)/*{{{*/ +{ + struct tm tm; + char *s, *z; + /* Format [weekday ,] day-of-month month year hour:minute:second timezone. + + Some of the ideas, sanity checks etc taken from parse.c in the mutt + sources, credit to Michael R. Elkins et al + */ + + s = date_string; + z = strchr(s, ','); + if (z) s = z + 1; + while (*s && isspace(*s)) s++; + /* Should now be looking at day number */ + if (!isdigit(*s)) goto tough_cheese; + tm.tm_mday = atoi(s); + if (tm.tm_mday > 31) goto tough_cheese; + + while (isdigit(*s)) s++; + while (*s && isspace(*s)) s++; + if (!*s) goto tough_cheese; + if (!strncasecmp(s, "jan", 3)) tm.tm_mon = 0; + else if (!strncasecmp(s, "feb", 3)) tm.tm_mon = 1; + else if (!strncasecmp(s, "mar", 3)) tm.tm_mon = 2; + else if (!strncasecmp(s, "apr", 3)) tm.tm_mon = 3; + else if (!strncasecmp(s, "may", 3)) tm.tm_mon = 4; + else if (!strncasecmp(s, "jun", 3)) tm.tm_mon = 5; + else if (!strncasecmp(s, "jul", 3)) tm.tm_mon = 6; + else if (!strncasecmp(s, "aug", 3)) tm.tm_mon = 7; + else if (!strncasecmp(s, "sep", 3)) tm.tm_mon = 8; + else if (!strncasecmp(s, "oct", 3)) tm.tm_mon = 9; + else if (!strncasecmp(s, "nov", 3)) tm.tm_mon = 10; + else if (!strncasecmp(s, "dec", 3)) tm.tm_mon = 11; + else goto tough_cheese; + + while (!isspace(*s)) s++; + while (*s && isspace(*s)) s++; + if (!isdigit(*s)) goto tough_cheese; + tm.tm_year = atoi(s); + if (tm.tm_year < 70) { + tm.tm_year += 100; + } else if (tm.tm_year >= 1900) { + tm.tm_year -= 1900; + } + + while (isdigit(*s)) s++; + while (*s && isspace(*s)) s++; + if (!*s) goto tough_cheese; + + /* Now looking at hms */ + /* For now, forget this. The searching will be vague enough that nearest day is good enough. */ + + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + tm.tm_isdst = 0; + return mktime(&tm); + +tough_cheese: + return (time_t) -1; /* default value */ +} +/*}}}*/ + +static void scan_status_flags(const char *s, struct headers *hdrs)/*{{{*/ +{ + const char *p; + for (p=s; *p; p++) { + switch (*p) { + case 'R': hdrs->flags.seen = 1; break; + case 'A': hdrs->flags.replied = 1; break; + case 'F': hdrs->flags.flagged = 1; break; + default: break; + } + } +} +/*}}}*/ + +/*{{{ data_to_rfc822() */ +struct rfc822 *data_to_rfc822(struct msg_src *src, + char *data, int length, + enum data_to_rfc822_error *error) +{ + struct rfc822 *result; + char *body_start; + struct line header; + struct line *x, *nx; + struct nvp *ct_nvp, *cte_nvp, *cd_nvp, *nvp; + int body_len; + + if (error) *error = DTR8_OK; /* default */ + result = new(struct rfc822); + init_headers(&result->hdrs); + result->atts.next = result->atts.prev = &result->atts; + + if (split_and_splice_header(src, data, &header, &body_start) < 0) { + if (verbose) { + fprintf(stderr, "Giving up on message %s with bad header\n", + format_msg_src(src)); + } + if (error) *error = DTR8_BAD_HEADERS; + return NULL; + } + + /* Extract key headers {{{*/ + ct_nvp = cte_nvp = cd_nvp = NULL; + for (x=header.next; x!=&header; x=x->next) { + if (match_string("to", x->text)) + copy_or_concat_header_value(&result->hdrs.to, x->text); + else if (match_string("cc", x->text)) + copy_or_concat_header_value(&result->hdrs.cc, x->text); + else if (!result->hdrs.from && match_string("from", x->text)) + result->hdrs.from = copy_header_value(x->text); + else if (!result->hdrs.subject && match_string("subject", x->text)) + result->hdrs.subject = copy_header_value(x->text); + else if (!ct_nvp && (nvp = make_nvp(src, x->text, "content-type:"))) + ct_nvp = nvp; + else if (!cte_nvp && (nvp = make_nvp(src, x->text, "content-transfer-encoding:"))) + cte_nvp = nvp; + else if (!cd_nvp && (nvp = make_nvp(src, x->text, "content-disposition:"))) + cd_nvp = nvp; + else if (!result->hdrs.date && match_string("date", x->text)) { + char *date_string = copy_header_value(x->text); + result->hdrs.date = parse_rfc822_date(date_string); + free(date_string); + } else if (!result->hdrs.message_id && match_string("message-id", x->text)) + result->hdrs.message_id = copy_header_value(x->text); + else if (!result->hdrs.in_reply_to && match_string("in-reply-to", x->text)) + result->hdrs.in_reply_to = copy_header_value(x->text); + else if (!result->hdrs.references && match_string("references", x->text)) + result->hdrs.references = copy_header_value(x->text); + else if (match_string("status", x->text)) + scan_status_flags(x->text + sizeof("status:"), &result->hdrs); + else if (match_string("x-status", x->text)) + scan_status_flags(x->text + sizeof("x-status:"), &result->hdrs); + } +/*}}}*/ + + /* Process body */ + body_len = length - (body_start - data); + do_body(src, body_start, body_len, ct_nvp, cte_nvp, cd_nvp, &result->atts, error); + + /* Free header memory */ + for (x=header.next; x!=&header; x=nx) { + nx = x->next; + free(x->text); + free(x); + } + + if (ct_nvp) free_nvp(ct_nvp); + if (cte_nvp) free_nvp(cte_nvp); + if (cd_nvp) free_nvp(cd_nvp); + + return result; + +} +/*}}}*/ + +#define ALLOC_NONE 1 +#define ALLOC_MMAP 2 +#define ALLOC_MALLOC 3 + +int data_alloc_type; + +#if USE_GZIP_MBOX || USE_BZIP_MBOX + +#define SIZE_STEP (8 * 1024 * 1024) + +#define COMPRESSION_NONE 0 +#define COMPRESSION_GZIP 1 +#define COMPRESSION_BZIP 2 + +static int get_compression_type(const char *filename) {/*{{{*/ + size_t len = strlen(filename); + int ptr; + +#ifdef USE_GZIP_MBOX + ptr = len - 3; + if (len > 3 && strncasecmp(filename + ptr, ".gz", 3) == 0) { + return COMPRESSION_GZIP; + } +#endif + +#ifdef USE_BZIP_MBOX + ptr = len - 4; + if (len > 3 && strncasecmp(filename + ptr, ".bz2", 4) == 0) { + return COMPRESSION_BZIP; + } +#endif + + return COMPRESSION_NONE; +} +/*}}}*/ + +static int is_compressed(const char *filename) {/*{{{*/ + return (get_compression_type(filename) != COMPRESSION_NONE); +} +/*}}}*/ + +struct zFile {/*{{{*/ + union { + /* Both gzFile and BZFILE* are defined as void pointers + * in their respective header files. + */ +#ifdef USE_GZIP_MBOX + gzFile gzf; +#endif +#ifdef USE_BZIP_MBOX + BZFILE *bzf; +#endif + void *zptr; + } foo; + int type; +}; +/*}}}*/ + +static struct zFile * xx_zopen(const char *filename, const char *mode) {/*{{{*/ + struct zFile *zf = new(struct zFile); + + zf->type = get_compression_type(filename); + switch (zf->type) { +#ifdef USE_GZIP_MBOX + case COMPRESSION_GZIP: + zf->foo.gzf = gzopen(filename, "rb"); + break; +#endif +#ifdef USE_BZIP_MBOX + case COMPRESSION_BZIP: + zf->foo.bzf = BZ2_bzopen(filename, "rb"); + break; +#endif + default: + zf->foo.zptr = NULL; + break; + } + + if (!zf->foo.zptr) { + free(zf); + return 0; + } + + return zf; +} +/*}}}*/ +static void xx_zclose(struct zFile *zf) {/*{{{*/ + switch (zf->type) { +#ifdef USE_GZIP_MBOX + case COMPRESSION_GZIP: + gzclose(zf->foo.gzf); + break; +#endif +#ifdef USE_BZIP_MBOX + case COMPRESSION_BZIP: + BZ2_bzclose(zf->foo.bzf); + break; +#endif + default: + zf->foo.zptr = NULL; + break; + } + free(zf); +} +/*}}}*/ +static int xx_zread(struct zFile *zf, void *buf, int len) {/*{{{*/ + switch (zf->type) { +#ifdef USE_GZIP_MBOX + case COMPRESSION_GZIP: + return gzread(zf->foo.gzf, buf, len); + break; +#endif +#ifdef USE_BZIP_MBOX + case COMPRESSION_BZIP: + return BZ2_bzread(zf->foo.bzf, buf, len); + break; +#endif + default: + return 0; + break; + } +} +/*}}}*/ +#endif + +#if USE_GZIP_MBOX || USE_BZIP_MBOX +/* do we need ROCACHE_SIZE > 1? the code supports any number here */ +#define ROCACHE_SIZE 1 +struct ro_mapping { + char *filename; + unsigned char *map; + size_t len; +}; +static int ro_cache_init = 0; +static struct ro_mapping ro_mapping_cache[ROCACHE_SIZE]; + +/* find a temp file in the mapping cache. If nothing is found lasti is + * set to the next slot to use for insertion. You have to check that slot + * to see if it is currently in use + */ +static struct ro_mapping *find_ro_cache(const char *filename, int *lasti) +{ + int i = 0; + struct ro_mapping *ro = NULL; + if (lasti) + *lasti = 0; + if (!ro_cache_init) + return NULL; + for (i = 0 ; i < ROCACHE_SIZE ; i++) { + ro = ro_mapping_cache + i; + if (!ro->map) { + if (lasti) + *lasti = i; + return NULL; + } + if (strcmp(filename, ro->filename) == 0) + return ro; + } + /* if we're here, the map is full. They will reuse slot 0 */ + return NULL; +} + +/* + * put a new tempfile into the cache. It is mmaped as part of this function + * so you can safely close the file handle after calling this. + */ +static struct ro_mapping *add_ro_cache(const char *filename, int fd, size_t len) +{ + int i = 0; + struct ro_mapping *ro = NULL; + if (!ro_cache_init) { + memset(&ro_mapping_cache, 0, sizeof(ro_mapping_cache)); + ro_cache_init = 1; + } + ro = find_ro_cache(filename, &i); + if (ro) { + fprintf(stderr, "%s already in ro cache\n", filename); + return NULL; + } + ro = ro_mapping_cache + i; + if (ro->map) { + munmap(ro->map, ro->len); + ro->map = NULL; + free(ro->filename); + } + ro->map = (unsigned char *)mmap(0, len, PROT_READ, MAP_SHARED, fd, 0); + if (ro->map == MAP_FAILED) { + ro->map = NULL; + perror("rfc822:mmap"); + return NULL; + } + ro->len = len; + ro->filename = new_string(filename); + return ro; +} +#endif /* USE_GZIP_MBOX || USE_BZIP_MBOX */ + +void create_ro_mapping(const char *filename, unsigned char **data, int *len)/*{{{*/ +{ + struct stat sb; + int fd; + +#if USE_GZIP_MBOX || USE_BZIP_MBOX + struct zFile *zf; +#endif + + if (stat(filename, &sb) < 0) + { + report_error("stat", filename); + *data = NULL; + return; + } + +#if USE_GZIP_MBOX || USE_BZIP_MBOX + if(is_compressed(filename)) { + unsigned char *p; + size_t cur_read; + struct ro_mapping *ro; + FILE *tmpf; + + /* this branch never returns things that are freeable */ + data_alloc_type = ALLOC_NONE; + ro = find_ro_cache(filename, NULL); + if (ro) { + *data = ro->map; + *len = ro->len; + return; + } + + if(verbose) { + fprintf(stderr, "Decompressing %s...\n", filename); + } + + tmpf = tmpfile(); + if (!tmpf) { + perror("tmpfile"); + goto comp_error; + } + zf = xx_zopen(filename, "rb"); + if (!zf) { + fprintf(stderr, "Could not open %s\n", filename); + goto comp_error; + } + p = new_array(unsigned char, SIZE_STEP); + cur_read = xx_zread(zf, p, SIZE_STEP); + if (fwrite(p, cur_read, 1, tmpf) != 1) { + fprintf(stderr, "failed writing to temp file for %s\n", filename); + goto comp_error; + } + *len = cur_read; + if (cur_read >= SIZE_STEP) { + while(1) { + int ret; + cur_read = xx_zread(zf, p, SIZE_STEP); + if (cur_read <= 0) + break; + *len += cur_read; + ret = fwrite(p, cur_read, 1, tmpf); + if (ret != 1) { + fprintf(stderr, "failed writing to temp file for %s\n", filename); + goto comp_error; + } + } + } + free(p); + xx_zclose(zf); + + if(*len > 0) { + ro = add_ro_cache(filename, fileno(tmpf), *len); + if (!ro) + goto comp_error; + *data = ro->map; + *len = ro->len; + } else { + *data = NULL; + } + fclose(tmpf); + return; + +comp_error: + *data = NULL; + *len = 0; + if (tmpf) + fclose(tmpf); + return; + } +#endif /* USE_GZIP_MBOX || USE_BZIP_MBOX */ + + *len = sb.st_size; + if (*len == 0) { + *data = NULL; + return; + } + + if (!S_ISREG(sb.st_mode)) { + *data = NULL; + return; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) + { + report_error("open", filename); + *data = NULL; + return; + } + + *data = (unsigned char *) mmap(0, *len, PROT_READ, MAP_SHARED, fd, 0); + if (close(fd) < 0) + report_error("close", filename); + if (*data == MAP_FAILED) { + report_error("rfc822:mmap", filename); + *data = NULL; + return; + } + data_alloc_type = ALLOC_MMAP; +} +/*}}}*/ +void free_ro_mapping(unsigned char *data, int len)/*{{{*/ +{ + int r; + + if(data_alloc_type == ALLOC_MALLOC) { + free(data); + } + + if(data_alloc_type == ALLOC_MMAP) { + r = munmap(data, len); + if(r < 0) { + fprintf(stderr, "munmap() errord\n"); + exit(1); + } + } +} +/*}}}*/ + +static struct msg_src *setup_msg_src(char *filename)/*{{{*/ +{ + static struct msg_src result; + result.type = MS_FILE; + result.filename = filename; + return &result; +} +/*}}}*/ +struct rfc822 *make_rfc822(char *filename)/*{{{*/ +{ + int len; + unsigned char *data; + struct rfc822 *result; + + create_ro_mapping(filename, &data, &len); + + /* Don't process empty files */ + result = NULL; + + if (data) + { + struct msg_src *src; + /* Now process the data */ + src = setup_msg_src(filename); + /* For one message per file, ignore missing end boundary condition. */ + result = data_to_rfc822(src, (char *) data, len, NULL); + + free_ro_mapping(data, len); + } + + return result; +} +/*}}}*/ +void free_rfc822(struct rfc822 *msg)/*{{{*/ +{ + struct attachment *a, *na; + + if (!msg) return; + + if (msg->hdrs.to) free(msg->hdrs.to); + if (msg->hdrs.cc) free(msg->hdrs.cc); + if (msg->hdrs.from) free(msg->hdrs.from); + if (msg->hdrs.subject) free(msg->hdrs.subject); + if (msg->hdrs.message_id) free(msg->hdrs.message_id); + if (msg->hdrs.in_reply_to) free(msg->hdrs.in_reply_to); + if (msg->hdrs.references) free(msg->hdrs.references); + + for (a = msg->atts.next; a != &msg->atts; a = na) { + na = a->next; + if (a->filename) free(a->filename); + if (a->ct == CT_MESSAGE_RFC822) { + free_rfc822(a->data.rfc822); + } else { + free(a->data.normal.bytes); + } + free(a); + } + free(msg); +} +/*}}}*/ + +#ifdef TEST + +static void do_indent(int indent)/*{{{*/ +{ + int i; + for (i=indent; i>0; i--) { + putchar(' '); + } +} +/*}}}*/ +static void show_header(char *tag, char *x, int indent)/*{{{*/ +{ + if (x) { + do_indent(indent); + printf("%s: %s\n", tag, x); + } +} +/*}}}*/ +static void show_rfc822(struct rfc822 *msg, int indent)/*{{{*/ +{ + struct attachment *a; + show_header("From", msg->hdrs.from, indent); + show_header("To", msg->hdrs.to, indent); + show_header("Cc", msg->hdrs.cc, indent); + show_header("Date", msg->hdrs.date, indent); + show_header("Subject", msg->hdrs.subject, indent); + + for (a = msg->atts.next; a != &msg->atts; a=a->next) { + printf("========================\n"); + switch (a->ct) { + case CT_TEXT_PLAIN: printf("Attachment type text/plain\n"); break; + case CT_TEXT_HTML: printf("Attachment type text/html\n"); break; + case CT_TEXT_OTHER: printf("Attachment type text/non-plain\n"); break; + case CT_MESSAGE_RFC822: printf("Attachment type message/rfc822\n"); break; + case CT_OTHER: printf("Attachment type other\n"); break; + } + if (a->ct != CT_MESSAGE_RFC822) { + printf("%d bytes\n", a->data.normal.len); + } + if ((a->ct == CT_TEXT_PLAIN) || (a->ct == CT_TEXT_HTML) || (a->ct == CT_TEXT_OTHER)) { + printf("----------\n"); + printf("%s\n", a->data.normal.bytes); + } + if (a->ct == CT_MESSAGE_RFC822) { + show_rfc822(a->data.rfc822, indent + 4); + } + } +} +/*}}}*/ + +int main (int argc, char **argv)/*{{{*/ +{ + struct rfc822 *msg; + + if (argc < 2) { + fprintf(stderr, "Need a path\n"); + unlock_and_exit(2); + } + + msg = make_rfc822(argv[1]); + show_rfc822(msg, 0); + free_rfc822(msg); + + /* Print out some stuff */ + + return 0; +} +/*}}}*/ +#endif /* TEST */ diff --git a/src/rfc822_mutt.c b/src/rfc822_mutt.c @@ -0,0 +1,768 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA. + */ + +/* $Id: rfc822.c,v 1.5 2005-10-29 14:48:11 roland Exp $ */ + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> + +#include "helpers.h" +#include "rfc822_mutt.h" + +const char RFC822Specials[] = "@.,:;<>[]\\\"()"; +#define is_special(x) strchr(RFC822Specials,x) + +int RFC822Error = 0; + +/* these must defined in the same order as the numerated errors given in rfc822.h */ +const char *RFC822Errors[] = { + "out of memory", + "mismatched parenthesis", + "mismatched quotes", + "bad route in <>", + "bad address in <>", + "bad address spec" +}; + +void rfc822_dequote_comment (char *s) +{ + char *w = s; + + for (; *s; s++) + { + if (*s == '\\') + { + if (!*++s) + break; /* error? */ + *w++ = *s; + } + else if (*s != '\"') + { + if (w != s) + *w = *s; + w++; + } + } + *w = 0; +} + +void rfc822_free_address (ADDRESS **p) +{ + ADDRESS *t; + + while (*p) + { + t = *p; + *p = (*p)->next; +#ifdef EXACT_ADDRESS + FREE (&t->val); +#endif + FREE (&t->personal); + FREE (&t->mailbox); + FREE (&t); + } +} + +static const char * +parse_comment (const char *s, + char *comment, size_t *commentlen, size_t commentmax) +{ + int level = 1; + + while (*s && level) + { + if (*s == '(') + level++; + else if (*s == ')') + { + if (--level == 0) + { + s++; + break; + } + } + else if (*s == '\\') + { + if (!*++s) + break; + } + if (*commentlen < commentmax) + comment[(*commentlen)++] = *s; + s++; + } + if (level) + { + RFC822Error = ERR_MISMATCH_PAREN; + return NULL; + } + return s; +} + +static const char * +parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax) +{ + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = '"'; + while (*s) + { + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + if (*s == '"') + return (s + 1); + if (*s == '\\') + { + if (!*++s) + break; + } + s++; + } + RFC822Error = ERR_MISMATCH_QUOTE; + return NULL; +} + +static const char * +next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax) +{ + if (*s == '(') + return (parse_comment (s + 1, token, tokenlen, tokenmax)); + if (*s == '"') + return (parse_quote (s + 1, token, tokenlen, tokenmax)); + if (is_special (*s)) + { + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + return (s + 1); + } + while (*s) + { + if (isspace (*s) || is_special (*s)) + break; + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = *s; + s++; + } + return s; +} + +static const char * +parse_mailboxdomain (const char *s, const char *nonspecial, + char *mailbox, size_t *mailboxlen, size_t mailboxmax, + char *comment, size_t *commentlen, size_t commentmax) +{ + const char *ps; + + while (*s) + { + SKIPWS (s); + if (strchr (nonspecial, *s) == NULL && is_special (*s)) + return s; + + if (*s == '(') + { + if (*commentlen && *commentlen < commentmax) + comment[(*commentlen)++] = ' '; + ps = next_token (s, comment, commentlen, commentmax); + } + else + ps = next_token (s, mailbox, mailboxlen, mailboxmax); + if (!ps) + return NULL; + s = ps; + } + + return s; +} + +static const char * +parse_address (const char *s, + char *token, size_t *tokenlen, size_t tokenmax, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + s = parse_mailboxdomain (s, ".\"(\\", + token, tokenlen, tokenmax, + comment, commentlen, commentmax); + if (!s) + return NULL; + + if (*s == '@') + { + if (*tokenlen < tokenmax) + token[(*tokenlen)++] = '@'; + s = parse_mailboxdomain (s + 1, ".([]\\", + token, tokenlen, tokenmax, + comment, commentlen, commentmax); + if (!s) + return NULL; + } + + token[*tokenlen] = 0; + addr->mailbox = safe_strdup (token); + + if (*commentlen && !addr->personal) + { + comment[*commentlen] = 0; + addr->personal = safe_strdup (comment); + } + + return s; +} + +static const char * +parse_route_addr (const char *s, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + char token[STRING]; + size_t tokenlen = 0; + + SKIPWS (s); + + /* find the end of the route */ + if (*s == '@') + { + while (s && *s == '@') + { + if (tokenlen < sizeof (token) - 1) + token[tokenlen++] = '@'; + s = parse_mailboxdomain (s + 1, ".\\[](", token, + &tokenlen, sizeof (token) - 1, + comment, commentlen, commentmax); + } + if (!s || *s != ':') + { + RFC822Error = ERR_BAD_ROUTE; + return NULL; /* invalid route */ + } + + if (tokenlen < sizeof (token) - 1) + token[tokenlen++] = ':'; + s++; + } + + if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL) + return NULL; + + if (*s != '>' || !addr->mailbox) + { + RFC822Error = ERR_BAD_ROUTE_ADDR; + return NULL; + } + + s++; + return s; +} + +static const char * +parse_addr_spec (const char *s, + char *comment, size_t *commentlen, size_t commentmax, + ADDRESS *addr) +{ + char token[STRING]; + size_t tokenlen = 0; + + s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr); + if (s && *s && *s != ',' && *s != ';') + { + RFC822Error = ERR_BAD_ADDR_SPEC; + return NULL; + } + return s; +} + +static void +add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase, + char *comment, size_t *commentlen, size_t commentmax) +{ + ADDRESS *cur = rfc822_new_address (); + + if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) + return; + + if (*last) + (*last)->next = cur; + else + *top = cur; + *last = cur; +} + +ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s) +{ + const char *begin, *ps; + char comment[STRING], phrase[STRING]; + size_t phraselen = 0, commentlen = 0; + ADDRESS *cur, *last = NULL; + + RFC822Error = 0; + + last = top; + while (last && last->next) + last = last->next; + + SKIPWS (s); + begin = s; + while (*s) + { + if (*s == ',') + { + if (phraselen) + { + phrase[phraselen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && last && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } + +#ifdef EXACT_ADDRESS + if (last && !last->val) + last->val = mutt_substrdup (begin, s); +#endif + commentlen = 0; + phraselen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == '(') + { + if (commentlen && commentlen < sizeof (comment) - 1) + comment[commentlen++] = ' '; + if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL) + { + rfc822_free_address (&top); + return NULL; + } + s = ps; + } + else if (*s == ':') + { + cur = rfc822_new_address (); + phrase[phraselen] = 0; + cur->mailbox = safe_strdup (phrase); + cur->group = 1; + + if (last) + last->next = cur; + else + top = cur; + last = cur; + +#ifdef EXACT_ADDRESS + last->val = mutt_substrdup (begin, s); +#endif + + phraselen = 0; + commentlen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == ';') + { + if (phraselen) + { + phrase[phraselen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } +#ifdef EXACT_ADDRESS + if (last && !last->val) + last->val = mutt_substrdup (begin, s); +#endif + + /* add group terminator */ + cur = rfc822_new_address (); + if (last) + { + last->next = cur; + last = cur; + } + + phraselen = 0; + commentlen = 0; + s++; + begin = s; + SKIPWS (begin); + } + else if (*s == '<') + { + phrase[phraselen] = 0; + cur = rfc822_new_address (); + if (phraselen) + { + if (cur->personal) + FREE (&cur->personal); + /* if we get something like "Michael R. Elkins" remove the quotes */ + rfc822_dequote_comment (phrase); + cur->personal = safe_strdup (phrase); + } + if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL) + { + rfc822_free_address (&top); + rfc822_free_address (&cur); + return NULL; + } + + if (last) + last->next = cur; + else + top = cur; + last = cur; + + phraselen = 0; + commentlen = 0; + s = ps; + } + else + { + if (phraselen && phraselen < sizeof (phrase) - 1) + phrase[phraselen++] = ' '; + if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) + { + rfc822_free_address (&top); + return NULL; + } + s = ps; + } + SKIPWS (s); + } + + if (phraselen) + { + phrase[phraselen] = 0; + comment[commentlen] = 0; + add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); + } + else if (commentlen && last && !last->personal) + { + comment[commentlen] = 0; + last->personal = safe_strdup (comment); + } +#ifdef EXACT_ADDRESS + if (last) + last->val = mutt_substrdup (begin, s); +#endif + + return top; +} + +void rfc822_qualify (ADDRESS *addr, const char *host) +{ + char *p; + + for (; addr; addr = addr->next) + if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) + { + p = safe_malloc (strlen (addr->mailbox) + strlen (host) + 2); + sprintf (p, "%s@%s", addr->mailbox, host); + safe_free (&addr->mailbox); + addr->mailbox = p; + } +} + +void +rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials) +{ + if (strpbrk (value, specials)) + { + char tmp[256], *pc = tmp; + size_t tmplen = sizeof (tmp) - 3; + + *pc++ = '"'; + for (; *value && tmplen > 1; value++) + { + if (*value == '\\' || *value == '"') + { + *pc++ = '\\'; + tmplen--; + } + *pc++ = *value; + tmplen--; + } + *pc++ = '"'; + *pc = 0; + strfcpy (buf, tmp, buflen); + } + else + strfcpy (buf, value, buflen); +} + +void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr) +{ + size_t len; + char *pbuf = buf; + char *pc; + + if (!addr) + return; + + buflen--; /* save room for the terminal nul */ + +#ifdef EXACT_ADDRESS + if (addr->val) + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->val, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + if (addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ':'; + buflen--; + *pbuf = 0; + } + return; + } +#endif + + if (addr->personal) + { + if (strpbrk (addr->personal, RFC822Specials)) + { + if (!buflen) + goto done; + *pbuf++ = '"'; + buflen--; + for (pc = addr->personal; *pc && buflen > 0; pc++) + { + if (*pc == '"' || *pc == '\\') + { + if (!buflen) + goto done; + *pbuf++ = '\\'; + buflen--; + } + if (!buflen) + goto done; + *pbuf++ = *pc; + buflen--; + } + if (!buflen) + goto done; + *pbuf++ = '"'; + buflen--; + } + else + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->personal, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + } + + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + + if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) + { + if (!buflen) + goto done; + *pbuf++ = '<'; + buflen--; + } + + if (addr->mailbox) + { + if (!buflen) + goto done; + strfcpy (pbuf, addr->mailbox, buflen); + len = strlen (pbuf); + pbuf += len; + buflen -= len; + + if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) + { + if (!buflen) + goto done; + *pbuf++ = '>'; + buflen--; + } + + if (addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ':'; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + } + else + { + if (!buflen) + goto done; + *pbuf++ = ';'; + buflen--; + } +done: + /* no need to check for length here since we already save space at the + beginning of this routine */ + *pbuf = 0; +} + +/* note: it is assumed that `buf' is nul terminated! */ +void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr) +{ + char *pbuf = buf; + size_t len = strlen (buf); + + buflen--; /* save room for the terminal nul */ + + if (len > 0) + { + if (len > buflen) + return; /* safety check for bogus arguments */ + + pbuf += len; + buflen -= len; + if (!buflen) + goto done; + *pbuf++ = ','; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + + for (; addr && buflen > 0; addr = addr->next) + { + /* use buflen+1 here because we already saved space for the trailing + nul char, and the subroutine can make use of it */ + rfc822_write_address_single (pbuf, buflen + 1, addr); + + /* this should be safe since we always have at least 1 char passed into + the above call, which means `pbuf' should always be nul terminated */ + len = strlen (pbuf); + pbuf += len; + buflen -= len; + + /* if there is another address, and its not a group mailbox name or + group terminator, add a comma to separate the addresses */ + if (addr->next && addr->next->mailbox && !addr->group) + { + if (!buflen) + goto done; + *pbuf++ = ','; + buflen--; + if (!buflen) + goto done; + *pbuf++ = ' '; + buflen--; + } + } +done: + *pbuf = 0; +} + +/* this should be rfc822_cpy_adr */ +ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr) +{ + ADDRESS *p = rfc822_new_address (); + +#ifdef EXACT_ADDRESS + p->val = safe_strdup (addr->val); +#endif + p->personal = safe_strdup (addr->personal); + p->mailbox = safe_strdup (addr->mailbox); + p->group = addr->group; + return p; +} + +/* this should be rfc822_cpy_adrlist */ +ADDRESS *rfc822_cpy_adr (ADDRESS *addr) +{ + ADDRESS *top = NULL, *last = NULL; + + for (; addr; addr = addr->next) + { + if (last) + { + last->next = rfc822_cpy_adr_real (addr); + last = last->next; + } + else + top = last = rfc822_cpy_adr_real (addr); + } + return top; +} + +/* append list 'b' to list 'a' and return the last element in the new list */ +ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b) +{ + ADDRESS *tmp = *a; + + while (tmp && tmp->next) + tmp = tmp->next; + if (!b) + return tmp; + if (tmp) + tmp->next = rfc822_cpy_adr (b); + else + tmp = *a = rfc822_cpy_adr (b); + while (tmp && tmp->next) + tmp = tmp->next; + return tmp; +} + +#ifdef TESTING +void safe_free (void *ptr) +{ + void **p = (void **)ptr; + if (*p) + { + free (*p); + *p = 0; + } +} + +int main (int argc, char **argv) +{ + ADDRESS *list; + char buf[256]; + char *str = "michael, Michael Elkins <me@cs.hmc.edu>, testing a really complex address: this example <@contains.a.source.route@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)"; + + list = rfc822_parse_adrlist (NULL, str); + buf[0] = 0; + rfc822_write_address (buf, sizeof (buf), list); + rfc822_free_address (&list); + puts (buf); + exit (0); +} +#endif diff --git a/src/rfc822.h b/src/rfc822_mutt.h diff --git a/src/zlibs/locking b/src/zlibs/locking @@ -22,7 +22,7 @@ lock() { func "lock: $1" - $WORKDIR/bin/dotlock ${1} + ${=dotlock} ${1} case $? in 1) error "Cannot lock non existing file: $1" return 1 ;; @@ -84,7 +84,7 @@ unlock() { pidcheck $1 { test $? = 0 } || { return 1 } - $WORKDIR/bin/dotlock -u "$1" + ${=dotlock} -u "$1" { test $? = 0 } || { rm -f "$1.lock" { test $? = 0 } || { error "Unable to unlock: $1"; return 1 } @@ -104,7 +104,7 @@ unlink() { # delete a file that we are locking ( ${=rm} ${1} touch ${1} - $WORKDIR/bin/dotlock -d -f ${1} + ${=dotlock} -d -f ${1} { test $? != 0 } && { error "Unable to unlink: $1"; return 1 } { test -r "${1}.pid" } && { rm -f ${1}.pid } rm -f ${1}.unlink