Merge tag 0.28.4
authorDavid Bremner <david@tethera.net>
Sun, 5 May 2019 19:38:51 +0000 (16:38 -0300)
committerDavid Bremner <david@tethera.net>
Sun, 5 May 2019 19:38:51 +0000 (16:38 -0300)
No functionality changes merged, since the bug in question was already
fixed on master.

82 files changed:
.gitignore
.travis.yml
INSTALL
Makefile.global
Makefile.local
NEWS
bindings/python/notmuch/database.py
bindings/python/notmuch/message.py
bindings/python/notmuch/query.py
configure
debian/control
debugger.c
doc/Makefile.local
doc/conf.py
doc/man1/notmuch-address.rst
doc/man1/notmuch-count.rst
doc/man1/notmuch-new.rst
doc/man1/notmuch-search.rst
doc/man1/notmuch-show.rst
doc/man7/notmuch-search-terms.rst
doc/notmuch-emacs.rst
emacs/.gitignore
emacs/Makefile.local
emacs/notmuch-draft.el
emacs/notmuch-lib.el
emacs/notmuch-message.el
emacs/notmuch-mua.el
emacs/notmuch-show.el
emacs/notmuch-wash.el
emacs/notmuch.el
emacs/rstdoc.el [new file with mode: 0644]
emacs/rstdoc.rsti [new file with mode: 0644]
gmime-filter-reply.c
lib/built-with.c
lib/database-private.h
lib/database.cc
lib/index.cc
lib/message-file.c
lib/message.cc
lib/notmuch.h
lib/regexp-fields.cc
lib/thread.cc
mime-node.c
notmuch-client.h
notmuch-config.c
notmuch-insert.c
notmuch-new.c
notmuch-reindex.c
notmuch-reply.c
notmuch-search.c
notmuch-show.c
notmuch.c
performance-test/T00-new.sh
performance-test/T03-reindex.sh
test/.gitignore
test/T030-config.sh
test/T040-setup.sh
test/T190-multipart.sh
test/T220-reply.sh
test/T230-reply-to-sender.sh
test/T310-emacs.sh
test/T350-crypto.sh
test/T355-smime.sh
test/T357-index-decryption.sh
test/T530-upgrade.sh
test/T650-regexp-query.sh
test/T720-emacs-attachment-warnings.sh [new file with mode: 0755]
test/T720-lib-lifetime.sh [new file with mode: 0755]
test/T730-emacs-forwarding.sh [new file with mode: 0755]
test/T740-body.sh [new file with mode: 0755]
test/T750-gzip.sh [new file with mode: 0755]
test/emacs-attachment-warnings.el [new file with mode: 0644]
test/test-lib.sh
util/crypto.c
util/crypto.h
util/error_util.h
util/gmime-extra.c
util/gmime-extra.h
util/hex-escape.h
util/talloc-extra.h
util/xutil.h
util/zlib-extra.h

index e06101ce51c67c65d9118b198b4c165501b0d6e5..468b660a70971fbac2269d914a70c6314b7bd453 100644 (file)
@@ -15,3 +15,4 @@ tags
 .*.swp
 /releases
 /.stamps
+*.stamp
index a2caf010fdec7ab8de6fadb7e5bb938099fa5858..90a1cc56137ac4a2543c8ab176ac5e99c21cc257 100644 (file)
@@ -4,10 +4,12 @@ dist: xenial
 
 addons:
   apt:
+    sources:
+    - sourceline: 'ppa:xapian-backports/ppa'
     packages:
     - dtach
     - libxapian-dev
-    - libgmime-2.6-dev
+    - libgmime-3.0-dev
     - libtalloc-dev
     - python3-sphinx
     - gpgsm
diff --git a/INSTALL b/INSTALL
index 6e6f4799aee7ffc8137c6b76b59ae32894d59df6..f1236e713e11085b626f79c5b22c89719cdd6b6d 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -20,7 +20,7 @@ configure stage.
 
 Dependencies
 ------------
-Notmuch depends on four libraries: Xapian, GMime 2.6,
+Notmuch depends on four libraries: Xapian, GMime 3.0,
 Talloc, and zlib which are each described below:
 
        Xapian
index cae4c7d1d80c19d576d6ce71d196abdc80a912cd..0aee5876532bf84a5d4495a1046e884d1686602e 100644 (file)
@@ -40,11 +40,11 @@ DEB_TAG=debian/$(UPSTREAM_TAG)-1
 RELEASE_HOST=notmuchmail.org
 RELEASE_DIR=/srv/notmuchmail.org/www/releases
 RELEASE_URL=https://notmuchmail.org/releases
-TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz
+TAR_FILE=$(PACKAGE)-$(VERSION).tar.xz
 ELPA_FILE:=$(PACKAGE)-emacs-$(ELPA_VERSION).tar
-DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz
-SHA256_FILE=$(TAR_FILE).sha256
-GPG_FILE=$(SHA256_FILE).asc
+DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.xz
+SHA256_FILE=$(TAR_FILE).sha256.asc
+DETACHED_SIG_FILE=$(TAR_FILE).asc
 
 PV_FILE=bindings/python/notmuch/version.py
 
index 82145e1b4e8e83122f44bb3313fbe457e261bfe9..3c6dacbccc4b3af1e43d89fcf8e69dd23997aed0 100644 (file)
@@ -36,14 +36,14 @@ $(TAR_FILE):
                --transform s_^_$(PACKAGE)-$(VERSION)/_  \
                --transform 's_.tmp$$__' --mtime=@$$ct version.tmp
        rm version.tmp
-       gzip -n < $(TAR_FILE).tmp > $(TAR_FILE)
+       xz -C sha256 -9 < $(TAR_FILE).tmp > $(TAR_FILE)
        @echo "Source is ready for release in $(TAR_FILE)"
 
 $(SHA256_FILE): $(TAR_FILE)
-       sha256sum $^ > $@
+       sha256sum $^ | gpg --clear-sign --output $@ -
 
-$(GPG_FILE): $(SHA256_FILE)
-       gpg --armor --sign $^
+$(DETACHED_SIG_FILE): $(TAR_FILE)
+       gpg --armor --detach-sign $^
 
 .PHONY: dist
 dist: $(TAR_FILE)
@@ -68,16 +68,16 @@ release: verify-source-tree-and-version
        $(MAKE) VERSION=$(VERSION) clean
        $(MAKE) VERSION=$(VERSION) test
        git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG)
-       $(MAKE) VERSION=$(VERSION) $(GPG_FILE)
+       $(MAKE) VERSION=$(VERSION) $(SHA256_FILE) $(DETACHED_SIG_FILE)
        ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
        pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
        git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG)
        mkdir -p releases
