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