(require 'notmuch-tag)
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
+(declare-function notmuch-message-mode "notmuch-mua")
(defgroup notmuch-draft nil
"Saving and editing drafts in Notmuch."
:type '(repeat string)
:group 'notmuch-send)
+(defcustom notmuch-draft-save-plaintext 'ask
+ "Should notmuch save/postpone in plaintext messages that seem
+ like they are intended to be sent encrypted
+(i.e with an mml encryption tag in it)."
+ :type '(radio
+ (const :tag "Never" nil)
+ (const :tag "Ask every time" ask)
+ (const :tag "Always" t))
+ :group 'notmuch-draft
+ :group 'notmuch-crypto)
+
+(defvar notmuch-draft-encryption-tag-regex
+ "<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)"
+ "Regular expression matching mml tags indicating encryption of part or message")
+
(defvar notmuch-draft-id nil
"Message-id of the most recent saved draft of this message")
(make-variable-buffer-local 'notmuch-draft-id)
(goto-char (+ (match-beginning 0) 2))
(insert "!"))))))
+(defun notmuch-draft-unquote-some-mml ()
+ "Unquote the mml tags in `notmuch-draft-quoted-tags`."
+ (save-excursion
+ (when notmuch-draft-quoted-tags
+ (let ((re (concat "<#!+/?\\("
+ (mapconcat 'regexp-quote notmuch-draft-quoted-tags "\\|")
+ "\\)")))
+ (message-goto-body)
+ (while (re-search-forward re nil t)
+ ;; Remove one ! from after the #.
+ (goto-char (+ (match-beginning 0) 2))
+ (delete-char 1))))
+ (let (secure-tag)
+ (save-restriction
+ (message-narrow-to-headers)
+ (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" 't))
+ (message-remove-header "X-Notmuch-Emacs-Secure"))
+ (message-goto-body)
+ (when secure-tag
+ (insert secure-tag "\n")))))
+
+(defun notmuch-draft--has-encryption-tag ()
+ "Returns t if there is an mml secure tag."
+ (save-excursion
+ (message-goto-body)
+ (re-search-forward notmuch-draft-encryption-tag-regex nil 't)))
+
+(defun notmuch-draft--query-encryption ()
+ "Checks if we should save a message that should be encrypted.
+
+`notmuch-draft-save-plaintext' controls the behaviour."
+ (case notmuch-draft-save-plaintext
+ ((ask)
+ (unless (yes-or-no-p "(Customize `notmuch-draft-save-plaintext' to avoid this warning)
+This message contains mml tags that suggest it is intended to be encrypted.
+Really save and index an unencrypted copy? ")
+ (error "Save aborted")))
+ ((nil)
+ (error "Refusing to save draft with encryption tags (see `notmuch-draft-save-plaintext')"))
+ ((t)
+ (ignore))))
+
(defun notmuch-draft--make-message-id ()
;; message-make-message-id gives the id inside a "<" ">" pair,
;; but notmuch doesn't want that form, so remove them.
`notmuch-draft-tags` (in addition to any default tags
applied to newly inserted messages)."
(interactive)
+ (when (notmuch-draft--has-encryption-tag)
+ (notmuch-draft--query-encryption))
(let ((id (notmuch-draft--make-message-id)))
(with-temporary-notmuch-message-buffer
;; We insert a Date header and a Message-ID header, the former
(notmuch-draft-save)
(kill-buffer))
+(defun notmuch-draft-resume (id)
+ "Resume editing of message with id ID."
+ (let* ((tags (process-lines notmuch-command "search" "--output=tags"
+ "--exclude=false" id))
+ (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
+ (when (or draft
+ (yes-or-no-p "Message does not appear to be a draft: really resume? "))
+ (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*")))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (let ((coding-system-for-read 'no-conversion))
+ (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (mime-to-mml)
+ (goto-char (point-min))
+ (when (re-search-forward "^$" nil t)
+ (replace-match mail-header-separator t t))
+ ;; Remove the Date and Message-ID headers (unless the user has
+ ;; explicitly customized emacs to tell us not to) as they will
+ ;; be replaced when the message is sent.
+ (save-restriction
+ (message-narrow-to-headers)
+ (when (member 'Message-ID message-deletable-headers)
+ (message-remove-header "Message-ID"))
+ (when (member 'Date message-deletable-headers)
+ (message-remove-header "Date"))
+ ;; The X-Notmuch-Emacs-Draft header is a more reliable
+ ;; indication of whether the message really is a draft.
+ (setq draft (> (message-remove-header "X-Notmuch-Emacs-Draft") 0)))
+ ;; If the message is not a draft we should not unquote any mml.
+ (when draft
+ (notmuch-draft-unquote-some-mml))
+ (notmuch-message-mode)
+ (message-goto-body)
+ (set-buffer-modified-p nil)
+ ;; If the resumed message was a draft then set the draft
+ ;; message-id so that we can delete the current saved draft if the
+ ;; message is resaved or sent.
+ (setq notmuch-draft-id (when draft id)))))
+
+
(add-hook 'message-send-hook 'notmuch-draft--mark-deleted)