X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-show.el;h=4dee34bfd9cfb2d6b1ad876884142d48427e1f26;hp=f29428ad98154c233ace674a122801bbe013d97a;hb=0c565fa29fc29f74209d4343e2fc88f3b8008aaa;hpb=991efcded840944b2f7ebf97d4c9df51a3d011cf diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index f29428ad..4dee34bf 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -47,6 +47,7 @@ (declare-function notmuch-tree "notmuch-tree" (&optional query query-context target buffer-name open-target)) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) +(declare-function notmuch-read-query "notmuch" (prompt)) (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") "Headers that should be shown in a message, in this order. @@ -99,6 +100,13 @@ visible for any given message." :group 'notmuch-show :group 'notmuch-hooks) +(defcustom notmuch-show-max-text-part-size 10000 + "Maximum size of a text part to be shown by default in characters. + +Set to 0 to show the part regardless of size." + :type 'integer + :group 'notmuch-show) + ;; Mostly useful for debugging. (defcustom notmuch-show-all-multipart/alternative-parts nil "Should all parts of multipart/alternative parts be shown?" @@ -136,6 +144,13 @@ indentation." :type 'boolean :group 'notmuch-show) +;; By default, block all external images to prevent privacy leaks and +;; potential attacks. +(defcustom notmuch-show-text/html-blocked-images "." + "Remote images that have URLs matching this regexp will be blocked." + :type '(choice (const nil) regexp) + :group 'notmuch-show) + (defvar notmuch-show-thread-id nil) (make-variable-buffer-local 'notmuch-show-thread-id) (put 'notmuch-show-thread-id 'permanent-local t) @@ -525,6 +540,73 @@ message at DEPTH in the current thread." (overlay-put overlay 'invisible (not show)) t))))) +;; Part content ID handling + +(defvar notmuch-show--cids nil + "Alist from raw content ID to (MSG PART).") +(make-variable-buffer-local 'notmuch-show--cids) + +(defun notmuch-show--register-cids (msg part) + "Register content-IDs in PART and all of PART's sub-parts." + (let ((content-id (plist-get part :content-id))) + (when content-id + ;; Note that content-IDs are globally unique, except when they + ;; aren't: RFC 2046 section 5.1.4 permits children of a + ;; multipart/alternative to have the same content-ID, in which + ;; case the MUA is supposed to pick the best one it can render. + ;; We simply add the content-ID to the beginning of our alist; + ;; so if this happens, we'll take the last (and "best") + ;; alternative (even if we can't render it). + (push (list content-id msg part) notmuch-show--cids))) + ;; Recurse on sub-parts + (let ((ctype (notmuch-split-content-type + (downcase (plist-get part :content-type))))) + (cond ((equal (first ctype) "multipart") + (mapc (apply-partially #'notmuch-show--register-cids msg) + (plist-get part :content))) + ((equal ctype '("message" "rfc822")) + (notmuch-show--register-cids + msg + (first (plist-get (first (plist-get part :content)) :body))))))) + +(defun notmuch-show--get-cid-content (cid) + "Return a list (CID-content content-type) or nil. + +This will only find parts from messages that have been inserted +into the current buffer. CID must be a raw content ID, without +enclosing angle brackets, a cid: prefix, or URL encoding. This +will return nil if the CID is unknown or cannot be retrieved." + (let ((descriptor (cdr (assoc cid notmuch-show--cids)))) + (when descriptor + (let* ((msg (first descriptor)) + (part (second descriptor)) + ;; Request caching for this content, as some messages + ;; reference the same cid: part many times (hundreds!). + (content (notmuch-get-bodypart-binary + msg part notmuch-show-process-crypto 'cache)) + (content-type (plist-get part :content-type))) + (list content content-type))))) + +(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--cid-w3m-retrieve) + w3m-cid-retrieve-function-alist))) + (setq mm-inline-text-html-with-images t)) + +(defvar w3m-current-buffer) ;; From `w3m.el'. +(defun notmuch-show--cid-w3m-retrieve (url &rest args) + ;; url includes the cid: prefix and is URL encoded (see RFC 2392). + (let* ((cid (url-unhex-string (substring url 4))) + (content-and-type + (with-current-buffer w3m-current-buffer + (notmuch-show--get-cid-content cid)))) + (when content-and-type + (insert (first content-and-type)) + (second content-and-type)))) + ;; MIME part renderers (defun notmuch-show-multipart/*-to-list (part) @@ -549,63 +631,11 @@ message at DEPTH in the current thread." (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) - -(defun notmuch-show-w3m-cid-store-internal (content-id msg part content) - (push (list content-id msg part content) - notmuch-show-w3m-cid-store)) - -(defun notmuch-show-w3m-cid-store (msg part) - (let ((content-id (plist-get part :content-id))) - (when content-id - (notmuch-show-w3m-cid-store-internal (concat "cid:" content-id) - msg part nil)))) - -(defun notmuch-show-w3m-cid-retrieve (url &rest args) - (let ((matching-part (with-current-buffer w3m-current-buffer - (assoc url notmuch-show-w3m-cid-store)))) - (if matching-part - (let* ((msg (nth 1 matching-part)) - (part (nth 2 matching-part)) - (content (nth 3 matching-part)) - (content-type (plist-get part :content-type))) - ;; If we don't already have the content, get it and cache - ;; it, as some messages reference the same cid: part many - ;; times (hundreds!), which results in many calls to - ;; `notmuch part'. - (unless content - (setq content (notmuch-get-bodypart-binary - msg part notmuch-show-process-crypto)) - (with-current-buffer w3m-current-buffer - (notmuch-show-w3m-cid-store-internal url msg part content))) - (insert content) - content-type) - nil))) - (defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button) (let ((inner-parts (plist-get part :content)) (start (point))) - ;; We assume that the first part is text/html and the remainder - ;; things that it references. - - ;; Stash the non-primary parts. - (mapc (lambda (part) - (notmuch-show-w3m-cid-store msg part)) - (cdr inner-parts)) - - ;; Render the primary part. + ;; Render the primary part. FIXME: Support RFC 2387 Start header. (notmuch-show-insert-bodypart msg (car inner-parts) depth) ;; Add hidden buttons for the rest (mapc (lambda (inner-part) @@ -752,14 +782,46 @@ message at DEPTH in the current thread." nil)))) (defun notmuch-show-insert-part-text/html (msg part content-type nth depth button) - ;; text/html handler to work around bugs in renderers and our - ;; invisibile parts code. In particular w3m sets up a keymap which - ;; "leaks" outside the invisible region and causes strange effects - ;; in notmuch. We set mm-inline-text-html-with-w3m-keymap to nil to - ;; tell w3m not to set a keymap (so the normal notmuch-show-mode-map - ;; remains). - (let ((mm-inline-text-html-with-w3m-keymap nil)) - (notmuch-show-insert-part-*/* msg part content-type nth depth button))) + (if (eq mm-text-html-renderer 'shr) + ;; It's easier to drive shr ourselves than to work around the + ;; goofy things `mm-shr' does (like irreversibly taking over + ;; content ID handling). + + ;; FIXME: If we block an image, offer a button to load external + ;; images. + (let ((shr-blocked-images notmuch-show-text/html-blocked-images)) + (notmuch-show--insert-part-text/html-shr msg part)) + ;; Otherwise, let message-mode do the heavy lifting + ;; + ;; w3m sets up a keymap which "leaks" outside the invisible region + ;; and causes strange effects in notmuch. We set + ;; mm-inline-text-html-with-w3m-keymap to nil to tell w3m not to + ;; set a keymap (so the normal notmuch-show-mode-map remains). + (let ((mm-inline-text-html-with-w3m-keymap nil) + ;; FIXME: If we block an image, offer a button to load external + ;; images. + (gnus-blocked-images notmuch-show-text/html-blocked-images)) + (notmuch-show-insert-part-*/* msg part content-type nth depth button)))) + +;; These functions are used by notmuch-show--insert-part-text/html-shr +(declare-function libxml-parse-html-region "xml.c") +(declare-function shr-insert-document "shr") + +(defun notmuch-show--insert-part-text/html-shr (msg part) + ;; Make sure shr is loaded before we start let-binding its globals + (require 'shr) + (let ((dom (let ((process-crypto notmuch-show-process-crypto)) + (with-temp-buffer + (insert (notmuch-get-bodypart-text msg part process-crypto)) + (libxml-parse-html-region (point-min) (point-max))))) + (shr-content-function + (lambda (url) + ;; shr strips the "cid:" part of URL, but doesn't + ;; URL-decode it (see RFC 2392). + (let ((cid (url-unhex-string url))) + (first (notmuch-show--get-cid-content cid)))))) + (shr-insert-document dom) + t)) (defun notmuch-show-insert-part-*/* (msg part content-type nth depth button) ;; This handler _must_ succeed - it is the handler of last resort. @@ -883,14 +945,20 @@ useful for quoting in replies)." "text/x-diff") content-type)) (nth (plist-get part :id)) + (long (and (notmuch-match-content-type mime-type "text/*") + (> notmuch-show-max-text-part-size 0) + (> (length (plist-get part :content)) notmuch-show-max-text-part-size))) (beg (point)) - ;; Hide the part initially if HIDE is t. - (show-part (not (equal hide t))) ;; We omit the part button for the first (or only) part if ;; this is text/plain, or HIDE is 'no-buttons. (button (unless (or (equal hide 'no-buttons) (and (string= mime-type "text/plain") (<= nth 1))) (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename)))) + ;; Hide the part initially if HIDE is t, or if it is too long + ;; and we have a button to allow toggling (thus reply which + ;; uses 'no-buttons automatically includes long parts) + (show-part (not (or (equal hide t) + (and long button)))) (content-beg (point))) ;; Store the computed mime-type for later use (e.g. by attachment handlers). @@ -917,6 +985,12 @@ useful for quoting in replies)." (defun notmuch-show-insert-body (msg body depth) "Insert the body BODY at depth DEPTH in the current thread." + + ;; Register all content IDs for this message. According to RFC + ;; 2392, content IDs are *global*, but it's okay if an MUA treats + ;; them as only global within a message. + (notmuch-show--register-cids msg (first body)) + (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body)) (defun notmuch-show-make-symbol (type) @@ -1208,6 +1282,16 @@ This includes: ")") notmuch-show-thread-id)) +(defun notmuch-show-goto-message (msg-id) + "Go to message with msg-id." + (goto-char (point-min)) + (unless (loop if (string= msg-id (notmuch-show-get-message-id)) + return t + until (not (notmuch-show-goto-message-next))) + (goto-char (point-min)) + (message "Message-id not found.")) + (notmuch-show-message-adjust)) + (defun notmuch-show-apply-state (state) "Apply STATE to the current buffer. @@ -1225,13 +1309,7 @@ This includes: until (not (notmuch-show-goto-message-next))) ;; Go to the previously open message. - (goto-char (point-min)) - (unless (loop if (string= current (notmuch-show-get-message-id)) - return t - until (not (notmuch-show-goto-message-next))) - (goto-char (point-min)) - (message "Previously current message not found.")) - (notmuch-show-message-adjust))) + (notmuch-show-goto-message current))) (defun notmuch-show-refresh-view (&optional reset-state) "Refresh the current view. @@ -1295,6 +1373,7 @@ reset based on the original query." (define-key map (kbd "") 'notmuch-show-previous-button) (define-key map (kbd "TAB") 'notmuch-show-next-button) (define-key map "f" 'notmuch-show-forward-message) + (define-key map "l" 'notmuch-show-filter-thread) (define-key map "r" 'notmuch-show-reply-sender) (define-key map "R" 'notmuch-show-reply) (define-key map "|" 'notmuch-show-pipe-message) @@ -1583,6 +1662,16 @@ user decision and we should not override it." (save-excursion (funcall notmuch-show-mark-read-function (window-start) (window-end))))) +(defun notmuch-show-filter-thread (query) + "Filter or LIMIT the current thread based on a new query string. + +Reshows the current thread with matches defined by the new query-string." + (interactive (list (notmuch-read-query "Filter thread: "))) + (let ((msg-id (notmuch-show-get-message-id))) + (setq notmuch-show-query-context (if (string= query "") nil query)) + (notmuch-show-refresh-view t) + (notmuch-show-goto-message msg-id))) + ;; Functions for getting attributes of several messages in the current ;; thread.