(Choosing Variables): Fix descriptions.
[gnus] / lisp / mm-view.el
1 ;;; mm-view.el --- functions for viewing MIME objects
2
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;;   2005 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29 (require 'mail-parse)
30 (require 'mailcap)
31 (require 'mm-bodies)
32 (require 'mm-decode)
33
34 (eval-and-compile
35   (autoload 'gnus-article-prepare-display "gnus-art")
36   (autoload 'vcard-parse-string "vcard")
37   (autoload 'vcard-format-string "vcard")
38   (autoload 'fill-flowed "flow-fill")
39   (autoload 'html2text "html2text"))
40
41 (defvar mm-text-html-renderer-alist
42   '((w3  . mm-inline-text-html-render-with-w3)
43     (w3m . mm-inline-text-html-render-with-w3m)
44     (w3m-standalone mm-inline-render-with-stdin nil
45                     "w3m" "-dump" "-T" "text/html")
46     (links mm-inline-render-with-file
47            mm-links-remove-leading-blank
48            "links" "-dump" file)
49     (lynx  mm-inline-render-with-stdin nil
50            "lynx" "-dump" "-force_html" "-stdin" "-nolist")
51     (html2text  mm-inline-render-with-function html2text))
52   "The attributes of renderer types for text/html.")
53
54 (defvar mm-text-html-washer-alist
55   '((w3  . gnus-article-wash-html-with-w3)
56     (w3m . gnus-article-wash-html-with-w3m)
57     (w3m-standalone mm-inline-wash-with-stdin nil
58                     "w3m" "-dump" "-T" "text/html")
59     (links mm-inline-wash-with-file
60            mm-links-remove-leading-blank
61            "links" "-dump" file)
62     (lynx  mm-inline-wash-with-stdin nil
63            "lynx" "-dump" "-force_html" "-stdin" "-nolist")
64     (html2text  html2text))
65   "The attributes of washer types for text/html.")
66
67 (defcustom mm-fill-flowed t
68   "If non-nil a format=flowed article will be displayed flowed."
69   :type 'boolean
70   :group 'mime-display)
71
72 ;;; Internal variables.
73
74 ;;;
75 ;;; Functions for displaying various formats inline
76 ;;;
77
78 (defun mm-inline-image-emacs (handle)
79   (let ((b (point-marker))
80         buffer-read-only)
81     (put-image (mm-get-image handle) b)
82     (insert "\n\n")
83     (mm-handle-set-undisplayer
84      handle
85      `(lambda ()
86         (let ((b ,b)
87               buffer-read-only)
88           (remove-images b b)
89           (delete-region b (+ b 2)))))))
90
91 (defun mm-inline-image-xemacs (handle)
92   (insert "\n\n")
93   (forward-char -2)
94   (let ((annot (make-annotation (mm-get-image handle) nil 'text))
95         buffer-read-only)
96     (mm-handle-set-undisplayer
97      handle
98      `(lambda ()
99         (let ((b ,(point-marker))
100               buffer-read-only)
101           (delete-annotation ,annot)
102           (delete-region (- b 2) b))))
103     (set-extent-property annot 'mm t)
104     (set-extent-property annot 'duplicable t)))
105
106 (eval-and-compile
107   (if (featurep 'xemacs)
108       (defalias 'mm-inline-image 'mm-inline-image-xemacs)
109     (defalias 'mm-inline-image 'mm-inline-image-emacs)))
110
111 (defvar mm-w3-setup nil)
112 (defun mm-setup-w3 ()
113   (unless mm-w3-setup
114     (require 'w3)
115     (w3-do-setup)
116     (require 'url)
117     (require 'w3-vars)
118     (require 'url-vars)
119     (setq mm-w3-setup t)))
120
121 (defun mm-inline-text-html-render-with-w3 (handle)
122   (mm-setup-w3)
123   (let ((text (mm-get-part handle))
124         (b (point))
125         (url-standalone-mode t)
126         (url-gateway-unplugged t)
127         (w3-honor-stylesheets nil)
128         (url-current-object
129          (url-generic-parse-url (format "cid:%s" (mm-handle-id handle))))
130         (width (window-width))
131         (charset (mail-content-type-get
132                   (mm-handle-type handle) 'charset)))
133     (save-excursion
134       (insert (if charset (mm-decode-string text charset) text))
135       (save-restriction
136         (narrow-to-region b (point))
137         (unless charset
138           (goto-char (point-min))
139           (when (or (and (boundp 'w3-meta-content-type-charset-regexp)
140                          (re-search-forward
141                           w3-meta-content-type-charset-regexp nil t))
142                     (and (boundp 'w3-meta-charset-content-type-regexp)
143                          (re-search-forward
144                           w3-meta-charset-content-type-regexp nil t)))
145             (setq charset
146                   (let ((bsubstr (buffer-substring-no-properties
147                                   (match-beginning 2)
148                                   (match-end 2))))
149                     (if (fboundp 'w3-coding-system-for-mime-charset)
150                         (w3-coding-system-for-mime-charset bsubstr)
151                       (mm-charset-to-coding-system bsubstr))))
152             (delete-region (point-min) (point-max))
153             (insert (mm-decode-string text charset))))
154         (save-window-excursion
155           (save-restriction
156             (let ((w3-strict-width width)
157                   ;; Don't let w3 set the global version of
158                   ;; this variable.
159                   (fill-column fill-column))
160               (if (or debug-on-error debug-on-quit)
161                   (w3-region (point-min) (point-max))
162                 (condition-case ()
163                     (w3-region (point-min) (point-max))
164                   (error
165                    (delete-region (point-min) (point-max))
166                    (let ((b (point))
167                          (charset (mail-content-type-get
168                                    (mm-handle-type handle) 'charset)))
169                      (if (or (eq charset 'gnus-decoded)
170                              (eq mail-parse-charset 'gnus-decoded))
171                        (save-restriction
172                          (narrow-to-region (point) (point))
173                          (mm-insert-part handle)
174                          (goto-char (point-max)))
175                        (insert (mm-decode-string (mm-get-part handle)
176                                                  charset))))
177                    (message
178                     "Error while rendering html; showing as text/plain")))))))
179         (mm-handle-set-undisplayer
180          handle
181          `(lambda ()
182             (let (buffer-read-only)
183               (if (functionp 'remove-specifier)
184                   (mapcar (lambda (prop)
185                             (remove-specifier
186                              (face-property 'default prop)
187                              (current-buffer)))
188                           '(background background-pixmap foreground)))
189               (delete-region ,(point-min-marker)
190                              ,(point-max-marker)))))))))
191
192 (defvar mm-w3m-setup nil
193   "Whether gnus-article-mode has been setup to use emacs-w3m.")
194
195 (defun mm-setup-w3m ()
196   "Setup gnus-article-mode to use emacs-w3m."
197   (unless mm-w3m-setup
198     (require 'w3m)
199     (unless (assq 'gnus-article-mode w3m-cid-retrieve-function-alist)
200       (push (cons 'gnus-article-mode 'mm-w3m-cid-retrieve)
201             w3m-cid-retrieve-function-alist))
202     (setq mm-w3m-setup t))
203   (setq w3m-display-inline-images mm-inline-text-html-with-images))
204
205 (defun mm-w3m-cid-retrieve-1 (url handle)
206   (dolist (elem handle)
207     (when (listp elem)
208       (if (equal url (mm-handle-id elem))
209           (progn
210             (mm-insert-part elem)
211             (throw 'found-handle (mm-handle-media-type elem))))
212       (if (equal "multipart" (mm-handle-media-supertype elem))
213           (mm-w3m-cid-retrieve-1 url elem)))))
214
215 (defun mm-w3m-cid-retrieve (url &rest args)
216   "Insert a content pointed by URL if it has the cid: scheme."
217   (when (string-match "\\`cid:" url)
218     (catch 'found-handle
219       (mm-w3m-cid-retrieve-1 (concat "<" (substring url (match-end 0)) ">")
220                              (with-current-buffer w3m-current-buffer
221                                gnus-article-mime-handles)))))
222
223 (defun mm-inline-text-html-render-with-w3m (handle)
224   "Render a text/html part using emacs-w3m."
225   (mm-setup-w3m)
226   (let ((text (mm-get-part handle))
227         (b (point))
228         (charset (mail-content-type-get (mm-handle-type handle) 'charset)))
229     (save-excursion
230       (insert (if charset (mm-decode-string text charset) text))
231       (save-restriction
232         (narrow-to-region b (point))
233         (unless charset
234           (goto-char (point-min))
235           (when (setq charset (w3m-detect-meta-charset))
236             (delete-region (point-min) (point-max))
237             (insert (mm-decode-string text charset))))
238         (let ((w3m-safe-url-regexp mm-w3m-safe-url-regexp)
239               w3m-force-redisplay)
240           (w3m-region (point-min) (point-max) nil charset))
241         (when (and mm-inline-text-html-with-w3m-keymap
242                    (boundp 'w3m-minor-mode-map)
243                    w3m-minor-mode-map)
244           (add-text-properties
245            (point-min) (point-max)
246            (list 'keymap w3m-minor-mode-map
247                  ;; Put the mark meaning this part was rendered by emacs-w3m.
248                  'mm-inline-text-html-with-w3m t))))
249       (mm-handle-set-undisplayer
250        handle
251        `(lambda ()
252           (let (buffer-read-only)
253             (if (functionp 'remove-specifier)
254                 (mapcar (lambda (prop)
255                           (remove-specifier
256                            (face-property 'default prop)
257                            (current-buffer)))
258                         '(background background-pixmap foreground)))
259             (delete-region ,(point-min-marker)
260                            ,(point-max-marker))))))))
261
262 (defun mm-links-remove-leading-blank ()
263   ;; Delete the annoying three spaces preceding each line of links
264   ;; output.
265   (goto-char (point-min))
266   (while (re-search-forward "^   " nil t)
267     (delete-region (match-beginning 0) (match-end 0))))
268
269 (defun mm-inline-wash-with-file (post-func cmd &rest args)
270   (let ((file (mm-make-temp-file
271                (expand-file-name "mm" mm-tmp-directory))))
272     (let ((coding-system-for-write 'binary))
273       (write-region (point-min) (point-max) file nil 'silent))
274     (delete-region (point-min) (point-max))
275     (unwind-protect
276         (apply 'call-process cmd nil t nil (mapcar 'eval args))
277       (delete-file file))
278     (and post-func (funcall post-func))))
279
280 (defun mm-inline-wash-with-stdin (post-func cmd &rest args)
281   (let ((coding-system-for-write 'binary))
282     (apply 'call-process-region (point-min) (point-max)
283            cmd t t nil args))
284   (and post-func (funcall post-func)))
285
286 (defun mm-inline-render-with-file (handle post-func cmd &rest args)
287   (let ((source (mm-get-part handle)))
288     (mm-insert-inline
289      handle
290      (mm-with-unibyte-buffer
291        (insert source)
292        (apply 'mm-inline-wash-with-file post-func cmd args)
293        (buffer-string)))))
294
295 (defun mm-inline-render-with-stdin (handle post-func cmd &rest args)
296   (let ((source (mm-get-part handle)))
297     (mm-insert-inline
298      handle
299      (mm-with-unibyte-buffer
300        (insert source)
301        (apply 'mm-inline-wash-with-stdin post-func cmd args)
302        (buffer-string)))))
303
304 (defun mm-inline-render-with-function (handle func &rest args)
305   (let ((source (mm-get-part handle))
306         (charset (mail-content-type-get (mm-handle-type handle) 'charset)))
307     (mm-insert-inline
308      handle
309      (mm-with-multibyte-buffer
310        (insert (if charset
311                    (mm-decode-string source charset)
312                  source))
313        (apply func args)
314        (buffer-string)))))
315
316 (defun mm-inline-text-html (handle)
317   (let* ((func (or mm-inline-text-html-renderer mm-text-html-renderer))
318          (entry (assq func mm-text-html-renderer-alist))
319          buffer-read-only)
320     (if entry
321         (setq func (cdr entry)))
322     (cond
323      ((functionp func)
324       (funcall func handle))
325      (t
326       (apply (car func) handle (cdr func))))))
327
328 (defun mm-inline-text-vcard (handle)
329   (let (buffer-read-only)
330     (mm-insert-inline
331      handle
332      (concat "\n-- \n"
333              (ignore-errors
334                (if (fboundp 'vcard-pretty-print)
335                    (vcard-pretty-print (mm-get-part handle))
336                  (vcard-format-string
337                   (vcard-parse-string (mm-get-part handle)
338                                       'vcard-standard-filter))))))))
339
340 (defun mm-inline-text (handle)
341   (let ((b (point))
342         (type (mm-handle-media-subtype handle))
343         (charset (mail-content-type-get
344                   (mm-handle-type handle) 'charset))
345         buffer-read-only)
346     (if (or (eq charset 'gnus-decoded)
347             ;; This is probably not entirely correct, but
348             ;; makes rfc822 parts with embedded multiparts work.
349             (eq mail-parse-charset 'gnus-decoded))
350         (save-restriction
351           (narrow-to-region (point) (point))
352           (mm-insert-part handle)
353           (goto-char (point-max)))
354       (insert (mm-decode-string (mm-get-part handle) charset)))
355     (when (and mm-fill-flowed
356                (equal type "plain")
357                (equal (cdr (assoc 'format (mm-handle-type handle)))
358                       "flowed"))
359       (save-restriction
360         (narrow-to-region b (point))
361         (goto-char b)
362         (fill-flowed)
363         (goto-char (point-max))))
364     (save-restriction
365       (narrow-to-region b (point))
366       (set-text-properties (point-min) (point-max) nil)
367       (when (or (equal type "enriched")
368                 (equal type "richtext"))
369         (ignore-errors
370           (enriched-decode (point-min) (point-max))))
371       (mm-handle-set-undisplayer
372        handle
373        `(lambda ()
374           (let (buffer-read-only)
375             (delete-region ,(point-min-marker)
376                            ,(point-max-marker))))))))
377
378 (defun mm-insert-inline (handle text)
379   "Insert TEXT inline from HANDLE."
380   (let ((b (point)))
381     (insert text)
382     (unless (bolp)
383       (insert "\n"))
384     (mm-handle-set-undisplayer
385      handle
386      `(lambda ()
387         (let (buffer-read-only)
388           (delete-region ,(set-marker (make-marker) b)
389                          ,(set-marker (make-marker) (point))))))))
390
391 (defun mm-inline-audio (handle)
392   (message "Not implemented"))
393
394 (defun mm-view-sound-file ()
395   (message "Not implemented"))
396
397 (defun mm-w3-prepare-buffer ()
398   (require 'w3)
399   (let ((url-standalone-mode t)
400         (url-gateway-unplugged t)
401         (w3-honor-stylesheets nil))
402     (w3-prepare-buffer)))
403
404 (defun mm-view-message ()
405   (mm-enable-multibyte)
406   (let (handles)
407     (let (gnus-article-mime-handles)
408       ;; Double decode problem may happen.  See mm-inline-message.
409       (run-hooks 'gnus-article-decode-hook)
410       (gnus-article-prepare-display)
411       (setq handles gnus-article-mime-handles))
412     (when handles
413       (setq gnus-article-mime-handles
414             (mm-merge-handles gnus-article-mime-handles handles))))
415   (fundamental-mode)
416   (goto-char (point-min)))
417
418 (defun mm-inline-message (handle)
419   (let ((b (point))
420         (bolp (bolp))
421         (charset (mail-content-type-get
422                   (mm-handle-type handle) 'charset))
423         gnus-displaying-mime handles)
424     (when (and charset
425                (stringp charset))
426       (setq charset (intern (downcase charset)))
427       (when (eq charset 'us-ascii)
428         (setq charset nil)))
429     (save-excursion
430       (save-restriction
431         (narrow-to-region b b)
432         (mm-insert-part handle)
433         (let (gnus-article-mime-handles
434               ;; disable prepare hook
435               gnus-article-prepare-hook
436               (gnus-newsgroup-charset
437                (or charset gnus-newsgroup-charset)))
438           (let ((gnus-original-article-buffer (mm-handle-buffer handle)))
439             (run-hooks 'gnus-article-decode-hook))
440           (gnus-article-prepare-display)
441           (setq handles gnus-article-mime-handles))
442         (goto-char (point-min))
443         (unless bolp
444           (insert "\n"))
445         (goto-char (point-max))
446         (unless (bolp)
447           (insert "\n"))
448         (insert "----------\n\n")
449         (when handles
450           (setq gnus-article-mime-handles
451                 (mm-merge-handles gnus-article-mime-handles handles)))
452         (mm-handle-set-undisplayer
453          handle
454          `(lambda ()
455             (let (buffer-read-only)
456               (if (fboundp 'remove-specifier)
457                   ;; This is only valid on XEmacs.
458                   (mapcar (lambda (prop)
459                             (remove-specifier
460                              (face-property 'default prop) (current-buffer)))
461                           '(background background-pixmap foreground)))
462               (delete-region ,(point-min-marker) ,(point-max-marker)))))))))
463
464 (defun mm-display-inline-fontify (handle mode)
465   (let ((charset (mail-content-type-get (mm-handle-type handle) 'charset))
466         text coding-system)
467     (unless (eq charset 'gnus-decoded)
468       (mm-with-unibyte-buffer
469         (mm-insert-part handle)
470         (mm-decompress-buffer
471          (or (mail-content-type-get (mm-handle-disposition handle) 'name)
472              (mail-content-type-get (mm-handle-disposition handle) 'filename))
473          t t)
474         (unless charset
475           (setq coding-system (mm-find-buffer-file-coding-system)))
476         (setq text (buffer-string))))
477     ;; XEmacs @#$@ version of font-lock refuses to fully turn itself
478     ;; on for buffers whose name begins with " ".  That's why we use
479     ;; `with-current-buffer'/`generate-new-buffer' rather than
480     ;; `with-temp-buffer'.
481     (with-current-buffer (generate-new-buffer "*fontification*")
482       (buffer-disable-undo)
483       (mm-enable-multibyte)
484       (insert (cond ((eq charset 'gnus-decoded)
485                      (mm-insert-part handle))
486                     (coding-system
487                      (mm-decode-coding-string text coding-system))
488                     (charset
489                      (mm-decode-string text charset))
490                     (t
491                      text)))
492       (require 'font-lock)
493       ;; Inhibit font-lock this time (*-mode-hook might run
494       ;; `turn-on-font-lock') so that jit-lock may not turn off
495       ;; font-lock immediately after this.
496       (let ((font-lock-mode t))
497         (funcall mode))
498       (let ((font-lock-verbose nil))
499         ;; I find font-lock a bit too verbose.
500         (font-lock-fontify-buffer))
501       ;; By default, XEmacs font-lock uses non-duplicable text
502       ;; properties.  This code forces all the text properties
503       ;; to be copied along with the text.
504       (when (fboundp 'extent-list)
505         (map-extents (lambda (ext ignored)
506                        (set-extent-property ext 'duplicable t)
507                        nil)
508                      nil nil nil nil nil 'text-prop))
509       (setq text (buffer-string))
510       (kill-buffer (current-buffer)))
511     (mm-insert-inline handle text)))
512
513 ;; Shouldn't these functions check whether the user even wants to use
514 ;; font-lock?  At least under XEmacs, this fontification is pretty
515 ;; much unconditional.  Also, it would be nice to change for the size
516 ;; of the fontified region.
517
518 (defun mm-display-patch-inline (handle)
519   (mm-display-inline-fontify handle 'diff-mode))
520
521 (defun mm-display-elisp-inline (handle)
522   (mm-display-inline-fontify handle 'emacs-lisp-mode))
523
524 (defun mm-display-dns-inline (handle)
525   (mm-display-inline-fontify handle 'dns-mode))
526
527 ;;      id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
528 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
529 (defvar mm-pkcs7-signed-magic
530   (mm-string-as-unibyte
531    (mapconcat 'char-to-string
532               (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
533                     ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
534                     ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
535                     ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x02) "")))
536
537 ;;      id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
538 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
539 (defvar mm-pkcs7-enveloped-magic
540   (mm-string-as-unibyte
541    (mapconcat 'char-to-string
542               (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
543                     ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
544                     ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
545                     ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x03) "")))
546
547 (defun mm-view-pkcs7-get-type (handle)
548   (mm-with-unibyte-buffer
549     (mm-insert-part handle)
550     (cond ((looking-at mm-pkcs7-enveloped-magic)
551            'enveloped)
552           ((looking-at mm-pkcs7-signed-magic)
553            'signed)
554           (t
555            (error "Could not identify PKCS#7 type")))))
556
557 (defun mm-view-pkcs7 (handle)
558   (case (mm-view-pkcs7-get-type handle)
559     (enveloped (mm-view-pkcs7-decrypt handle))
560     (signed (mm-view-pkcs7-verify handle))
561     (otherwise (error "Unknown or unimplemented PKCS#7 type"))))
562
563 (defun mm-view-pkcs7-verify (handle)
564   ;; A bogus implementation of PKCS#7. FIXME::
565   (mm-insert-part handle)
566   (goto-char (point-min))
567   (if (search-forward "Content-Type: " nil t)
568       (delete-region (point-min) (match-beginning 0)))
569   (goto-char (point-max))
570   (if (re-search-backward "--\r?\n?" nil t)
571       (delete-region (match-end 0) (point-max)))
572   (goto-char (point-min))
573   (while (search-forward "\r\n" nil t)
574     (replace-match "\n"))
575   (message "Verify signed PKCS#7 message is unimplemented.")
576   (sit-for 1)
577   t)
578
579 (defun mm-view-pkcs7-decrypt (handle)
580   (insert-buffer-substring (mm-handle-buffer handle))
581   (goto-char (point-min))
582   (insert "MIME-Version: 1.0\n")
583   (mm-insert-headers "application/pkcs7-mime" "base64" "smime.p7m")
584   (smime-decrypt-region
585    (point-min) (point-max)
586    (if (= (length smime-keys) 1)
587        (cadar smime-keys)
588      (smime-get-key-by-email
589       (completing-read
590        (concat "Decipher using which key? "
591                (if smime-keys (concat "(default " (caar smime-keys) ") ")
592                  ""))
593        smime-keys nil nil nil nil (car-safe (car-safe smime-keys))))))
594   (goto-char (point-min))
595   (while (search-forward "\r\n" nil t)
596     (replace-match "\n"))
597   (goto-char (point-min)))
598
599 (provide 'mm-view)
600
601 ;;; arch-tag: b60e749a-d05c-47f2-bccd-bdaa59327cb2
602 ;;; mm-view.el ends here