From: David Bremner Date: Sun, 5 May 2019 19:38:51 +0000 (-0300) Subject: Merge tag 0.28.4 X-Git-Tag: archive/debian/0.29_rc0-1~69 X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=commitdiff_plain;h=6682b4e686b7972883626c9b0f941ae4bf02dedb;hp=93bd675c2a900b234536c5beaf0f7749e8fbe872 Merge tag 0.28.4 No functionality changes merged, since the bug in question was already fixed on master. --- diff --git a/.gitignore b/.gitignore index e06101ce..468b660a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tags .*.swp /releases /.stamps +*.stamp diff --git a/.travis.yml b/.travis.yml index a2caf010..90a1cc56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 6e6f4799..f1236e71 100644 --- 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 diff --git a/Makefile.global b/Makefile.global index cae4c7d1..0aee5876 100644 --- a/Makefile.global +++ b/Makefile.global @@ -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 diff --git a/Makefile.local b/Makefile.local index 82145e1b..3c6dacbc 100644 --- a/Makefile.local +++ b/Makefile.local @@ -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 c5b7eec9..26b8160c 100644 --- 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) =========================== diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 342d665a..88ca836e 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -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 diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index de0fb415..6e32b5f7 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -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 diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 06c7b11b..cc70e2aa 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -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. diff --git a/configure b/configure index ea22587b..9140026a 100755 --- 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 diff --git a/debian/control b/debian/control index 922f6d8c..31d6471c 100644 --- a/debian/control +++ b/debian/control @@ -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~), diff --git a/debugger.c b/debugger.c index 5cb38ac4..0febf170 100644 --- a/debugger.c +++ b/debugger.c @@ -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; } diff --git a/doc/Makefile.local b/doc/Makefile.local index cb0f1f64..651168f4 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -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 diff --git a/doc/conf.py b/doc/conf.py index 0ef72327..5f8c9f1c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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' diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst index 12d86e89..2a7df6f0 100644 --- a/doc/man1/notmuch-address.rst +++ b/doc/man1/notmuch-address.rst @@ -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. diff --git a/doc/man1/notmuch-count.rst b/doc/man1/notmuch-count.rst index 9ca20dab..0eac5dbe 100644 --- a/doc/man1/notmuch-count.rst +++ b/doc/man1/notmuch-count.rst @@ -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`` diff --git a/doc/man1/notmuch-new.rst b/doc/man1/notmuch-new.rst index 5eeb4926..20a1103b 100644 --- a/doc/man1/notmuch-new.rst +++ b/doc/man1/notmuch-new.rst @@ -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. diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst index 654c5f2c..ed9ff4e5 100644 --- a/doc/man1/notmuch-search.rst +++ b/doc/man1/notmuch-search.rst @@ -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. diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index 8bfa87c6..becd3e79 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -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:" as can be diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst index f7a39ceb..fd8bf634 100644 --- a/doc/man7/notmuch-search-terms.rst +++ b/doc/man7/notmuch-search-terms.rst @@ -44,6 +44,9 @@ results to those whose value matches a regular expression (see notmuch search 'from:"/bob@.*[.]example[.]com/"' +body: + Match terms in the body of messages. + from: or from:// 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:** diff --git a/doc/notmuch-emacs.rst b/doc/notmuch-emacs.rst index ce2e358e..1655e2f0 100644 --- a/doc/notmuch-emacs.rst +++ b/doc/notmuch-emacs.rst @@ -62,7 +62,7 @@ notmuch-hello key bindings ```` 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 ```` 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` + ```` 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 diff --git a/emacs/.gitignore b/emacs/.gitignore index fbf8dde6..b9873b0a 100644 --- a/emacs/.gitignore +++ b/emacs/.gitignore @@ -1,4 +1,5 @@ /.eldeps* /*.elc +/*.rsti /notmuch-version.el /notmuch-pkg.el diff --git a/emacs/Makefile.local b/emacs/Makefile.local index 5e4ae1bd..40b7c14f 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -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 diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index fb7f4f55..e22e0d16 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -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 ;; David Bremner +;; Leo Gaspard ;;; 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))) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 25d83fd6..546ab6fd 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -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) diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el index 55e4cfee..0164472f 100644 --- a/emacs/notmuch-message.el +++ b/emacs/notmuch-message.el @@ -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) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index df2ac447..7fdd76bc 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -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. diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index a0a58373..e13ca3d7 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -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 diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 5f8b9267..54108d93 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -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) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 44402f8a..804e78ab 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -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 index 00000000..2225aefc --- /dev/null +++ b/emacs/rstdoc.el @@ -0,0 +1,85 @@ +;;; rstdoc.el --- help generate documentation from docstrings -*-lexical-binding: t-*- + +;; Copyright (C) 2018 David Bremner + +;; Author: David Bremner +;; 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 . +;; + +;;; 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 index 00000000..a449b58e --- /dev/null +++ b/emacs/rstdoc.rsti @@ -0,0 +1,21 @@ +.. -*- rst -*- + +.. |br| replace:: |br-texinfo| |br-html| + +.. |br-texinfo| raw:: texinfo + + @* @* + +.. |br-html| raw:: html + +

