Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id 3D9FF429E25 for ; Fri, 18 Nov 2011 15:45:28 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: -0.799 X-Spam-Level: X-Spam-Status: No, score=-0.799 tagged_above=-999 required=5 tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 5dVuWTj9TDEK for ; Fri, 18 Nov 2011 15:45:25 -0800 (PST) Received: from mail-bw0-f53.google.com (mail-bw0-f53.google.com [209.85.214.53]) (using TLSv1 with cipher RC4-SHA (128/128 bits)) (No client certificate requested) by olra.theworths.org (Postfix) with ESMTPS id A88FE431FD0 for ; Fri, 18 Nov 2011 15:45:24 -0800 (PST) Received: by bkaq10 with SMTP id q10so4401847bka.26 for ; Fri, 18 Nov 2011 15:45:23 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=from:to:subject:date:message-id:x-mailer:mime-version:content-type :content-transfer-encoding; bh=iPCTQynsTs3zP2Tx9a2FU7l6GyheJeBxqxox+HandH8=; b=VNdWIn7gJXo9BINAwGaVivtGQ4LvLncF9FnmJQvjrKWgIcVL7+WblRN7681w/J2E9H LptQN5R1f/jzDDgzJe0XwWws2JgAph+UKycr2W6Kb83Ds3c7YQpjYil4Hc9klnAE2wWN vHKDMaAE8nnYbH4S1jq92R2jSASv86E2Urrxs= Received: by 10.204.141.2 with SMTP id k2mr2087713bku.81.1321659922273; Fri, 18 Nov 2011 15:45:22 -0800 (PST) Received: from localhost ([91.144.186.21]) by mx.google.com with ESMTPS id q6sm1874625bka.6.2011.11.18.15.45.20 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 18 Nov 2011 15:45:21 -0800 (PST) From: Dmitry Kurochkin To: notmuch@notmuchmail.org Subject: [PATCH] Output unmodified Content-Type header value for JSON format. Date: Sat, 19 Nov 2011 03:45:05 +0400 Message-Id: <1321659905-24367-1-git-send-email-dmitry.kurochkin@gmail.com> X-Mailer: git-send-email 1.7.7.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 18 Nov 2011 23:45:28 -0000 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. --- emacs/notmuch-show.el | 28 ++++++++++++++++++---------- notmuch-show.c | 14 ++++++++++++-- test/crypto | 23 ++++++++--------------- test/json | 6 +++--- test/maildir-sync | 1 - test/multipart | 36 ++++++++++++++++++------------------ 6 files changed, 59 insertions(+), 49 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index d5c95d8..d2c2fa3 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,47 @@ 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\"." + (mail-header-parse-content-type (or (notmuch-show-get-prop :content-type props) + "text/plain"))) + ;; 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 */ #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 ", "To": "test_suite@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 ", "To": "test_suite@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 (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 ", "To": "test_suite@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 <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 ", "To": "test_suite@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 ", "To": "test_suite@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 ", "To": "test_suite@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 (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 Subject: Re: test encrypted message 002 On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite 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 ", "To": "test_suite@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 \", \"To\": \"Notmuch Test Suite \", \"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 \", \"To\": \"Notmuch Test Suite \", \"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 \", \"To\": \"Notmuch Test Suite \", \"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 \", \"To\": \"Notmuch Test Suite \", \"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@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 \", \"To\": \"test_suite@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 \", \"To\": \"test_suite@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@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 ", "To": "Notmuch Test Suite ", "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@yoom.home.cworth.org' >OUTPUT cat <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@yoom.home.cworth.org'" test_begin_subtest "--format=json --part=0, full message" notmuch show --format=json --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED {"id": "87liy5ap00.fsf@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 ", "To": "cworth@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 ", "To": "cworth@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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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 ", "To": "cworth@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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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 ", "To": "cworth@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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED {"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org' | sed 's|{"id":|\n{"id":|g' >OUTPUT echo >>OUTPUT # expect *no* newline at end of output cat <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@yoom.home.cworth.org'" test_begin_subtest "--format=raw" notmuch show --format=raw 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart -- 1.7.7.3