.*.swp
/releases
/.stamps
+*.stamp
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
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
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
--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)
$(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."
@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)"
+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)
===========================
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
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
_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
_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.
WITH_EMACS=1
WITH_DESKTOP=1
WITH_BASH=1
+WITH_RPATH=1
WITH_RUBY=1
WITH_ZSH=1
WITH_RETRY_LOCK=1
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
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
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
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
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:
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)"
# 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}
-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) \\
# 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}
# 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
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~),
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;
}
# 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.
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))
%.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.
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.
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
# 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'
``--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.
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``
``--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.
``--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.
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).
``--body=(true|false)``
If true (the default) **notmuch show** includes the bodies of the
messages in the output; if false, bodies are omitted.
- ``--body=false`` is only implemented for the json and sexp formats
- and it is incompatible with ``--part > 0.``
+ ``--body=false`` is only implemented for the text, json and sexp
+ formats and it is incompatible with ``--part > 0.``
This is useful if the caller only needs the headers as body-less
output is much faster and substantially smaller.
``--include-html``
- Include "text/html" parts as part of the output (currently only
- supported with ``--format=json`` and ``--format=sexp``). By default,
- unless ``--part=N`` is used to select a specific part or
- ``--include-html`` is used to include all "text/html" parts, no
- part with content type "text/html" is included in the output.
+ Include "text/html" parts as part of the output (currently
+ only supported with ``--format=text``, ``--format=json`` and
+ ``--format=sexp``). By default, unless ``--part=N`` is used to
+ select a specific part or ``--include-html`` is used to include all
+ "text/html" parts, no part with content type "text/html" is included
+ in the output.
A common use of **notmuch show** is to display a single thread of email
messages. For this, use a search term of "thread:<thread-id>" as can be
notmuch search 'from:"/bob@.*[.]example[.]com/"'
+body:<word-or-quoted-phrase>
+ Match terms in the body of messages.
+
from:<name-or-address> or from:/<regex>/
The **from:** prefix is used to match the name or address of
the sender of an email message.
Boolean
**tag:**, **id:**, **thread:**, **folder:**, **path:**, **property:**
Probabilistic
- **to:**, **attachment:**, **mimetype:**
+ **body:**, **to:**, **attachment:**, **mimetype:**
Special
**from:**, **query:**, **subject:**
``<return>``
Activate the current widget.
-``=``
+``g`` ``=``
Refresh the buffer; mainly update the counts of messages for various
saved searches.
``<return>``
Open thread on current line in :ref:`notmuch-show` mode
+``g`` ``=``
+ Refresh the buffer
+
``?``
Display full set of key bindings
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
``?``
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
message giving the relative date, the author, subject, and any
tags.
+``c``
+ :ref:`show-copy`
+
``<return>``
Displays that message.
``p``
Move to previous matching message
+``g`` ``=``
+ Refresh the buffer
+
``?``
Display full set of key bindings
: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
=============
--------------
:index:`notmuch-poll`
+ |docstring::notmuch-poll|
:index:`notmuch-poll-script`
+ |docstring::notmuch-poll-script|
Init File
---------
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
/.eldeps*
/*.elc
+/*.rsti
/notmuch-version.el
/notmuch-pkg.el
$(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,
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)
ifeq ($(WITH_EMACS),1)
ifeq ($(HAVE_EMACS),1)
-all: $(emacs_bytecode)
+all: $(emacs_bytecode) $(emacs_docstrings)
install-emacs: $(emacs_bytecode)
endif
-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
;;
;; Copyright © Mark Walters
;; Copyright © David Bremner
+;; Copyright © Leo Gaspard
;;
;; This file is part of Notmuch.
;;
;;
;; Authors: Mark Walters <markwalters1009@gmail.com>
;; David Bremner <david@tethera.net>
+;; Leo Gaspard <leo@gaspard.io>
;;; Code:
"--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)
(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)))
(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)
"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.
;; 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)
(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.
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)
(require 'notmuch-lib)
(require 'notmuch-address)
(require 'notmuch-draft)
+(require 'notmuch-message)
(eval-when-compile (require 'cl))
(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)
(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.
(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))
(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))
;; 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)
(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.
(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)
(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)
;; 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)))
(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."
(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."
(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
;;; 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)
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\"))))
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
--- /dev/null
+;;; rstdoc.el --- help generate documentation from docstrings -*-lexical-binding: t-*-
+
+;; Copyright (C) 2018 David Bremner
+
+;; Author: David Bremner <david@tethera.net>
+;; Created: 26 May 2018
+;; Keywords: emacs lisp, documentation
+;; Homepage: https://notmuchmail.org
+
+;; This file is not part of GNU Emacs.
+
+;; rstdoc.el is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; rstdoc.el is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with rstdoc.el. If not, see <https://www.gnu.org/licenses/>.
+;;
+
+;;; Commentary:
+;;
+
+;; Rstdoc provides a facility to extract all of the docstrings defined in
+;; an elisp source file. Usage:
+;;
+;; emacs -Q --batch -L . -l rstdoc -f rstdoc-batch-extract foo.el foo.rsti
+
+;;; Code:
+
+(provide 'rstdoc)
+
+(defun rstdoc-batch-extract ()
+ "Extract docstrings to and from the files on the command line"
+ (apply #'rstdoc-extract command-line-args-left))
+
+(defun rstdoc-extract (in-file out-file)
+ "Write docstrings from IN-FILE to OUT-FILE"
+ (load-file in-file)
+ (let* ((definitions (cdr (assoc (expand-file-name in-file) load-history)))
+ (doc-hash (make-hash-table :test 'eq)))
+ (mapc
+ (lambda (elt)
+ (let ((pair
+ (pcase elt
+ (`(defun . ,name) (cons name (documentation name)))
+ (`(,_ . ,_) nil)
+ (sym (cons sym (get sym 'variable-documentation))))))
+ (when (and pair (cdr pair))
+ (puthash (car pair) (cdr pair) doc-hash))))
+ definitions)
+ (with-temp-buffer
+ (maphash
+ (lambda (key val)
+ (rstdoc--insert-docstring key val))
+ doc-hash)
+ (write-region (point-min) (point-max) out-file))))
+
+(defun rstdoc--insert-docstring (symbol docstring)
+ (insert (format "\n.. |docstring::%s| replace::\n" symbol))
+ (insert (replace-regexp-in-string "^" " " (rstdoc--rst-quote-string docstring)))
+ (insert "\n"))
+
+(defvar rst--escape-alist
+ '( ("\\\\='" . "\\\\'")
+ ("\\([^\\]\\)'" . "\\1`")
+ ("^[[:space:]\t]*$" . "|br|")
+ ("^[[:space:]\t]" . "|indent| "))
+ "list of (regex . replacement) pairs")
+
+(defun rstdoc--rst-quote-string (str)
+ (with-temp-buffer
+ (insert str)
+ (dolist (pair rst--escape-alist)
+ (goto-char (point-min))
+ (while (re-search-forward (car pair) nil t)
+ (replace-match (cdr pair))))
+ (buffer-substring (point-min) (point-max))))
+
+;;; rstdoc.el ends here
--- /dev/null
+.. -*- rst -*-
+
+.. |br| replace:: |br-texinfo| |br-html|
+
+.. |br-texinfo| raw:: texinfo
+
+ @* @*
+
+.. |br-html| raw:: html
+
+ <br /><br />
+
+.. |indent| replace:: |indent-texinfo| |indent-html|
+
+.. |indent-texinfo| raw:: texinfo
+
+ @* @ @ @ @
+
+.. |indent-html| raw:: html
+
+ <br />
#include <stdbool.h>
#include "gmime-filter-reply.h"
+#include "notmuch-client.h"
/**
* SECTION: gmime-filter-reply
**/
-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);
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);
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);
} 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;
}
*
* 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
* 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:
*
* 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.
*
{ "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 |
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
*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);
"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 *
* 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) {
/* Initialize gmime */
if (! initialized) {
- g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+ g_mime_init ();
initialized = 1;
}
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);
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);
{
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);
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. */
}
_index_content_type (message, part);
- content_type = g_mime_object_get_content_type (part);
if (GMIME_IS_MULTIPART (part)) {
GMimeMultipart *multipart = GMIME_MULTIPART (part);
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) {
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),
}
}
- 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);
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;
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) {
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));
_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);
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);
#include <glib.h> /* GHashTable */
struct _notmuch_message_file {
- /* File object */
- FILE *file;
+ /* open stream to (possibly gzipped) file */
+ GMimeStream *stream;
char *filename;
/* Cache for decoded headers */
if (message->message)
g_object_unref (message->message);
- if (message->file)
- fclose (message->file);
-
return 0;
}
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;
}
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;
}
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;
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;
}
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;
}
DONE:
- g_object_unref (stream);
+ g_mime_stream_reset (message->stream);
g_object_unref (parser);
if (status) {
message->message = NULL;
}
- rewind (message->file);
}
return status;
_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);
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)
return combined;
}
-#endif
const char *
_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 ("");
}
}
/* 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,
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;
* 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))
* {
* 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);
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);
* 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;
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);
* Austin Clements <aclements@csail.mit.edu>
*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
#include "notmuch-client.h"
/* Context that gets inherited from the root node. */
typedef struct mime_node_context {
/* Per-message resources. These are allocated internally and must
* be destroyed. */
- FILE *file;
GMimeStream *stream;
GMimeParser *parser;
GMimeMessage *mime_message;
if (res->stream)
g_object_unref (res->stream);
- if (res->file)
- fclose (res->file);
-
return 0;
}
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) {
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
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;
}
}
- 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) {
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;
return status;
}
-/* Signature list destructor (GMime 2.6) */
+/* Signature list destructor */
static int
_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)
{
}
}
-/* 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);
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;
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",
}
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);
notmuch_message_add_property (message, "session-key",
session_key));
}
-#endif
g_object_unref (decrypt_result);
}
_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;
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) {
"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) {
"(must be exactly 2)\n",
node->nchildren);
} else {
- node_verify (node, part, cryptoctx);
+ node_verify (node, part);
}
}
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);
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
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 {
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. */
&(config->search_exclude_tags));
}
-#if (GMIME_MAJOR_VERSION < 3)
-const char *
-notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
-{
- return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
-}
-
-void
-notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
- const char *gpg_path)
-{
- _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
-}
-#endif
-
/* Given a configuration item of the form <group>.<key> return the
* component group and key. If any error occurs, print a message on
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));
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));
ret = notmuch_message_reindex(message, indexopts);
if (ret != NOTMUCH_STATUS_SUCCESS)
break;
+ notmuch_message_destroy (message);
}
if (!ret)
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));
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");
}
}
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));
}
}
scan_address_list (InternetAddressList *list,
notmuch_config_t *config,
GMimeMessage *message,
- GMimeRecipientType type,
+ GMimeAddressType type,
const char **user_from)
{
InternetAddress *address;
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++;
}
}
*/
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);
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 },
}
}
+ /* 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;
}
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)
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);
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;
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;
/* name_addr has the name part quoted if necessary. Compare
* 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
- name_addr = internet_address_to_string (ia, false);
+ name_addr = internet_address_to_string (ia, NULL, false);
if (format->is_text_printer) {
if (ctx->output & OUTPUT_COUNT) {
if (value == NULL)
return;
- list = internet_address_list_parse_string (value);
+ list = internet_address_list_parse (NULL, value);
if (list == NULL)
return;
#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)
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)
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;
}
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);
/* 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);
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;
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) {
}
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);
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);
}
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);
}
{
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);
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);
notmuch_message_t *message = node->envelope_file;
const char *filename;
- FILE *file;
+ gzFile file;
const char *from;
time_t date;
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));
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);
printf ("\n");
- fclose (file);
+ gzclose (file);
return NOTMUCH_STATUS_SUCCESS;
}
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);
/* 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);
* 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)
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 &&
}
}
}
-#endif
DONE:
talloc_free (local);
return status;
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);
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;
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);
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;
}
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
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
#!/bin/bash
-test_description='tagging'
+test_description='reindexing'
. $(dirname "$0")/perf-test-lib.sh || exit 1
/test-results
/ghost-report
/tmp.*
+/message-id-parse
test_expect_equal "$(notmuch config get foo.nonexistent)" ""
test_begin_subtest "List all items"
-notmuch config list 2>&1 | notmuch_config_sanitize > OUTPUT
-
-if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
- config_gpg_path="crypto.gpg_path=gpg
-"
-fi
+notmuch config list > STDOUT 2> STDERR
+printf "%s\n====\n%s\n" "$(< STDOUT)" "$(< STDERR)" | notmuch_config_sanitize > OUTPUT
cat <<EOF > EXPECTED
-Error opening database at MAIL_DIR/.notmuch: No such file or directory
database.path=MAIL_DIR
user.name=Notmuch Test Suite
user.primary_email=test_suite@notmuchmail.org
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
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
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"
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "--format=text --part=0 --body=false, message header"
+notmuch show --format=text --part=0 --body=false 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fmessage{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 excluded:0 filename:${MAIL_DIR}/multipart
+\fheader{
+Carl Worth <cworth@cworth.org> (2001-01-05) (attachment inbox signed unread)
+Subject: Multipart message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+\fheader}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_begin_subtest "--format=text --part=1, message body"
notmuch show --format=text --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
cat <<EOF >EXPECTED
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "--format=text --include-html --part=5, rfc822's html part"
+notmuch show --format=text --include-html --part=5 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 5, Content-type: text/html
+<p>This is an embedded message, with a multipart/alternative part.</p>
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_begin_subtest "--format=text --part=6, rfc822's text part"
notmuch show --format=text --part=6 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
cat <<EOF >EXPECTED
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
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
Subject: Re: This subject is exactly 200 bytes in length. Other than its
length there is not much of note here. Note that the length of 200 bytes
includes the Subject: and Re: prefixes with two spaces
+To: test_suite@notmuchmail.org
In-Reply-To: <${gen_msg_id}>
References: <${gen_msg_id}>
Subject: Re: This subject is exactly 200 bytes in length. Other than its
length there is not much of note here. Note that the length of 200 bytes
includes the Subject: and Re: prefixes with two spaces
+To: test_suite@notmuchmail.org
In-Reply-To: <${gen_msg_id}>
References: <${gen_msg_id}>
test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage-with-fourfold-indentation OUTPUT
test_begin_subtest "notmuch-show for message with invalid From"
-test_subtest_broken_gmime_3
+test_subtest_known_broken
add_message "[subject]=\"message-with-invalid-from\"" \
"[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\""
thread=$(notmuch search --output=threads subject:message-with-invalid-from)
(test-output))"
cat <<EOF >EXPECTED
From: Notmuch Test Suite <test_suite@notmuchmail.org>
-To:
+To: test_suite@notmuchmail.org
Subject: Re: Reply within emacs to an html-only message
In-Reply-To: <${gen_msg_id}>
Fcc: ${MAIL_DIR}/sent
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 '()))
(test-output))"
cat <<EOF >EXPECTED
From: Notmuch Test Suite <test_suite@notmuchmail.org>
-To:
+To: test_suite@notmuchmail.org
Subject: Re: Quote MML tags in reply
In-Reply-To: <test-emacs-mml-quoting@message.id>
Fcc: ${MAIL_DIR}/sent
##################################################
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 \
"$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
"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",
"(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|')
| notmuch_drop_mail_headers In-Reply-To References)
expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: test encrypted message 002
+To: test_suite@notmuchmail.org
On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
> This is another test encrypted message.'
(notmuch-show \"subject:test.encrypted.message.002\")
(notmuch-show-reply)
(test-output))"
-# the empty To: is probably a bug, but it's not to do with encryption
-grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' -e 'To:' < OUTPUT > OUTPUT.clean
+grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' < OUTPUT > OUTPUT.clean
cat <<EOF >EXPECTED
From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: test_suite@notmuchmail.org
Subject: Re: test encrypted message 002
--text follows this line--
<#secure method=pgpmime mode=signencrypt>
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|' \
"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",
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"
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"
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"
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"
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"
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"
MAIL_DIR/bar/new/22:2,
MAIL_DIR/cur/51:2,"
+test_begin_subtest "body: same as unprefixed before reindex"
+notmuch search --output=messages body:close > OUTPUT
+notmuch search --output=messages close > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "body: subset of unprefixed after reindex"
+notmuch reindex '*'
+notmuch search --output=messages body:close | sort > BODY
+notmuch search --output=messages close | sort > UNPREFIXED
+diff -e UNPREFIXED BODY | cut -c2- > OUTPUT
+cat <<EOF > EXPECTED
+d
+d
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_done
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
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp error reporting"
-notmuch search 'from:/unbalanced[/' 1>OUTPUT 2>&1
+notmuch search 'from:/unbalanced[/' 2>&1 | sed -e '/^A Xapian/ s/[^:]*$//' > OUTPUT
cat <<EOF > EXPECTED
notmuch search: A Xapian exception occurred
-A Xapian exception occurred parsing query: Invalid regular expression
+A Xapian exception occurred parsing query: Regexp error:
Query string was: from:/unbalanced[/
EOF
test_expect_equal_file EXPECTED OUTPUT
--- /dev/null
+#!/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
--- /dev/null
+#!/usr/bin/env bash
+#
+# Copyright (c) 2018 rhn
+#
+
+
+test_description="Lifetime constraints for library"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus threading
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+test_begin_subtest "Message outlives parent Messages from replies"
+
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <stdlib.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ notmuch_status_t stat;
+ stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, &db);
+ if (stat != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "error opening database: %d\n", stat);
+ exit (1);
+ }
+
+ notmuch_query_t *query = notmuch_query_create (db, "id:B00-root@example.org");
+ notmuch_threads_t *threads;
+
+ stat = notmuch_query_search_threads (query, &threads);
+ if (stat != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "error querying threads: %d\n", stat);
+ exit (1);
+ }
+
+ if (!notmuch_threads_valid (threads)) {
+ fprintf (stderr, "invalid threads");
+ exit (1);
+ }
+
+ notmuch_thread_t *thread = notmuch_threads_get (threads);
+ notmuch_messages_t *messages = notmuch_thread_get_messages (thread);
+
+ if (!notmuch_messages_valid (messages)) {
+ fprintf (stderr, "invalid messages");
+ exit (1);
+ }
+
+ notmuch_message_t *message = notmuch_messages_get (messages);
+ notmuch_messages_t *replies = notmuch_message_get_replies (message);
+ if (!notmuch_messages_valid (replies)) {
+ fprintf (stderr, "invalid replies");
+ exit (1);
+ }
+
+ notmuch_message_t *reply = notmuch_messages_get (replies);
+
+ notmuch_messages_destroy (replies); // the reply should not get destroyed here
+ notmuch_message_destroy (message);
+ notmuch_messages_destroy (messages); // nor here
+
+ const char *mid = notmuch_message_get_message_id (reply); // should not crash when accessing
+ fprintf (stdout, "Reply id: %s\n", mid);
+ notmuch_message_destroy (reply);
+ notmuch_thread_destroy (thread); // this destroys the reply
+ notmuch_threads_destroy (threads);
+ notmuch_query_destroy (query);
+ notmuch_database_destroy (db);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+Reply id: B01-child@example.org
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
--- /dev/null
+#!/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
--- /dev/null
+#!/usr/bin/env bash
+test_description='search body'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message "[body]=thebody-1" "[subject]=subject-1"
+add_message "[body]=nothing-to-see-here-1" "[subject]=thebody-1"
+
+test_begin_subtest 'search with body: prefix'
+notmuch search body:thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search without body: prefix'
+notmuch search thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; thebody-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'negated body: prefix'
+notmuch search thebody and not body:thebody | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; thebody-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search unprefixed for prefixed term'
+notmuch search subject | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; subject-1 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search with body: prefix for term only in subject'
+notmuch search body:subject | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
--- /dev/null
+#!/usr/bin/env bash
+test_description='support for gzipped messages'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+#######################################################################
+# notmuch new
+test_begin_subtest "Single new gzipped message"
+generate_message
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Single new gzipped message (full-scan)"
+generate_message
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Multiple new messages, one gzipped"
+generate_message
+gzip $gen_msg_filename
+generate_message
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "Multiple new messages, one gzipped (full-scan)"
+generate_message
+gzip $gen_msg_filename
+generate_message
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "Renamed (gzipped) message"
+generate_message
+echo $gen_message_filename
+notmuch new > /dev/null
+gzip $gen_msg_filename
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
+No new mail. Detected 1 file rename."
+
+######################################################################
+# notmuch search
+
+test_begin_subtest "notmuch search with partially gzipped mail store"
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Single new gzipped message (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Single new gzipped message (full-scan) (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (full-scan) (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Multiple new messages, one gzipped (full-scan) (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Renamed (gzipped) message (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch search --output=files with partially gzipped mail store"
+notmuch search --output=files '*' | notmuch_search_files_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+MAIL_DIR/msg-001.gz
+MAIL_DIR/msg-002.gz
+MAIL_DIR/msg-003.gz
+MAIL_DIR/msg-004
+MAIL_DIR/msg-005.gz
+MAIL_DIR/msg-006
+MAIL_DIR/msg-007.gz
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+######################################################################
+# notmuch show
+
+test_begin_subtest "show un-gzipped message"
+notmuch show id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+\fmessage{ id:msg-006@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-006
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Multiple new messages, one gzipped (full-scan)
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#6)
+\fpart}
+\fbody}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show un-gzipped message (format mbox)"
+notmuch show --format=mbox id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From test_suite@notmuchmail.org Fri Jan 5 15:43:51 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-006@notmuch-test-suite>
+Subject: Multiple new messages, one gzipped (full-scan)
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+
+This is just a test message (#6)
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show un-gzipped message (format raw)"
+notmuch show --format=raw id:msg-006@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-006@notmuch-test-suite>
+Subject: Multiple new messages, one gzipped (full-scan)
+Date: Fri, 05 Jan 2001 15:43:51 +0000
+
+This is just a test message (#6)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message"
+notmuch show id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+\fmessage{ id:msg-007@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-007.gz
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Renamed (gzipped) message
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#7)
+\fpart}
+\fbody}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message (mbox)"
+notmuch show --format=mbox id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From test_suite@notmuchmail.org Fri Jan 5 15:43:50 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-007@notmuch-test-suite>
+Subject: Renamed (gzipped) message
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+
+This is just a test message (#7)
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show gzipped message (raw)"
+notmuch show --format=raw id:msg-007@notmuch-test-suite | notmuch_show_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-007@notmuch-test-suite>
+Subject: Renamed (gzipped) message
+Date: Fri, 05 Jan 2001 15:43:50 +0000
+
+This is just a test message (#7)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
--- /dev/null
+(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)))
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:
. "$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
#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)
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;
}
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;
}
if (ret)
return ret;
}
-#endif
if (err && *err) {
g_error_free (*err);
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;
}
#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
#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).
_internal_error (format " (%s).\n", \
##__VA_ARGS__, __location__)
+#ifdef __cplusplus
+}
+#endif
#endif
#include "gmime-extra.h"
#include <string.h>
+static
GMimeStream *
-g_mime_stream_stdout_new()
-{
- GMimeStream *stream_stdout = NULL;
- GMimeStream *stream_buffered = NULL;
+_gzfile_maybe_filter (GMimeStream *file_stream) {
+ char buf[4];
+ int bytes_read;
- stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
- if (!stream_stdout)
+ if ((bytes_read = g_mime_stream_read (file_stream, buf, sizeof (buf))) < 0)
return NULL;
- g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
+ if (g_mime_stream_reset (file_stream))
+ return NULL;
- stream_buffered = g_mime_stream_buffer_new (stream_stdout, GMIME_STREAM_BUFFER_BLOCK_WRITE);
+ /* check for gzipped input */
+ if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char)buf[1] == 0x8b) {
+ GMimeStream *gzstream;
+ GMimeFilter *gzfilter;
- g_object_unref (stream_stdout);
+ gzfilter = g_mime_filter_gzip_new (GMIME_FILTER_GZIP_MODE_UNZIP, 0);
+ if (! gzfilter)
+ return NULL;
- return stream_buffered;
-}
+ gzstream = g_mime_stream_filter_new (file_stream);
+ if (! gzstream)
+ return NULL;
-/**
- * copy a glib string into a talloc context, and free it.
- */
-static char*
-g_string_talloc_strdup (void *ctx, char *g_string)
-{
- char *new_str = talloc_strdup (ctx, g_string);
- g_free (g_string);
- return new_str;
+ /* ignore filter id */
+ (void)g_mime_stream_filter_add ((GMimeStreamFilter *)gzstream, gzfilter);
+ return gzstream;
+ } else {
+ return file_stream;
+ }
}
-#if (GMIME_MAJOR_VERSION < 3)
-
-const char *
-g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
+GMimeStream *
+g_mime_stream_gzfile_new (int fd)
{
- /* output user id only if validity is FULL or ULTIMATE. */
- /* note that gmime 2.6 is using the term "trust" here, which
- * is WRONG. It's actually user id "validity". */
- const char *name = g_mime_certificate_get_name (cert);
- if (name == NULL)
- return name;
- GMimeCertificateTrust trust = g_mime_certificate_get_trust (cert);
- if (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)
- return name;
- return NULL;
-}
+ GMimeStream *file_stream;
-char *
-g_mime_message_get_address_string (GMimeMessage *message, GMimeRecipientType type)
-{
- InternetAddressList *list = g_mime_message_get_recipients (message, type);
- return internet_address_list_to_string (list, 0);
-}
+ file_stream = g_mime_stream_fs_new (fd);
+ if (! file_stream)
+ return NULL;
-inline InternetAddressList *
-g_mime_message_get_addresses (GMimeMessage *message, GMimeRecipientType type)
-{
- return g_mime_message_get_recipients (message, type);
+ return _gzfile_maybe_filter (file_stream);
}
-char *
-g_mime_message_get_date_string (void *ctx, GMimeMessage *message)
+GMimeStream *
+g_mime_stream_gzfile_open (const char *filename)
{
- char *date = g_mime_message_get_date_as_string (message);
- return g_string_talloc_strdup (ctx, date);
-}
+ GMimeStream *file_stream;
-InternetAddressList *
-g_mime_message_get_from (GMimeMessage *message)
-{
- return internet_address_list_parse_string (g_mime_message_get_sender (message));
-}
+ file_stream = g_mime_stream_fs_open (filename, 0, 0, NULL);
+ if (! file_stream)
+ return NULL;
-const char *
-g_mime_message_get_from_string (GMimeMessage *message) {
- return g_mime_message_get_sender (message);
+ return _gzfile_maybe_filter (file_stream);
}
-InternetAddressList *
-g_mime_message_get_reply_to_list (GMimeMessage *message)
+GMimeStream *
+g_mime_stream_stdout_new()
{
- const char *reply_to;
+ GMimeStream *stream_stdout = NULL;
+ GMimeStream *stream_buffered = NULL;
- reply_to = g_mime_message_get_reply_to (message);
- if (reply_to && *reply_to)
- return internet_address_list_parse_string (reply_to);
- else
+ stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
+ if (!stream_stdout)
return NULL;
-}
-/**
- * return talloc allocated reply-to string
- */
-char *
-g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message)
-{
- return talloc_strdup(ctx, g_mime_message_get_reply_to (message));
-}
+ g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
-gboolean
-g_mime_signature_status_good (GMimeSignatureStatus status) {
- return (status == GMIME_SIGNATURE_STATUS_GOOD);
-}
+ stream_buffered = g_mime_stream_buffer_new (stream_stdout, GMIME_STREAM_BUFFER_BLOCK_WRITE);
-gboolean
-g_mime_signature_status_bad (GMimeSignatureStatus status) {
- return (status == GMIME_SIGNATURE_STATUS_BAD);
-}
+ g_object_unref (stream_stdout);
-gboolean
-g_mime_signature_status_error (GMimeSignatureError error) {
- return (error != GMIME_SIGNATURE_ERROR_NONE);
+ return stream_buffered;
}
-gint64
-g_mime_utils_header_decode_date_unix (const char *date) {
- return (gint64) g_mime_utils_header_decode_date (date, NULL);
+/**
+ * copy a glib string into a talloc context, and free it.
+ */
+static char*
+g_string_talloc_strdup (void *ctx, char *g_string)
+{
+ char *new_str = talloc_strdup (ctx, g_string);
+ g_free (g_string);
+ return new_str;
}
-#else /* GMime >= 3.0 */
-
const char *
g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
{
return ret;
}
-
-#endif
#ifndef _GMIME_EXTRA_H
#define _GMIME_EXTRA_H
#include <gmime/gmime.h>
+#include <talloc.h>
-GMimeStream *g_mime_stream_stdout_new(void);
+#ifdef __cplusplus
+extern "C" {
+#endif
-#include <talloc.h>
+GMimeStream *g_mime_stream_stdout_new(void);
+/* Return a GMime stream for this open file descriptor, un-gzipping if
+ * necessary */
+GMimeStream *g_mime_stream_gzfile_new (int fd);
-#if (GMIME_MAJOR_VERSION < 3)
-
-#define GMIME_ADDRESS_TYPE_TO GMIME_RECIPIENT_TYPE_TO
-#define GMIME_ADDRESS_TYPE_CC GMIME_RECIPIENT_TYPE_CC
-#define GMIME_ADDRESS_TYPE_BCC GMIME_RECIPIENT_TYPE_BCC
-
-#define g_mime_2_6_unref(obj) g_object_unref (obj)
-#define g_mime_3_unused(arg) arg
-#define g_mime_certificate_get_fpr16(cert) g_mime_certificate_get_key_id (cert)
-#else /* GMime >= 3.0 */
-
-#define GMIME_ENABLE_RFC_2047_WORKAROUNDS 0xdeadbeef
-#define g_mime_content_type_to_string(c) g_mime_content_type_get_mime_type (c)
-#define g_mime_filter_crlf_new(encode,dots) g_mime_filter_dos2unix_new (FALSE)
-#define g_mime_gpg_context_new(func,path) g_mime_gpg_context_new ()
-#define g_mime_gpg_context_set_use_agent(ctx,val) /*ignore*/
-#define g_mime_gpg_context_set_always_trust(ctx,val) /*ignore*/
-#define g_mime_init(flags) g_mime_init()
-#define g_mime_message_add_recipient(m,t,n,a) g_mime_message_add_mailbox (m,t,n,a)
-#define g_mime_message_set_subject(m,s) g_mime_message_set_subject(m,s,NULL)
-#define g_mime_multipart_signed_verify(mps,ctx,err) g_mime_multipart_signed_verify(mps, GMIME_ENCRYPT_NONE, err)
-#define g_mime_object_write_to_stream(o,s) g_mime_object_write_to_stream (o,NULL,s)
-#define g_mime_object_set_header(o,h,v) g_mime_object_set_header (o,h,v,NULL)
-#define g_mime_parser_construct_message(p) g_mime_parser_construct_message (p, g_mime_parser_options_get_default ())
-#define g_mime_part_get_content_object(p) g_mime_part_get_content (p)
-#define g_mime_pkcs7_context_new(arg) g_mime_pkcs7_context_new()
-#define g_mime_pkcs7_context_set_always_trust(ctx,val) /*ignore*/
-#define g_mime_signature_get_errors(sig) g_mime_signature_get_status (sig)
-#define g_mime_utils_header_decode_text(txt) g_mime_utils_header_decode_text (NULL, txt)
-#define internet_address_to_string(ia,encode) internet_address_to_string (ia,NULL,encode)
-#define internet_address_list_parse_string(str) internet_address_list_parse (NULL,str)
-
-typedef GMimeAddressType GMimeRecipientType;
-
-typedef GMimeSignatureStatus GMimeSignatureError;
-
-#define g_mime_2_6_unref(obj) /*ignore*/
-#define g_mime_3_unused(arg) unused(arg)
-#endif
+/* Return a GMime stream for this path, un-gzipping if
+ * necessary */
+GMimeStream *g_mime_stream_gzfile_open (const char *filename);
/**
* Get last 16 hex digits of fingerprint ("keyid")
* 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
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);
*/
const char * g_mime_certificate_get_valid_userid (GMimeCertificate *cert);
+#ifdef __cplusplus
+}
+#endif
+
#endif
#ifndef _HEX_ESCAPE_H
#define _HEX_ESCAPE_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
typedef enum hex_status {
HEX_SUCCESS = 0,
HEX_SYNTAX_ERROR,
*/
hex_status_t
hex_decode_inplace (char *s);
+
+#ifdef __cplusplus
+}
+#endif
+
#endif
#include <talloc.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* Like talloc_strndup, but take an extra parameter for the internal talloc
* name (for debugging) */
#define talloc_strndup_debug(ctx, str, len) talloc_strndup_named_const (ctx, str, len, __location__)
+#ifdef __cplusplus
+}
+#endif
+
#endif
#include <sys/types.h>
#include <regex.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* xutil.c */
void *
xcalloc (size_t nmemb, size_t size);
xregexec (const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
+#ifdef __cplusplus
+}
+#endif
+
#endif
#include "util.h"
#include <zlib.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* Like getline, but read from a gzFile. Allocation is with talloc.
* Returns:
*
const char *
gz_error_string (util_status_t status, gzFile stream);
+
+#ifdef __cplusplus
+}
+#endif
+
#endif