+ +.. |indent| replace:: |indent-texinfo| |indent-html| + +.. |indent-texinfo| raw:: texinfo + + @* @ @ @ @ + +.. |indent-html| raw:: html + +
     diff --git a/gmime-filter-reply.c b/gmime-filter-reply.c index f673c0a2..480d9381 100644 --- a/gmime-filter-reply.c +++ b/gmime-filter-reply.c @@ -19,6 +19,7 @@ #include #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); diff --git a/lib/built-with.c b/lib/built-with.c index 9cffd9f9..320be6c5 100644 --- a/lib/built-with.c +++ b/lib/built-with.c @@ -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; } diff --git a/lib/database-private.h b/lib/database-private.h index a499b259..293f2db4 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -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 diff --git a/lib/database.cc b/lib/database.cc index 9cf8062c..1753117f 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -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; } diff --git a/lib/index.cc b/lib/index.cc index 3f694387..41822488 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -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); diff --git a/lib/message-file.c b/lib/message-file.c index 8f0dbbda..50855067 100644 --- a/lib/message-file.c +++ b/lib/message-file.c @@ -27,8 +27,8 @@ #include /* 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 (""); } diff --git a/lib/message.cc b/lib/message.cc index 6f2f6345..38a48933 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -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; diff --git a/lib/notmuch.h b/lib/notmuch.h index 247f6ad7..24708f3c 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -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); diff --git a/lib/regexp-fields.cc b/lib/regexp-fields.cc index 084bc8c0..5d4cf80a 100644 --- a/lib/regexp-fields.cc +++ b/lib/regexp-fields.cc @@ -35,9 +35,9 @@ compile_regex (regex_t ®exp, const char *str) if (err != 0) { size_t len = regerror (err, ®exp, NULL, 0); char *buffer = new char[len]; - std::string msg; + std::string msg = "Regexp error: "; (void) regerror (err, ®exp, 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; diff --git a/lib/thread.cc b/lib/thread.cc index 47c90664..c9c9398f 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -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); diff --git a/mime-node.c b/mime-node.c index 2a24e537..e33336bb 100644 --- a/mime-node.c +++ b/mime-node.c @@ -21,13 +21,16 @@ * Austin Clements */ +#include +#include +#include + #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); } } diff --git a/notmuch-client.h b/notmuch-client.h index 6c84ecc0..d762d3cc 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -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 diff --git a/notmuch-config.c b/notmuch-config.c index bf77cc9d..e029e306 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -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 . return the * component group and key. If any error occurs, print a message on diff --git a/notmuch-insert.c b/notmuch-insert.c index d229c9dc..327470d4 100644 --- a/notmuch-insert.c +++ b/notmuch-insert.c @@ -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)); diff --git a/notmuch-new.c b/notmuch-new.c index 6a54a1a1..184e9aa2 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -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)); diff --git a/notmuch-reindex.c b/notmuch-reindex.c index d8589120..3139a8c6 100644 --- a/notmuch-reindex.c +++ b/notmuch-reindex.c @@ -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)); diff --git a/notmuch-reply.c b/notmuch-reply.c index 75cf7ecb..7f284229 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -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, ¬much)) return EXIT_FAILURE; diff --git a/notmuch-search.c b/notmuch-search.c index 8f467db4..e2dee418 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -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 ' vs. '"Doe, John" ' */ - 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; diff --git a/notmuch-show.c b/notmuch-show.c index 98b85352..b95fc389 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -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; diff --git a/notmuch.c b/notmuch.c index 7810b685..eeb794e8 100644 --- 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 diff --git a/performance-test/T00-new.sh b/performance-test/T00-new.sh index 68750129..25391136 100755 --- a/performance-test/T00-new.sh +++ b/performance-test/T00-new.sh @@ -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 diff --git a/performance-test/T03-reindex.sh b/performance-test/T03-reindex.sh index d6d5c3c3..8e0a77f4 100755 --- a/performance-test/T03-reindex.sh +++ b/performance-test/T03-reindex.sh @@ -1,6 +1,6 @@ #!/bin/bash -test_description='tagging' +test_description='reindexing' . $(dirname "$0")/perf-test-lib.sh || exit 1 diff --git a/test/.gitignore b/test/.gitignore index 73fe7e24..69080e5e 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -9,3 +9,4 @@ /test-results /ghost-report /tmp.* +/message-id-parse diff --git a/test/T030-config.sh b/test/T030-config.sh index f36695c6..883541d5 100755 --- a/test/T030-config.sh +++ b/test/T030-config.sh @@ -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 < 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 diff --git a/test/T040-setup.sh b/test/T040-setup.sh index 56efe1d5..fbfe200a 100755 --- a/test/T040-setup.sh +++ b/test/T040-setup.sh @@ -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" diff --git a/test/T190-multipart.sh b/test/T190-multipart.sh index 3eeac1db..5cfa9d33 100755 --- a/test/T190-multipart.sh +++ b/test/T190-multipart.sh @@ -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 <EXPECTED + message{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 excluded:0 filename:${MAIL_DIR}/multipart + header{ +Carl Worth (2001-01-05) (attachment inbox signed unread) +Subject: Multipart message +From: Carl Worth +To: cworth@cworth.org +Date: Fri, 05 Jan 2001 15:43:57 +0000 + header} + message} +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 <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 <EXPECTED + part{ ID: 5, Content-type: text/html +

