2002-01-25 Katsumi Yamaoka <yamaoka@jpl.org>
[gnus] / lisp / mm-view.el
1 ;;; mm-view.el --- functions for viewing MIME objects
2 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; This file is part of GNU Emacs.
6
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
10 ;; any later version.
11
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
21
22 ;;; Commentary:
23
24 ;;; Code:
25
26 (eval-when-compile (require 'cl))
27 (require 'mail-parse)
28 (require 'mailcap)
29 (require 'mm-bodies)
30 (require 'mm-decode)
31
32 (eval-and-compile
33   (autoload 'gnus-article-prepare-display "gnus-art")
34   (autoload 'vcard-parse-string "vcard")
35   (autoload 'vcard-format-string "vcard")
36   (autoload 'fill-flowed "flow-fill")
37   (unless (fboundp 'diff-mode)
38     (autoload 'diff-mode "diff-mode" "" t nil)))
39
40 ;;;
41 ;;; Functions for displaying various formats inline
42 ;;;
43
44 (defun mm-inline-image-emacs (handle)
45   (let ((b (point-marker))
46         buffer-read-only)
47     (insert "\n")
48     (put-image (mm-get-image handle) b)
49     (mm-handle-set-undisplayer
50      handle
51      `(lambda () (remove-images ,b (1+ ,b))))))
52
53 (defun mm-inline-image-xemacs (handle)
54   (insert "\n")
55   (forward-char -1)
56   (let ((b (point))
57         (annot (make-annotation (mm-get-image handle) nil 'text))
58         buffer-read-only)
59     (mm-handle-set-undisplayer
60      handle
61      `(lambda ()
62         (let (buffer-read-only)
63           (delete-annotation ,annot)
64           (delete-region ,(set-marker (make-marker) b)
65                          ,(set-marker (make-marker) (point))))))
66     (set-extent-property annot 'mm t)
67     (set-extent-property annot 'duplicable t)))
68
69 (eval-and-compile
70   (if (featurep 'xemacs)
71       (defalias 'mm-inline-image 'mm-inline-image-xemacs)
72     (defalias 'mm-inline-image 'mm-inline-image-emacs)))
73
74 (defvar mm-w3-setup nil)
75 (defun mm-setup-w3 ()
76   (unless mm-w3-setup
77     (require 'w3)
78     (w3-do-setup)
79     (require 'url)
80     (require 'w3-vars)
81     (require 'url-vars)
82     (setq mm-w3-setup t)))
83
84 (defun mm-inline-text-html-render-with-w3 (handle)
85   (mm-setup-w3)
86   (let ((text (mm-get-part handle))
87         (b (point))
88         (url-standalone-mode t)
89         (w3-honor-stylesheets nil)
90         (w3-delay-image-loads t)
91         (url-current-object
92          (url-generic-parse-url (format "cid:%s" (mm-handle-id handle))))
93         (width (window-width))
94         (charset (mail-content-type-get
95                   (mm-handle-type handle) 'charset)))
96     (save-excursion
97       (insert text)
98       (save-restriction
99         (narrow-to-region b (point))
100         (goto-char (point-min))
101         (if (or (and (boundp 'w3-meta-content-type-charset-regexp)
102                      (re-search-forward
103                       w3-meta-content-type-charset-regexp nil t))
104                 (and (boundp 'w3-meta-charset-content-type-regexp)
105                      (re-search-forward
106                       w3-meta-charset-content-type-regexp nil t)))
107             (setq charset
108                   (or (let ((bsubstr (buffer-substring-no-properties
109                                       (match-beginning 2)
110                                       (match-end 2))))
111                         (if (fboundp 'w3-coding-system-for-mime-charset)
112                             (w3-coding-system-for-mime-charset bsubstr)
113                           (mm-charset-to-coding-system bsubstr)))
114                       charset)))
115         (delete-region (point-min) (point-max))
116         (insert (mm-decode-string text charset))
117         (save-window-excursion
118           (save-restriction
119             (let ((w3-strict-width width)
120                   ;; Don't let w3 set the global version of
121                   ;; this variable.
122                   (fill-column fill-column)
123                   (w3-honor-stylesheets nil)
124                   (w3-delay-image-loads t)
125                   (url-standalone-mode t))
126               (condition-case var
127                   (w3-region (point-min) (point-max))
128                 (error
129                  (delete-region (point-min) (point-max))
130                  (let ((b (point))
131                        (charset (mail-content-type-get
132                                  (mm-handle-type handle) 'charset)))
133                    (if (or (eq charset 'gnus-decoded)
134                            (eq mail-parse-charset 'gnus-decoded))
135                        (save-restriction
136                          (narrow-to-region (point) (point))
137                          (mm-insert-part handle)
138                          (goto-char (point-max)))
139                      (insert (mm-decode-string (mm-get-part handle)
140                                                charset))))
141                  (message
142                   "Error while rendering html; showing as text/plain"))))))
143         (mm-handle-set-undisplayer
144          handle
145          `(lambda ()
146             (let (buffer-read-only)
147               (if (functionp 'remove-specifier)
148                   (mapcar (lambda (prop)
149                             (remove-specifier
150                              (face-property 'default prop)
151                              (current-buffer)))
152                           '(background background-pixmap foreground)))
153               (delete-region ,(point-min-marker)
154                              ,(point-max-marker)))))))))
155
156 (defvar mm-w3m-minor-mode nil)
157 (make-variable-buffer-local 'mm-w3m-minor-mode)
158 (defvar mm-w3m-setup nil)
159 (defun mm-setup-w3m ()
160   (unless mm-w3m-setup
161     (require 'w3m)
162     (gnus-add-minor-mode 'mm-w3m-minor-mode " w3m" w3m-mode-map)
163     (setq mm-w3m-setup t)))
164
165 (defun mm-inline-text-html-render-with-w3m (handle)
166   (mm-setup-w3m)
167   (let ((text (mm-get-part handle))
168         (b (point))
169         (charset (mail-content-type-get (mm-handle-type handle) 'charset)))
170     (save-excursion
171       (insert text)
172       (save-restriction
173         (narrow-to-region b (point))
174         (goto-char (point-min))
175         (when (re-search-forward w3m-meta-content-type-charset-regexp nil t)
176           (setq charset (or (w3m-charset-to-coding-system (match-string 2))
177                             charset)))
178         (when charset
179           (delete-region (point-min) (point-max))
180           (insert (mm-decode-string text charset)))
181         (w3m-region (point-min) (point-max))
182         (setq mm-w3m-minor-mode t))
183       (mm-handle-set-undisplayer
184        handle
185        `(lambda ()
186           (let (buffer-read-only)
187             (setq mm-w3m-minor-mode nil)
188             (if (functionp 'remove-specifier)
189                 (mapcar (lambda (prop)
190                           (remove-specifier
191                            (face-property 'default prop)
192                            (current-buffer)))
193                         '(background background-pixmap foreground)))
194             (delete-region ,(point-min-marker)
195                            ,(point-max-marker))))))))
196
197 (defun mm-inline-text (handle)
198   (let ((type (mm-handle-media-subtype handle))
199         buffer-read-only)
200     (cond
201      ((equal type "html")
202       (funcall mm-inline-text-html-renderer handle))
203      ((equal type "x-vcard")
204       (mm-insert-inline
205        handle
206        (concat "\n-- \n"
207                (ignore-errors
208                  (if (fboundp 'vcard-pretty-print)
209                      (vcard-pretty-print (mm-get-part handle))
210                    (vcard-format-string
211                     (vcard-parse-string (mm-get-part handle)
212                                         'vcard-standard-filter)))))))
213      (t
214       (let ((b (point))
215             (charset (mail-content-type-get
216                       (mm-handle-type handle) 'charset)))
217         (if (or (eq charset 'gnus-decoded)
218                 ;; This is probably not entirely correct, but
219                 ;; makes rfc822 parts with embedded multiparts work.
220                 (eq mail-parse-charset 'gnus-decoded))
221             (save-restriction
222               (narrow-to-region (point) (point))
223               (mm-insert-part handle)
224               (goto-char (point-max)))
225           (insert (mm-decode-string (mm-get-part handle) charset)))
226         (when (and (equal type "plain")
227                    (equal (cdr (assoc 'format (mm-handle-type handle)))
228                           "flowed"))
229           (save-restriction
230             (narrow-to-region b (point))
231             (goto-char b)
232             (fill-flowed)
233             (goto-char (point-max))))
234         (save-restriction
235           (narrow-to-region b (point))
236           (set-text-properties (point-min) (point-max) nil)
237           (when (or (equal type "enriched")
238                     (equal type "richtext"))
239             (enriched-decode (point-min) (point-max)))
240           (mm-handle-set-undisplayer
241            handle
242            `(lambda ()
243               (let (buffer-read-only)
244                 (delete-region ,(point-min-marker)
245                                ,(point-max-marker)))))))))))
246
247 (defun mm-insert-inline (handle text)
248   "Insert TEXT inline from HANDLE."
249   (let ((b (point)))
250     (insert text)
251     (mm-handle-set-undisplayer
252      handle
253      `(lambda ()
254         (let (buffer-read-only)
255           (delete-region ,(set-marker (make-marker) b)
256                          ,(set-marker (make-marker) (point))))))))
257
258 (defun mm-inline-audio (handle)
259   (message "Not implemented"))
260
261 (defun mm-view-sound-file ()
262   (message "Not implemented"))
263
264 (defun mm-w3-prepare-buffer ()
265   (require 'w3)
266   (let ((url-standalone-mode t)
267         (w3-honor-stylesheets nil)
268         (w3-delay-image-loads t))
269     (w3-prepare-buffer)))
270
271 (defun mm-view-message ()
272   (mm-enable-multibyte)
273   (let (handles)
274     (let (gnus-article-mime-handles)
275       ;; Double decode problem may happen.  See mm-inline-message.
276       (run-hooks 'gnus-article-decode-hook)
277       (gnus-article-prepare-display)
278       (setq handles gnus-article-mime-handles))
279     (when handles
280       (setq gnus-article-mime-handles
281             (mm-merge-handles gnus-article-mime-handles handles))))
282   (fundamental-mode)
283   (goto-char (point-min)))
284
285 (defun mm-inline-message (handle)
286   (let ((b (point))
287         (bolp (bolp))
288         (charset (mail-content-type-get
289                   (mm-handle-type handle) 'charset))
290         gnus-displaying-mime handles)
291     (when (and charset
292                (stringp charset))
293       (setq charset (intern (downcase charset)))
294       (when (eq charset 'us-ascii)
295         (setq charset nil)))
296     (save-excursion
297       (save-restriction
298         (narrow-to-region b b)
299         (mm-insert-part handle)
300         (let (gnus-article-mime-handles
301               ;; disable prepare hook
302               gnus-article-prepare-hook
303               (gnus-newsgroup-charset
304                (or charset gnus-newsgroup-charset)))
305           (run-hooks 'gnus-article-decode-hook)
306           (gnus-article-prepare-display)
307           (setq handles gnus-article-mime-handles))
308         (goto-char (point-min))
309         (unless bolp
310           (insert "\n"))
311         (goto-char (point-max))
312         (unless (bolp)
313           (insert "\n"))
314         (insert "----------\n\n")
315         (when handles
316           (setq gnus-article-mime-handles
317                 (mm-merge-handles gnus-article-mime-handles handles)))
318         (mm-handle-set-undisplayer
319          handle
320          `(lambda ()
321             (let (buffer-read-only)
322               (if (fboundp 'remove-specifier)
323                   ;; This is only valid on XEmacs.
324                   (mapcar (lambda (prop)
325                             (remove-specifier
326                              (face-property 'default prop) (current-buffer)))
327                           '(background background-pixmap foreground)))
328               (delete-region ,(point-min-marker) ,(point-max-marker)))))))))
329
330 (defun mm-display-inline-fontify (handle mode)
331   (let (text)
332     ;; XEmacs @#$@ version of font-lock refuses to fully turn itself
333     ;; on for buffers whose name begins with " ".  That's why we use
334     ;; save-current-buffer/get-buffer-create rather than
335     ;; with-temp-buffer.
336     (save-current-buffer
337       (set-buffer (generate-new-buffer "*fontification*"))
338       (unwind-protect
339           (progn
340             (buffer-disable-undo)
341             (mm-insert-part handle)
342             (funcall mode)
343             (require 'font-lock)
344             (let ((font-lock-verbose nil))
345               ;; I find font-lock a bit too verbose.
346               (font-lock-fontify-buffer))
347             ;; By default, XEmacs font-lock uses non-duplicable text
348             ;; properties.  This code forces all the text properties
349             ;; to be copied along with the text.
350             (when (fboundp 'extent-list)
351               (map-extents (lambda (ext ignored)
352                              (set-extent-property ext 'duplicable t)
353                              nil)
354                            nil nil nil nil nil 'text-prop))
355             (setq text (buffer-string)))
356         (kill-buffer (current-buffer))))
357     (mm-insert-inline handle text)))
358
359 ;; Shouldn't these functions check whether the user even wants to use
360 ;; font-lock?  At least under XEmacs, this fontification is pretty
361 ;; much unconditional.  Also, it would be nice to change for the size
362 ;; of the fontified region.
363
364 (defun mm-display-patch-inline (handle)
365   (mm-display-inline-fontify handle 'diff-mode))
366
367 (defun mm-display-elisp-inline (handle)
368   (mm-display-inline-fontify handle 'emacs-lisp-mode))
369
370 ;;      id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
371 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
372 (defvar mm-pkcs7-signed-magic
373   (mm-string-as-unibyte
374    (apply 'concat
375           (mapcar 'char-to-string
376                   (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
377                         ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
378                         ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
379                         ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x02)))))
380   
381 ;;      id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
382 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
383 (defvar mm-pkcs7-enveloped-magic
384   (mm-string-as-unibyte
385    (apply 'concat
386           (mapcar 'char-to-string
387                   (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
388                         ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
389                         ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
390                         ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x03)))))
391   
392 (defun mm-view-pkcs7-get-type (handle)
393   (mm-with-unibyte-buffer
394     (mm-insert-part handle)
395     (cond ((looking-at mm-pkcs7-enveloped-magic)
396            'enveloped)
397           ((looking-at mm-pkcs7-signed-magic)
398            'signed)
399           (t
400            (error "Could not identify PKCS#7 type")))))
401
402 (defun mm-view-pkcs7 (handle)
403   (case (mm-view-pkcs7-get-type handle)
404     (enveloped (mm-view-pkcs7-decrypt handle))
405     (otherwise (error "Unknown or unimplemented PKCS#7 type"))))
406
407 (defun mm-view-pkcs7-decrypt (handle)
408   (insert-buffer (mm-handle-buffer handle))
409   (goto-char (point-min))
410   (insert "MIME-Version: 1.0\n")
411   (mm-insert-headers "application/pkcs7-mime" "base64" "smime.p7m")
412   (smime-decrypt-region
413    (point-min) (point-max)
414    (if (= (length smime-keys) 1)
415        (cadar smime-keys)
416      (smime-get-key-by-email
417       (completing-read "Decrypt this part with which key? "
418                        smime-keys nil nil
419                        (and (listp (car-safe smime-keys))
420                             (caar smime-keys)))))))
421
422 (provide 'mm-view)
423
424 ;;; mm-view.el ends here