[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