-       mv $(TAR_FILE) $(SHA256_FILE) $(GPG_FILE) releases
+       mv $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
        $(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce
 ifeq ($(REALLY_UPLOAD),yes)
        git push origin $(VERSION) $(DEB_TAG) release pristine-tar
-       cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(GPG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
+       cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
        ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)"
 endif
        @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template."
@@ -121,7 +121,7 @@ release-message:
        @echo -n "  "
        @cat releases/$(SHA256_FILE)
        @echo ""
-       @echo "  $(RELEASE_URL)/$(GPG_FILE)"
+       @echo "  $(RELEASE_URL)/$(DETACHED_SIG_FILE)"
        @echo "  (signed by `getent passwd "$$USER" | cut -d: -f 5 | cut -d, -f 1`)"
        @echo ""
        @echo "What's new in notmuch $(VERSION)"
diff --git a/NEWS b/NEWS
index c5b7eec97f98d5e5341f664d30d4eb1c988f6fac..26b8160c8ee253821604c0063f0eb8ed6539f1dd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,18 @@
+Notmuch 0.29 (UNRELEASED)
+=========================
+
+Command Line Interface
+----------------------
+
+`notmuch show` now supports --body=false and --include-html with
+--format=text
+
+Emacs
+-----
+
+Support for GNU Emacs older than 25.1 is deprecated with this release,
+and may be removed in a future release.
+
 Notmuch 0.28.4 (2019-05-05)
 ===========================
 
index 342d665a224761a2557ea00233f7d99c7dbae321..88ca836e2d1aa4c78471ede28f7541fac630aafa 100644 (file)
@@ -675,7 +675,10 @@ class Database(object):
         if not config.has_option('database', 'path'):
             raise NotmuchError(message="No DB path specified"
                                        " and no user default found")
-        return config.get('database', 'path')
+        db_path = config.get('database', 'path')
+        if not os.path.isabs(db_path):
+            db_path = os.path.expanduser(os.path.join("~", db_path))
+        return db_path
 
     """notmuch_database_get_config"""
     _get_config = nmlib.notmuch_database_get_config
index de0fb4151e52e50c3f3eb684932b65820d74cf82..6e32b5f7ada2d825d4cd6c23403f6b6b1271691f 100644 (file)
@@ -482,7 +482,9 @@ class Message(Python3StringMixIn):
         if status != 0:
             raise NotmuchError(status)
 
-        return value.value.decode('utf-8') if value is not None else None
+        if value is None or value.value is None:
+            return None
+        return value.value.decode('utf-8')
 
     def get_properties(self, prop="", exact=False):
         """ Get the properties of the message, returning a generator of
index 06c7b11bb8648de7488092c6c2d63efed3e13bf0..cc70e2aa3acc937fcc43d6a577a86337305f8b90 100644 (file)
@@ -108,7 +108,7 @@ class Query(object):
 
     _set_sort = nmlib.notmuch_query_set_sort
     _set_sort.argtypes = [NotmuchQueryP, c_uint]
-    _set_sort.argtypes = None
+    _set_sort.restype = None
 
     def set_sort(self, sort):
         """Set the sort order future results will be delivered in
@@ -121,7 +121,7 @@ class Query(object):
 
     _exclude_tag = nmlib.notmuch_query_add_tag_exclude
     _exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
-    _exclude_tag.resttype = None
+    _exclude_tag.restype = None
 
     def exclude_tag(self, tagname):
         """Add a tag that will be excluded from the query results by default.
index ea22587b24ff257bfe8d2b2dff3e27e7d9d2f2f2..9140026a71f0873e78be32b1c27eee699e66180f 100755 (executable)
--- a/configure
+++ b/configure
@@ -82,6 +82,7 @@ WITH_API_DOCS=1
 WITH_EMACS=1
 WITH_DESKTOP=1
 WITH_BASH=1
+WITH_RPATH=1
 WITH_RUBY=1
 WITH_ZSH=1
 WITH_RETRY_LOCK=1
@@ -241,6 +242,14 @@ for option; do
        fi
     elif [ "${option}" = '--without-bash-completion' ] ; then
        WITH_BASH=0
+    elif [ "${option%%=*}" = '--with-rpath' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_RPATH=0
+       else
+           WITH_RPATH=1
+       fi
+    elif [ "${option}" = '--without-rpath' ] ; then
+       WITH_RPATH=0
     elif [ "${option%%=*}" = '--with-ruby' ]; then
        if [ "${option#*=}" = 'no' ]; then
            WITH_RUBY=0
@@ -480,33 +489,16 @@ EOF
     rm -rf test.db _default_backend _default_backend.cc
 fi
 
-# we need to have a version >= 2.6.5 to avoid a crypto bug. We need
-# 2.6.7 for permissive "From " header handling.
-GMIME_MINVER=2.6.7
-GMIME3_MINVER=3.0.3
+GMIME_MINVER=3.0.3
 
 printf "Checking for GMime development files... "
-if pkg-config --exists "gmime-3.0 > $GMIME3_MINVER"; then
-    printf "Yes (3.0).\n"
+if pkg-config --exists "gmime-3.0 > $GMIME_MINVER"; then
+    printf "Yes.\n"
     have_gmime=1
     gmime_cflags=$(pkg-config --cflags gmime-3.0)
     gmime_ldflags=$(pkg-config --libs gmime-3.0)
-    gmime_major=3
-    have_gmime_session_keys=1
-elif pkg-config --exists "gmime-2.6 >= $GMIME_MINVER"; then
-    printf "Yes (2.6).\n"
-    have_gmime=1
-    gmime_cflags=$(pkg-config --cflags gmime-2.6)
-    gmime_ldflags=$(pkg-config --libs gmime-2.6)
-    gmime_major=2
-    if pkg-config --exists "gmime-2.6 >= 2.6.21"; then
-        have_gmime_session_keys=1
-    else
-        have_gmime_session_keys=0
-    fi
 else
     have_gmime=0
-    have_gmime_session_keys=0
     printf "No.\n"
     errors=$((errors + 1))
 fi
@@ -623,8 +615,8 @@ if [ -z "${EMACSETCDIR-}" ]; then
     EMACSETCDIR="\$(prefix)/share/emacs/site-lisp"
 fi
 
-printf "Checking if emacs is available... "
-if emacs --quick --batch > /dev/null 2>&1; then
+printf "Checking if emacs (>= 24) is available... "
+if emacs --quick --batch --eval '(if (< emacs-major-version 24) (kill-emacs 1))' > /dev/null 2>&1; then
     printf "Yes.\n"
     have_emacs=1
 else
@@ -779,7 +771,7 @@ EOF
        echo
     fi
     if [ $have_gmime -eq 0 ]; then
-       echo "  GMime 2.6 library >= $GMIME_MINVER"
+       echo "  GMime library >= $GMIME_MINVER"
        echo "  (including development files such as headers)"
        echo "  https://github.com/jstedfast/gmime/"
        echo
@@ -801,7 +793,7 @@ case a simple command will install everything you need. For example:
 
 On Debian and similar systems:
 
-       sudo apt-get install libxapian-dev libgmime-2.6-dev libtalloc-dev zlib1g-dev
+       sudo apt-get install libxapian-dev libgmime-3.0-dev libtalloc-dev zlib1g-dev
 
 Or on Fedora and similar systems:
 
@@ -928,7 +920,7 @@ fi
 rm -f compat/check_asctime
 
 printf "Checking for rpath support... "
-if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1
+if [ $WITH_RPATH = "1" ] && ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1
 then
     printf "Yes.\n"
     rpath_ldflags="-Wl,--enable-new-dtags -Wl,-rpath,\$(libdir)"
@@ -1165,9 +1157,6 @@ HAVE_TIMEGM = ${have_timegm}
 # Whether struct dirent has d_type (if not, then notmuch will use stat)
 HAVE_D_TYPE = ${have_d_type}
 
-# Whether the GMime version can handle extraction and reuse of session keys
-HAVE_GMIME_SESSION_KEYS = ${have_gmime_session_keys}
-
 # Whether the Xapian version in use supports compaction
 HAVE_XAPIAN_COMPACT = ${have_xapian_compact}
 
@@ -1254,7 +1243,6 @@ COMMON_CONFIGURE_CFLAGS = \\
        -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                           \\
        -DSTD_GETPWUID=\$(STD_GETPWUID)                         \\
        -DSTD_ASCTIME=\$(STD_ASCTIME)                           \\
-       -DHAVE_GMIME_SESSION_KEYS=\$(HAVE_GMIME_SESSION_KEYS)   \\
        -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)           \\
        -DSILENCE_XAPIAN_DEPRECATION_WARNINGS                   \\
        -DHAVE_XAPIAN_FIELD_PROCESSOR=\$(HAVE_XAPIAN_FIELD_PROCESSOR) \\
@@ -1283,9 +1271,6 @@ NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR=${have_xapian_field_processor}
 # Whether the Xapian version in use supports lock retry
 NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${have_xapian_db_retry_lock}
 
-# Whether the GMime version can handle extraction and reuse of session keys
-NOTMUCH_HAVE_GMIME_SESSION_KEYS=${have_gmime_session_keys}
-
 # Which backend will Xapian use by default?
 NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
 
@@ -1310,9 +1295,6 @@ NOTMUCH_RUBY=${RUBY}
 # building/testing ruby bindings.
 NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev}
 
-# Major version of gmime
-NOTMUCH_GMIME_MAJOR=${gmime_major}
-
 # Platform we are run on
 PLATFORM=${platform}
 EOF
index 922f6d8c847a9488ce6ca5cfc409f95c33e3950e..31d6471c28b29dee9a19e567e57196493302afee 100644 (file)
@@ -11,7 +11,7 @@ Build-Depends:
  debhelper (>= 11~),
  pkg-config,
  libxapian-dev,
- libgmime-3.0-dev (>= 3.0.3~) | libgmime-2.6-dev (>= 2.6.7~),
+ libgmime-3.0-dev (>= 3.0.3~),
  libtalloc-dev,
  libz-dev,
  python-all (>= 2.6.6-3~),
index 5cb38ac444e35d774152b64973790c4fd5ec7f4d..0febf170d729167b6697f18fffaae467d8c84259 100644 (file)
@@ -32,13 +32,14 @@ bool
 debugger_is_active (void)
 {
     char buf[1024];
+    char buf2[1024];
 
     if (RUNNING_ON_VALGRIND)
        return true;
 
     sprintf (buf, "/proc/%d/exe", getppid ());
-    if (readlink (buf, buf, sizeof (buf)) != -1 &&
-       strncmp (basename (buf), "gdb", 3) == 0)
+    if (readlink (buf, buf2, sizeof (buf2)) != -1 &&
+       strncmp (basename (buf2), "gdb", 3) == 0)
     {
        return true;
     }
index cb0f1f6488f6d9cbe0e3e8d345c5d90053c2de9c..651168f4d123ca13b7963aee4a550832c3272279 100644 (file)
@@ -4,7 +4,7 @@ dir := doc
 
 # You can set these variables from the command line.
 SPHINXOPTS    := -q
-SPHINXBUILD   = sphinx-build
+SPHINXBUILD   = HAVE_EMACS=${HAVE_EMACS} sphinx-build
 DOCBUILDDIR      := $(dir)/_build
 
 # Internal variables.
@@ -16,6 +16,7 @@ MAN1_RST := $(wildcard $(srcdir)/doc/man1/*.rst)
 MAN5_RST := $(wildcard $(srcdir)/doc/man5/*.rst)
 MAN7_RST := $(wildcard $(srcdir)/doc/man7/*.rst)
 MAN_RST_FILES := $(MAN1_RST) $(MAN5_RST) $(MAN7_RST)
+ALL_RST_FILES := $(MAN_RST_FILES) $(srcdir)/doc/notmuch-emacs.rst
 
 MAN1_ROFF := $(patsubst $(srcdir)/doc/%,$(DOCBUILDDIR)/man/%,$(MAN1_RST:.rst=.1))
 MAN5_ROFF := $(patsubst $(srcdir)/doc/%,$(DOCBUILDDIR)/man/%,$(MAN5_RST:.rst=.5))
@@ -37,6 +38,10 @@ INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
 %.gz: %
        rm -f $@ && gzip --stdout $^ > $@
 
+ifeq ($(WITH_EMACS),1)
+$(DOCBUILDDIR)/.roff.stamp sphinx-html sphinx-texinfo: docstring.stamp
+endif
+
 # Sequentialize the calls to sphinx-build to avoid races with
 # reading/writing cached state. This uses GNU make specific
 # "order-only" prerequisites.
@@ -45,14 +50,20 @@ sphinx-html: | $(DOCBUILDDIR)/.roff.stamp
 sphinx-texinfo: | sphinx-html
 sphinx-info: | sphinx-texinfo
 
-sphinx-html:
+sphinx-html: $(DOCBUILDDIR)/.html.stamp
+
+$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES)
        $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
+       touch $@
+
+sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp
 
-sphinx-texinfo:
+$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES)
        $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
+       touch $@
 
 sphinx-info: sphinx-texinfo
-       make -C $(DOCBUILDDIR)/texinfo info
+       $(MAKE) -C $(DOCBUILDDIR)/texinfo info
 
 # Use the man page converter that is available. We should never depend
 # on MAN_ROFF_FILES if a converter is not available.
@@ -133,5 +144,6 @@ $(dir)/config.dox: version.stamp
        echo "PROJECT_NAME = \"Notmuch $(VERSION)\"" > $@
        echo "INPUT=${srcdir}/lib/notmuch.h" >> $@
 
-CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp
+CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
+CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp
 CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
index 0ef723279a6c012e0450a4a42cd72733b2a116c1..5f8c9f1c8752d21e7dc28e8e890e5808c84a92ba 100644 (file)
@@ -29,6 +29,11 @@ release = version
 # directories to ignore when looking for source files.
 exclude_patterns = ['_build']
 
+# If we don't have emacs, don't build the notmuch-emacs docs, as they need
+# emacs to generate the docstring include files
+if os.environ.get('HAVE_EMACS') != '1':
+    exclude_patterns.append('notmuch-emacs.rst')
+
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
index 12d86e8952c5e70f5959097930b5a3607094597b..2a7df6f0d80f21133fee3f58902f907e83d7a34b 100644 (file)
@@ -92,7 +92,7 @@ Supported options for **address** include
 
 ``--exclude=(true|false)``
     A message is called "excluded" if it matches at least one tag in
-    search.tag\_exclude that does not appear explicitly in the search
+    search.exclude\_tags that does not appear explicitly in the search
     terms. This option specifies whether to omit excluded messages in
     the search process.
 
index 9ca20dab274733ed0a049f83f55e5cd83b25951b..0eac5dbef4ba09242795cf14abc3de2affe96444 100644 (file)
@@ -36,7 +36,7 @@ Supported options for **count** include
         same message-id).
 
 ``--exclude=(true|false)``
-    Specify whether to omit messages matching search.tag\_exclude from
+    Specify whether to omit messages matching search.exclude\_tags from
     the count (the default) or not.
 
 ``--batch``
index 5eeb49262e0dd6442a103fa02415fda394eb9797..20a1103b3363a85e78d9ec07845ad4ca068eb81d 100644 (file)
@@ -43,6 +43,10 @@ Supported options for **new** include
 ``--quiet``
     Do not print progress or results.
 
+``--verbose``
+    Print file names being processed. Ignored when combined with
+    ``--quiet``.
+
 ``--decrypt=(true|nostash|auto|false)``
     If ``true``, when encountering an encrypted message, try to
     decrypt it while indexing, and stash any discovered session keys.
index 654c5f2cfbcab5cdc6f798cd873110ec1813a8c2..ed9ff4e5b96522751b056192bb7eabc5b0da75dd 100644 (file)
@@ -100,7 +100,7 @@ Supported options for **search** include
 
 ``--exclude=(true|false|all|flag)``
     A message is called "excluded" if it matches at least one tag in
-    search.tag\_exclude that does not appear explicitly in the search
+    search.exclude\_tags that does not appear explicitly in the search
     terms. This option specifies whether to omit excluded messages in
     the search process.
 
index 8bfa87c664f98124bbbe5a763cfb45e0c6603589..becd3e79929023b089a9e5542c15380961aae58f 100644 (file)
@@ -161,7 +161,7 @@ Supported options for **show** include
     Default: ``auto``
 
 ``--exclude=(true|false)``
-    Specify whether to omit threads only matching search.tag\_exclude
+    Specify whether to omit threads only matching search.exclude\_tags
     from the search results (the default) or not. In either case the
     excluded message will be marked with the exclude flag (except when
     output=mbox when there is nowhere to put the flag).
@@ -176,18 +176,19 @@ Supported options for **show** include
 ``--body=(true|false)``
     If true (the default) **notmuch show** includes the bodies of the
     messages in the output; if false, bodies are omitted.
-    ``--body=false`` is only implemented for the json and sexp formats
-    and it is incompatible with ``--part > 0.``
+    ``--body=false`` is only implemented for the text, json and sexp
+    formats and it is incompatible with ``--part > 0.``
 
     This is useful if the caller only needs the headers as body-less
     output is much faster and substantially smaller.
 
 ``--include-html``
-    Include "text/html" parts as part of the output (currently only
-    supported with ``--format=json`` and ``--format=sexp``). By default,
-    unless ``--part=N`` is used to select a specific part or
-    ``--include-html`` is used to include all "text/html" parts, no
-    part with content type "text/html" is included in the output.
+    Include "text/html" parts as part of the output (currently
+    only supported with ``--format=text``, ``--format=json`` and
+    ``--format=sexp``). By default, unless ``--part=N`` is used to
+    select a specific part or ``--include-html`` is used to include all
+    "text/html" parts, no part with content type "text/html" is included
+    in the output.
 
 A common use of **notmuch show** is to display a single thread of email
 messages. For this, use a search term of "thread:<thread-id>" as can be
index f7a39ceb9df4bc6efb9ae598d70150701374dc48..fd8bf634363d9b757c2317b611d4c7ed69d8d985 100644 (file)
@@ -44,6 +44,9 @@ results to those whose value matches a regular expression (see
 
    notmuch search 'from:"/bob@.*[.]example[.]com/"'
 
+body:<word-or-quoted-phrase>
+    Match terms in the body of messages.
+
 from:<name-or-address> or from:/<regex>/
     The **from:** prefix is used to match the name or address of
     the sender of an email message.
@@ -249,7 +252,7 @@ follows.
 Boolean
    **tag:**, **id:**, **thread:**, **folder:**, **path:**, **property:**
 Probabilistic
-  **to:**, **attachment:**, **mimetype:**
+  **body:**, **to:**, **attachment:**, **mimetype:**
 Special
    **from:**, **query:**, **subject:**
 
index ce2e358e2134e2dfb4c86a2fe89f2d00a7eb4f45..1655e2f0a7ad5b160130580f0e9463706b803e72 100644 (file)
@@ -62,7 +62,7 @@ notmuch-hello key bindings
 ``<return>``
     Activate the current widget.
 
-``=``
+``g`` ``=``
     Refresh the buffer; mainly update the counts of messages for various
     saved searches.
 
@@ -159,6 +159,9 @@ menu of results that the user can explore further by pressing
 ``<return>``
     Open thread on current line in :ref:`notmuch-show` mode
 
+``g`` ``=``
+    Refresh the buffer
+
 ``?``
     Display full set of key bindings
 
@@ -190,6 +193,9 @@ pressing RET after positioning the cursor on a hidden part.
     advance to the next message, or advance to the next thread (if
     already on the last message of a thread).
 
+``c``
+    :ref:`show-copy`
+
 ``N``
     Move to next message
 
@@ -208,6 +214,63 @@ pressing RET after positioning the cursor on a hidden part.
 ``?``
     Display full set of key bindings
 
+Display of messages can be controlled by the following variables
+
+:index:`notmuch-message-headers`
+       |docstring::notmuch-message-headers|
+
+:index:`notmuch-message-headers-visible`
+       |docstring::notmuch-message-headers-visible|
+
+.. _show-copy:
+
+Copy to kill-ring
+-----------------
+
+You can use the usually Emacs ways of copying text to the kill-ring,
+but notmuch also provides some shortcuts. These keys are available in
+:ref:`notmuch-show`, and :ref:`notmuch-tree`. A subset are available
+in :ref:`notmuch-search`.
+
+``c F``        ``notmuch-show-stash-filename``
+   |docstring::notmuch-show-stash-filename|
+
+``c G`` ``notmuch-show-stash-git-send-email``
+   |docstring::notmuch-show-stash-git-send-email|
+
+``c I`` ``notmuch-show-stash-message-id-stripped``
+   |docstring::notmuch-show-stash-message-id-stripped|
+
+``c L`` ``notmuch-show-stash-mlarchive-link-and-go``
+   |docstring::notmuch-show-stash-mlarchive-link-and-go|
+
+``c T`` ``notmuch-show-stash-tags``
+   |docstring::notmuch-show-stash-tags|
+
+``c c`` ``notmuch-show-stash-cc``
+   |docstring::notmuch-show-stash-cc|
+
+``c d`` ``notmuch-show-stash-date``
+   |docstring::notmuch-show-stash-date|
+
+``c f`` ``notmuch-show-stash-from``
+   |docstring::notmuch-show-stash-from|
+
+``c i`` ``notmuch-show-stash-message-id``
+   |docstring::notmuch-show-stash-message-id|
+
+``c l`` ``notmuch-show-stash-mlarchive-link``
+   |docstring::notmuch-show-stash-mlarchive-link|
+
+``c s`` ``notmuch-show-stash-subject``
+   |docstring::notmuch-show-stash-subject|
+
+``c t`` ``notmuch-show-stash-to``
+   |docstring::notmuch-show-stash-to|
+
+``c ?``
+    Show all available copying commands
+
 .. _notmuch-tree:
 
 notmuch-tree
@@ -218,6 +281,9 @@ email archives. Each line in the buffer represents a single
 message giving the relative date, the author, subject, and any
 tags.
 
+``c``
+    :ref:`show-copy`
+
 ``<return>``
    Displays that message.
 
@@ -233,6 +299,9 @@ tags.
 ``p``
     Move to previous matching message
 
+``g`` ``=``
+    Refresh the buffer
+
 ``?``
     Display full set of key bindings
 
@@ -275,7 +344,13 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
 
 :index:`notmuch-tagging-keys`
 
-   A list of keys and corresponding tagging operations.
+  |docstring::notmuch-tagging-keys|
+
+Buffer navigation
+=================
+
+:index:`notmuch-cycle-notmuch-buffers`
+   |docstring::notmuch-cycle-notmuch-buffers|
 
 Configuration
 =============
@@ -286,8 +361,10 @@ Importing Mail
 --------------
 
 :index:`notmuch-poll`
+   |docstring::notmuch-poll|
 
 :index:`notmuch-poll-script`
+   |docstring::notmuch-poll-script|
 
 Init File
 ---------
@@ -300,3 +377,13 @@ suffix exist it will be read instead (just one of these, chosen in this
 order). Most often users create ``~/.emacs.d/notmuch-config.el`` and just
 work with it. If Emacs was invoked with the ``-q`` or ``--no-init-file``
 options, ``notmuch-init-file`` is not read.
+
+.. include:: ../emacs/rstdoc.rsti
+
+.. include:: ../emacs/notmuch.rsti
+
+.. include:: ../emacs/notmuch-lib.rsti
+
+.. include:: ../emacs/notmuch-show.rsti
+
+.. include:: ../emacs/notmuch-tag.rsti
index fbf8dde6f1760c409036d6b9b473a59d3c943ae6..b9873b0ab6667eedf018150217f8ba2d9f1eaa83 100644 (file)
@@ -1,4 +1,5 @@
 /.eldeps*
 /*.elc
+/*.rsti
 /notmuch-version.el
 /notmuch-pkg.el
index 5e4ae1bda257f61cac0f296ee5a9ff2bec80f4f9..40b7c14fff280e84bcaae09fd706aab4afa6ba6d 100644 (file)
@@ -45,6 +45,15 @@ emacs_images := \
        $(srcdir)/$(dir)/notmuch-logo.png
 
 emacs_bytecode = $(emacs_sources:.el=.elc)
+emacs_docstrings = $(emacs_sources:.el=.rsti)
+
+ifneq ($(HAVE_SPHINX)$(HAVE_EMACS),11)
+docstring.stamp:
+       @echo "Missing prerequisites, not collecting docstrings"
+else
+docstring.stamp: ${emacs_docstrings}
+       touch $@
+endif
 
 # Because of defmacro's and defsubst's, we have to account for load
 # dependencies between Elisp files when byte compiling.  Otherwise,
@@ -76,6 +85,8 @@ CLEAN+=$(dir)/.eldeps $(dir)/.eldeps.tmp $(dir)/.eldeps.x
 ifeq ($(HAVE_EMACS),1)
 %.elc: %.el $(global_deps)
        $(call quiet,EMACS) --directory emacs -batch -f batch-byte-compile $<
+%.rsti: %.el
+       $(call quiet,EMACS) -batch -L emacs -l rstdoc -f rstdoc-batch-extract $< $@
 endif
 
 elpa: $(ELPA_FILE)
@@ -93,7 +104,7 @@ endif
 
 ifeq ($(WITH_EMACS),1)
 ifeq ($(HAVE_EMACS),1)
-all: $(emacs_bytecode)
+all: $(emacs_bytecode) $(emacs_docstrings)
 install-emacs: $(emacs_bytecode)
 endif
 
@@ -120,4 +131,5 @@ ifeq ($(WITH_DESKTOP),1)
        -update-desktop-database "$(DESTDIR)$(desktop_dir)"
 endif
 
-CLEAN := $(CLEAN) $(emacs_bytecode) $(dir)/notmuch-version.el $(dir)/notmuch-pkg.el
+CLEAN := $(CLEAN) $(emacs_bytecode) $(dir)/notmuch-version.el $(dir)/notmuch-pkg.el \
+       $(emacs_docstrings) docstring.stamp
index fb7f4f55ed572d5d37d4e1e17f666493f6a25422..e22e0d1638b7b206fe4bd08470ec5e04a707b7f7 100644 (file)
@@ -2,6 +2,7 @@
 ;;
 ;; Copyright © Mark Walters
 ;; Copyright © David Bremner
+;; Copyright © Leo Gaspard
 ;;
 ;; This file is part of Notmuch.
 ;;
@@ -20,6 +21,7 @@
 ;;
 ;; Authors: Mark Walters <markwalters1009@gmail.com>
 ;;         David Bremner <david@tethera.net>
+;;         Leo Gaspard <leo@gaspard.io>
 
 ;;; Code:
 
@@ -225,7 +227,7 @@ applied to newly inserted messages)."
                              "--exclude=false" id))
         (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
     (when (or draft
-             (yes-or-no-p "Message does not appear to be a draft: really resume? "))
+             (yes-or-no-p "Message does not appear to be a draft: edit as new? "))
       (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*")))
       (setq buffer-read-only nil)
       (erase-buffer)
@@ -244,6 +246,7 @@ applied to newly inserted messages)."
          (message-remove-header "Message-ID"))
        (when (member 'Date message-deletable-headers)
          (message-remove-header "Date"))
+       (unless draft (notmuch-fcc-header-setup))
        ;; The X-Notmuch-Emacs-Draft header is a more reliable
        ;; indication of whether the message really is a draft.
        (setq draft (> (message-remove-header "X-Notmuch-Emacs-Draft") 0)))
index 25d83fd61b49ca01aaa129de9f3ead93bec30ae6..546ab6fda41f654e8e64d1a85408f64fc6f78ffe 100644 (file)
@@ -155,6 +155,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map "s" 'notmuch-search)
     (define-key map "z" 'notmuch-tree)
     (define-key map "m" 'notmuch-mua-new-mail)
+    (define-key map "g" 'notmuch-refresh-this-buffer)
     (define-key map "=" 'notmuch-refresh-this-buffer)
     (define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
     (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
@@ -297,7 +298,7 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
   "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL
 
 It does not prepend if ACTUAL-KEY is already listed in TAIL."
-  (let ((key-string (concat prefix (format-kbd-macro actual-key))))
+  (let ((key-string (concat prefix (key-description actual-key))))
     ;; We don't include documentation if the key-binding is
     ;; over-ridden. Note, over-riding a binding automatically hides the
     ;; prefixed version too.
@@ -312,7 +313,7 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
       ;; Documentation for command
       (push (cons key-string
                  (or (and (symbolp binding) (get binding 'notmuch-doc))
-                     (notmuch-documentation-first-line binding)))
+                     (and (functionp binding) (notmuch-documentation-first-line binding))))
            tail)))
     tail)
 
index 55e4cfee98cee93cc2e6f06591f0bad81b7cc1ca..0164472f7b48e90d7a4ad74c27c390bccb270dc4 100644 (file)
@@ -23,7 +23,6 @@
 
 (require 'message)
 (require 'notmuch-tag)
-(require 'notmuch-mua)
 
 (defcustom notmuch-message-replied-tags '("+replied")
   "List of tag changes to apply to a message when it has been replied to.
@@ -34,18 +33,39 @@ will be removed from the message being replied to.
 
 For example, if you wanted to add a \"replied\" tag and remove
 the \"inbox\" and \"todo\" tags, you would set:
-    (\"+replied\" \"-inbox\" \"-todo\"\)"
+    (\"+replied\" \"-inbox\" \"-todo\")"
   :type '(repeat string)
   :group 'notmuch-send)
 
-(defun notmuch-message-mark-replied ()
-  ;; get the in-reply-to header and parse it for the message id.
-  (let ((rep (mail-header-parse-addresses (message-field-value "In-Reply-To"))))
-    (when (and notmuch-message-replied-tags rep)
-      (notmuch-tag (notmuch-id-to-query (car (car rep)))
-              (notmuch-tag-change-list notmuch-message-replied-tags)))))
+(defcustom notmuch-message-forwarded-tags '("+forwarded")
+  "List of tag changes to apply to a message when it has been forwarded.
 
-(add-hook 'message-send-hook 'notmuch-message-mark-replied)
+Tags starting with \"+\" (or not starting with either \"+\" or
+\"-\") in the list will be added, and tags starting with \"-\"
+will be removed from the message being forwarded.
+
+For example, if you wanted to add a \"forwarded\" tag and remove
+the \"inbox\" tag, you would set:
+    (\"+forwarded\" \"-inbox\")"
+  :type '(repeat string)
+  :group 'notmuch-send)
+
+(defconst notmuch-message-queued-tag-changes nil
+  "List of messages and corresponding tag-changes to be applied when sending a message.
+
+This variable is overridden by buffer-local versions in message
+buffers where tag changes should be triggered when sending off
+the message.  Each item in this list is a list of strings, where
+the first is a notmuch query and the rest are the tag changes to
+be applied to the matching messages.")
+
+(defun notmuch-message-apply-queued-tag-changes ()
+  ;; Apply the tag changes queued in the buffer-local variable notmuch-message-queued-tag-changes.
+  (dolist (query-and-tags notmuch-message-queued-tag-changes)
+    (notmuch-tag (car query-and-tags)
+                (cdr query-and-tags))))
+
+(add-hook 'message-send-hook 'notmuch-message-apply-queued-tag-changes)
 
 (provide 'notmuch-message)
 
index df2ac4470f5200fcfb183f5c1cf01c5c84a15c24..7fdd76bc6d54d08a6f6241e40ca98af71f4cd1a3 100644 (file)
@@ -28,6 +28,7 @@
 (require 'notmuch-lib)
 (require 'notmuch-address)
 (require 'notmuch-draft)
+(require 'notmuch-message)
 
 (eval-when-compile (require 'cl))
 
@@ -115,8 +116,47 @@ multiple parts get a header."
                (function :tag "Other"))
   :group 'notmuch-reply)
 
+(defcustom notmuch-mua-attachment-regexp
+  "\\b\\(attache\?ment\\|attached\\|attach\\|pi[èe]ce\s+jointe?\\)\\b"
+  "Message body text indicating that an attachment is expected.
+
+This is not used unless `notmuch-mua-attachment-check' is added
+to `notmuch-mua-send-hook'."
+  :type 'regexp
+  :group 'notmuch-send)
+
 ;;
 
+(defun notmuch-mua-attachment-check ()
+  "Signal an error if the message text indicates that an
+attachment is expected but no MML referencing an attachment is
+found.
+
+Typically this is added to `notmuch-mua-send-hook'."
+  (when (and
+        ;; When the message mentions attachment...
+        (save-excursion
+          (message-goto-body)
+          (loop while (re-search-forward notmuch-mua-attachment-regexp (point-max) t)
+                ;; For every instance of the "attachment" string
+                ;; found, examine the text properties. If the text
+                ;; has either a `face' or `syntax-table' property
+                ;; then it is quoted text and should *not* cause the
+                ;; user to be asked about a missing attachment.
+                if (let ((props (text-properties-at (match-beginning 0))))
+                     (not (or (memq 'syntax-table props)
+                              (memq 'face props))))
+                return t
+                finally return nil))
+        ;; ...but doesn't have a part with a filename...
+        (save-excursion
+          (message-goto-body)
+          (not (re-search-forward "^<#part [^>]*filename=" nil t)))
+        ;; ...and that's not okay...
+        (not (y-or-n-p "Attachment mentioned, but no attachment - is that okay?")))
+    ;; ...signal an error.
+    (error "Missing attachment")))
+
 (defun notmuch-mua-get-switch-function ()
   "Get a switch function according to `notmuch-mua-compose-in'."
   (cond ((eq notmuch-mua-compose-in 'current-window)
@@ -222,6 +262,11 @@ multiple parts get a header."
                            (notmuch-headers-plist-to-alist reply-headers)
                            nil (notmuch-mua-get-switch-function))))
 
+      ;; Create a buffer-local queue for tag changes triggered when sending the reply
+      (when notmuch-message-replied-tags
+       (setq-local notmuch-message-queued-tag-changes
+                   (list (cons query-string notmuch-message-replied-tags))))
+
       ;; Insert the message body - but put it in front of the signature
       ;; if one is present, and after any other content
       ;; message*setup-hooks may have added to the message body already.
@@ -306,7 +351,7 @@ modified. This function is notmuch addaptation of
          (if window
              ;; Raise the frame already displaying the message buffer.
              (progn
-               (gnus-select-frame-set-input-focus (window-frame window))
+               (select-frame-set-input-focus (window-frame window))
                (select-window window))
            (funcall switch-function buffer)
            (set-buffer buffer))
@@ -433,8 +478,10 @@ the From: address."
   (let* ((other-headers
          (when (or prompt-for-sender notmuch-always-prompt-for-sender)
            (list (cons 'From (notmuch-mua-prompt-for-sender)))))
-        forward-subject) ;; Comes from the first message and is
+        forward-subject  ;; Comes from the first message and is
                          ;; applied later.
+        forward-references ;; List of accumulated message-references of forwarded messages
+        forward-queries) ;; List of corresponding message-query
 
     ;; Generate the template for the outgoing message.
     (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function))
@@ -452,7 +499,9 @@ the From: address."
                  ;; Because we process the messages in reverse order,
                  ;; always generate a forwarded subject, then use the
                  ;; last (i.e. first) one.
-                 (setq forward-subject (message-make-forward-subject)))
+                 (setq forward-subject (message-make-forward-subject))
+                 (push (message-fetch-field "Message-ID") forward-references)
+                 (push id forward-queries))
                ;; Make a copy ready to be forwarded in the
                ;; composition buffer.
                (message-forward-make-body temp-buffer)
@@ -466,7 +515,18 @@ the From: address."
       (save-restriction
        (message-narrow-to-headers)
        (message-remove-header "Subject")
-       (message-add-header (concat "Subject: " forward-subject)))
+       (message-add-header (concat "Subject: " forward-subject))
+       (message-remove-header "References")
+       (message-add-header (concat "References: "
+                                   (mapconcat 'identity forward-references " "))))
+
+      ;; Create a buffer-local queue for tag changes triggered when sending the message
+      (when notmuch-message-forwarded-tags
+       (setq-local notmuch-message-queued-tag-changes
+                   (loop for id in forward-queries
+                         collect
+                         (cons id
+                               notmuch-message-forwarded-tags))))
 
       ;; `message-forward-make-body' shows the User-agent header.  Hide
       ;; it again.
index a0a58373aa675a330dc57c04f94bcde622a0f53e..e13ca3d76c50c8c2e73012c835ea2014cb81fb55 100644 (file)
@@ -604,7 +604,7 @@ will return nil if the CID is unknown or cannot be retrieved."
     (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)
       (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve)
            w3m-cid-retrieve-function-alist)))
-  (setq mm-inline-text-html-with-images t))
+  (setq mm-html-inhibit-images nil))
 
 (defvar w3m-current-buffer) ;; From `w3m.el'.
 (defun notmuch-show--cid-w3m-retrieve (url &rest args)
@@ -1511,6 +1511,7 @@ reset based on the original query."
     (define-key map "<" 'notmuch-show-toggle-thread-indentation)
     (define-key map "t" 'toggle-truncate-lines)
     (define-key map "." 'notmuch-show-part-map)
+    (define-key map "B" 'notmuch-show-browse-urls)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
@@ -1573,6 +1574,8 @@ All currently available key bindings:
 ;; region a->b is not found when point is at b. We walk backwards
 ;; until finding the property.
 (defun notmuch-show-message-extent ()
+  "Return a cons cell containing the start and end buffer offset
+of the current message."
   (let (r)
     (save-excursion
       (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
@@ -2253,7 +2256,7 @@ message will be \"unarchived\", i.e. the tag changes in
           (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
 (defun notmuch-show-archive-message-then-next-or-exit ()
-  "Archive the current message, then show the next open message in the current thread.
+  "Archive current message, then show next open message in current thread.
 
 If at the last open message in the current thread, then exit back
 to search results."
@@ -2262,7 +2265,7 @@ to search results."
   (notmuch-show-next-open-message t))
 
 (defun notmuch-show-archive-message-then-next-or-next-thread ()
-  "Archive the current message, then show the next open message in the current thread.
+  "Archive current message, then show next open message in current or next thread.
 
 If at the last open message in the current thread, then show next
 thread from search."
@@ -2519,6 +2522,32 @@ beginning of the line."
                                    (point))
                                  (line-end-position)))
 
+(defmacro notmuch-show--with-currently-shown-message (&rest body)
+  "Evaluate BODY with display restricted to the currently shown
+message."
+  `(save-excursion
+     (save-restriction
+      (let ((extent (notmuch-show-message-extent)))
+       (narrow-to-region (car extent) (cdr extent))
+       ,@body))))
+
+(defun notmuch-show--gather-urls ()
+  "Gather any URLs in the current message."
+  (notmuch-show--with-currently-shown-message
+   (let (urls)
+     (goto-char (point-min))
+     (while (re-search-forward goto-address-url-regexp (point-max) t)
+       (push (match-string-no-properties 0) urls))
+     (reverse urls))))
+
+(defun notmuch-show-browse-urls ()
+  "Offer to browse any URLs in the current message."
+  (interactive)
+  (let ((urls (notmuch-show--gather-urls)))
+    (if urls
+       (browse-url (completing-read "Browse URL: " (cdr urls) nil nil (car urls)))
+      (message "No URLs found."))))
+
 (provide 'notmuch-show)
 
 ;;; notmuch-show.el ends here
index 5f8b92671b39ac2419ef4808e27296d062bc885a..54108d93607bf83b228f5f2b17ada5f215e62192 100644 (file)
@@ -24,7 +24,7 @@
 ;;; Code:
 
 (require 'coolj)
-
+(require 'notmuch-lib)
 (declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
 (defvar notmuch-show-indent-messages-width)
 
index 44402f8aa8256b55816b9a256b6b7f7468e54e9d..804e78ab8c53cf855cd9a5ea3713f19b40da4886 100644 (file)
@@ -711,7 +711,7 @@ A thread with TAG will have FACE applied.
 Here is an example of how to color search results based on tags.
  (the following text would be placed in your ~/.emacs file):
 
- (setq notmuch-search-line-faces '((\"unread\" . (:foreground \"green\"))
+ (setq notmuch-search-line-faces \\='((\"unread\" . (:foreground \"green\"))
                                    (\"deleted\" . (:foreground \"red\"
                                                  :background \"blue\"))))
 
@@ -1076,7 +1076,7 @@ current search results AND the additional query string provided."
 Runs a new search matching only messages that match both the
 current search results AND that are tagged with the given tag."
   (interactive
-   (list (notmuch-select-tag-with-completion "Filter by tag: ")))
+   (list (notmuch-select-tag-with-completion "Filter by tag: " notmuch-search-query-string)))
   (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first))
 
 ;;;###autoload
diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el
new file mode 100644 (file)
index 0000000..2225aef
--- /dev/null
@@ -0,0 +1,85 @@
+;;; rstdoc.el --- help generate documentation from docstrings -*-lexical-binding: t-*-
+
+;; Copyright (C) 2018 David Bremner
+
+;; Author: David Bremner <david@tethera.net>
+;; Created: 26 May 2018
+;; Keywords: emacs lisp, documentation
+;; Homepage: https://notmuchmail.org
+
+;; This file is not part of GNU Emacs.
+
+;; rstdoc.el 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; rstdoc.el 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 rstdoc.el.  If not, see <https://www.gnu.org/licenses/>.
+;;
+
+;;; Commentary:
+;;
+
+;; Rstdoc provides a facility to extract all of the docstrings defined in
+;; an elisp source file. Usage:
+;;
+;; emacs -Q --batch -L . -l rstdoc -f rstdoc-batch-extract foo.el foo.rsti
+
+;;; Code:
+
+(provide 'rstdoc)
+
+(defun rstdoc-batch-extract ()
+  "Extract docstrings to and from the files on the command line"
+  (apply #'rstdoc-extract command-line-args-left))
+
+(defun rstdoc-extract (in-file out-file)
+  "Write docstrings from IN-FILE to OUT-FILE"
+  (load-file in-file)
+  (let* ((definitions (cdr (assoc (expand-file-name in-file) load-history)))
+        (doc-hash (make-hash-table :test 'eq)))
+    (mapc
+     (lambda (elt)
+       (let ((pair
+             (pcase elt
+               (`(defun . ,name) (cons name (documentation name)))
+               (`(,_ . ,_)  nil)
+               (sym (cons sym (get sym 'variable-documentation))))))
+        (when (and pair (cdr pair))
+          (puthash (car pair) (cdr pair) doc-hash))))
+     definitions)
+    (with-temp-buffer
+      (maphash
+       (lambda (key val)
+        (rstdoc--insert-docstring key val))
+       doc-hash)
+      (write-region (point-min) (point-max) out-file))))
+
+(defun rstdoc--insert-docstring (symbol docstring)
+  (insert (format "\n.. |docstring::%s| replace::\n" symbol))
+  (insert (replace-regexp-in-string "^" "    " (rstdoc--rst-quote-string docstring)))
+  (insert "\n"))
+
+(defvar rst--escape-alist
+  '( ("\\\\='" . "\\\\'")
+     ("\\([^\\]\\)'" . "\\1`")
+     ("^[[:space:]\t]*$" . "|br|")
+     ("^[[:space:]\t]" . "|indent| "))
+    "list of (regex . replacement) pairs")
+
+(defun rstdoc--rst-quote-string (str)
+  (with-temp-buffer
+    (insert str)
+    (dolist (pair rst--escape-alist)
+      (goto-char (point-min))
+      (while (re-search-forward (car pair) nil t)
+       (replace-match (cdr pair))))
+    (buffer-substring (point-min) (point-max))))
+
+;;; rstdoc.el ends here
diff --git a/emacs/rstdoc.rsti b/emacs/rstdoc.rsti
new file mode 100644 (file)
index 0000000..a449b58
--- /dev/null
@@ -0,0 +1,21 @@
+.. -*- rst -*-
+
+.. |br| replace:: |br-texinfo| |br-html|
+       
+.. |br-texinfo| raw:: texinfo
+
+   @* @*
+
+.. |br-html| raw:: html
+
+   <br /><br />
+
+.. |indent| replace:: |indent-texinfo| |indent-html|
+
+.. |indent-texinfo| raw:: texinfo
+
+   @* @ @ @ @
+
+.. |indent-html| raw:: html
+
+   <br />&nbsp;&nbsp;&nbsp;&nbsp;
index f673c0a27385803a684540b6ce0078ad453f6f92..480d93817bb627e6afb2c1ef5cc8fe7093355945 100644 (file)
@@ -19,6 +19,7 @@
 #include <stdbool.h>
 
 #include "gmime-filter-reply.h"
+#include "notmuch-client.h"
 
 /**
  * SECTION: gmime-filter-reply
@@ -29,7 +30,7 @@
  **/
 
 
-static void g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass);
+static void g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass, void *class_data);
 static void g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass);
 static void g_mime_filter_reply_finalize (GObject *object);
 
@@ -50,16 +51,16 @@ g_mime_filter_reply_get_type (void)
 
        if (!type) {
                static const GTypeInfo info = {
-                       sizeof (GMimeFilterReplyClass),
-                       NULL, /* base_class_init */
-                       NULL, /* base_class_finalize */
-                       (GClassInitFunc) g_mime_filter_reply_class_init,
-                       NULL, /* class_finalize */
-                       NULL, /* class_data */
-                       sizeof (GMimeFilterReply),
-                       0,    /* n_preallocs */
-                       (GInstanceInitFunc) g_mime_filter_reply_init,
-                       NULL    /* value_table */
+                       .class_size = sizeof (GMimeFilterReplyClass),
+                       .base_init = NULL,
+                       .base_finalize = NULL,
+                       .class_init = (GClassInitFunc) g_mime_filter_reply_class_init,
+                       .class_finalize = NULL,
+                       .class_data = NULL,
+                       .instance_size = sizeof (GMimeFilterReply),
+                       .n_preallocs = 0,
+                       .instance_init = (GInstanceInitFunc) g_mime_filter_reply_init,
+                       .value_table = NULL,
                };
 
                type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0);
@@ -70,7 +71,7 @@ g_mime_filter_reply_get_type (void)
 
 
 static void
-g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass)
+g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass, unused (void *class_data))
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
        GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
index 9cffd9f91d5a6cecdc736c8f3564cf5d6361b657..320be6c5ed20d1e2114ae88ed1d37b92666a0b12 100644 (file)
@@ -31,7 +31,7 @@ notmuch_built_with (const char *name)
     } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
        return HAVE_XAPIAN_DB_RETRY_LOCK;
     } else if (STRNCMP_LITERAL (name, "session_key") == 0) {
-       return HAVE_GMIME_SESSION_KEYS;
+       return true;
     } else {
        return false;
     }
index a499b2594be44d5a12ae671e668f5ab1f6b9c905..293f2db42a31ff2cd0dbc73ff9775c53d8de85d5 100644 (file)
@@ -108,6 +108,12 @@ enum _notmuch_features {
      *
      * Introduced: version 3. */
     NOTMUCH_FEATURE_LAST_MOD = 1 << 6,
+
+    /* If set, unprefixed terms are stored only for the message body,
+     * not for headers.
+     *
+     * Introduced: version 3. */
+    NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY = 1 << 7,
 };
 
 /* In C++, a named enum is its own type, so define bitwise operators
index 9cf8062cbe7caa2c5f7ecd714ca934c7f96b1e76..1753117f99361b8f1fe25cacd1152dc25694e446 100644 (file)
@@ -63,14 +63,19 @@ typedef struct {
  * We currently have three different types of documents (mail, ghost,
  * and directory) and also some metadata.
  *
+ * There are two kinds of prefixes used in notmuch. There are the
+ * human friendly 'prefix names' like "thread:", which are also used
+ * in the query parser, and the actual prefix terms in the database
+ * (e.g. "G"). The correspondence is maintained in the file scope data
+ * structure 'prefix_table'.
+ *
  * Mail document
  * -------------
  * A mail document is associated with a particular email message. It
- * is stored in one or more files on disk (though only one has its
- * content indexed) and is uniquely identified  by its "id" field
- * (which is generally the message ID). It is indexed with the
- * following prefixed terms which the database uses to construct
- * threads, etc.:
+ * is stored in one or more files on disk and is uniquely identified
+ * by its "id" field (which is generally the message ID). It is
+ * indexed with the following prefixed terms which the database uses
+ * to construct threads, etc.:
  *
  *    Single terms of given prefix:
  *
@@ -117,11 +122,16 @@ typedef struct {
  *     LAST_MOD:       The revision number as of the last tag or
  *                     filename change.
  *
- * In addition, terms from the content of the message are added with
- * "from", "to", "attachment", and "subject" prefixes for use by the
- * user in searching. Similarly, terms from the path of the mail
- * message are added with "folder" and "path" prefixes. But the
- * database doesn't really care itself about any of these.
+ * The prefixed terms described above are also searchable without an
+ * explicit field name, but as of notmuch 0.29 this is due to
+ * query-parser setup, not extra terms in the database.  In addition,
+ * terms from the content of the message are added without a prefix
+ * for use by the user in searching. Note that the prefix name "body"
+ * is used to refer to the empty prefix string in the database.
+ *
+ * The path of the containing folder is added with the "folder" prefix
+ * (see _notmuch_message_add_folder_terms).  Sub-paths of the the path
+ * of the mail message are added with the "path" prefix.
  *
  * The data portion of a mail document is empty.
  *
@@ -259,6 +269,8 @@ prefix_t prefix_table[] = {
     { "directory",             "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
     { "file-direntry",         "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
     { "directory-direntry",    "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "body",                  "",             NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC},
     { "thread",                        "G",            NOTMUCH_FIELD_EXTERNAL |
                                                NOTMUCH_FIELD_PROCESSOR },
     { "tag",                   "K",            NOTMUCH_FIELD_EXTERNAL |
@@ -302,6 +314,8 @@ prefix_t prefix_table[] = {
 static void
 _setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
 {
+    if (prefix->prefix)
+       notmuch->query_parser->add_prefix ("",prefix->prefix);
     if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
        notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
     else
@@ -326,6 +340,8 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
                                            *notmuch->query_parser, notmuch))->release ();
 
        /* we treat all field-processor fields as boolean in order to get the raw input */
+       if (prefix->prefix)
+           notmuch->query_parser->add_prefix ("",prefix->prefix);
        notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
     } else {
        _setup_query_field_default (prefix, notmuch);
@@ -383,6 +399,10 @@ static const struct {
       "indexed MIME types", "w"},
     { NOTMUCH_FEATURE_LAST_MOD,
       "modification tracking", "w"},
+    /* Existing databases will work fine for all queries not involving
+     * 'body:' */
+    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
+      "index body and headers separately", "w"},
 };
 
 const char *
@@ -656,6 +676,7 @@ notmuch_database_create_verbose (const char *path,
      * new databases have them. */
     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
+    notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
 
     status = notmuch_database_upgrade (notmuch, NULL, NULL);
     if (status) {
@@ -859,7 +880,7 @@ notmuch_database_open_verbose (const char *path,
 
     /* Initialize gmime */
     if (! initialized) {
-       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+       g_mime_init ();
        initialized = 1;
     }
 
index 3f694387c36c2cc21c1509881f7596e3b7980b14..418224880a2ff9a9b9dc565e9182a8a9538097c1 100644 (file)
@@ -142,7 +142,8 @@ static void filter_reset (GMimeFilter *filter);
 static GMimeFilterClass *parent_class = NULL;
 
 static void
-notmuch_filter_discard_non_term_class_init (NotmuchFilterDiscardNonTermClass *klass)
+notmuch_filter_discard_non_term_class_init (NotmuchFilterDiscardNonTermClass *klass,
+                                           unused (void *class_data))
 {
     GObjectClass *object_class = G_OBJECT_CLASS (klass);
     GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
@@ -246,16 +247,16 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type)
 
     if (!type) {
        static const GTypeInfo info = {
-           sizeof (NotmuchFilterDiscardNonTermClass),
-           NULL, /* base_class_init */
-           NULL, /* base_class_finalize */
-           (GClassInitFunc) notmuch_filter_discard_non_term_class_init,
-           NULL, /* class_finalize */
-           NULL, /* class_data */
-           sizeof (NotmuchFilterDiscardNonTerm),
-           0,    /* n_preallocs */
-           NULL, /* instance_init */
-           NULL  /* value_table */
+           .class_size = sizeof (NotmuchFilterDiscardNonTermClass),
+           .base_init = NULL,
+           .base_finalize = NULL,
+           .class_init = (GClassInitFunc) notmuch_filter_discard_non_term_class_init,
+           .class_finalize = NULL,
+           .class_data = NULL,
+           .instance_size = sizeof (NotmuchFilterDiscardNonTerm),
+           .n_preallocs = 0,
+           .instance_init = NULL,
+           .value_table = NULL,
        };
 
        type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info, (GTypeFlags) 0);
@@ -356,7 +357,7 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part)
 {
     GMimeContentType *content_type = g_mime_object_get_content_type (part);
     if (content_type) {
-       char *mime_string = g_mime_content_type_to_string (content_type);
+       char *mime_string = g_mime_content_type_get_mime_type (content_type);
        if (mime_string) {
            _notmuch_message_gen_terms (message, "mimetype", mime_string);
            g_free (mime_string);
@@ -366,7 +367,6 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part)
 
 static void
 _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
-                           GMimeContentType *content_type,
                            GMimeMultipartEncrypted *part);
 
 /* Callback to generate terms for each mime part of a message. */
@@ -391,7 +391,6 @@ _index_mime_part (notmuch_message_t *message,
     }
 
     _index_content_type (message, part);
-    content_type = g_mime_object_get_content_type (part);
 
     if (GMIME_IS_MULTIPART (part)) {
        GMimeMultipart *multipart = GMIME_MULTIPART (part);
@@ -420,7 +419,6 @@ _index_mime_part (notmuch_message_t *message,
                                     g_mime_multipart_get_part (multipart, i));
                if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
                    _index_encrypted_mime_part(message, indexopts,
-                                              content_type,
                                               GMIME_MULTIPART_ENCRYPTED (part));
                } else {
                    if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
@@ -475,6 +473,7 @@ _index_mime_part (notmuch_message_t *message,
 
     filter = g_mime_stream_filter_new (stream);
 
+    content_type = g_mime_object_get_content_type (part);
     discard_non_term_filter = notmuch_filter_discard_non_term_new (content_type);
 
     g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter),
@@ -494,7 +493,7 @@ _index_mime_part (notmuch_message_t *message,
        }
     }
 
-    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+    wrapper = g_mime_part_get_content (GMIME_PART (part));
     if (wrapper)
        g_mime_data_wrapper_write_to_stream (wrapper, filter);
 
@@ -517,7 +516,6 @@ _index_mime_part (notmuch_message_t *message,
 static void
 _index_encrypted_mime_part (notmuch_message_t *message,
                            notmuch_indexopts_t *indexopts,
-                           g_mime_3_unused(GMimeContentType *content_type),
                            GMimeMultipartEncrypted *encrypted_data)
 {
     notmuch_status_t status;
@@ -530,29 +528,11 @@ _index_encrypted_mime_part (notmuch_message_t *message,
 
     notmuch = notmuch_message_get_database (message);
 
-    GMimeCryptoContext* crypto_ctx = NULL;
-#if (GMIME_MAJOR_VERSION < 3)
-    {
-       const char *protocol = NULL;
-       protocol = g_mime_content_type_get_parameter (content_type, "protocol");
-       status = _notmuch_crypto_get_gmime_ctx_for_protocol (&(indexopts->crypto),
-                                                        protocol, &crypto_ctx);
-       if (status) {
-           _notmuch_database_log (notmuch, "Warning: setup failed for decrypting "
-                                  "during indexing. (%d)\n", status);
-           status = notmuch_message_add_property (message, "index.decryption", "failure");
-           if (status)
-               _notmuch_database_log_append (notmuch, "failed to add index.decryption "
-                                             "property (%d)\n", status);
-           return;
-       }
-    }
-#endif
     bool attempted = false;
     GMimeDecryptResult *decrypt_result = NULL;
-    bool get_sk = (HAVE_GMIME_SESSION_KEYS && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
+    bool get_sk = (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
     clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts),
-                                    message, crypto_ctx, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
+                                    message, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
     if (!attempted)
        return;
     if (err || !clear) {
@@ -573,7 +553,6 @@ _index_encrypted_mime_part (notmuch_message_t *message,
        return;
     }
     if (decrypt_result) {
-#if HAVE_GMIME_SESSION_KEYS
        if (get_sk) {
            status = notmuch_message_add_property (message, "session-key",
                                                   g_mime_decrypt_result_get_session_key (decrypt_result));
@@ -581,7 +560,6 @@ _index_encrypted_mime_part (notmuch_message_t *message,
                _notmuch_database_log (notmuch, "failed to add session-key "
                                       "property (%d)\n", status);
        }
-#endif
        g_object_unref (decrypt_result);
     }
     _index_mime_part (message, indexopts, clear);
@@ -612,7 +590,6 @@ _notmuch_message_index_file (notmuch_message_t *message,
     addresses = g_mime_message_get_from (mime_message);
     if (addresses) {
        _index_address_list (message, "from", addresses);
-       g_mime_2_6_unref (addresses);
     }
 
     addresses = g_mime_message_get_all_recipients (mime_message);
index 8f0dbbda872570e184e2e4f8dc1b8800f04fc4e5..5085506700640f2f6f541dc71a8f9db306d7123c 100644 (file)
@@ -27,8 +27,8 @@
 #include <glib.h> /* GHashTable */
 
 struct _notmuch_message_file {
-    /* File object */
-    FILE *file;
+    /* open stream to (possibly gzipped) file */
+    GMimeStream *stream;
     char *filename;
 
     /* Cache for decoded headers */
@@ -46,9 +46,6 @@ _notmuch_message_file_destructor (notmuch_message_file_t *message)
     if (message->message)
        g_object_unref (message->message);
 
-    if (message->file)
-       fclose (message->file);
-
     return 0;
 }
 
@@ -64,15 +61,14 @@ _notmuch_message_file_open_ctx (notmuch_database_t *notmuch,
     if (unlikely (message == NULL))
        return NULL;
 
-    /* Only needed for error messages during parsing. */
     message->filename = talloc_strdup (message, filename);
     if (message->filename == NULL)
        goto FAIL;
 
     talloc_set_destructor (message, _notmuch_message_file_destructor);
 
-    message->file = fopen (filename, "r");
-    if (message->file == NULL)
+    message->stream = g_mime_stream_gzfile_open (filename);
+    if (message->stream == NULL)
        goto FAIL;
 
     return message;
@@ -105,17 +101,17 @@ _notmuch_message_file_close (notmuch_message_file_t *message)
 }
 
 static bool
-_is_mbox (FILE *file)
+_is_mbox (GMimeStream *stream)
 {
     char from_buf[5];
     bool ret = false;
 
     /* Is this mbox? */
-    if (fread (from_buf, sizeof (from_buf), 1, file) == 1 &&
+    if (g_mime_stream_read (stream, from_buf, sizeof (from_buf)) == sizeof(from_buf) &&
        strncmp (from_buf, "From ", 5) == 0)
        ret = true;
 
-    rewind (file);
+    g_mime_stream_reset (stream);
 
     return ret;
 }
@@ -123,7 +119,6 @@ _is_mbox (FILE *file)
 notmuch_status_t
 _notmuch_message_file_parse (notmuch_message_file_t *message)
 {
-    GMimeStream *stream;
     GMimeParser *parser;
     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
     static int initialized = 0;
@@ -132,10 +127,10 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
     if (message->message)
        return NOTMUCH_STATUS_SUCCESS;
 
-    is_mbox = _is_mbox (message->file);
+    is_mbox = _is_mbox (message->stream);
 
     if (! initialized) {
-       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+       g_mime_init ();
        initialized = 1;
     }
 
@@ -144,15 +139,10 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
     if (! message->headers)
        return NOTMUCH_STATUS_OUT_OF_MEMORY;
 
-    stream = g_mime_stream_file_new (message->file);
-
-    /* We'll own and fclose the FILE* ourselves. */
-    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), false);
-
-    parser = g_mime_parser_new_with_stream (stream);
+    parser = g_mime_parser_new_with_stream (message->stream);
     g_mime_parser_set_scan_from (parser, is_mbox);
 
-    message->message = g_mime_parser_construct_message (parser);
+    message->message = g_mime_parser_construct_message (parser, NULL);
     if (! message->message) {
        status = NOTMUCH_STATUS_FILE_NOT_EMAIL;
        goto DONE;
@@ -167,7 +157,7 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
     }
 
   DONE:
-    g_object_unref (stream);
+    g_mime_stream_reset (message->stream);
     g_object_unref (parser);
 
     if (status) {
@@ -179,7 +169,6 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
            message->message = NULL;
        }
 
-       rewind (message->file);
     }
 
     return status;
@@ -212,7 +201,7 @@ static char *
 _extend_header (char *combined, const char *value) {
     char *decoded;
 
-    decoded = g_mime_utils_header_decode_text (value);
+    decoded = g_mime_utils_header_decode_text (NULL, value);
     if (! decoded) {
        if (combined) {
            g_free (combined);
@@ -238,47 +227,6 @@ _extend_header (char *combined, const char *value) {
     return combined;
 }
 
-#if (GMIME_MAJOR_VERSION < 3)
-static char *
-_notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
-                                          const char *header)
-{
-    GMimeHeaderList *headers;
-    GMimeHeaderIter *iter;
-    char *combined = NULL;
-
-    headers = g_mime_object_get_header_list (GMIME_OBJECT (message->message));
-    if (! headers)
-       return NULL;
-
-    iter = g_mime_header_iter_new ();
-    if (! iter)
-       return NULL;
-
-    if (! g_mime_header_list_get_iter (headers, iter))
-       goto DONE;
-
-    do {
-       const char *value;
-       if (strcasecmp (g_mime_header_iter_get_name (iter), header) != 0)
-           continue;
-
-       /* Note that GMime retains ownership of value... */
-       value = g_mime_header_iter_get_value (iter);
-
-       combined = _extend_header (combined, value);
-    } while (g_mime_header_iter_next (iter));
-
-    /* Return empty string for non-existing headers. */
-    if (! combined)
-       combined = g_strdup ("");
-
-  DONE:
-    g_mime_header_iter_free (iter);
-
-    return combined;
-}
-#else
 static char *
 _notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
                                           const char *header)
@@ -310,7 +258,6 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
 
     return combined;
 }
-#endif
 
 const char *
 _notmuch_message_file_get_header (notmuch_message_file_t *message,
@@ -338,7 +285,7 @@ _notmuch_message_file_get_header (notmuch_message_file_t *message,
        value = g_mime_object_get_header (GMIME_OBJECT (message->message),
                                          header);
        if (value)
-           decoded = g_mime_utils_header_decode_text (value);
+           decoded = g_mime_utils_header_decode_text (NULL, value);
        else
            decoded = g_strdup ("");
     }
index 6f2f634512453770a358b374ff1e3a3073888d0e..38a48933a9ba1bd69f3074a0c9d7b97ef66cd481 100644 (file)
@@ -1419,8 +1419,9 @@ _notmuch_message_add_term (notmuch_message_t *message,
 }
 
 /* Parse 'text' and add a term to 'message' for each parsed word. Each
- * term will be added both prefixed (if prefix_name is not NULL) and
- * also non-prefixed). */
+ * term will be added with the appropriate prefix if prefix_name is
+ * non-NULL.
+ */
 notmuch_private_status_t
 _notmuch_message_gen_terms (notmuch_message_t *message,
                            const char *prefix_name,
@@ -1432,22 +1433,17 @@ _notmuch_message_gen_terms (notmuch_message_t *message,
        return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
 
     term_gen->set_document (message->doc);
+    term_gen->set_termpos (message->termpos);
 
     if (prefix_name) {
-       const char *prefix = _find_prefix (prefix_name);
-
-       term_gen->set_termpos (message->termpos);
-       term_gen->index_text (text, 1, prefix);
-       /* Create a gap between this an the next terms so they don't
-        * appear to be a phrase. */
-       message->termpos = term_gen->get_termpos () + 100;
-
        _notmuch_message_invalidate_metadata (message, prefix_name);
+       term_gen->index_text (text, 1, _find_prefix (prefix_name));
+    } else {
+       term_gen->index_text (text);
     }
 
-    term_gen->set_termpos (message->termpos);
-    term_gen->index_text (text);
-    /* Create a term gap, as above. */
+    /* Create a gap between this an the next terms so they don't
+     * appear to be a phrase. */
     message->termpos = term_gen->get_termpos () + 100;
 
     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
index 247f6ad71ebf1223b32f1c2b5e226261a309ffc1..24708f3c5947c6819570f493bb249284e40407cc 100644 (file)
@@ -889,10 +889,12 @@ notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag);
  *     notmuch_query_t *query;
  *     notmuch_threads_t *threads;
  *     notmuch_thread_t *thread;
+ *     notmuch_status_t stat;
  *
  *     query = notmuch_query_create (database, query_string);
  *
- *     for (threads = notmuch_query_search_threads (query);
+ *     for (stat = notmuch_query_search_threads (query, &threads);
+ *         stat == NOTMUCH_STATUS_SUCCESS &&
  *          notmuch_threads_valid (threads);
  *          notmuch_threads_move_to_next (threads))
  *     {
@@ -1400,6 +1402,8 @@ notmuch_message_get_thread_id (notmuch_message_t *message);
  * If there are no replies to 'message', this function will return
  * NULL. (Note that notmuch_messages_valid will accept that NULL
  * value as legitimate, and simply return FALSE for it.)
+ *
+ * The returned list will be destroyed when the thread is destroyed.
  */
 notmuch_messages_t *
 notmuch_message_get_replies (notmuch_message_t *message);
index 084bc8c019999cefe7b7d0dff01d27463d6cc4a7..5d4cf80aeea6f145630002c45ac31c4bda5f526c 100644 (file)
@@ -35,9 +35,9 @@ compile_regex (regex_t &regexp, const char *str)
     if (err != 0) {
        size_t len = regerror (err, &regexp, NULL, 0);
        char *buffer = new char[len];
-       std::string msg;
+       std::string msg = "Regexp error: ";
        (void) regerror (err, &regexp, buffer, len);
-       msg.assign (buffer, len);
+       msg.append (buffer, len);
        delete[] buffer;
 
        throw Xapian::QueryParserError (msg);
@@ -194,7 +194,7 @@ RegexpFieldProcessor::operator() (const std::string & str)
             * phrase parsing, when possible */
            std::string query_str;
 
-           if (str.find (' ') != std::string::npos)
+           if (*str.rbegin () != '*' || str.find (' ') != std::string::npos)
                query_str = '"' + str + '"';
            else
                query_str = str;
index 47c9066406e084c51735b0dc6960055904763659..c9c9398fd9d0788e2cebe86d43848ebc82a5a412 100644 (file)
@@ -284,7 +284,7 @@ _thread_add_message (notmuch_thread_t *thread,
 
     from = notmuch_message_get_header (message, "from");
     if (from)
-       list = internet_address_list_parse_string (from);
+       list = internet_address_list_parse (NULL, from);
 
     if (list) {
        address = internet_address_list_get_address (list, 0);
index 2a24e537c6a25fa4b79785f33062826666b63b88..e33336bbb32e8ed11cdc58f88369e675652bd250 100644 (file)
  *          Austin Clements <aclements@csail.mit.edu>
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
 #include "notmuch-client.h"
 
 /* Context that gets inherited from the root node. */
 typedef struct mime_node_context {
     /* Per-message resources.  These are allocated internally and must
      * be destroyed. */
-    FILE *file;
     GMimeStream *stream;
     GMimeParser *parser;
     GMimeMessage *mime_message;
@@ -48,9 +51,6 @@ _mime_node_context_free (mime_node_context_t *res)
     if (res->stream)
        g_object_unref (res->stream);
 
-    if (res->file)
-       fclose (res->file);
-
     return 0;
 }
 
@@ -62,6 +62,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
     mime_node_context_t *mctx;
     mime_node_t *root;
     notmuch_status_t status;
+    int fd;
 
     root = talloc_zero (ctx, mime_node_t);
     if (root == NULL) {
@@ -80,8 +81,8 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
     talloc_set_destructor (mctx, _mime_node_context_free);
 
     /* Fast path */
-    mctx->file = fopen (filename, "r");
-    if (! mctx->file) {
+    fd = open (filename, O_RDONLY);
+    if (fd == -1) {
        /* Slow path - for some reason the first file in the list is
         * not available anymore. This is clearly a problem in the
         * database, but we are not going to let this problem be a
@@ -92,13 +93,13 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
             notmuch_filenames_move_to_next (filenames))
        {
            filename = notmuch_filenames_get (filenames);
-           mctx->file = fopen (filename, "r");
-           if (mctx->file)
+           fd = open (filename, O_RDONLY);
+           if (fd != -1)
                break;
        }
 
        talloc_free (filenames);
-       if (! mctx->file) {
+       if (fd == -1) {
            /* Give up */
            fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
                status = NOTMUCH_STATUS_FILE_ERROR;
@@ -106,13 +107,12 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
            }
        }
 
-    mctx->stream = g_mime_stream_file_new (mctx->file);
+    mctx->stream = g_mime_stream_gzfile_new (fd);
     if (!mctx->stream) {
        fprintf (stderr, "Out of memory.\n");
        status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
-    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), false);
 
     mctx->parser = g_mime_parser_new_with_stream (mctx->stream);
     if (!mctx->parser) {
@@ -121,7 +121,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
        goto DONE;
     }
 
-    mctx->mime_message = g_mime_parser_construct_message (mctx->parser);
+    mctx->mime_message = g_mime_parser_construct_message (mctx->parser, NULL);
     if (!mctx->mime_message) {
        fprintf (stderr, "Failed to parse %s\n", filename);
        status = NOTMUCH_STATUS_FILE_ERROR;
@@ -149,7 +149,7 @@ DONE:
     return status;
 }
 
-/* Signature list destructor (GMime 2.6) */
+/* Signature list destructor */
 static int
 _signature_list_free (GMimeSignatureList **proxy)
 {
@@ -157,7 +157,7 @@ _signature_list_free (GMimeSignatureList **proxy)
     return 0;
 }
 
-/* Set up signature list destructor (GMime 2.6) */
+/* Set up signature list destructor */
 static void
 set_signature_list_destructor (mime_node_t *node)
 {
@@ -168,16 +168,15 @@ set_signature_list_destructor (mime_node_t *node)
     }
 }
 
-/* Verify a signed mime node (GMime 2.6) */
+/* Verify a signed mime node */
 static void
-node_verify (mime_node_t *node, GMimeObject *part,
-            g_mime_3_unused(GMimeCryptoContext *cryptoctx))
+node_verify (mime_node_t *node, GMimeObject *part)
 {
     GError *err = NULL;
 
     node->verify_attempted = true;
     node->sig_list = g_mime_multipart_signed_verify
-       (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
+       (GMIME_MULTIPART_SIGNED (part), GMIME_ENCRYPT_NONE, &err);
 
     if (node->sig_list)
        set_signature_list_destructor (node);
@@ -189,10 +188,9 @@ node_verify (mime_node_t *node, GMimeObject *part,
        g_error_free (err);
 }
 
-/* Decrypt and optionally verify an encrypted mime node (GMime 2.6) */
+/* Decrypt and optionally verify an encrypted mime node */
 static void
-node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
-                        g_mime_3_unused(GMimeCryptoContext *cryptoctx))
+node_decrypt_and_verify (mime_node_t *node, GMimeObject *part)
 {
     GError *err = NULL;
     GMimeDecryptResult *decrypt_result = NULL;
@@ -209,7 +207,7 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
        node->decrypted_child = _notmuch_crypto_decrypt (&node->decrypt_attempted,
                                                         node->ctx->crypto->decrypt,
                                                         message,
-                                                        cryptoctx, encrypteddata, &decrypt_result, &err);
+                                                        encrypteddata, &decrypt_result, &err);
     }
     if (! node->decrypted_child) {
        fprintf (stderr, "Failed to decrypt part: %s\n",
@@ -218,17 +216,16 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
     }
 
     node->decrypt_success = true;
-    node->verify_attempted = true;
 
     if (decrypt_result) {
        /* This may be NULL if the part is not signed. */
        node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
        if (node->sig_list) {
+           node->verify_attempted = true;
            g_object_ref (node->sig_list);
            set_signature_list_destructor (node);
        }
 
-#if HAVE_GMIME_SESSION_KEYS
        if (node->ctx->crypto->decrypt == NOTMUCH_DECRYPT_TRUE && message) {
            notmuch_database_t *db = notmuch_message_get_database (message);
            const char *session_key = g_mime_decrypt_result_get_session_key (decrypt_result);
@@ -238,7 +235,6 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
                                      notmuch_message_add_property (message, "session-key",
                                                                    session_key));
        }
-#endif
        g_object_unref (decrypt_result);
     }
 
@@ -251,7 +247,6 @@ static mime_node_t *
 _mime_node_create (mime_node_t *parent, GMimeObject *part)
 {
     mime_node_t *node = talloc_zero (parent, mime_node_t);
-    GMimeCryptoContext *cryptoctx = NULL;
 
     /* Set basic node properties */
     node->part = part;
@@ -284,22 +279,6 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part)
        return NULL;
     }
 
-#if (GMIME_MAJOR_VERSION < 3)
-    if ((GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE))
-       || (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) {
-       GMimeContentType *content_type = g_mime_object_get_content_type (part);
-       const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol");
-       notmuch_status_t status;
-       status = _notmuch_crypto_get_gmime_ctx_for_protocol (node->ctx->crypto,
-                                                            protocol, &cryptoctx);
-       if (status) /* this is a warning, not an error */
-           fprintf (stderr, "Warning: %s (%s).\n", notmuch_status_to_string (status),
-                    protocol ? protocol : "NULL");
-       if (!cryptoctx)
-           return node;
-    }
-#endif
-
     /* Handle PGP/MIME parts */
     if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
        if (node->nchildren != 2) {
@@ -308,7 +287,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part)
                     "message (must be exactly 2)\n",
                     node->nchildren);
        } else {
-           node_decrypt_and_verify (node, part, cryptoctx);
+           node_decrypt_and_verify (node, part);
        }
     } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify) {
        if (node->nchildren != 2) {
@@ -317,7 +296,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part)
                     "(must be exactly 2)\n",
                     node->nchildren);
        } else {
-           node_verify (node, part, cryptoctx);
+           node_verify (node, part);
        }
     }
 
index 6c84ecc01af6cebb5f9df2325c024933e0c36cba..d762d3cc5cfc3a24bd17c98365672588f4913c5d 100644 (file)
@@ -276,15 +276,6 @@ void
 notmuch_config_set_database_path (notmuch_config_t *config,
                                  const char *database_path);
 
-#if (GMIME_MAJOR_VERSION < 3)
-const char *
-notmuch_config_get_crypto_gpg_path (notmuch_config_t *config);
-
-void
-notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
-                                 const char *gpg_path);
-#endif
-
 const char *
 notmuch_config_get_user_name (notmuch_config_t *config);
 
@@ -515,6 +506,6 @@ struct _notmuch_client_indexing_cli_choices {
 extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
 extern const notmuch_opt_desc_t  notmuch_shared_indexing_options [];
 notmuch_status_t
-notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, notmuch_config_t *config);
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch);
 
 #endif
index bf77cc9d43831460c63625a8fcef47a9f6464612..e029e3062846e9f3446c299841fac88be76f6b66 100644 (file)
@@ -104,19 +104,11 @@ static const char search_config_comment[] =
 static const char crypto_config_comment[] =
     " Cryptography related configuration\n"
     "\n"
-#if (GMIME_MAJOR_VERSION < 3)
-    " The following *deprecated* option is currently supported:\n"
-    "\n"
-    "\tgpg_path\n"
-    "\t\tbinary name or full path to invoke gpg.\n"
-    "\t\tNOTE: In a future build, this option will be ignored.\n"
-#else
     " The following old option is now ignored:\n"
     "\n"
     "\tgpgpath\n"
     "\t\tThis option was used by older builds of notmuch to choose\n"
     "\t\tthe version of gpg to use.\n"
-#endif
     "\t\tSetting $PATH is a better approach.\n";
 
 struct _notmuch_config {
@@ -470,12 +462,6 @@ notmuch_config_open (void *ctx,
        g_error_free (error);
     }
 
-#if (GMIME_MAJOR_VERSION < 3)
-    if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
-       notmuch_config_set_crypto_gpg_path (config, "gpg");
-    }
-#endif
-
     /* Whenever we know of configuration sections that don't appear in
      * the configuration file, we add some comments to help the user
      * understand what can be done. */
@@ -776,21 +762,6 @@ notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
                      &(config->search_exclude_tags));
 }
 
-#if (GMIME_MAJOR_VERSION < 3)
-const char *
-notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
-{
-    return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
-}
-
-void
-notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
-                             const char *gpg_path)
-{
-    _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
-}
-#endif
-
 
 /* Given a configuration item of the form <group>.<key> return the
  * component group and key. If any error occurs, print a message on
index d229c9dc7ac9fa28468436d6146ed3770cdb80cc..327470d4288ee30a290e92b5c4fa74558779c009 100644 (file)
@@ -550,7 +550,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_exit_if_unmatched_db_uuid (notmuch);
 
-    status = notmuch_process_shared_indexing_options (notmuch, config);
+    status = notmuch_process_shared_indexing_options (notmuch);
     if (status != NOTMUCH_STATUS_SUCCESS) {
        fprintf (stderr, "Error: Failed to process index options. (%s)\n",
                 notmuch_status_to_string (status));
index 6a54a1a169feb23aa0d860f66548e69d60e24dc0..184e9aa2189ab7d201f5e5ebc56db9ea9f2c4be6 100644 (file)
@@ -1198,7 +1198,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     if (notmuch == NULL)
        return EXIT_FAILURE;
 
-    status = notmuch_process_shared_indexing_options (notmuch, config);
+    status = notmuch_process_shared_indexing_options (notmuch);
     if (status != NOTMUCH_STATUS_SUCCESS) {
        fprintf (stderr, "Error: Failed to process index options. (%s)\n",
                 notmuch_status_to_string (status));
index d8589120cae94ed4fa4efa1c3cfdda9520d5697d..3139a8c665a0fb46bb6626a77c32f26c4ff5c60a 100644 (file)
@@ -71,6 +71,7 @@ reindex_query (notmuch_database_t *notmuch, const char *query_string,
        ret = notmuch_message_reindex(message, indexopts);
        if (ret != NOTMUCH_STATUS_SUCCESS)
            break;
+       notmuch_message_destroy (message);
     }
 
     if (!ret)
@@ -116,7 +117,7 @@ notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_exit_if_unmatched_db_uuid (notmuch);
 
-    status = notmuch_process_shared_indexing_options (notmuch, config);
+    status = notmuch_process_shared_indexing_options (notmuch);
     if (status != NOTMUCH_STATUS_SUCCESS) {
        fprintf (stderr, "Error: Failed to process index options. (%s)\n",
                 notmuch_status_to_string (status));
index 75cf7ecb5068534df678e37fd0a606cd6215739e..7f284229e38c02ff56e1c23433723b9470d8d9ab 100644 (file)
@@ -28,7 +28,7 @@ static void
 show_reply_headers (GMimeStream *stream, GMimeMessage *message)
 {
     /* Output RFC 2822 formatted (and RFC 2047 encoded) headers. */
-    if (g_mime_object_write_to_stream (GMIME_OBJECT(message), stream) < 0) {
+    if (g_mime_object_write_to_stream (GMIME_OBJECT(message), NULL, stream) < 0) {
        INTERNAL_ERROR("failed to write headers to stdout\n");
     }
 }
@@ -75,10 +75,10 @@ format_part_reply (GMimeStream *stream, mime_node_t *node)
                               GMIME_DISPOSITION_ATTACHMENT) == 0) {
            const char *filename = g_mime_part_get_filename (GMIME_PART (node->part));
            g_mime_stream_printf (stream, "Attachment: %s (%s)\n", filename,
-                                 g_mime_content_type_to_string (content_type));
+                                 g_mime_content_type_get_mime_type (content_type));
        } else {
            g_mime_stream_printf (stream, "Non-text part: %s\n",
-                                 g_mime_content_type_to_string (content_type));
+                                 g_mime_content_type_get_mime_type (content_type));
        }
     }
 
@@ -176,7 +176,7 @@ static unsigned int
 scan_address_list (InternetAddressList *list,
                   notmuch_config_t *config,
                   GMimeMessage *message,
-                  GMimeRecipientType type,
+                  GMimeAddressType type,
                   const char **user_from)
 {
     InternetAddress *address;
@@ -209,7 +209,7 @@ scan_address_list (InternetAddressList *list,
                if (user_from && *user_from == NULL)
                    *user_from = addr;
            } else if (message) {
-               g_mime_message_add_recipient (message, type, name, addr);
+               g_mime_message_add_mailbox (message, type, name, addr);
                n++;
            }
        }
@@ -285,8 +285,6 @@ static InternetAddressList *get_sender(GMimeMessage *message)
         */
        if (! reply_to_header_is_redundant (message, reply_to_list))
            return reply_to_list;
-
-       g_mime_2_6_unref (G_OBJECT (reply_to_list));
     }
 
     return g_mime_message_get_from (message);
@@ -325,15 +323,9 @@ add_recipients_from_message (GMimeMessage *reply,
                             GMimeMessage *message,
                             bool reply_all)
 {
-
-    /* There is a memory leak here with gmime-2.6 because get_sender
-     * returns a newly allocated list, while the others return
-     * references to libgmime owned data. This leak will be fixed with
-     * the transition to gmime-3.0.
-     */
     struct {
        InternetAddressList * (*get_header)(GMimeMessage *message);
-       GMimeRecipientType recipient_type;
+       GMimeAddressType recipient_type;
     } reply_to_map[] = {
        { get_sender,   GMIME_ADDRESS_TYPE_TO },
        { get_to,       GMIME_ADDRESS_TYPE_TO },
@@ -369,6 +361,14 @@ add_recipients_from_message (GMimeMessage *reply,
        }
     }
 
+    /* If no recipients were added but we found one of the user's
+     * addresses to use as a from address then the message is from the
+     * user to the user - add the discovered from address to the list
+     * of recipients so that the reply goes back to the user.
+     */
+    if (n == 0 && from_addr)
+       g_mime_message_add_mailbox (reply, GMIME_ADDRESS_TYPE_TO, NULL, from_addr);
+
     return from_addr;
 }
 
@@ -541,7 +541,7 @@ create_reply_message(void *ctx,
     in_reply_to = talloc_asprintf (ctx, "<%s>",
                                   notmuch_message_get_message_id (message));
 
-    g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to);
+    g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to, NULL);
 
     orig_references = notmuch_message_get_header (message, "references");
     if (orig_references && *orig_references)
@@ -550,7 +550,7 @@ create_reply_message(void *ctx,
     else
        references = talloc_strdup (ctx, in_reply_to);
 
-    g_mime_object_set_header (GMIME_OBJECT (reply), "References", references);
+    g_mime_object_set_header (GMIME_OBJECT (reply), "References", references, NULL);
 
     from_addr = add_recipients_from_message (reply, config,
                                             mime_message, reply_all);
@@ -589,13 +589,13 @@ create_reply_message(void *ctx,
     from_addr = talloc_asprintf (ctx, "%s <%s>",
                                 notmuch_config_get_user_name (config),
                                 from_addr);
-    g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr);
+    g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr, NULL);
 
     subject = notmuch_message_get_header (message, "subject");
     if (subject) {
        if (strncasecmp (subject, "Re:", 3))
            subject = talloc_asprintf (ctx, "Re: %s", subject);
-       g_mime_message_set_subject (reply, subject);
+       g_mime_message_set_subject (reply, subject, NULL);
     }
 
     return reply;
@@ -746,10 +746,6 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
        return EXIT_FAILURE;
     }
 
-#if (GMIME_MAJOR_VERSION < 3)
-    params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config);
-#endif
-
     if (notmuch_database_open (notmuch_config_get_database_path (config),
                               NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
        return EXIT_FAILURE;
index 8f467db43cf083482cb04fae411b530e1504ec52..e2dee4181b7400907723177e1cba687c8a9f21de 100644 (file)
@@ -364,7 +364,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 
     /* name_addr has the name part quoted if necessary. Compare
      * 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
-    name_addr = internet_address_to_string (ia, false);
+    name_addr = internet_address_to_string (ia, NULL, false);
 
     if (format->is_text_printer) {
        if (ctx->output & OUTPUT_COUNT) {
@@ -446,7 +446,7 @@ process_address_header (const search_context_t *ctx, const char *value)
     if (value == NULL)
        return;
 
-    list = internet_address_list_parse_string (value);
+    list = internet_address_list_parse (NULL, value);
     if (list == NULL)
        return;
 
index 98b8535265f997c5c7651527a361ac2bbe92a6d5..b95fc389cf009eec354a44bc09787e7009083fad 100644 (file)
@@ -21,6 +21,7 @@
 #include "notmuch-client.h"
 #include "gmime-filter-reply.h"
 #include "sprinter.h"
+#include "zlib-extra.h"
 
 static const char *
 _get_tags_as_string (const void *ctx, notmuch_message_t *message)
@@ -146,7 +147,7 @@ _extract_email_address (const void *ctx, const char *from)
     InternetAddressMailbox *mailbox;
     const char *email = "MAILER-DAEMON";
 
-    addresses = internet_address_list_parse_string (from);
+    addresses = internet_address_list_parse (NULL, from);
 
     /* Bail if there is no address here. */
     if (addresses == NULL || internet_address_list_length (addresses) < 1)
@@ -278,14 +279,14 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
 
     if (! g_mime_content_type_is_type (content_type, "text", "*"))
        INTERNAL_ERROR ("Illegal request to format non-text part (%s) as text.",
-                       g_mime_content_type_to_string (content_type));
+                       g_mime_content_type_get_mime_type (content_type));
 
     if (stream_out == NULL)
        return;
 
     charset = g_mime_object_get_content_type_parameter (part, "charset");
     charset = charset ? g_mime_charset_canon_name (charset) : NULL;
-    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+    wrapper = g_mime_part_get_content (GMIME_PART (part));
     if (wrapper && charset && !g_ascii_strncasecmp (charset, "iso-8859-", 9)) {
        GMimeStream *null_stream = NULL;
        GMimeStream *null_stream_filter = NULL;
@@ -309,7 +310,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
     }
 
     stream_filter = g_mime_stream_filter_new (stream_out);
-    crlf_filter = g_mime_filter_crlf_new (false, false);
+    crlf_filter = g_mime_filter_dos2unix_new (false);
     g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
                             crlf_filter);
     g_object_unref (crlf_filter);
@@ -363,13 +364,13 @@ signature_status_to_string (GMimeSignatureStatus status)
 
 /* Print signature flags */
 struct key_map_struct {
-    GMimeSignatureError bit;
+    GMimeSignatureStatus bit;
     const char * string;
 };
 
 static void
 do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
-                           unsigned int array_map_len, GMimeSignatureError errors) {
+                           unsigned int array_map_len, GMimeSignatureStatus errors) {
     sp->map_key (sp, "errors");
     sp->begin_map (sp);
 
@@ -383,30 +384,10 @@ do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
     sp->end (sp);
 }
 
-#if (GMIME_MAJOR_VERSION < 3)
 static void
 format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
 {
-    GMimeSignatureError errors = g_mime_signature_get_errors (signature);
-
-    if (errors == GMIME_SIGNATURE_ERROR_NONE)
-       return;
-
-    struct key_map_struct key_map[] = {
-       { GMIME_SIGNATURE_ERROR_EXPSIG, "sig-expired" },
-       { GMIME_SIGNATURE_ERROR_NO_PUBKEY, "key-missing"},
-       { GMIME_SIGNATURE_ERROR_EXPKEYSIG, "key-expired"},
-       { GMIME_SIGNATURE_ERROR_REVKEYSIG, "key-revoked"},
-       { GMIME_SIGNATURE_ERROR_UNSUPP_ALGO, "alg-unsupported"},
-    };
-
-    do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors);
-}
-#else
-static void
-format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
-{
-    GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+    GMimeSignatureStatus errors = g_mime_signature_get_status (signature);
 
     if (!(errors & GMIME_SIGNATURE_STATUS_ERROR_MASK))
        return;
@@ -425,17 +406,14 @@ format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
 
     do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors);
 }
-#endif
 
-/* Signature status sprinter (GMime 2.6) */
+/* Signature status sprinter */
 static void
-format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist)
 {
     /* Any changes to the JSON or S-Expression format should be
      * reflected in the file devel/schemata. */
 
-    GMimeSignatureList *siglist = node->sig_list;
-
     sp->begin_list (sp);
 
     if (!siglist) {
@@ -488,7 +466,7 @@ format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
        }
 
        if (notmuch_format_version <= 3) {
-           GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+           GMimeSignatureStatus errors = g_mime_signature_get_status (signature);
            if (g_mime_signature_status_error (errors)) {
                sp->map_key (sp, "errors");
                sp->integer (sp, errors);
@@ -547,7 +525,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
        if (cid)
            g_mime_stream_printf (stream, ", Content-id: %s", cid);
 
-       content_string = g_mime_content_type_to_string (content_type);
+       content_string = g_mime_content_type_get_mime_type (content_type);
        g_mime_stream_printf (stream, ", Content-type: %s\n", content_string);
        g_free (content_string);
     }
@@ -574,16 +552,22 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
        g_mime_stream_printf (stream, "Date: %s\n", date_string);
        g_mime_stream_printf (stream, "\fheader}\n");
 
+       if (!params->output_body)
+       {
+           g_mime_stream_printf (stream, "\f%s}\n", part_type);
+           return NOTMUCH_STATUS_SUCCESS;
+       }
        g_mime_stream_printf (stream, "\fbody{\n");
     }
 
     if (leaf) {
        if (g_mime_content_type_is_type (content_type, "text", "*") &&
-           !g_mime_content_type_is_type (content_type, "text", "html"))
+           (params->include_html ||
+            ! g_mime_content_type_is_type (content_type, "text", "html")))
        {
            show_text_part_content (node->part, stream, 0);
        } else {
-           char *content_string = g_mime_content_type_to_string (content_type);
+           char *content_string = g_mime_content_type_get_mime_type (content_type);
            g_mime_stream_printf (stream, "Non-text part: %s\n", content_string);
            g_free (content_string);
        }
@@ -605,7 +589,7 @@ format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart
 {
     const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
     const char *cte = g_mime_object_get_header (meta, "content-transfer-encoding");
-    GMimeDataWrapper *wrapper = g_mime_part_get_content_object (part);
+    GMimeDataWrapper *wrapper = g_mime_part_get_content (part);
     GMimeStream *stream = g_mime_data_wrapper_get_stream (wrapper);
     ssize_t content_length = g_mime_stream_length (stream);
 
@@ -678,11 +662,11 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->verify_attempted) {
        sp->map_key (sp, "sigstatus");
-       format_part_sigstatus_sprinter (sp, node);
+       format_part_sigstatus_sprinter (sp, node->sig_list);
     }
 
     sp->map_key (sp, "content-type");
-    content_string = g_mime_content_type_to_string (content_type);
+    content_string = g_mime_content_type_get_mime_type (content_type);
     sp->string (sp, content_string);
     g_free (content_string);
 
@@ -775,7 +759,7 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
     notmuch_message_t *message = node->envelope_file;
 
     const char *filename;
-    FILE *file;
+    gzFile file;
     const char *from;
 
     time_t date;
@@ -783,14 +767,14 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
     char date_asctime[26];
 
     char *line = NULL;
-    size_t line_size;
+    ssize_t line_size;
     ssize_t line_len;
 
     if (!message)
        INTERNAL_ERROR ("format_part_mbox requires a root part");
 
     filename = notmuch_message_get_filename (message);
-    file = fopen (filename, "r");
+    file = gzopen (filename, "r");
     if (file == NULL) {
        fprintf (stderr, "Failed to open %s: %s\n",
                 filename, strerror (errno));
@@ -806,7 +790,7 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
 
     printf ("From %s %s", from, date_asctime);
 
-    while ((line_len = getline (&line, &line_size, file)) != -1 ) {
+    while ((line_len = gz_getline (message, &line, &line_size, file)) != UTIL_EOF ) {
        if (_is_from_line (line))
            putchar ('>');
        printf ("%s", line);
@@ -814,7 +798,7 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
 
     printf ("\n");
 
-    fclose (file);
+    gzclose (file);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -827,39 +811,44 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
     if (node->envelope_file) {
        /* Special case the entire message to avoid MIME parsing. */
        const char *filename;
-       FILE *file;
-       size_t size;
+       GMimeStream *stream = NULL;
+       ssize_t ssize;
        char buf[4096];
+       notmuch_status_t ret = NOTMUCH_STATUS_FILE_ERROR;
 
        filename = notmuch_message_get_filename (node->envelope_file);
        if (filename == NULL) {
            fprintf (stderr, "Error: Cannot get message filename.\n");
-           return NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
        }
 
-       file = fopen (filename, "r");
-       if (file == NULL) {
+       stream = g_mime_stream_gzfile_open (filename);
+       if (stream == NULL) {
            fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
-           return NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
        }
 
-       while (!feof (file)) {
-           size = fread (buf, 1, sizeof (buf), file);
-           if (ferror (file)) {
+       while (! g_mime_stream_eos (stream)) {
+           ssize = g_mime_stream_read (stream, buf, sizeof(buf));
+           if (ssize < 0) {
                fprintf (stderr, "Error: Read failed from %s\n", filename);
-               fclose (file);
-               return NOTMUCH_STATUS_FILE_ERROR;
+               goto DONE;
            }
 
-           if (size > 0 && fwrite (buf, size, 1, stdout) != 1) {
-               fprintf (stderr, "Error: Write failed\n");
-               fclose (file);
-               return NOTMUCH_STATUS_FILE_ERROR;
+           if (ssize > 0 && fwrite (buf, ssize, 1, stdout) != 1) {
+               fprintf (stderr, "Error: Write %ld chars to stdout failed\n", ssize);
+               goto DONE;
            }
        }
 
-       fclose (file);
-       return NOTMUCH_STATUS_SUCCESS;
+       ret = NOTMUCH_STATUS_SUCCESS;
+
+       /* XXX This DONE is just for the special case of a node in a single file */
+    DONE:
+       if (stream)
+           g_object_unref (stream);
+
+       return ret;
     }
 
     GMimeStream *stream_filter = g_mime_stream_filter_new (params->out_stream);
@@ -868,7 +857,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
        /* For leaf parts, we emit only the transfer-decoded
         * body. */
        GMimeDataWrapper *wrapper;
-       wrapper = g_mime_part_get_content_object (GMIME_PART (node->part));
+       wrapper = g_mime_part_get_content (GMIME_PART (node->part));
 
        if (wrapper && stream_filter)
            g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
@@ -879,7 +868,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
         * encapsulating part's headers).  For multipart parts,
         * this will include the headers. */
        if (stream_filter)
-           g_mime_object_write_to_stream (node->part, stream_filter);
+           g_mime_object_write_to_stream (node->part, NULL, stream_filter);
     }
 
     if (stream_filter)
@@ -911,7 +900,6 @@ show_message (void *ctx,
     part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
     if (part)
        status = format->part (local, sp, part, indent, params);
-#if HAVE_GMIME_SESSION_KEYS
     if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE && session_key_count_error == NOTMUCH_STATUS_SUCCESS) {
        unsigned int new_session_keys = 0;
        if (notmuch_message_count_properties (message, "session-key", &new_session_keys) == NOTMUCH_STATUS_SUCCESS &&
@@ -925,7 +913,6 @@ show_message (void *ctx,
            }
        }
     }
-#endif
   DONE:
     talloc_free (local);
     return status;
@@ -1204,15 +1191,19 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
            fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
            params.output_body = true;
        } else {
-           if (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)
+           if (format != NOTMUCH_FORMAT_TEXT &&
+               format != NOTMUCH_FORMAT_JSON &&
+               format != NOTMUCH_FORMAT_SEXP)
                fprintf (stderr,
-                        "Warning: --body=false only implemented for format=json and format=sexp\n");
+                        "Warning: --body=false only implemented for format=text, format=json and format=sexp\n");
        }
     }
 
     if (params.include_html &&
-        (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)) {
-       fprintf (stderr, "Warning: --include-html only implemented for format=json and format=sexp\n");
+        (format != NOTMUCH_FORMAT_TEXT &&
+        format != NOTMUCH_FORMAT_JSON &&
+        format != NOTMUCH_FORMAT_SEXP)) {
+       fprintf (stderr, "Warning: --include-html only implemented for format=text, format=json and format=sexp\n");
     }
 
     query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
@@ -1226,10 +1217,6 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        return EXIT_FAILURE;
     }
 
-#if (GMIME_MAJOR_VERSION < 3)
-    params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config);
-#endif
-
     notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
     if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
        mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
index 7810b68569f5eb47be8d54bca78aa5206e6cd741..eeb794e85216115adaf8583744f51f382f8e5817 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -112,7 +112,7 @@ const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
 
 
 notmuch_status_t
-notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch)
 {
     if (indexing_cli_choices.opts == NULL)
        indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
@@ -129,14 +129,6 @@ notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_u
            return status;
        }
     }
-#if (GMIME_MAJOR_VERSION < 3)
-    if (indexing_cli_choices.opts && notmuch_indexopts_get_decrypt_policy (indexing_cli_choices.opts) != NOTMUCH_DECRYPT_FALSE) {
-       const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
-       if (gpg_path && strcmp(gpg_path, "gpg"))
-           fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
-                    "\tbut ignoring (use $PATH instead)\n", gpg_path);
-    }
-#endif
     return NOTMUCH_STATUS_SUCCESS;
 }
 
@@ -471,7 +463,7 @@ main (int argc, char *argv[])
 
     local = talloc_new (NULL);
 
-    g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+    g_mime_init ();
 #if !GLIB_CHECK_VERSION(2, 35, 1)
     g_type_init ();
 #endif
index 687501294e2ce0751311703b7d283fc37db0b2e0..25391136c041c8417912e4dab82b3cd541b6521c 100755 (executable)
@@ -12,4 +12,22 @@ for i in $(seq 2 6); do
     time_run "notmuch new #$i" 'notmuch new'
 done
 
+manifest=$(mktemp manifestXXXXXX)
+
+find mail -type f ! -path 'mail/.notmuch/*' | sed -n '1~4 p' > $manifest
+# arithmetic context is to eat extra whitespace on e.g. some BSDs
+count=$((`wc -l < $manifest`))
+
+perl -nle 'rename $_, "$_.renamed"' $manifest
+
+time_run "new ($count mv)" 'notmuch new'
+
+perl -nle 'rename "$_.renamed", $_' $manifest
+
+time_run "new ($count mv back)" 'notmuch new'
+
+perl -nle 'link $_, "$_.copy"' $manifest
+
+time_run "new ($count cp)" 'notmuch new'
+
 time_done
index d6d5c3c3ae83640fe0fdd7f94d396d085fdc182e..8e0a77f42c5d2da47b14fa1eb8c40c2ab2ac35e6 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-test_description='tagging'
+test_description='reindexing'
 
 . $(dirname "$0")/perf-test-lib.sh || exit 1
 
index 73fe7e24620f923826803dbaa0449e1350cd1716..69080e5e6123d75e3dec3d98e2d0d41c7f72b1a4 100644 (file)
@@ -9,3 +9,4 @@
 /test-results
 /ghost-report
 /tmp.*
+/message-id-parse
index f36695c66d78bccc5d586a1b762e063c4c53cd4d..883541d500798188714009b2ce703fe14450882f 100755 (executable)
@@ -43,15 +43,10 @@ notmuch config set foo.nonexistent
 test_expect_equal "$(notmuch config get foo.nonexistent)" ""
 
 test_begin_subtest "List all items"
-notmuch config list 2>&1 | notmuch_config_sanitize > OUTPUT
-
-if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
-    config_gpg_path="crypto.gpg_path=gpg
-"
-fi
+notmuch config list > STDOUT 2> STDERR
+printf "%s\n====\n%s\n" "$(< STDOUT)" "$(< STDERR)" | notmuch_config_sanitize > OUTPUT
 
 cat <<EOF > EXPECTED
-Error opening database at MAIL_DIR/.notmuch: No such file or directory
 database.path=MAIL_DIR
 user.name=Notmuch Test Suite
 user.primary_email=test_suite@notmuchmail.org
@@ -60,11 +55,13 @@ new.tags=unread;inbox;
 new.ignore=
 search.exclude_tags=
 maildir.synchronize_flags=true
-${config_gpg_path}foo.string=this is another string value
+foo.string=this is another string value
 foo.list=this;is another;list value;
 built_with.compact=something
 built_with.field_processor=something
 built_with.retry_lock=something
+====
+Error opening database at MAIL_DIR/.notmuch: No such file or directory
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
index 56efe1d57b496536166a8db1d90d3d2bc3b652a1..fbfe200aa58d3260341d0f8475e543a48a07d37e 100755 (executable)
@@ -20,11 +20,6 @@ foo bar
 baz
 EOF
 
-if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
-    config_gpg_path="crypto.gpg_path=gpg
-"
-fi
-
 output=$(notmuch --config=new-notmuch-config config list | notmuch_built_with_sanitize)
 test_expect_equal "$output" "\
 database.path=/path/to/maildir
@@ -35,7 +30,6 @@ new.tags=foo;bar;
 new.ignore=
 search.exclude_tags=baz;
 maildir.synchronize_flags=true
-""${config_gpg_path}""\
 built_with.compact=something
 built_with.field_processor=something
 built_with.retry_lock=something"
index 3eeac1db7920fad67b1c935a13f20b376748d2ee..5cfa9d3393f4e6f30d65f4cd9e6de52364270bf3 100755 (executable)
@@ -190,6 +190,21 @@ Non-text part: application/pgp-signature
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "--format=text --part=0 --body=false, message header"
+notmuch show --format=text --part=0  --body=false 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fmessage{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 excluded:0 filename:${MAIL_DIR}/multipart
+\fheader{
+Carl Worth <cworth@cworth.org> (2001-01-05) (attachment inbox signed unread)
+Subject: Multipart message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+\fheader}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "--format=text --part=1, message body"
 notmuch show --format=text --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
@@ -310,6 +325,15 @@ Non-text part: text/html
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "--format=text --include-html --part=5, rfc822's html part"
+notmuch show --format=text --include-html --part=5 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 5, Content-type: text/html
+<p>This is an embedded message, with a multipart/alternative part.</p>
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "--format=text --part=6, rfc822's text part"
 notmuch show --format=text --part=6 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
@@ -465,7 +489,6 @@ notmuch show --format=raw --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUT
 test_expect_equal_file "${MAIL_DIR}"/multipart OUTPUT
 
 test_begin_subtest "--format=raw --part=1, message body"
-test_subtest_broken_gmime_2
 notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 test_expect_equal_file multipart_body OUTPUT
 
@@ -519,7 +542,6 @@ notmuch show --format=raw --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUT
 test_expect_equal_file embedded_message OUTPUT
 
 test_begin_subtest "--format=raw --part=4, rfc822's multipart"
-test_subtest_broken_gmime_2
 notmuch show --format=raw --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 test_expect_equal_file embedded_message_body OUTPUT
 
index ebe710f98c55a6d9130449be5c5dc47143bff51e..4db3a95872df60e3fa8e952da5720e7110276339 100755 (executable)
@@ -180,6 +180,7 @@ test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.or
 Subject: Re: This subject is exactly 200 bytes in length. Other than its
  length there is not much of note here. Note that the length of 200 bytes
  includes the Subject: and Re: prefixes with two spaces
+To: test_suite@notmuchmail.org
 In-Reply-To: <${gen_msg_id}>
 References: <${gen_msg_id}>
 
index 134a106365c4618064218c4f5619af5f5a8d54a3..bbeaa2b9c875fae21440af832e649264b9a9c60e 100755 (executable)
@@ -203,6 +203,7 @@ test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.or
 Subject: Re: This subject is exactly 200 bytes in length. Other than its
  length there is not much of note here. Note that the length of 200 bytes
  includes the Subject: and Re: prefixes with two spaces
+To: test_suite@notmuchmail.org
 In-Reply-To: <${gen_msg_id}>
 References: <${gen_msg_id}>
 
index 5935819f4ba4404eb14802d921ff88c3cd2ea1db..c06a8133e0faff9155e38e789e7b9b1538abee48 100755 (executable)
@@ -86,7 +86,7 @@ test_emacs "(let ((notmuch-show-indent-messages-width 4))
 test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage-with-fourfold-indentation OUTPUT
 
 test_begin_subtest "notmuch-show for message with invalid From"
-test_subtest_broken_gmime_3
+test_subtest_known_broken
 add_message "[subject]=\"message-with-invalid-from\"" \
            "[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\""
 thread=$(notmuch search --output=threads subject:message-with-invalid-from)
@@ -610,7 +610,7 @@ test_emacs "(let ((message-hidden-headers '()))
            (test-output))"
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
-To: 
+To: test_suite@notmuchmail.org
 Subject: Re: Reply within emacs to an html-only message
 In-Reply-To: <${gen_msg_id}>
 Fcc: ${MAIL_DIR}/sent
@@ -623,7 +623,6 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Reply within emacs to message from self"
-test_subtest_known_broken
 add_message '[from]="test_suite@notmuchmail.org"' \
            '[to]="test_suite@notmuchmail.org"'
 test_emacs "(let ((message-hidden-headers '()))
@@ -657,7 +656,7 @@ test_emacs "(let ((message-hidden-headers '()))
              (test-output))"
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
-To: 
+To: test_suite@notmuchmail.org
 Subject: Re: Quote MML tags in reply
 In-Reply-To: <test-emacs-mml-quoting@message.id>
 Fcc: ${MAIL_DIR}/sent
index a776ec35043b324efd1c38572ff18683b0afd960..6045a7dc3d0b764c4546531174db13456d35b084 100755 (executable)
@@ -10,8 +10,6 @@ test_description='PGP/MIME signature verification and decryption'
 ##################################################
 
 add_gnupg_home
-# Change this if we ship a new test key
-FINGERPRINT="5AEAB11F5E33DCE875DDB75B6D92612D94E46381"
 
 test_begin_subtest "emacs delivery of signed message"
 test_expect_success \
@@ -138,7 +136,6 @@ test_expect_equal_json \
     "$expected"
 
 test_begin_subtest "signature verification with full owner trust"
-test_subtest_broken_gmime_2
 # give the key full owner trust
 echo "${FINGERPRINT}:6:" | gpg --no-tty --import-ownertrust >>"$GNUPGHOME"/trust.log 2>&1
 gpg --no-tty --check-trustdb >>"$GNUPGHOME"/trust.log 2>&1
@@ -271,7 +268,6 @@ expected='[[[{"id": "XXXXX",
  "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
  "body": [{"id": 1,
  "encstatus": [{"status": "good"}],
- "sigstatus": [],
  "content-type": "multipart/encrypted",
  "content": [{"id": 2,
  "content-type": "application/pgp-encrypted",
@@ -350,7 +346,6 @@ test_expect_success \
     "(mml-secure-message-sign-encrypt)"'
 
 test_begin_subtest "decryption + signature verification"
-test_subtest_broken_gmime_2
 output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 002" \
     | notmuch_json_show_sanitize \
     | sed -e 's|"created": [1234567890]*|"created": 946728000|')
@@ -388,6 +383,7 @@ output=$(notmuch reply --decrypt=true subject:"test encrypted message 002" \
     | notmuch_drop_mail_headers In-Reply-To References)
 expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
 Subject: Re: test encrypted message 002
+To: test_suite@notmuchmail.org
 
 On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
 > This is another test encrypted message.'
@@ -401,10 +397,10 @@ test_emacs "(let ((message-hidden-headers '())
   (notmuch-show \"subject:test.encrypted.message.002\")
   (notmuch-show-reply)
   (test-output))"
-# the empty To: is probably a bug, but it's not to do with encryption
-grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' -e 'To:' < OUTPUT > OUTPUT.clean
+grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' < OUTPUT > OUTPUT.clean
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: test_suite@notmuchmail.org
 Subject: Re: test encrypted message 002
 --text follows this line--
 <#secure method=pgpmime mode=signencrypt>
index be45e3b11f22655833c7de7978b8cb68bbe21cde..e410286b1ef6da126266c5015c158276ce2658cc 100755 (executable)
@@ -48,12 +48,6 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "signature verification (notmuch CLI)"
-if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
-    # gmime 2 can't report User IDs properly for S/MIME
-    USERID=''
-else
-    USERID='"userid": "CN=Notmuch Test Suite",'
-fi
 output=$(notmuch show --format=json --verify subject:"test signed message 001" \
     | notmuch_json_show_sanitize \
     | sed -e 's|"created": [-1234567890]*|"created": 946728000|' \
@@ -71,7 +65,8 @@ expected='[[[{"id": "XXXXX",
  "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
  "body": [{"id": 1,
  "sigstatus": [{"fingerprint": "'$FINGERPRINT'",
- "status": "good",'$USERID'
+ "status": "good",
+ "userid": "CN=Notmuch Test Suite",
  "expires": 424242424,
  "created": 946728000}],
  "content-type": "multipart/signed",
index c5435f4faeb70292ba824bca05a03930523fa372..0a602e50c3b6fb7833e170edc1e26a0a4bfca41a 100755 (executable)
@@ -52,9 +52,6 @@ test_begin_subtest "show the message body of the encrypted message"
 notmuch dump wumpus
 output=$(notmuch show wumpus | notmuch_show_part 3)
 expected='This is a test encrypted message with a wumpus.'
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
@@ -91,9 +88,6 @@ test_expect_equal \
 test_begin_subtest "search should now find the contents"
 output=$(notmuch search wumpus)
 expected='thread:0000000000000003   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
@@ -163,9 +157,6 @@ test_begin_subtest 'reindex in auto mode'
 test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
 test_begin_subtest "reindexed encrypted messages, should not have changed"
 output=$(notmuch search wumpus)
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
@@ -256,9 +247,6 @@ EOF
 notmuch reindex id:simple-encrypted@crypto.notmuchmail.org
 output=$(notmuch search sekrit)
 expected='thread:0000000000000001   2016-12-22 [1/1] Daniel Kahn Gillmor; encrypted message (encrypted inbox unread)'
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
@@ -266,9 +254,6 @@ test_expect_equal \
 test_begin_subtest "notmuch reply should show cleartext if session key is present"
 output=$(notmuch reply id:simple-encrypted@crypto.notmuchmail.org | grep '^>')
 expected='> This is a top sekrit message.'
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
@@ -276,9 +261,6 @@ test_expect_equal \
 test_begin_subtest "notmuch show should show cleartext if session key is present"
 output=$(notmuch show id:simple-encrypted@crypto.notmuchmail.org | notmuch_show_part 3)
 expected='This is a top sekrit message.'
-if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal \
     "$output" \
     "$expected"
index 69ebec68846482a1a701c72e788eee7bdd9135c6..2124dde28a04ade0cb8c680f63d6f1a8bec8821e 100755 (executable)
@@ -117,4 +117,20 @@ MAIL_DIR/bar/new/21:2,
 MAIL_DIR/bar/new/22:2,
 MAIL_DIR/cur/51:2,"
 
+test_begin_subtest "body: same as unprefixed before reindex"
+notmuch search --output=messages body:close > OUTPUT
+notmuch search --output=messages close  > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "body: subset of unprefixed after reindex"
+notmuch reindex '*'
+notmuch search --output=messages body:close | sort > BODY
+notmuch search --output=messages close | sort > UNPREFIXED
+diff -e UNPREFIXED BODY | cut -c2- > OUTPUT
+cat <<EOF > EXPECTED
+d
+d
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
index 4085340f66d42b270c13ced3c5eb845fa2cc48c6..92334ba0d78f1e8ed789672d8535de7c602f3f99 100755 (executable)
@@ -77,6 +77,17 @@ test_expect_equal_file cworth.msg-ids OUTPUT
 test_begin_subtest "xapian wildcard search for subject:"
 test_expect_equal $(notmuch count 'subject:count*') 1
 
+add_message '[from]="and"' '[subject]="and-and-and"'
+printf "id:$gen_msg_id\n" > EXPECTED
+
+test_begin_subtest "quoted xapian keyword search for from:"
+notmuch search --output=messages 'from:"and"' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "quoted xapian keyword search for subject:"
+notmuch search --output=messages 'subject:"and-and-and"' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "regexp from search, case sensitive"
 notmuch search --output=messages from:/carl/ > OUTPUT
 test_expect_equal_file /dev/null OUTPUT
@@ -137,10 +148,10 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "regexp error reporting"
-notmuch search 'from:/unbalanced[/' 1>OUTPUT 2>&1
+notmuch search 'from:/unbalanced[/' 2>&1 | sed -e '/^A Xapian/ s/[^:]*$//' > OUTPUT
 cat <<EOF > EXPECTED
 notmuch search: A Xapian exception occurred
-A Xapian exception occurred parsing query: Invalid regular expression
+A Xapian exception occurred parsing query: Regexp error:
 Query string was: from:/unbalanced[/
 EOF
 test_expect_equal_file EXPECTED OUTPUT
diff --git a/test/T720-emacs-attachment-warnings.sh b/test/T720-emacs-attachment-warnings.sh
new file mode 100755 (executable)
index 0000000..c8d2bcc
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+test_description="emacs attachment warnings"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "notmuch-test-attachment-warning part 1"
+test_emacs_expect_t '(notmuch-test-attachment-warning-1)'
+
+test_done
diff --git a/test/T720-lib-lifetime.sh b/test/T720-lib-lifetime.sh
new file mode 100755 (executable)
index 0000000..3d94d4d
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2018 rhn
+#
+
+
+test_description="Lifetime constraints for library"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus threading
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+test_begin_subtest "Message outlives parent Messages from replies"
+
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <stdlib.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, &db);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d\n", stat);
+     exit (1);
+   }
+
+   notmuch_query_t *query = notmuch_query_create (db, "id:B00-root@example.org");
+   notmuch_threads_t *threads;
+
+   stat = notmuch_query_search_threads (query, &threads);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error querying threads: %d\n", stat);
+     exit (1);
+   }
+
+   if (!notmuch_threads_valid (threads)) {
+     fprintf (stderr, "invalid threads");
+     exit (1);
+   }
+
+   notmuch_thread_t *thread = notmuch_threads_get (threads);
+   notmuch_messages_t *messages = notmuch_thread_get_messages (thread);
+
+   if (!notmuch_messages_valid (messages)) {
+     fprintf (stderr, "invalid messages");
+     exit (1);
+   }
+
+   notmuch_message_t *message = notmuch_messages_get (messages);
+   notmuch_messages_t *replies = notmuch_message_get_replies (message);
+   if (!notmuch_messages_valid (replies)) {
+     fprintf (stderr, "invalid replies");
+     exit (1);
+   }
+
+   notmuch_message_t *reply = notmuch_messages_get (replies);
+
+   notmuch_messages_destroy (replies); // the reply should not get destroyed here
+   notmuch_message_destroy (message);
+   notmuch_messages_destroy (messages); // nor here
+
+   const char *mid = notmuch_message_get_message_id (reply); // should not crash when accessing
+   fprintf (stdout, "Reply id: %s\n", mid);
+   notmuch_message_destroy (reply);
+   notmuch_thread_destroy (thread); // this destroys the reply
+   notmuch_threads_destroy (threads);
+   notmuch_query_destroy (query);
+   notmuch_database_destroy (db);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+Reply id: B01-child@example.org
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T730-emacs-forwarding.sh b/test/T730-emacs-forwarding.sh
new file mode 100755 (executable)
index 0000000..45e6156
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+test_description="emacs forwarding"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Forward setting the correct references header"
+# Check that, when forwarding a message, the new message has
+# a References-header pointing to the original (forwarded) message.
+
+message_id='OriginalMessage@notmuchmail.org'
+add_message \
+    [id]="$message_id" \
+    '[from]="user@example.com"' \
+    '[subject]="This is the original message"' \
+    '[body]="Dummy text."'
+
+test_emacs_expect_t "
+  (let ((message-send-mail-function (lambda () t)))
+    (notmuch-show \"id:$message_id\")
+    (notmuch-show-forward-message)
+    (save-restriction
+      (message-narrow-to-headers)
+      (message-remove-header \"Fcc\")
+      (message-remove-header \"To\")
+      (message-add-header \"To: nobody@example.com\"))
+
+    (notmuch-mua-send)
+    (notmuch-test-expect-equal
+        (message-field-value \"References\") \"<$message_id>\"))
+"
+
+test_begin_subtest "Forwarding adding the forwarded tag"
+# Check that sending the forwarding message in the previous
+# subtest did add the forwarded-tag to the message that was forwarded.
+
+test_expect_equal "$(notmuch search --output=tags id:$message_id | sort)" \
+"forwarded
+inbox
+unread"
+
+test_done
diff --git a/test/T740-body.sh b/test/T740-body.sh
new file mode 100755 (executable)
index 0000000..548b30a
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+test_description='search body'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message "[body]=thebody-1" "[subject]=subject-1"
+add_message "[body]=nothing-to-see-here-1" "[subject]=thebody-1"
+
+test_begin_subtest 'search with body: prefix'
+notmuch search body:thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search without body: prefix'
+notmuch search thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; thebody-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'negated body: prefix'
+notmuch search thebody and not body:thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; thebody-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search unprefixed for prefixed term'
+notmuch search subject | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search with body: prefix for term only in subject'
+notmuch search body:subject | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T750-gzip.sh b/test/T750-gzip.sh
new file mode 100755 (executable)
index 0000000..5b678fa
--- /dev/null
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+test_description='support for gzipped messages'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+#######################################################################
+# notmuch new
+test_begin_subtest "Single new gzipped message"
+generate_message
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Single new gzipped message (full-scan)"
+generate_message
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Multiple new messages, one gzipped"
+generate_message
+gzip $gen_msg_filename
+generate_message
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "Multiple new messages, one gzipped (full-scan)"
+generate_message
+gzip $gen_msg_filename
+generate_message
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "Renamed (gzipped) message"
+generate_message
+echo $gen_message_filename
+notmuch new > /dev/null
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
+No new mail. Detected 1 file rename."
+
+######################################################################
+# notmuch search
+
+test_begin_subtest "notmuch search with partially gzipped mail store"
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Single new gzipped message (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Single new gzipped message (full-scan) (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (full-scan) (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (full-scan) (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Renamed (gzipped) message (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch search --output=files with partially gzipped mail store"
+notmuch search --output=files '*' | notmuch_search_files_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+MAIL_DIR/msg-001.gz
+MAIL_DIR/msg-002.gz
+MAIL_DIR/msg-003.gz
+MAIL_DIR/msg-004
+MAIL_DIR/msg-005.gz
+MAIL_DIR/msg-006
+MAIL_DIR/msg-007.gz
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+######################################################################
+# notmuch show
+
+test_begin_subtest "show un-gzipped message"
+notmuch show id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+\fmessage{ id:msg-006@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-006
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Multiple new messages, one gzipped (full-scan)
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#6)
+\fpart}
+\fbody}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show un-gzipped message (format mbox)"
+notmuch show --format=mbox id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From test_suite@notmuchmail.org Fri Jan  5 15:43:51 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-006@notmuch-test-suite>
+Subject: Multiple new messages, one gzipped (full-scan)
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+
+This is just a test message (#6)
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show un-gzipped message (format raw)"
+notmuch show --format=raw id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-006@notmuch-test-suite>
+Subject: Multiple new messages, one gzipped (full-scan)
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+
+This is just a test message (#6)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message"
+notmuch show id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+\fmessage{ id:msg-007@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-007.gz
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Renamed (gzipped) message
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#7)
+\fpart}
+\fbody}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message (mbox)"
+notmuch show --format=mbox id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From test_suite@notmuchmail.org Fri Jan  5 15:43:50 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-007@notmuch-test-suite>
+Subject: Renamed (gzipped) message
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+
+This is just a test message (#7)
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message (raw)"
+notmuch show --format=raw id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-007@notmuch-test-suite>
+Subject: Renamed (gzipped) message
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+
+This is just a test message (#7)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/emacs-attachment-warnings.el b/test/emacs-attachment-warnings.el
new file mode 100644 (file)
index 0000000..200ca7b
--- /dev/null
@@ -0,0 +1,68 @@
+(require 'notmuch-mua)
+
+(defun attachment-check-test (&optional fn)
+  "Test `notmuch-mua-attachment-check' using a message where optional FN is evaluated.
+
+Return `t' if the message would be sent, otherwise `nil'"
+  (notmuch-mua-mail)
+  (message-goto-body)
+  (when fn
+    (funcall fn))
+  (prog1
+      (condition-case nil
+         ;; Force `y-or-n-p' to always return `nil', as if the user
+         ;; pressed "n".
+         (letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
+           (notmuch-mua-attachment-check)
+           t)
+       ('error nil))
+    (set-buffer-modified-p nil)
+    (kill-buffer (current-buffer))))
+
+(defvar attachment-check-tests
+  '(
+    ;; These are all okay:
+    (t)
+    (t . (lambda () (insert "Nothing is a-tt-a-ch-ed!\n")))
+    (t . (lambda ()
+          (insert "Here is an attachment:\n")
+          (insert "<#part filename=\"foo\" />\n")))
+    (t . (lambda () (insert "<#part filename=\"foo\" />\n")))
+    (t . (lambda ()
+          ;; "attachment" is only mentioned in a quoted section.
+          (insert "> I sent you an attachment!\n")
+          ;; Code in `notmuch-mua-attachment-check' avoids matching on
+          ;; "attachment" in a quoted section of the message by looking at
+          ;; fontification properties. For fontification to happen we need to
+          ;; allow some time for redisplay.
+          (sit-for 0.01)))
+
+    ;; These should not be okay:
+    (nil . (lambda () (insert "Here is an attachment:\n")))
+    (nil . (lambda ()
+            ;; "attachment" is mentioned in both a quoted section and
+            ;; outside of it.
+            (insert "> I sent you an attachment!\n")
+            (insert "The attachment was missing!\n")
+            ;; Code in `notmuch-mua-attachment-check' avoids matching
+            ;; on "attachment" in a quoted section of the message by
+            ;; looking at fontification properties. For fontification
+            ;; to happen we need to allow some time for redisplay.
+            (sit-for 0.01)))
+    ))
+
+(defun notmuch-test-attachment-warning-1 ()
+  (let (output expected)
+    (mapcar (lambda (test)
+             (let* ((expect (car test))
+                    (body (cdr test))
+                    (result (attachment-check-test body)))
+               (push expect expected)
+               (push (if (eq result expect)
+                         result
+                       ;; In the case of a failure, include the test
+                       ;; details to make it simpler to debug.
+                       (format "%S <-- %S" result body))
+                     output)))
+           attachment-check-tests)
+    (notmuch-test-expect-equal output expected)))
index fca5277dfea3d4e21af869f5d4cf0031f3b8347e..04d93f7d07c03dee970716b805dbb8c14cea6134 100644 (file)
@@ -117,6 +117,9 @@ add_gnupg_home ()
        echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
     fi
     echo no-emit-version >> "$GNUPGHOME"/gpg.conf
+
+    # Change this if we ship a new test key
+    FINGERPRINT="5AEAB11F5E33DCE875DDB75B6D92612D94E46381"
 }
 
 # Each test should start with something like this, after copyright notices:
@@ -1076,22 +1079,6 @@ TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
 
 . "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
 
-if [ "${NOTMUCH_GMIME_MAJOR}" = 3 ]; then
-    test_subtest_broken_gmime_3 () {
-       test_subtest_known_broken
-    }
-    test_subtest_broken_gmime_2 () {
-       true
-    }
-else
-    test_subtest_broken_gmime_3 () {
-       true
-    }
-    test_subtest_broken_gmime_2 () {
-       test_subtest_known_broken
-    }
-fi
-
 emacs_generate_script
 
 
index 9d3b6dad9d17e4b32f645d0d5c0a2fbbf9fddf48..ba67d4f4ebbd378d9833a1bb46c9a511d5a4fb91 100644 (file)
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
 
-#if (GMIME_MAJOR_VERSION < 3)
-/* Create or pass on a GPG context (GMime 2.6) */
-static notmuch_status_t
-get_gpg_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
-{
-    if (ctx == NULL || crypto == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
-
-    if (crypto->gpgctx) {
-       *ctx = crypto->gpgctx;
-       return NOTMUCH_STATUS_SUCCESS;
-    }
-
-    /* TODO: GMimePasswordRequestFunc */
-    crypto->gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
-    if (! crypto->gpgctx) {
-       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
-    }
-
-    g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) crypto->gpgctx, true);
-    g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) crypto->gpgctx, false);
-
-    *ctx = crypto->gpgctx;
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-/* Create or pass on a PKCS7 context (GMime 2.6) */
-static notmuch_status_t
-get_pkcs7_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
-{
-    if (ctx == NULL || crypto == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
-
-    if (crypto->pkcs7ctx) {
-       *ctx = crypto->pkcs7ctx;
-       return NOTMUCH_STATUS_SUCCESS;
-    }
-
-    /* TODO: GMimePasswordRequestFunc */
-    crypto->pkcs7ctx = g_mime_pkcs7_context_new (NULL);
-    if (! crypto->pkcs7ctx) {
-       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
-    }
-
-    g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) crypto->pkcs7ctx,
-                                          false);
-
-    *ctx = crypto->pkcs7ctx;
-    return NOTMUCH_STATUS_SUCCESS;
-}
-static const struct {
-    const char *protocol;
-    notmuch_status_t (*get_context) (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx);
-} protocols[] = {
-    {
-       .protocol = "application/pgp-signature",
-       .get_context = get_gpg_context,
-    },
-    {
-       .protocol = "application/pgp-encrypted",
-       .get_context = get_gpg_context,
-    },
-    {
-       .protocol = "application/pkcs7-signature",
-       .get_context = get_pkcs7_context,
-    },
-    {
-       .protocol = "application/x-pkcs7-signature",
-       .get_context = get_pkcs7_context,
-    },
-};
-
-/* for the specified protocol return the context pointer (initializing
- * if needed) */
-notmuch_status_t
-_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
-                                           const char *protocol,
-                                           GMimeCryptoContext **ctx)
-{
-    if (! protocol)
-       return NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL;
-
-    /* As per RFC 1847 section 2.1: "the [protocol] value token is
-     * comprised of the type and sub-type tokens of the Content-Type".
-     * As per RFC 1521 section 2: "Content-Type values, subtypes, and
-     * parameter names as defined in this document are
-     * case-insensitive."  Thus, we use strcasecmp for the protocol.
-     */
-    for (size_t i = 0; i < ARRAY_SIZE (protocols); i++) {
-       if (strcasecmp (protocol, protocols[i].protocol) == 0)
-           return protocols[i].get_context (crypto, ctx);
-    }
-
-    return NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL;
-}
-
-void
-_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto)
-{
-    if (crypto->gpgctx) {
-       g_object_unref (crypto->gpgctx);
-       crypto->gpgctx = NULL;
-    }
-
-    if (crypto->pkcs7ctx) {
-       g_object_unref (crypto->pkcs7ctx);
-       crypto->pkcs7ctx = NULL;
-    }
-}
-#else
 void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
 {
 }
-#endif
 
 GMimeObject *
 _notmuch_crypto_decrypt (bool *attempted,
                         notmuch_decryption_policy_t decrypt,
                         notmuch_message_t *message,
-                        g_mime_3_unused(GMimeCryptoContext* crypto_ctx),
                         GMimeMultipartEncrypted *part,
                         GMimeDecryptResult **decrypt_result,
                         GError **err)
@@ -153,7 +41,6 @@ _notmuch_crypto_decrypt (bool *attempted,
        return NULL;
 
     /* the versions of notmuch that can support session key decryption */
-#if HAVE_GMIME_SESSION_KEYS
     if (message) {
        notmuch_message_properties_t *list = NULL;
 
@@ -165,17 +52,10 @@ _notmuch_crypto_decrypt (bool *attempted,
            }
            if (attempted)
                *attempted = true;
-#if (GMIME_MAJOR_VERSION < 3)
-           ret = g_mime_multipart_encrypted_decrypt_session (part,
-                                                             crypto_ctx,
-                                                             notmuch_message_properties_value (list),
-                                                             decrypt_result, err);
-#else
            ret = g_mime_multipart_encrypted_decrypt (part,
                                                      GMIME_DECRYPT_NONE,
                                                      notmuch_message_properties_value (list),
                                                      decrypt_result, err);
-#endif
            if (ret)
                break;
        }
@@ -184,7 +64,6 @@ _notmuch_crypto_decrypt (bool *attempted,
        if (ret)
            return ret;
     }
-#endif
 
     if (err && *err) {
        g_error_free (*err);
@@ -196,26 +75,10 @@ _notmuch_crypto_decrypt (bool *attempted,
 
     if (attempted)
        *attempted = true;
-#if (GMIME_MAJOR_VERSION < 3)
-#if HAVE_GMIME_SESSION_KEYS
-    gboolean oldgetsk = g_mime_crypto_context_get_retrieve_session_key (crypto_ctx);
-    gboolean newgetsk = (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result);
-    if (newgetsk != oldgetsk)
-       /* This could return an error, but we can't do anything about it, so ignore it */
-       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, newgetsk, NULL);
-#endif
-    ret = g_mime_multipart_encrypted_decrypt(part, crypto_ctx,
-                                            decrypt_result, err);
-#if HAVE_GMIME_SESSION_KEYS
-    if (newgetsk != oldgetsk)
-       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, oldgetsk, NULL);
-#endif
-#else
     GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
     if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result)
        flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
     ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
                                             decrypt_result, err);
-#endif
     return ret;
 }
index c384601c11d35772a727c55996f4c3180076481e..af3998e8466e346bdcef95310031d88e68742c7a 100644 (file)
@@ -5,33 +5,27 @@
 #include "gmime-extra.h"
 #include "notmuch.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef struct _notmuch_crypto {
     bool verify;
     notmuch_decryption_policy_t decrypt;
-#if (GMIME_MAJOR_VERSION < 3)
-    GMimeCryptoContext* gpgctx;
-    GMimeCryptoContext* pkcs7ctx;
-    const char *gpgpath;
-#endif
 } _notmuch_crypto_t;
 
 GMimeObject *
 _notmuch_crypto_decrypt (bool *attempted,
                         notmuch_decryption_policy_t decrypt,
                         notmuch_message_t *message,
-                        GMimeCryptoContext* crypto_ctx,
                         GMimeMultipartEncrypted *part,
                         GMimeDecryptResult **decrypt_result,
                         GError **err);
 
-#if (GMIME_MAJOR_VERSION < 3)
-notmuch_status_t
-_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
-                                           const char *protocol,
-                                           GMimeCryptoContext **ctx);
-#endif
-
 void
 _notmuch_crypto_cleanup (_notmuch_crypto_t *crypto);
 
+#ifdef __cplusplus
+}
+#endif
 #endif
index 4bb338a2abeacd1253c1287c39f8d6769e1c5405..aa3b77c43fd92d95ac55babf345b977861644333 100644 (file)
 
 #include "function-attributes.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* There's no point in continuing when we've detected that we've done
  * something wrong internally (as opposed to the user passing in a
  * bogus value).
@@ -44,4 +48,7 @@ _internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2) NORETURN_ATTRI
     _internal_error (format " (%s).\n",                        \
                     ##__VA_ARGS__, __location__)
 
+#ifdef __cplusplus
+}
+#endif
 #endif
index bc1e3c4d94bb75b04c7b24c8e8816770a7e83434..7562d9062a20ab1b1a2a9c917c3d4e9b56afe7f5 100644 (file)
 #include "gmime-extra.h"
 #include <string.h>
 
+static
 GMimeStream *
-g_mime_stream_stdout_new()
-{
-    GMimeStream *stream_stdout = NULL;
-    GMimeStream *stream_buffered = NULL;
+_gzfile_maybe_filter (GMimeStream *file_stream) {
+    char buf[4];
+    int bytes_read;
 
-    stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
-    if (!stream_stdout)
+    if ((bytes_read = g_mime_stream_read (file_stream, buf, sizeof (buf))) < 0)
        return NULL;
 
-    g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
+    if (g_mime_stream_reset (file_stream))
+       return NULL;
 
-    stream_buffered = g_mime_stream_buffer_new (stream_stdout, GMIME_STREAM_BUFFER_BLOCK_WRITE);
+    /* check for gzipped input */
+    if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char)buf[1] == 0x8b) {
+       GMimeStream *gzstream;
+       GMimeFilter *gzfilter;
 
-    g_object_unref (stream_stdout);
+       gzfilter = g_mime_filter_gzip_new (GMIME_FILTER_GZIP_MODE_UNZIP, 0);
+       if (! gzfilter)
+           return NULL;
 
-    return stream_buffered;
-}
+       gzstream = g_mime_stream_filter_new (file_stream);
+       if (! gzstream)
+           return NULL;
 
-/**
- * copy a glib string into a talloc context, and free it.
- */
-static char*
-g_string_talloc_strdup (void *ctx, char *g_string)
-{
-    char *new_str = talloc_strdup (ctx, g_string);
-    g_free (g_string);
-    return new_str;
+       /* ignore filter id */
+       (void)g_mime_stream_filter_add ((GMimeStreamFilter *)gzstream, gzfilter);
+       return gzstream;
+    } else {
+       return file_stream;
+    }
 }
 
-#if (GMIME_MAJOR_VERSION < 3)
-
-const char *
-g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
+GMimeStream *
+g_mime_stream_gzfile_new (int fd)
 {
-    /* output user id only if validity is FULL or ULTIMATE. */
-    /* note that gmime 2.6 is using the term "trust" here, which
-     * is WRONG.  It's actually user id "validity". */
-    const char *name = g_mime_certificate_get_name (cert);
-    if (name == NULL)
-       return name;
-    GMimeCertificateTrust trust = g_mime_certificate_get_trust (cert);
-    if (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)
-       return name;
-    return NULL;
-}
+    GMimeStream *file_stream;
 
-char *
-g_mime_message_get_address_string (GMimeMessage *message, GMimeRecipientType type)
-{
-    InternetAddressList *list = g_mime_message_get_recipients (message, type);
-    return internet_address_list_to_string (list, 0);
-}
+    file_stream = g_mime_stream_fs_new (fd);
+    if (! file_stream)
+       return NULL;
 
-inline InternetAddressList *
-g_mime_message_get_addresses (GMimeMessage *message, GMimeRecipientType type)
-{
-    return g_mime_message_get_recipients (message, type);
+    return _gzfile_maybe_filter (file_stream);
 }
 
-char *
-g_mime_message_get_date_string (void *ctx, GMimeMessage *message)
+GMimeStream *
+g_mime_stream_gzfile_open (const char *filename)
 {
-    char *date = g_mime_message_get_date_as_string (message);
-    return g_string_talloc_strdup (ctx, date);
-}
+    GMimeStream *file_stream;
 
-InternetAddressList *
-g_mime_message_get_from (GMimeMessage *message)
-{
-    return internet_address_list_parse_string (g_mime_message_get_sender (message));
-}
+    file_stream = g_mime_stream_fs_open (filename, 0, 0, NULL);
+    if (! file_stream)
+       return NULL;
 
-const char *
-g_mime_message_get_from_string (GMimeMessage *message) {
-    return  g_mime_message_get_sender (message);
+    return _gzfile_maybe_filter (file_stream);
 }
 
-InternetAddressList *
-g_mime_message_get_reply_to_list (GMimeMessage *message)
+GMimeStream *
+g_mime_stream_stdout_new()
 {
-    const char *reply_to;
+    GMimeStream *stream_stdout = NULL;
+    GMimeStream *stream_buffered = NULL;
 
-    reply_to = g_mime_message_get_reply_to (message);
-    if (reply_to && *reply_to)
-       return internet_address_list_parse_string (reply_to);
-    else
+    stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
+    if (!stream_stdout)
        return NULL;
-}
 
-/**
- * return talloc allocated reply-to string
- */
-char *
-g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message)
-{
-    return talloc_strdup(ctx, g_mime_message_get_reply_to (message));
-}
+    g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
 
-gboolean
-g_mime_signature_status_good (GMimeSignatureStatus status) {
-    return (status == GMIME_SIGNATURE_STATUS_GOOD);
-}
+    stream_buffered = g_mime_stream_buffer_new (stream_stdout, GMIME_STREAM_BUFFER_BLOCK_WRITE);
 
-gboolean
-g_mime_signature_status_bad (GMimeSignatureStatus status) {
-    return (status == GMIME_SIGNATURE_STATUS_BAD);
-}
+    g_object_unref (stream_stdout);
 
-gboolean
-g_mime_signature_status_error (GMimeSignatureError error) {
-    return (error != GMIME_SIGNATURE_ERROR_NONE);
+    return stream_buffered;
 }
 
-gint64
-g_mime_utils_header_decode_date_unix (const char *date) {
-    return (gint64) g_mime_utils_header_decode_date (date, NULL);
+/**
+ * copy a glib string into a talloc context, and free it.
+ */
+static char*
+g_string_talloc_strdup (void *ctx, char *g_string)
+{
+    char *new_str = talloc_strdup (ctx, g_string);
+    g_free (g_string);
+    return new_str;
 }
 
-#else /* GMime >= 3.0 */
-
 const char *
 g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
 {
@@ -223,5 +189,3 @@ g_mime_utils_header_decode_date_unix (const char *date) {
 
     return ret;
 }
-
-#endif
index ca822b8cf53b4e0b1b0509b3dcb5d4d92d46ca54..b0c8d3d84a07429a44d84c5c7f1c03780b3bee51 100644 (file)
@@ -1,51 +1,21 @@
 #ifndef _GMIME_EXTRA_H
 #define _GMIME_EXTRA_H
 #include <gmime/gmime.h>
+#include <talloc.h>
 
-GMimeStream *g_mime_stream_stdout_new(void);
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-#include <talloc.h>
+GMimeStream *g_mime_stream_stdout_new(void);
 
+/* Return a GMime stream for this open file descriptor, un-gzipping if
+ * necessary */
+GMimeStream *g_mime_stream_gzfile_new (int fd);
 
-#if (GMIME_MAJOR_VERSION < 3)
-
-#define GMIME_ADDRESS_TYPE_TO GMIME_RECIPIENT_TYPE_TO
-#define GMIME_ADDRESS_TYPE_CC GMIME_RECIPIENT_TYPE_CC
-#define GMIME_ADDRESS_TYPE_BCC GMIME_RECIPIENT_TYPE_BCC
-
-#define g_mime_2_6_unref(obj) g_object_unref (obj)
-#define g_mime_3_unused(arg) arg
-#define g_mime_certificate_get_fpr16(cert) g_mime_certificate_get_key_id (cert)
-#else /* GMime >= 3.0 */
-
-#define GMIME_ENABLE_RFC_2047_WORKAROUNDS 0xdeadbeef
-#define g_mime_content_type_to_string(c) g_mime_content_type_get_mime_type (c)
-#define g_mime_filter_crlf_new(encode,dots) g_mime_filter_dos2unix_new (FALSE)
-#define g_mime_gpg_context_new(func,path) g_mime_gpg_context_new ()
-#define g_mime_gpg_context_set_use_agent(ctx,val) /*ignore*/
-#define g_mime_gpg_context_set_always_trust(ctx,val) /*ignore*/
-#define g_mime_init(flags) g_mime_init()
-#define g_mime_message_add_recipient(m,t,n,a) g_mime_message_add_mailbox (m,t,n,a)
-#define g_mime_message_set_subject(m,s) g_mime_message_set_subject(m,s,NULL)
-#define g_mime_multipart_signed_verify(mps,ctx,err) g_mime_multipart_signed_verify(mps, GMIME_ENCRYPT_NONE, err)
-#define g_mime_object_write_to_stream(o,s) g_mime_object_write_to_stream (o,NULL,s)
-#define g_mime_object_set_header(o,h,v) g_mime_object_set_header (o,h,v,NULL)
-#define g_mime_parser_construct_message(p) g_mime_parser_construct_message (p, g_mime_parser_options_get_default ())
-#define g_mime_part_get_content_object(p) g_mime_part_get_content (p)
-#define g_mime_pkcs7_context_new(arg) g_mime_pkcs7_context_new()
-#define g_mime_pkcs7_context_set_always_trust(ctx,val) /*ignore*/
-#define g_mime_signature_get_errors(sig) g_mime_signature_get_status (sig)
-#define g_mime_utils_header_decode_text(txt) g_mime_utils_header_decode_text (NULL, txt)
-#define internet_address_to_string(ia,encode) internet_address_to_string (ia,NULL,encode)
-#define internet_address_list_parse_string(str) internet_address_list_parse (NULL,str)
-
-typedef GMimeAddressType GMimeRecipientType;
-
-typedef GMimeSignatureStatus GMimeSignatureError;
-
-#define g_mime_2_6_unref(obj) /*ignore*/
-#define g_mime_3_unused(arg) unused(arg)
-#endif
+/* Return a GMime stream for this path, un-gzipping if
+ * necessary */
+GMimeStream *g_mime_stream_gzfile_open (const char *filename);
 
 /**
  * Get last 16 hex digits of fingerprint ("keyid")
@@ -55,9 +25,9 @@ const char *g_mime_certificate_get_fpr16 (GMimeCertificate *cert);
  * Return the contents of the appropriate address header as a string
  * Should be freed using g_free
  */
-char *g_mime_message_get_address_string (GMimeMessage *message, GMimeRecipientType type);
+char *g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type);
 
-InternetAddressList * g_mime_message_get_addresses (GMimeMessage *message, GMimeRecipientType type);
+InternetAddressList * g_mime_message_get_addresses (GMimeMessage *message, GMimeAddressType type);
 
 /**
  * return talloc allocated date string
@@ -91,7 +61,7 @@ gboolean g_mime_signature_status_good (GMimeSignatureStatus status);
 
 gboolean g_mime_signature_status_bad (GMimeSignatureStatus status);
 
-gboolean g_mime_signature_status_error (GMimeSignatureError status);
+gboolean g_mime_signature_status_error (GMimeSignatureStatus status);
 
 gint64 g_mime_utils_header_decode_date_unix (const char *date);
 
@@ -100,4 +70,8 @@ gint64 g_mime_utils_header_decode_date_unix (const char *date);
  */
 const char * g_mime_certificate_get_valid_userid (GMimeCertificate *cert);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index 5182042e07b3dcde81c3cc594528dc2ae4754f23..50d946eda4463b4bbf539625ed9336697ee41312 100644 (file)
@@ -1,6 +1,10 @@
 #ifndef _HEX_ESCAPE_H
 #define _HEX_ESCAPE_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef enum hex_status {
     HEX_SUCCESS = 0,
     HEX_SYNTAX_ERROR,
@@ -38,4 +42,9 @@ hex_decode (void *talloc_ctx, const char *in, char **out,
  */
 hex_status_t
 hex_decode_inplace (char *s);
+
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index eac5dc0534716a73c3d3e98d8158783f45cb5144..e2e617342a92544191ee9aca06d157494e76b96d 100644 (file)
@@ -3,6 +3,10 @@
 
 #include <talloc.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Like talloc_strndup, but take an extra parameter for the internal talloc
  * name (for debugging) */
 
@@ -15,4 +19,8 @@ talloc_strndup_named_const (void *ctx, const char *str,
 
 #define talloc_strndup_debug(ctx, str, len) talloc_strndup_named_const (ctx, str, len, __location__)
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index 4829f33c9ec8c73875d3f378382ad6c55625f6f8..e27070009b6839f51653f946bdc0971653915c68 100644 (file)
 #include <sys/types.h>
 #include <regex.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* xutil.c */
 void *
 xcalloc (size_t nmemb, size_t size);
@@ -49,4 +53,8 @@ int
 xregexec (const regex_t *preg, const char *string,
          size_t nmatch, regmatch_t pmatch[], int eflags);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index aedfd48fcb06b73e52ff3514e9fb152dec1b831e..209fa9989437d8cf77bf9ac60c739cb9fcb47a8f 100644 (file)
@@ -4,6 +4,10 @@
 #include "util.h"
 #include <zlib.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Like getline, but read from a gzFile. Allocation is with talloc.
  * Returns:
  *
@@ -22,4 +26,9 @@ gz_getline (void *ctx, char **lineptr, ssize_t *bytes_read, gzFile stream);
 
 const char *
 gz_error_string (util_status_t status, gzFile stream);
+
+#ifdef __cplusplus
+}
+#endif
+
 #endif