2001-08-02 Simon Josefsson <jas@extundo.com>
[gnus] / 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 ?\x82 ?\x2e ?\x2e ?\x06 ?\x09 ?\x5c ?\x2a
330                         ?\x86 ?\x48 ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x02)))))
331   
332 ;;      id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
333 ;;          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
334 (defvar mm-pkcs7-enveloped-magic
335   (mm-string-as-unibyte
336    (apply 'concat
337           (mapcar 'char-to-string
338                   (list ?\x30 ?\x82 ?\x2e ?\x2e ?\x06 ?\x09 ?\x5c ?\x2a
339                         ?\x86 ?\x48 ?\x86 ?\xf7 ?\x0d ?\x01 ?\x07 ?\x03)))))
340   
341 (defun mm-view-pkcs7-get-type (handle)
342   (mm-with-unibyte-buffer
343     (mm-insert-part handle)
344     (cond ((looking-at mm-pkcs7-enveloped-magic)
345            'enveloped)
346           ((looking-at mm-pkcs7-signed-magic)
347            'signed)
348           (t
349            (error "Could not identify PKCS#7 type")))))
350
351 (defun mm-view-pkcs7 (handle)
352   (case (mm-view-pkcs7-get-type handle)
353     (enveloped (mm-view-pkcs7-decrypt handle))
354     (otherwise (error "Unknown or unimplemented PKCS#7 type"))))
355
356 (defun mm-view-pkcs7-decrypt (handle)
357   (if (cond
358        ((eq mm-decrypt-option 'never) nil)
359        ((eq mm-decrypt-option 'always) t)
360        ((eq mm-decrypt-option 'known) t)
361        (t (y-or-n-p
362            (format "Decrypt (S/MIME) part? "))))
363       (let (res)
364         (with-temp-buffer
365           (insert-buffer (mm-handle-buffer handle))
366           (goto-char (point-min))
367           (insert "MIME-Version: 1.0\n")
368           (mm-insert-headers "application/pkcs7-mime" "base64" "smime.p7m")
369           (smime-decrypt-region
370            (point-min) (point-max)
371            (if (= (length smime-keys) 1)
372                (cadar smime-keys)
373              (smime-get-key-by-email
374               (completing-read "Decrypt this part with which key? "
375                                smime-keys nil nil
376                                (and (listp (car-safe smime-keys))
377                                     (caar smime-keys))))))
378           (setq res (buffer-string)))
379         (mm-insert-inline handle res))))
380
381 (provide 'mm-view)
382
383 ;;; mm-view.el ends here