[PATCH v2] Output unmodified Content-Type header value for JSON format.
Dmitry Kurochkin
dmitry.kurochkin at gmail.com
Fri Nov 18 20:18:41 PST 2011
Before the change, notmuch used g_mime_content_type_to_string(3)
function to output Content-Type header value. Turns out it outputs
only "type/subtype" part and ignores all parameters. Also, if there
is no Content-Type header, default "text/plain" value is used.
JSON is supposed to be a "low-level" structured format and should not
add missing values or throw away information. The patch changes
notmuch show to use unmodified Content-Type value for JSON format.
Also, no default value is added if the header is missing.
Corresponding changes to Emacs UI are made to handle full Content-Type
header values. The header is parsed using MIME
`mail-header-parse-content-type' function. All message part rendering
functions have access to full Content-Type value. In particular, this
is important for `notmuch-show-mm-display-part-inline' which uses
`mm-display-part' to display parts that notmuch-show does not handle.
Expected results for the tests are updated accordingly.
---
Changes in v2 since v1:
* Use "text/plain; charset=us-ascii" for default Content-Type value in
`notmuch-show-get-content-type' function as defined in RFC 2045.
emacs/notmuch-show.el | 29 +++++++++++++++++++----------
notmuch-show.c | 14 ++++++++++++--
test/crypto | 23 ++++++++---------------
test/json | 6 +++---
test/maildir-sync | 1 -
test/multipart | 36 ++++++++++++++++++------------------
6 files changed, 60 insertions(+), 49 deletions(-)
diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
index d5c95d8..cb801ae 100644
--- a/emacs/notmuch-show.el
+++ b/emacs/notmuch-show.el
@@ -261,120 +261,120 @@ message at DEPTH in the current thread."
(if (and header-value
(not (string-equal "" header-value)))
(notmuch-show-insert-header header header-value))))
notmuch-message-headers)
(save-excursion
(save-restriction
(narrow-to-region start (point-max))
(run-hooks 'notmuch-show-markup-headers-hook)))))
(define-button-type 'notmuch-show-part-button-type
'action 'notmuch-show-part-button-action
'follow-link t
'face 'message-mml)
(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
(let ((button))
(setq button
(insert-button
(concat "[ "
(if name (concat name ": ") "")
- declared-type
- (if (not (string-equal declared-type content-type))
- (concat " (as " content-type ")")
+ (car declared-type)
+ (if (not (string-equal (car declared-type) (car content-type)))
+ (concat " (as " (car content-type) ")")
"")
(or comment "")
" ]")
:type 'notmuch-show-part-button-type
:notmuch-part nth
:notmuch-filename name))
(insert "\n")
;; return button
button))
;; Functions handling particular MIME parts.
(defun notmuch-show-save-part (message-id nth &optional filename)
(let ((process-crypto notmuch-show-process-crypto))
(with-temp-buffer
(setq notmuch-show-process-crypto process-crypto)
;; Always acquires the part via `notmuch part', even if it is
;; available in the JSON output.
(insert (notmuch-show-get-bodypart-internal message-id nth))
(let ((file (read-file-name
"Filename to save as: "
(or mailcap-download-directory "~/")
nil nil
filename)))
;; Don't re-compress .gz & al. Arguably we should make
;; `file-name-handler-alist' nil, but that would chop
;; ange-ftp, which is reasonable to use here.
(mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t)))))
(defun notmuch-show-mm-display-part-inline (msg part content-type content)
"Use the mm-decode/mm-view functions to display a part in the
current buffer, if possible."
(let ((display-buffer (current-buffer)))
(with-temp-buffer
(insert content)
- (let ((handle (mm-make-handle (current-buffer) (list content-type))))
+ (let ((handle (mm-make-handle (current-buffer) content-type)))
(set-buffer display-buffer)
(if (and (mm-inlinable-p handle)
(mm-inlined-p handle))
(progn
(mm-display-part handle)
t)
nil)))))
(defvar notmuch-show-multipart/alternative-discouraged
'(
;; Avoid HTML parts.
"text/html"
;; multipart/related usually contain a text/html part and some associated graphics.
"multipart/related"
))
(defun notmuch-show-multipart/*-to-list (part)
- (mapcar '(lambda (inner-part) (plist-get inner-part :content-type))
+ (mapcar '(lambda (inner-part) (car (notmuch-show-get-content-type inner-part)))
(plist-get part :content)))
(defun notmuch-show-multipart/alternative-choose (types)
;; Based on `mm-preferred-alternative-precedence'.
(let ((seq types))
(dolist (pref (reverse notmuch-show-multipart/alternative-discouraged))
(dolist (elem (copy-sequence seq))
(when (string-match pref elem)
(setq seq (nconc (delete elem seq) (list elem))))))
seq))
(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth declared-type)
(notmuch-show-insert-part-header nth declared-type content-type nil)
(let ((chosen-type (car (notmuch-show-multipart/alternative-choose (notmuch-show-multipart/*-to-list part))))
(inner-parts (plist-get part :content))
(start (point)))
;; This inserts all parts of the chosen type rather than just one,
;; but it's not clear that this is the wrong thing to do - which
;; should be chosen if there are more than one that match?
(mapc (lambda (inner-part)
- (let ((inner-type (plist-get inner-part :content-type)))
+ (let ((inner-type (notmuch-show-get-content-type inner-part)))
(if (or notmuch-show-all-multipart/alternative-parts
- (string= chosen-type inner-type))
+ (string= chosen-type (car inner-type)))
(notmuch-show-insert-bodypart msg inner-part depth)
(notmuch-show-insert-part-header (plist-get inner-part :id) inner-type inner-type nil " (not shown)"))))
inner-parts)
(when notmuch-show-indent-multipart
(indent-rigidly start (point) 1)))
t)
(defun notmuch-show-setup-w3m ()
"Instruct w3m how to retrieve content from a \"related\" part of a message."
(interactive)
(if (boundp 'w3m-cid-retrieve-function-alist)
(unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)
(push (cons 'notmuch-show-mode 'notmuch-show-w3m-cid-retrieve)
w3m-cid-retrieve-function-alist)))
(setq mm-inline-text-html-with-images t))
(defvar w3m-current-buffer) ;; From `w3m.el'.
(defvar notmuch-show-w3m-cid-store nil)
(make-variable-buffer-local 'notmuch-show-w3m-cid-store)
@@ -557,41 +557,42 @@ current buffer, if possible."
(set-buffer (get-file-buffer file))
(setq result (buffer-substring (point-min) (point-max)))
(set-buffer-modified-p nil)
(kill-buffer (current-buffer))
(delete-file file)
result)))
t)
(defun notmuch-show-insert-part-application/octet-stream (msg part content-type nth depth declared-type)
;; If we can deduce a MIME type from the filename of the attachment,
;; do so and pass it on to the handler for that type.
(if (plist-get part :filename)
(let ((extension (file-name-extension (plist-get part :filename)))
mime-type)
(if extension
(progn
(mailcap-parse-mimetypes)
(setq mime-type (mailcap-extension-to-mime extension))
(if (and mime-type
(not (string-equal mime-type "application/octet-stream")))
- (notmuch-show-insert-bodypart-internal msg part mime-type nth depth content-type)
+ (notmuch-show-insert-bodypart-internal msg part (list mime-type)
+ nth depth content-type)
nil))
nil))))
(defun notmuch-show-insert-part-application/* (msg part content-type nth depth declared-type
)
;; do not render random "application" parts
(notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename)))
(defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type)
;; This handler _must_ succeed - it is the handler of last resort.
(notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))
(let ((content (notmuch-show-get-bodypart-content msg part nth)))
(if content
(notmuch-show-mm-display-part-inline msg part content-type content)))
t)
;; Functions for determining how to handle MIME parts.
(defun notmuch-show-split-content-type (content-type)
(split-string content-type "/"))
@@ -618,51 +619,51 @@ current buffer, if possible."
(defun notmuch-show-get-bodypart-internal (message-id part-number)
(let ((args '("show" "--format=raw"))
(part-arg (format "--part=%s" part-number)))
(setq args (append args (list part-arg)))
(if notmuch-show-process-crypto
(setq args (append args '("--decrypt"))))
(setq args (append args (list message-id)))
(with-temp-buffer
(let ((coding-system-for-read 'no-conversion))
(progn
(apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))
(buffer-string))))))
(defun notmuch-show-get-bodypart-content (msg part nth)
(or (plist-get part :content)
(notmuch-show-get-bodypart-internal (concat "id:" (plist-get msg :id)) nth)))
;;
(defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth declared-type)
- (let ((handlers (notmuch-show-handlers-for content-type)))
+ (let ((handlers (notmuch-show-handlers-for (car content-type))))
;; Run the content handlers until one of them returns a non-nil
;; value.
(while (and handlers
(not (funcall (car handlers) msg part content-type nth depth declared-type)))
(setq handlers (cdr handlers))))
t)
(defun notmuch-show-insert-bodypart (msg part depth)
"Insert the body part PART at depth DEPTH in the current thread."
- (let ((content-type (downcase (plist-get part :content-type)))
+ (let ((content-type (notmuch-show-get-content-type part))
(nth (plist-get part :id)))
(notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type))
;; Some of the body part handlers leave point somewhere up in the
;; part, so we make sure that we're down at the end.
(goto-char (point-max))
;; Ensure that the part ends with a carriage return.
(if (not (bolp))
(insert "\n")))
(defun notmuch-show-insert-body (msg body depth)
"Insert the body BODY at depth DEPTH in the current thread."
(mapc '(lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
(defun notmuch-show-make-symbol (type)
(make-symbol (concat "notmuch-show-" type)))
(defun notmuch-show-strip-re (string)
(replace-regexp-in-string "\\([Rr]e: *\\)+" "" string))
(defvar notmuch-show-previous-subject "")
@@ -1054,40 +1055,48 @@ All currently available key bindings:
(save-excursion
(notmuch-show-move-to-message-top)
(get-text-property (point) :notmuch-message-properties)))
(defun notmuch-show-set-prop (prop val &optional props)
(let ((inhibit-read-only t)
(props (or props
(notmuch-show-get-message-properties))))
(plist-put props prop val)
(notmuch-show-set-message-properties props)))
(defun notmuch-show-get-prop (prop &optional props)
(let ((props (or props
(notmuch-show-get-message-properties))))
(plist-get props prop)))
(defun notmuch-show-get-message-id ()
"Return the message id of the current message."
(concat "id:\"" (notmuch-show-get-prop :id) "\""))
+(defun notmuch-show-get-content-type (&optional props)
+ "Return parsed Content-Type of the given message, or part, or
+current message if no `props` is given. If there is no
+Content-Type header, it defaults to
+\"text/plain; charset=us-ascii\" as defined in RFC 2045."
+ (mail-header-parse-content-type (or (notmuch-show-get-prop :content-type props)
+ "text/plain; charset=us-ascii")))
+
;; dme: Would it make sense to use a macro for many of these?
(defun notmuch-show-get-filename ()
"Return the filename of the current message."
(notmuch-show-get-prop :filename))
(defun notmuch-show-get-header (header)
"Return the named header of the current message, if any."
(plist-get (notmuch-show-get-prop :headers) header))
(defun notmuch-show-get-cc ()
(notmuch-show-get-header :Cc))
(defun notmuch-show-get-date ()
(notmuch-show-get-header :Date))
(defun notmuch-show-get-from ()
(notmuch-show-get-header :From))
(defun notmuch-show-get-subject ()
diff --git a/notmuch-show.c b/notmuch-show.c
index 603992a..da3e87f 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -3,40 +3,42 @@
* Copyright © 2009 Carl Worth
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth at cworth.org>
*/
#include "notmuch-client.h"
+static const char HEADER_CONTENT_TYPE[] = "Content-Type";
+
static void
format_message_text (unused (const void *ctx),
notmuch_message_t *message,
int indent);
static void
format_headers_text (const void *ctx,
notmuch_message_t *message);
static void
format_headers_message_part_text (GMimeMessage *message);
static void
format_part_start_text (GMimeObject *part,
int *part_count);
static void
format_part_content_text (GMimeObject *part);
static void
format_part_end_text (GMimeObject *part);
@@ -640,42 +642,50 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity)
}
printf ("}");
signer = signer->next;
}
printf ("]");
talloc_free (ctx_quote);
}
static void
format_part_content_json (GMimeObject *part)
{
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
GMimeStream *stream_memory = g_mime_stream_mem_new ();
const char *cid = g_mime_object_get_content_id (part);
void *ctx = talloc_new (NULL);
GByteArray *part_content;
- printf (", \"content-type\": %s",
- json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
+ {
+ /* Output full Content-Type header value,
+ * g_mime_content_type_to_string(3) does not include
+ * parameters. Content-Type header may be missing,
+ * g_mime_object_get_content_type(3) defaults to "text/plain"
+ * in this case. */
+ const char *const h = g_mime_object_get_header (part, HEADER_CONTENT_TYPE);
+ if (h)
+ printf (", \"content-type\": %s", json_quote_str (ctx, h));
+ }
if (cid != NULL)
printf(", \"content-id\": %s", json_quote_str (ctx, cid));
if (GMIME_IS_PART (part))
{
const char *filename = g_mime_part_get_filename (GMIME_PART (part));
if (filename)
printf (", \"filename\": %s", json_quote_str (ctx, filename));
}
if (g_mime_content_type_is_type (content_type, "text", "*") &&
!g_mime_content_type_is_type (content_type, "text", "html"))
{
show_text_part_content (part, stream_memory);
part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
printf (", \"content\": %s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len));
}
else if (g_mime_content_type_is_type (content_type, "multipart", "*"))
diff --git a/test/crypto b/test/crypto
index 0af4aa8..b923d22 100755
--- a/test/crypto
+++ b/test/crypto
@@ -40,111 +40,108 @@ test_expect_success 'emacs delivery of signed message' \
test_begin_subtest "signature verification"
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["inbox","signed"],
"headers": {"Subject": "test signed message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"sigstatus": [{"status": "good",
"fingerprint": "'$FINGERPRINT'",
"created": 946728000}],
- "content-type": "multipart/signed",
+ "content-type": "multipart/signed; boundary=\"=-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"",
"content": [{"id": 2,
- "content-type": "text/plain",
"content": "This is a test signed message.\n"},
{"id": 3,
"content-type": "application/pgp-signature"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "signature verification with full owner trust"
# 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
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["inbox","signed"],
"headers": {"Subject": "test signed message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"sigstatus": [{"status": "good",
"fingerprint": "'$FINGERPRINT'",
"created": 946728000,
"userid": " Notmuch Test Suite <test_suite at notmuchmail.org> (INSECURE!)"}],
- "content-type": "multipart/signed",
+ "content-type": "multipart/signed; boundary=\"=-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"",
"content": [{"id": 2,
- "content-type": "text/plain",
"content": "This is a test signed message.\n"},
{"id": 3,
"content-type": "application/pgp-signature"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "signature verification with signer key unavailable"
# move the gnupghome temporarily out of the way
mv "${GNUPGHOME}"{,.bak}
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["inbox","signed"],
"headers": {"Subject": "test signed message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"sigstatus": [{"status": "error",
"keyid": "'$(echo $FINGERPRINT | cut -c 25-)'",
"errors": 2}],
- "content-type": "multipart/signed",
+ "content-type": "multipart/signed; boundary=\"=-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"",
"content": [{"id": 2,
- "content-type": "text/plain",
"content": "This is a test signed message.\n"},
{"id": 3,
"content-type": "application/pgp-signature"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
mv "${GNUPGHOME}"{.bak,}
# create a test encrypted message with attachment
cat <<EOF >TESTATTACHMENT
This is a test file.
EOF
test_expect_success 'emacs delivery of encrypted message with attachment' \
'emacs_deliver_message \
"test encrypted message 001" \
"This is a test encrypted message.\n" \
"(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
test_begin_subtest "decryption, --format=text"
@@ -181,138 +178,135 @@ test_expect_equal \
test_begin_subtest "decryption, --format=json"
output=$(notmuch show --format=json --decrypt subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["encrypted","inbox"],
"headers": {"Subject": "test encrypted message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"encstatus": [{"status": "good"}],
"sigstatus": [],
- "content-type": "multipart/encrypted",
+ "content-type": "multipart/encrypted; boundary=\"==-=-=\";\tprotocol=\"application/pgp-encrypted\"",
"content": [{"id": 2,
"content-type": "application/pgp-encrypted"},
{"id": 3,
- "content-type": "multipart/mixed",
+ "content-type": "multipart/mixed; boundary=\"=-=-=\"",
"content": [{"id": 4,
- "content-type": "text/plain",
"content": "This is a test encrypted message.\n"},
{"id": 5,
"content-type": "application/octet-stream",
"filename": "TESTATTACHMENT"}]}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "decryption, --format=json, --part=4"
output=$(notmuch show --format=json --part=4 --decrypt subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='{"id": 4,
- "content-type": "text/plain",
"content": "This is a test encrypted message.\n"}'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "decrypt attachment (--part=5 --format=raw)"
notmuch show \
--format=raw \
--part=5 \
--decrypt \
subject:"test encrypted message 001" >OUTPUT
test_expect_equal_file OUTPUT TESTATTACHMENT
test_begin_subtest "decryption failure with missing key"
mv "${GNUPGHOME}"{,.bak}
output=$(notmuch show --format=json --decrypt subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["encrypted","inbox"],
"headers": {"Subject": "test encrypted message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"encstatus": [{"status": "bad"}],
- "content-type": "multipart/encrypted",
+ "content-type": "multipart/encrypted; boundary=\"==-=-=\";\tprotocol=\"application/pgp-encrypted\"",
"content": [{"id": 2,
"content-type": "application/pgp-encrypted"},
{"id": 3,
"content-type": "application/octet-stream"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
mv "${GNUPGHOME}"{.bak,}
test_expect_success 'emacs delivery of encrypted + signed message' \
'emacs_deliver_message \
"test encrypted message 002" \
"This is another test encrypted message.\n" \
"(mml-secure-message-sign-encrypt)"'
test_begin_subtest "decryption + signature verification"
output=$(notmuch show --format=json --decrypt subject:"test encrypted message 002" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["encrypted","inbox"],
"headers": {"Subject": "test encrypted message 002",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"encstatus": [{"status": "good"}],
"sigstatus": [{"status": "good",
"fingerprint": "'$FINGERPRINT'",
"created": 946728000,
"userid": " Notmuch Test Suite <test_suite at notmuchmail.org> (INSECURE!)"}],
- "content-type": "multipart/encrypted",
+ "content-type": "multipart/encrypted; boundary=\"=-=-=\";\tprotocol=\"application/pgp-encrypted\"",
"content": [{"id": 2,
"content-type": "application/pgp-encrypted"},
{"id": 3,
- "content-type": "text/plain",
"content": "This is another test encrypted message.\n"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "reply to encrypted message"
output=$(notmuch reply --decrypt subject:"test encrypted message 002" \
| grep -v -e '^In-Reply-To:' -e '^References:')
expected='From: Notmuch Test Suite <test_suite at notmuchmail.org>
Subject: Re: test encrypted message 002
On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite at notmuchmail.org> wrote:
> This is another test encrypted message.'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "signature verification with revoked key"
# generate revocation certificate and load it to revoke key
@@ -327,32 +321,31 @@ y
| gpg --no-tty --quiet --import
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"match": true,
"filename": "YYYYY",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["inbox","signed"],
"headers": {"Subject": "test signed message 001",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "test_suite at notmuchmail.org",
"Cc": "",
"Bcc": "",
"Date": "01 Jan 2000 12:00:00 -0000"},
"body": [{"id": 1,
"sigstatus": [{"status": "error",
"keyid": "6D92612D94E46381",
"errors": 8}],
- "content-type": "multipart/signed",
+ "content-type": "multipart/signed; boundary=\"=-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"",
"content": [{"id": 2,
- "content-type": "text/plain",
"content": "This is a test signed message.\n"},
{"id": 3,
"content-type": "application/pgp-signature"}]}]},
[]]]]'
test_expect_equal \
"$output" \
"$expected"
test_done
diff --git a/test/json b/test/json
index 592b068..64f35cf 100755
--- a/test/json
+++ b/test/json
@@ -1,50 +1,50 @@
#!/usr/bin/env bash
test_description="--format=json output"
. ./test-lib.sh
test_begin_subtest "Show message: json"
add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-show-message\""
output=$(notmuch show --format=json "json-show-message")
-test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]"
+test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content\": \"json-show-message\n\"}]}, []]]]"
test_begin_subtest "Search message: json"
add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\""
output=$(notmuch search --format=json "json-search-message" | notmuch_search_sanitize)
test_expect_equal "$output" "[{\"thread\": \"XXX\",
\"timestamp\": 946728000,
\"matched\": 1,
\"total\": 1,
\"authors\": \"Notmuch Test Suite\",
\"subject\": \"json-search-subject\",
\"tags\": [\"inbox\", \"unread\"]}]"
test_begin_subtest "Show message: json, utf-8"
add_message "[subject]=\"json-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
output=$(notmuch show --format=json "jsön-show-méssage")
-test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]"
+test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content\": \"jsön-show-méssage\n\"}]}, []]]]"
test_begin_subtest "Show message: json, inline attachment filename"
subject='json-show-inline-attachment-filename'
id="json-show-inline-attachment-filename at notmuchmail.org"
emacs_deliver_message \
"$subject" \
'This is a test message with inline attachment with a filename' \
"(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
(message-goto-eoh)
(insert \"Message-ID: <$id>\n\")"
output=$(notmuch show --format=json "id:$id")
filename=$(notmuch search --output=files "id:$id")
-test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"test_suite at notmuchmail.org\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]"
+test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite at notmuchmail.org>\", \"To\": \"test_suite at notmuchmail.org\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed; boundary=\\\"=-=-=\\\"\", \"content\": [{\"id\": 2, \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]"
test_begin_subtest "Search message: json, utf-8"
add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
output=$(notmuch search --format=json "jsön-search-méssage" | notmuch_search_sanitize)
test_expect_equal "$output" "[{\"thread\": \"XXX\",
\"timestamp\": 946728000,
\"matched\": 1,
\"total\": 1,
\"authors\": \"Notmuch Test Suite\",
\"subject\": \"json-search-utf8-body-sübjéct\",
\"tags\": [\"inbox\", \"unread\"]}]"
test_done
diff --git a/test/maildir-sync b/test/maildir-sync
index a60854f..c7ca22f 100755
--- a/test/maildir-sync
+++ b/test/maildir-sync
@@ -41,41 +41,40 @@ add_message [subject]='"Adding replied tag"' [filename]='adding-replied-tag:2,S'
notmuch tag +replied subject:"Adding replied tag"
output=$(cd ${MAIL_DIR}/cur; ls -1 adding-replied*)
test_expect_equal "$output" "adding-replied-tag:2,RS"
test_begin_subtest "notmuch show works with renamed file (without notmuch new)"
output=$(notmuch show --format=json id:${gen_msg_id} | filter_show_json)
test_expect_equal "$output" '[[[{"id": "adding-replied-tag at notmuch-test-suite",
"match": true,
"filename": "MAIL_DIR/cur/adding-replied-tag:2,RS",
"timestamp": 978709437,
"date_relative": "2001-01-05",
"tags": ["inbox","replied"],
"headers": {"Subject": "Adding replied tag",
"From": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"To": "Notmuch Test Suite <test_suite at notmuchmail.org>",
"Cc": "",
"Bcc": "",
"Date": "Tue,
05 Jan 2001 15:43:57 -0000"},
"body": [{"id": 1,
-"content-type": "text/plain",
"content": "This is just a test message (#3)\n"}]},
[]]]]'
test_expect_success 'notmuch reply works with renamed file (without notmuch new)' 'notmuch reply id:${gen_msg_id}'
test_begin_subtest "notmuch new detects no file rename after tag->flag synchronization"
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail."
test_begin_subtest "When read, message moved from new to cur"
add_message [subject]='"Message to move to cur"' [date]='"Sat, 01 Jan 2000 12:00:00 -0000"' [filename]='message-to-move-to-cur' [dir]=new
notmuch tag -unread subject:"Message to move to cur"
output=$(cd "$MAIL_DIR/cur"; ls message-to-move*)
test_expect_equal "$output" "message-to-move-to-cur:2,S"
test_begin_subtest "No rename should be detected by notmuch new"
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail."
# (*) If notmuch new was not run we've got "Processed 1 file in almost
# no time" here. The reason is that removing unread tag in a previous
diff --git a/test/multipart b/test/multipart
index f83526b..ca4db71 100755
--- a/test/multipart
+++ b/test/multipart
@@ -306,140 +306,140 @@ test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=text --part=9, pgp signature (unverified)"
notmuch show --format=text --part=9 'id:87liy5ap00.fsf at yoom.home.cworth.org' >OUTPUT
cat <<EOF >EXPECTED
part{ ID: 9, Content-type: application/pgp-signature
Non-text part: application/pgp-signature
part}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_expect_success \
"--format=text --part=8, no part, expect error" \
"notmuch show --format=text --part=8 'id:87liy5ap00.fsf at yoom.home.cworth.org'"
test_begin_subtest "--format=json --part=0, full message"
notmuch show --format=json --part=0 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
{"id": "87liy5ap00.fsf at yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth <cworth at cworth.org>", "To": "cworth at cworth.org", "Cc": "", "Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [
-{"id": 1, "content-type": "multipart/signed", "content": [
-{"id": 2, "content-type": "multipart/mixed", "content": [
+{"id": 1, "content-type": "multipart/signed; boundary=\"==-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"", "content": [
+{"id": 2, "content-type": "multipart/mixed; boundary=\"=-=-=\"", "content": [
{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth <cworth at cworth.org>", "To": "cworth at cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
-{"id": 4, "content-type": "multipart/alternative", "content": [
+{"id": 4, "content-type": "multipart/alternative; boundary=\"==-=-==\"", "content": [
{"id": 5, "content-type": "text/html"},
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]},
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"},
-{"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]},
+{"id": 7, "filename": "attachment", "content": "This is a text attachment.\n"},
+{"id": 8, "content": "And this message is signed.\n\n-Carl\n"}]},
{"id": 9, "content-type": "application/pgp-signature"}]}]}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=1, message body"
notmuch show --format=json --part=1 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
-{"id": 1, "content-type": "multipart/signed", "content": [
-{"id": 2, "content-type": "multipart/mixed", "content": [
+{"id": 1, "content-type": "multipart/signed; boundary=\"==-=-=\";\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"", "content": [
+{"id": 2, "content-type": "multipart/mixed; boundary=\"=-=-=\"", "content": [
{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth <cworth at cworth.org>", "To": "cworth at cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
-{"id": 4, "content-type": "multipart/alternative", "content": [
+{"id": 4, "content-type": "multipart/alternative; boundary=\"==-=-==\"", "content": [
{"id": 5, "content-type": "text/html"},
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]},
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"},
-{"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]},
+{"id": 7, "filename": "attachment", "content": "This is a text attachment.\n"},
+{"id": 8, "content": "And this message is signed.\n\n-Carl\n"}]},
{"id": 9, "content-type": "application/pgp-signature"}]}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=2, multipart/mixed"
notmuch show --format=json --part=2 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
-{"id": 2, "content-type": "multipart/mixed", "content": [
+{"id": 2, "content-type": "multipart/mixed; boundary=\"=-=-=\"", "content": [
{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth <cworth at cworth.org>", "To": "cworth at cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
-{"id": 4, "content-type": "multipart/alternative", "content": [
+{"id": 4, "content-type": "multipart/alternative; boundary=\"==-=-==\"", "content": [
{"id": 5, "content-type": "text/html"},
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]},
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"},
-{"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]}
+{"id": 7, "filename": "attachment", "content": "This is a text attachment.\n"},
+{"id": 8, "content": "And this message is signed.\n\n-Carl\n"}]}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=3, rfc822 part"
notmuch show --format=json --part=3 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth <cworth at cworth.org>", "To": "cworth at cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
-{"id": 4, "content-type": "multipart/alternative", "content": [
+{"id": 4, "content-type": "multipart/alternative; boundary=\"==-=-==\"", "content": [
{"id": 5, "content-type": "text/html"},
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=4, rfc822's multipart/alternative"
notmuch show --format=json --part=4 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
-{"id": 4, "content-type": "multipart/alternative", "content": [
+{"id": 4, "content-type": "multipart/alternative; boundary=\"==-=-==\"", "content": [
{"id": 5, "content-type": "text/html"},
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=5, rfc822's html part"
notmuch show --format=json --part=5 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
{"id": 5, "content-type": "text/html"}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=6, rfc822's text part"
notmuch show --format=json --part=6 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=7, inline attachment"
notmuch show --format=json --part=7 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"}
+{"id": 7, "filename": "attachment", "content": "This is a text attachment.\n"}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=8, plain text part"
notmuch show --format=json --part=8 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
-{"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}
+{"id": 8, "content": "And this message is signed.\n\n-Carl\n"}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--format=json --part=9, pgp signature (unverified)"
notmuch show --format=json --part=9 'id:87liy5ap00.fsf at yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT
echo >>OUTPUT # expect *no* newline at end of output
cat <<EOF >EXPECTED
{"id": 9, "content-type": "application/pgp-signature"}
EOF
test_expect_equal_file OUTPUT EXPECTED
test_expect_success \
"--format=json --part=10, no part, expect error" \
"notmuch show --format=json --part=10 'id:87liy5ap00.fsf at yoom.home.cworth.org'"
test_begin_subtest "--format=raw"
notmuch show --format=raw 'id:87liy5ap00.fsf at yoom.home.cworth.org' >OUTPUT
test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart
--
1.7.7.3
More information about the notmuch
mailing list