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