This is an embedded message, with a multipart/alternative part.

+ part} +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 <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 diff --git a/test/T220-reply.sh b/test/T220-reply.sh index ebe710f9..4db3a958 100755 --- a/test/T220-reply.sh +++ b/test/T220-reply.sh @@ -180,6 +180,7 @@ test_expect_equal "$output" "From: Notmuch Test Suite References: <${gen_msg_id}> diff --git a/test/T230-reply-to-sender.sh b/test/T230-reply-to-sender.sh index 134a1063..bbeaa2b9 100755 --- a/test/T230-reply-to-sender.sh +++ b/test/T230-reply-to-sender.sh @@ -203,6 +203,7 @@ test_expect_equal "$output" "From: Notmuch Test Suite References: <${gen_msg_id}> diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh index 5935819f..c06a8133 100755 --- a/test/T310-emacs.sh +++ b/test/T310-emacs.sh @@ -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\\\" \"" thread=$(notmuch search --output=threads subject:message-with-invalid-from) @@ -610,7 +610,7 @@ test_emacs "(let ((message-hidden-headers '())) (test-output))" cat <EXPECTED From: Notmuch Test Suite -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 <EXPECTED From: Notmuch Test Suite -To: +To: test_suite@notmuchmail.org Subject: Re: Quote MML tags in reply In-Reply-To: Fcc: ${MAIL_DIR}/sent diff --git a/test/T350-crypto.sh b/test/T350-crypto.sh index a776ec35..6045a7dc 100755 --- a/test/T350-crypto.sh +++ b/test/T350-crypto.sh @@ -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 Subject: Re: test encrypted message 002 +To: test_suite@notmuchmail.org On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite 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 <EXPECTED From: Notmuch Test Suite +To: test_suite@notmuchmail.org Subject: Re: test encrypted message 002 --text follows this line-- <#secure method=pgpmime mode=signencrypt> diff --git a/test/T355-smime.sh b/test/T355-smime.sh index be45e3b1..e410286b 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -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", diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh index c5435f4f..0a602e50 100755 --- a/test/T357-index-decryption.sh +++ b/test/T357-index-decryption.sh @@ -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" diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh index 69ebec68..2124dde2 100755 --- a/test/T530-upgrade.sh +++ b/test/T530-upgrade.sh @@ -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 < EXPECTED +d +d +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done diff --git a/test/T650-regexp-query.sh b/test/T650-regexp-query.sh index 4085340f..92334ba0 100755 --- a/test/T650-regexp-query.sh +++ b/test/T650-regexp-query.sh @@ -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 < 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 index 00000000..c8d2bcc2 --- /dev/null +++ b/test/T720-emacs-attachment-warnings.sh @@ -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 index 00000000..3d94d4df --- /dev/null +++ b/test/T720-lib-lifetime.sh @@ -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 +#include +#include +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 index 00000000..45e61568 --- /dev/null +++ b/test/T730-emacs-forwarding.sh @@ -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 index 00000000..548b30a4 --- /dev/null +++ b/test/T740-body.sh @@ -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 < 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 < 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 < 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 < 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 < 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 index 00000000..5b678fa1 --- /dev/null +++ b/test/T750-gzip.sh @@ -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 < 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 < 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 < EXPECTED + message{ id:msg-006@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-006 + header{ +Notmuch Test Suite (2001-01-05) (inbox unread) +Subject: Multiple new messages, one gzipped (full-scan) +From: Notmuch Test Suite +To: Notmuch Test Suite +Date: Fri, 05 Jan 2001 15:43:51 +0000 + header} + body{ + part{ ID: 1, Content-type: text/plain +This is just a test message (#6) + part} + body} + message} +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 < EXPECTED +From test_suite@notmuchmail.org Fri Jan 5 15:43:51 2001 +From: Notmuch Test Suite +To: Notmuch Test Suite +Message-Id: +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 < EXPECTED +From: Notmuch Test Suite +To: Notmuch Test Suite +Message-Id: +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 < EXPECTED + message{ id:msg-007@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-007.gz + header{ +Notmuch Test Suite (2001-01-05) (inbox unread) +Subject: Renamed (gzipped) message +From: Notmuch Test Suite +To: Notmuch Test Suite +Date: Fri, 05 Jan 2001 15:43:50 +0000 + header} + body{ + part{ ID: 1, Content-type: text/plain +This is just a test message (#7) + part} + body} + message} +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 < EXPECTED +From test_suite@notmuchmail.org Fri Jan 5 15:43:50 2001 +From: Notmuch Test Suite +To: Notmuch Test Suite +Message-Id: +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 < EXPECTED +From: Notmuch Test Suite +To: Notmuch Test Suite +Message-Id: +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 index 00000000..200ca7ba --- /dev/null +++ b/test/emacs-attachment-warnings.el @@ -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))) diff --git a/test/test-lib.sh b/test/test-lib.sh index fca5277d..04d93f7d 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -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 diff --git a/util/crypto.c b/util/crypto.c index 9d3b6dad..ba67d4f4 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -24,126 +24,14 @@ #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; } diff --git a/util/crypto.h b/util/crypto.h index c384601c..af3998e8 100644 --- a/util/crypto.h +++ b/util/crypto.h @@ -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 diff --git a/util/error_util.h b/util/error_util.h index 4bb338a2..aa3b77c4 100644 --- a/util/error_util.h +++ b/util/error_util.h @@ -25,6 +25,10 @@ #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 diff --git a/util/gmime-extra.c b/util/gmime-extra.c index bc1e3c4d..7562d906 100644 --- a/util/gmime-extra.c +++ b/util/gmime-extra.c @@ -1,127 +1,93 @@ #include "gmime-extra.h" #include +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 diff --git a/util/gmime-extra.h b/util/gmime-extra.h index ca822b8c..b0c8d3d8 100644 --- a/util/gmime-extra.h +++ b/util/gmime-extra.h @@ -1,51 +1,21 @@ #ifndef _GMIME_EXTRA_H #define _GMIME_EXTRA_H #include +#include -GMimeStream *g_mime_stream_stdout_new(void); +#ifdef __cplusplus +extern "C" { +#endif -#include +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 diff --git a/util/hex-escape.h b/util/hex-escape.h index 5182042e..50d946ed 100644 --- a/util/hex-escape.h +++ b/util/hex-escape.h @@ -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 diff --git a/util/talloc-extra.h b/util/talloc-extra.h index eac5dc05..e2e61734 100644 --- a/util/talloc-extra.h +++ b/util/talloc-extra.h @@ -3,6 +3,10 @@ #include +#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 diff --git a/util/xutil.h b/util/xutil.h index 4829f33c..e2707000 100644 --- a/util/xutil.h +++ b/util/xutil.h @@ -25,6 +25,10 @@ #include #include +#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 diff --git a/util/zlib-extra.h b/util/zlib-extra.h index aedfd48f..209fa998 100644 --- a/util/zlib-extra.h +++ b/util/zlib-extra.h @@ -4,6 +4,10 @@ #include "util.h" #include +#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