Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / mm-view.el
1 ;;; mm-view.el --- Functions for viewing MIME objects
2 ;; Copyright (C) 1998, 1999, 2000, 2001 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 (defun mm-inline-image-emacs (handle)
44   (let ((b (point-marker))
45         buffer-read-only)
46     (insert "\n")
47     (put-image (mm-get-image handle) b)
48     (mm-handle-set-undisplayer
49      handle
50      `(lambda () (remove-images ,b (1+ ,b))))))
51
52 (defun mm-inline-image-xemacs (handle)
53   (insert "\n")
54   (forward-char -1)
55   (let ((b (point))
56         (annot (make-annotation (mm-get-image handle) nil 'text))
57         buffer-read-only)
58     (mm-handle-set-undisplayer
59      handle
60      `(lambda ()
61         (let (buffer-read-only)
62           (delete-annotation ,annot)
63           (delete-region ,(set-marker (make-marker) b)
64                          ,(set-marker (make-marker) (point))))))
65     (set-extent-property annot 'mm t)
66     (set-extent-property annot 'duplicable t)))
67
68 (eval-and-compile
69   (if (featurep 'xemacs)
70       (defalias 'mm-inline-image 'mm-inline-image-xemacs)
71     (defalias 'mm-inline-image 'mm-inline-image-emacs)))
72
73 (defvar mm-w3-setup nil)
74 (defun mm-setup-w3 ()
75   (unless mm-w3-setup
76     (require 'w3)
77     (w3-do-setup)
78     (require 'url)
79     (require 'w3-vars)
80     (require 'url-vars)
81     (setq mm-w3-setup t)))
82
83 (defun mm-inline-text (handle)
84   (let ((type (mm-handle-media-subtype handle))
85         text buffer-read-only)
86     (cond
87      ((equal type "html")
88       (mm-setup-w3)
89       (setq text (mm-get-part handle))
90       (let ((b (point))
91             (url-standalone-mode t)
92             (url-current-object
93              (url-generic-parse-url (format "cid:%s" (mm-handle-id handle))))
94             (width (window-width))
95             (charset (mail-content-type-get
96                       (mm-handle-type handle) 'charset)))
97         (save-excursion
98           (insert text)
99           (save-restriction
100             (narrow-to-region b (point))
101             (goto-char (point-min))
102             (if (or (and (boundp 'w3-meta-content-type-charset-regexp)
103                          (re-search-forward
104                           w3-meta-content-type-charset-regexp nil t))
105                     (and (boundp 'w3-meta-charset-content-type-regexp)
106                          (re-search-forward
107                           w3-meta-charset-content-type-regexp nil t)))
108                 (setq charset
109                       (or (let ((bsubstr (buffer-substring-no-properties
110                                           (match-beginning 2)
111                                           (match-end 2))))
112                             (if (fboundp 'w3-coding-system-for-mime-charset)
113                                 (w3-coding-system-for-mime-charset bsubstr)
114                               (mm-charset-to-coding-system bsubstr)))
115                           charset)))
116             (delete-region (point-min) (point-max))
117             (insert (mm-decode-string text charset))
118             (save-window-excursion
119               (save-restriction
120                 (let ((w3-strict-width width)
121                       ;; Don't let w3 set the global version of
122                       ;; this variable.
123                       (fill-column fill-column)
124                       (url-standalone-mode t))
125                   (condition-case var
126                       (w3-region (point-min) (point-max))
127                     (error
128                      (delete-region (point-min) (point-max))
129                      (let ((b (point))
130                            (charset (mail-content-type-get
131                                      (mm-handle-type handle) 'charset)))
132                        (if (or (eq charset 'gnus-decoded)
133                                (eq mail-parse-charset 'gnus-decoded))
134                            (save-restriction
135                              (narrow-to-region (point) (point))
136                              (mm-insert-part handle)
137                              (goto-char (point-max)))
138                          (insert (mm-decode-string (mm-get-part handle)
139                                                    charset))))
140                      (message
141                       "Error while rendering html; showing as text/plain"))))))
142             (mm-handle-set-undisplayer
143              handle
144              `(lambda ()
145                 (let (buffer-read-only)
146                   (if (functionp 'remove-specifier)
147                       (mapcar (lambda (prop)
148                                 (remove-specifier
149                                  (face-property 'default prop)
150                                  (current-buffer)))
151                               '(background background-pixmap foreground)))
152                   (delete-region ,(point-min-marker)
153                                  ,(point-max-marker)))))))))
154      ((or (equal type "enriched")
155           (equal type "richtext"))
156       (save-excursion
157         (mm-with-unibyte-buffer
158           (mm-insert-part handle)
159           (save-window-excursion
160             (enriched-decode (point-min) (point-max))
161             (setq text (buffer-string)))))
162       (mm-insert-inline handle text))
163      ((equal type "x-vcard")
164       (mm-insert-inline
165        handle
166        (concat "\n-- \n"
167                (if (fboundp 'vcard-pretty-print)
168                    (vcard-pretty-print (mm-get-part handle))
169                  (vcard-format-string
170                   (vcard-parse-string (mm-get-part handle)
171                                       'vcard-standard-filter))))))
172      (t
173       (let ((b (point))
174             (charset (mail-content-type-get
175                       (mm-handle-type handle) 'charset)))
176         (if (or (eq charset 'gnus-decoded)
177                 ;; This is probably not entirely correct, but
178                 ;; makes rfc822 parts with embedded multiparts work.
179                 (eq mail-parse-charset 'gnus-decoded))
180             (save-restriction
181               (narrow-to-region (point) (point))
182               (mm-insert-part handle)
183               (goto-char (point-max)))
184           (insert (mm-decode-string (mm-get-part handle) charset)))
185         (when (and (equal type "plain")
186                    (equal (cdr (assoc 'format (mm-handle-type handle)))
187                           "flowed"))
188           (save-restriction
189             (narrow-to-region b (point))
190             (goto-char b)
191             (fill-flowed)
192             (goto-char (point-max))))
193         (save-restriction
194           (narrow-to-region b (point))
195           (set-text-properties (point-min) (point-max) nil)
196           (mm-handle-set-undisplayer
197            handle
198            `(lambda ()
199               (let (buffer-read-only)
200                 (delete-region ,(point-min-marker)
201                                ,(point-max-marker)))))))))))
202
203 (defun mm-insert-inline (handle text)
204   "Insert TEXT inline from HANDLE."
205   (let ((b (point)))
206     (insert text)
207     (mm-handle-set-undisplayer
208      handle
209      `(lambda ()
210         (let (buffer-read-only)
211           (delete-region ,(set-marker (make-marker) b)
212                          ,(set-marker (make-marker) (point))))))))
213
214 (defun mm-inline-audio (handle)
215   (message "Not implemented"))
216
217 (defun mm-view-sound-file ()
218   (message "Not implemented"))
219
220 (defun mm-w3-prepare-buffer ()
221   (require 'w3)
222   (let ((url-standalone-mode t))
223     (w3-prepare-buffer)))
224
225 (defun mm-view-message ()
226   (mm-enable-multibyte)
227   (let (handles)
228     (let (gnus-article-mime-handles)
229       ;; Double decode problem may happen.  See mm-inline-message.
230       (run-hooks 'gnus-article-decode-hook)
231       (gnus-article-prepare-display)
232       (setq handles gnus-article-mime-handles))
233     (when handles
234       (setq gnus-article-mime-handles
235             (mm-merge-handles gnus-article-mime-handles handles))))
236   (fundamental-mode)
237   (goto-char (point-min)))
238
239 (defun mm-inline-message (handle)
240   (let ((b (point))
241         (bolp (bolp))
242         (charset (mail-content-type-get
243                   (mm-handle-type handle) 'charset))
244         gnus-displaying-mime handles)
245     (when (and charset
246                (stringp charset))
247       (setq charset (intern (downcase charset)))
248       (when (eq charset 'us-ascii)
249         (setq charset nil)))
250     (save-excursion
251       (save-restriction
252         (narrow-to-region b b)
253         (mm-insert-part handle)
254         (let (gnus-article-mime-handles
255               ;; disable prepare hook
256               gnus-article-prepare-hook
257               (gnus-newsgroup-charset
258                (or charset gnus-newsgroup-charset)))
259           (run-hooks 'gnus-article-decode-hook)
260           (gnus-article-prepare-display)
261           (setq handles gnus-article-mime-handles))
262         (goto-char (point-min))
263         (unless bolp
264           (insert "\n"))
265         (goto-char (point-max))
266         (unless (bolp)
267           (insert "\n"))
268         (insert "----------\n\n")
269         (when handles
270           (setq gnus-article-mime-handles
271                 (mm-merge-handles gnus-article-mime-handles handles)))
272         (mm-handle-set-undisplayer
273          handle
274          `(lambda ()
275             (let (buffer-read-only)
276               (if (fboundp 'remove-specifier)
277                   ;; This is only valid on XEmacs.
278                   (mapcar (lambda (prop)
279                             (remove-specifier
280                              (face-property 'default prop) (current-buffer)))
281                           '(background background-pixmap foreground)))
282               (delete-region ,(point-min-marker) ,(point-max-marker)))))))))
283
284 (defun mm-display-inline-fontify (handle mode)
285   (let (text)
286     ;; XEmacs @#$@ version of font-lock refuses to fully turn itself
287     ;; on for buffers whose name begins with " ".  That's why we use
288     ;; save-current-buffer/get-buffer-create rather than
289     ;; with-temp-buffer.
290     (save-current-buffer
291       (set-buffer (generate-new-buffer "*fontification*"))
292       (unwind-protect
293           (progn
294             (buffer-disable-undo)
295             (mm-insert-part handle)
296             (funcall mode)
297             (let ((font-lock-verbose nil))
298               ;; I find font-lock a bit too verbose.
299               (font-lock-fontify-buffer))
300             ;; By default, XEmacs font-lock uses non-duplicable text
301             ;; properties.  This code forces all the text properties
302             ;; to be copied along with the text.
303             (when (fboundp 'extent-list)
304               (map-extents (lambda (ext ignored)
305                              (set-extent-property ext 'duplicable t)
306                              nil)
307                            nil nil nil nil nil 'text-prop))
308             (setq text (buffer-string)))
309         (kill-buffer (current-buffer))))
310     (mm-insert-inline handle text)))
311
312 ;; Shouldn't these functions check whether the user even wants to use
313 ;; font-lock?  At least under XEmacs, this fontification is pretty
314 ;; much unconditional.  Also, it would be nice to change for the size
315 ;; of the fontified region.
316
317 (defun mm-display-patch-inline (handle)
318   (mm-display-inline-fontify handle 'diff-mode))
319
320 (defun mm-display-elisp-inline (handle)
321   (mm-display-inline-fontify handle 'emacs-lisp-mode))
322
323 ;;      id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
324 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
325 (defvar mm-pkcs7-signed-magic
326   (mm-string-as-unibyte
327    (apply 'concat
328           (mapcar 'char-to-string
329                   (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
330                         ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
331                         ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
332                         ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x02)))))
333
334 ;;      id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
335 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
336 (defvar mm-pkcs7-enveloped-magic
337   (mm-string-as-unibyte
338    (apply 'concat
339           (mapcar 'char-to-string
340                   (list ?\x30 ?\x5c ?\x28 ?\x80 ?\x5c ?\x7c ?\x81 ?\x2e ?\x5c
341                         ?\x7c ?\x82 ?\x2e ?\x2e ?\x5c ?\x7c ?\x83 ?\x2e ?\x2e
342                         ?\x2e ?\x5c ?\x29 ?\x06 ?\x09 ?\x5c ?\x2a ?\x86 ?\x48
343                         ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x03)))))
344
345 (defun mm-view-pkcs7-get-type (handle)
346   (mm-with-unibyte-buffer
347    (mm-insert-part handle)
348    (cond ((looking-at mm-pkcs7-enveloped-magic)
349           'enveloped)
350          ((looking-at mm-pkcs7-signed-magic)
351           'signed)
352          (t
353           (error "Could not identify PKCS#7 type")))))
354
355 (defun mm-view-pkcs7 (handle)
356   (case (mm-view-pkcs7-get-type handle)
357     (enveloped (mm-view-pkcs7-decrypt handle))
358     (otherwise (error "Unknown or unimplemented PKCS#7 type"))))
359
360 (defun mm-view-pkcs7-decrypt (handle)
361   (if (cond
362        ((eq mm-decrypt-option 'never) nil)
363        ((eq mm-decrypt-option 'always) t)
364        ((eq mm-decrypt-option 'known) t)
365        (t (y-or-n-p
366            (format "Decrypt (S/MIME) part? "))))
367       (let (res)
368         (with-temp-buffer
369           (insert-buffer (mm-handle-buffer handle))
370           (goto-char (point-min))
371           (insert "MIME-Version: 1.0\n")
372           (mm-insert-headers "application/pkcs7-mime" "base64" "smime.p7m")
373           (smime-decrypt-region
374            (point-min) (point-max)
375            (if (= (length smime-keys) 1)
376                (cadar smime-keys)
377              (smime-get-key-by-email
378               (completing-read "Decrypt this part with which key? "
379                                smime-keys nil nil
380                                (and (listp (car-safe smime-keys))
381                                     (caar smime-keys))))))
382           (setq res (buffer-string)))
383         (mm-insert-inline handle res))))
384
385 (provide 'mm-view)
386
387 ;;; mm-view.el ends here