* gnus.el: Don't test for `mm-guess-mime-charset'.
[gnus] / lisp / mm-decode.el
index 66db063..3cf9a0b 100644 (file)
@@ -1,5 +1,6 @@
 ;;; mm-decode.el --- Functions for decoding MIME things
-;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000, 2001, 2002,
+;;        2003 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;     MORIOKA Tomohiko <morioka@jaist.ac.jp>
 (require 'mail-parse)
 (require 'mailcap)
 (require 'mm-bodies)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl)
+                  (require 'term))
 
 (eval-and-compile
+  (autoload 'executable-find "executable")
   (autoload 'mm-inline-partial "mm-partial")
-  (autoload 'mm-inline-external-body "mm-extern"))
+  (autoload 'mm-inline-external-body "mm-extern")
+  (autoload 'mm-insert-inline "mm-view"))
+
+(add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
 
 (defgroup mime-display ()
   "Display of MIME in mail and news articles."
   :link '(custom-manual "(emacs-mime)Customization")
+  :version "21.1"
   :group 'mail
   :group 'news
   :group 'multimedia)
@@ -79,6 +86,8 @@
   `(nth 7 ,handle))
 (defmacro mm-handle-multipart-original-buffer (handle)
   `(get-text-property 0 'buffer (car ,handle)))
+(defmacro mm-handle-multipart-from (handle)
+  `(get-text-property 0 'from (car ,handle)))
 (defmacro mm-handle-multipart-ctl-parameter (handle parameter)
   `(get-text-property 0 ,parameter (car ,handle)))
 
   `(list ,buffer ,type ,encoding ,undisplayer
         ,disposition ,description ,cache ,id))
 
+(defcustom mm-text-html-renderer
+  (cond ((locate-library "w3") 'w3)
+       ((locate-library "w3m") 'w3m)
+       ((executable-find "links") 'links)
+       ((executable-find "lynx") 'lynx)
+       (t 'html2text))
+  "Render of HTML contents.
+It is one of defined renderer types, or a rendering function.
+The defined renderer types are:
+`w3'   : using Emacs/W3;
+`w3m'  : using emacs-w3m;
+`links': using links;
+`lynx' : using lynx;
+`html2text' : using html2text;
+nil    : using external viewer."
+  :type '(choice (const w3)
+                (const w3m)
+                (const links)
+                (const lynx)
+                (const html2text)
+                (const nil)
+                (function))
+  :version "21.3"
+  :group 'mime-display)
+
+(defvar mm-inline-text-html-renderer nil
+  "Function used for rendering inline HTML contents.
+It is suggested to customize `mm-text-html-renderer' instead.")
+
+(defcustom mm-inline-text-html-with-images nil
+  "If non-nil, Gnus will allow retrieving images in the HTML contents
+with <img> tags.  It has no effect on Emacs/w3.  See also
+the documentation for the option `mm-w3m-safe-url-regexp'."
+  :type 'boolean
+  :group 'mime-display)
+
+(defcustom mm-w3m-safe-url-regexp "\\`cid:"
+  "Regexp that matches safe url names.  Some HTML mails might have a
+trick of spammers using <img> tags.  It is likely to be intended to
+verify whether you have read the mail.  You can prevent your personal
+informations from leaking by setting this to the regexp which matches
+the safe url names.  The value of the variable `w3m-safe-url-regexp'
+will be bound with this value.  You may set this value to nil if you
+consider all the urls to be safe."
+  :type '(choice (regexp :tag "Regexp")
+                (const :tag "All URLs are safe" nil))
+  :group 'mime-display)
+
+(defcustom mm-inline-text-html-with-w3m-keymap t
+  "If non-nil, use emacs-w3m command keys in the article buffer."
+  :type 'boolean
+  :group 'mime-display)
+
 (defcustom mm-inline-media-tests
   '(("image/jpeg"
      mm-inline-image
      mm-inline-image
      (lambda (handle)
        (mm-valid-and-fit-image-p 'xpm handle)))
-    ("image/x-pixmap"
+    ("image/x-xpixmap"
      mm-inline-image
      (lambda (handle)
        (mm-valid-and-fit-image-p 'xpm handle)))
      mm-inline-image
      (lambda (handle)
        (mm-valid-and-fit-image-p 'bmp handle)))
+    ("image/x-portable-bitmap"
+     mm-inline-image
+     (lambda (handle)
+       (mm-valid-and-fit-image-p 'pbm handle)))
     ("text/plain" mm-inline-text identity)
     ("text/enriched" mm-inline-text identity)
     ("text/richtext" mm-inline-text identity)
      (lambda (handle)
        (locate-library "diff-mode")))
     ("application/emacs-lisp" mm-display-elisp-inline identity)
+    ("application/x-emacs-lisp" mm-display-elisp-inline identity)
     ("text/html"
-     mm-inline-text
+     mm-inline-text-html
      (lambda (handle)
-       (locate-library "w3")))
+       (or mm-inline-text-html-renderer
+          mm-text-html-renderer)))
     ("text/x-vcard"
-     mm-inline-text
+     mm-inline-text-vcard
      (lambda (handle)
        (or (featurep 'vcard)
           (locate-library "vcard"))))
     ("application/pgp-signature" ignore identity)
     ("application/x-pkcs7-signature" ignore identity)
     ("application/pkcs7-signature" ignore identity)
+    ("application/x-pkcs7-mime" ignore identity)
+    ("application/pkcs7-mime" ignore identity)
     ("multipart/alternative" ignore identity)
     ("multipart/mixed" ignore identity)
-    ("multipart/related" ignore identity))
+    ("multipart/related" ignore identity)
+    ;; Disable audio and image
+    ("audio/.*" ignore ignore)
+    ("image/.*" ignore ignore)
+    ;; Default to displaying as text
+    (".*" mm-inline-text mm-readable-p))
   "Alist of media types/tests saying whether types can be displayed inline."
   :type '(repeat (list (string :tag "MIME type")
                       (function :tag "Display function")
 (defcustom mm-inlined-types
   '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
     "message/partial" "message/external-body" "application/emacs-lisp"
+    "application/x-emacs-lisp"
     "application/pgp-signature" "application/x-pkcs7-signature"
-    "application/pkcs7-signature")
-  "List of media types that are to be displayed inline."
+    "application/pkcs7-signature" "application/x-pkcs7-mime"
+    "application/pkcs7-mime")
+  "List of media types that are to be displayed inline.
+See also `mm-inline-media-tests', which says how to display a media
+type inline."
+  :type '(repeat string)
+  :group 'mime-display)
+
+(defcustom mm-keep-viewer-alive-types
+  '("application/postscript" "application/msword" "application/vnd.ms-excel"
+    "application/pdf" "application/x-dvi")
+  "List of media types for which the external viewer will not be killed
+when selecting a different article."
   :type '(repeat string)
   :group 'mime-display)
-  
+
 (defcustom mm-automatic-display
   '("text/plain" "text/enriched" "text/richtext" "text/html"
     "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
     "message/rfc822" "text/x-patch" "application/pgp-signature"
-    "application/emacs-lisp" "application/x-pkcs7-signature"
-    "application/pkcs7-signature")
+    "application/emacs-lisp" "application/x-emacs-lisp"
+    "application/x-pkcs7-signature"
+    "application/pkcs7-signature" "application/x-pkcs7-mime"
+    "application/pkcs7-mime")
   "A list of MIME types to be displayed automatically."
   :type '(repeat string)
   :group 'mime-display)
 
-(defcustom mm-attachment-override-types '("text/x-vcard")
+(defcustom mm-attachment-override-types '("text/x-vcard"
+                                         "application/pkcs7-mime"
+                                         "application/x-pkcs7-mime"
+                                         "application/pkcs7-signature"
+                                         "application/x-pkcs7-signature")
   "Types to have \"attachment\" ignored if they can be displayed inline."
   :type '(repeat string)
   :group 'mime-display)
@@ -214,22 +307,60 @@ to:
   :type '(repeat string)
   :group 'mime-display)
 
-(defvar mm-tmp-directory
-  (cond ((fboundp 'temp-directory) (temp-directory))
-       ((boundp 'temporary-file-directory) temporary-file-directory)
-       ("/tmp/"))
-  "Where mm will store its temporary files.")
+(defcustom mm-tmp-directory
+  (if (fboundp 'temp-directory)
+      (temp-directory)
+    (if (boundp 'temporary-file-directory)
+       temporary-file-directory
+      "/tmp/"))
+  "Where mm will store its temporary files."
+  :type 'directory
+  :group 'mime-display)
 
 (defcustom mm-inline-large-images nil
   "If non-nil, then all images fit in the buffer."
   :type 'boolean
   :group 'mime-display)
 
+(defvar mm-file-name-rewrite-functions nil
+  "*List of functions used for rewriting file names of MIME parts.
+Each function takes a file name as input and returns a file name.
+
+Ready-made functions include
+`mm-file-name-delete-whitespace',
+`mm-file-name-trim-whitespace',
+`mm-file-name-collapse-whitespace',
+`mm-file-name-replace-whitespace',
+`capitalize', `downcase', `upcase', and
+`upcase-initials'.")
+
+(defvar mm-path-name-rewrite-functions nil
+  "*List of functions for rewriting the full file names of MIME parts.
+This is used when viewing parts externally, and is meant for
+transforming the absolute name so that non-compliant programs can find
+the file where it's saved.
+
+Each function takes a file name as input and returns a file name.")
+
+(defvar mm-file-name-replace-whitespace nil
+  "String used for replacing whitespace characters; default is `\"_\"'.")
+
+(defcustom mm-default-directory nil
+  "The default directory where mm will save files.
+If not set, `default-directory' will be used."
+  :type '(choice directory (const :tag "Default" nil))
+  :group 'mime-display)
+
+(defcustom mm-external-terminal-program "xterm"
+  "The program to start an external terminal."
+  :type 'string
+  :group 'mime-display)
+
 ;;; Internal variables.
 
-(defvar mm-dissection-list nil)
 (defvar mm-last-shell-command "")
 (defvar mm-content-id-alist nil)
+(defvar mm-postponed-undisplay-list nil)
 
 ;; According to RFC2046, in particular, in a digest, the default
 ;; Content-Type value for a body part is changed from "text/plain" to
@@ -239,15 +370,20 @@ to:
 (autoload 'mml2015-verify "mml2015")
 (autoload 'mml2015-verify-test "mml2015")
 (autoload 'mml-smime-verify "mml-smime")
+(autoload 'mml-smime-verify-test "mml-smime")
 
 (defvar mm-verify-function-alist
   '(("application/pgp-signature" mml2015-verify "PGP" mml2015-verify-test)
-    ("application/pkcs7-signature" mml-smime-verify "S/MIME" nil)
-    ("application/x-pkcs7-signature" mml-smime-verify "S/MIME" nil)))
-
-(defcustom mm-verify-option 'known
+    ("application/x-gnus-pgp-signature" mm-uu-pgp-signed-extract-1 "PGP"
+     mm-uu-pgp-signed-test)
+    ("application/pkcs7-signature" mml-smime-verify "S/MIME"
+     mml-smime-verify-test)
+    ("application/x-pkcs7-signature" mml-smime-verify "S/MIME"
+     mml-smime-verify-test)))
+
+(defcustom mm-verify-option 'never
   "Option of verifying signed parts.
-`never', not verify; `always', always verify; 
+`never', not verify; `always', always verify;
 `known', only verify known protocols. Otherwise, ask user."
   :type '(choice (item always)
                 (item never)
@@ -259,11 +395,13 @@ to:
 (autoload 'mml2015-decrypt-test "mml2015")
 
 (defvar mm-decrypt-function-alist
-  '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)))
+  '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)
+    ("application/x-gnus-pgp-encrypted" mm-uu-pgp-encrypted-extract-1 "PGP"
+     mm-uu-pgp-encrypted-test)))
 
-(defcustom mm-decrypt-option 'known
-  "Option of decrypting signed parts.
-`never', not decrypt; `always', always decrypt; 
+(defcustom mm-decrypt-option nil
+  "Option of decrypting encrypted parts.
+`never', not decrypt; `always', always decrypt;
 `known', only decrypt known protocols. Otherwise, ask user."
   :type '(choice (item always)
                 (item never)
@@ -278,7 +416,16 @@ to:
   "Keymap for input viewer with completion.")
 
 ;; Should we bind other key to minibuffer-complete-word?
-(define-key mm-viewer-completion-map " " 'self-insert-command) 
+(define-key mm-viewer-completion-map " " 'self-insert-command)
+
+(defvar mm-viewer-completion-map
+  (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
+    (set-keymap-parent map minibuffer-local-completion-map)
+    map)
+  "Keymap for input viewer with completion.")
+
+;; Should we bind other key to minibuffer-complete-word?
+(define-key mm-viewer-completion-map " " 'self-insert-command)
 
 ;;; The functions.
 
@@ -300,20 +447,53 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
       (setq alist (cdr alist)))
     (nreverse plist)))
 
-(defun mm-dissect-buffer (&optional no-strict-mime)
+(defun mm-keep-viewer-alive-p (handle)
+  "Say whether external viewer for HANDLE should stay alive."
+  (let ((types mm-keep-viewer-alive-types)
+       (type (mm-handle-media-type handle))
+       ty)
+    (catch 'found
+      (while (setq ty (pop types))
+       (when (string-match ty type)
+         (throw 'found t))))))
+
+(defun mm-handle-set-external-undisplayer (handle function)
+  "Set the undisplayer for this handle; postpone undisplaying of viewers
+for types in mm-keep-viewer-alive-types."
+  (if (mm-keep-viewer-alive-p handle)
+      (let ((new-handle (copy-sequence handle)))
+       (mm-handle-set-undisplayer new-handle function)
+       (mm-handle-set-undisplayer handle nil)
+       (push new-handle mm-postponed-undisplay-list))
+    (mm-handle-set-undisplayer handle function)))
+
+(defun mm-destroy-postponed-undisplay-list ()
+  (when mm-postponed-undisplay-list
+    (message "Destroying external MIME viewers")
+    (mm-destroy-parts mm-postponed-undisplay-list)))
+
+(defun mm-dissect-buffer (&optional no-strict-mime loose-mime)
   "Dissect the current buffer and return a list of MIME handles."
   (save-excursion
-    (let (ct ctl type subtype cte cd description id result)
+    (let (ct ctl type subtype cte cd description id result from)
       (save-restriction
        (mail-narrow-to-head)
        (when (or no-strict-mime
+                 loose-mime
                  (mail-fetch-field "mime-version"))
          (setq ct (mail-fetch-field "content-type")
                ctl (ignore-errors (mail-header-parse-content-type ct))
                cte (mail-fetch-field "content-transfer-encoding")
                cd (mail-fetch-field "content-disposition")
                description (mail-fetch-field "content-description")
-               id (mail-fetch-field "content-id"))))
+               from (mail-fetch-field "from")
+               id (mail-fetch-field "content-id"))
+         ;; FIXME: In some circumstances, this code is running within
+         ;; an unibyte macro.  mail-extract-address-components
+         ;; creates unibyte buffers. This `if', though not a perfect
+         ;; solution, avoids most of them.
+         (if from
+             (setq from (cadr (mail-extract-address-components from))))))
       (when cte
        (setq cte (mail-header-strip cte)))
       (if (or (not ctl)
@@ -336,27 +516,33 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
           (let ((mm-dissect-default-type (if (equal subtype "digest")
                                              "message/rfc822"
                                            "text/plain")))
-             (add-text-properties 0 (length (car ctl))
-                                  (mm-alist-to-plist (cdr ctl)) (car ctl))
+            (add-text-properties 0 (length (car ctl))
+                                 (mm-alist-to-plist (cdr ctl)) (car ctl))
 
             ;; what really needs to be done here is a way to link a
             ;; MIME handle back to it's parent MIME handle (in a multilevel
             ;; MIME article).  That would probably require changing
             ;; the mm-handle API so we simply store the multipart buffert
             ;; name as a text property of the "multipart/whatever" string.
-             (add-text-properties 0 (length (car ctl))
+            (add-text-properties 0 (length (car ctl))
                                  (list 'buffer (mm-copy-to-buffer))
-                                  (car ctl))
+                                 (car ctl))
+            (add-text-properties 0 (length (car ctl))
+                                 (list 'from from)
+                                 (car ctl))
             (cons (car ctl) (mm-dissect-multipart ctl))))
          (t
-          (mm-dissect-singlepart
-           ctl
-           (and cte (intern (downcase (mail-header-remove-whitespace
-                                       (mail-header-remove-comments
-                                        cte)))))
-           no-strict-mime
-           (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
-           description id))))
+          (mm-possibly-verify-or-decrypt
+           (mm-dissect-singlepart
+            ctl
+            (and cte (intern (downcase (mail-header-remove-whitespace
+                                        (mail-header-remove-comments
+                                         cte)))))
+            no-strict-mime
+            (and cd (ignore-errors
+                      (mail-header-parse-content-disposition cd)))
+            description id)
+           ctl))))
        (when id
          (when (string-match " *<\\(.*\\)> *" id)
            (setq id (match-string 1 id)))
@@ -368,16 +554,8 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
            (if (equal "text/plain" (car ctl))
                (assoc 'format ctl)
              t))
-    (let ((res (mm-make-handle
-               (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
-      (push (car res) mm-dissection-list)
-      res)))
-
-(defun mm-remove-all-parts ()
-  "Remove all MIME handles."
-  (interactive)
-  (mapcar 'mm-remove-part mm-dissection-list)
-  (setq mm-dissection-list nil))
+    (mm-make-handle
+     (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
 
 (defun mm-dissect-multipart (ctl)
   (goto-char (point-min))
@@ -390,16 +568,18 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
                    (match-beginning 0)
                  (point-max)))))
     (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
-    (while (re-search-forward boundary end t)
+    (while (and (< (point) end) (re-search-forward boundary end t))
       (goto-char (match-beginning 0))
       (when start
        (save-excursion
          (save-restriction
            (narrow-to-region start (point))
            (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
-      (forward-line 2)
+      (end-of-line 2)
+      (or (looking-at boundary)
+         (forward-line 1))
       (setq start (point)))
-    (when start
+    (when (and start (< start end))
       (save-excursion
        (save-restriction
          (narrow-to-region start end)
@@ -418,6 +598,16 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
       (insert-buffer-substring obuf beg)
       (current-buffer))))
 
+(defun mm-display-parts (handle &optional no-default)
+  (if (stringp (car handle))
+      (mapcar 'mm-display-parts (cdr handle))
+    (if (bufferp (car handle))
+       (save-restriction
+         (narrow-to-region (point) (point))
+         (mm-display-part handle)
+         (goto-char (point-max)))
+      (mapcar 'mm-display-parts handle))))
+
 (defun mm-display-part (handle &optional no-default)
   "Display the MIME part represented by HANDLE.
 Returns nil if the part is removed; inline if displayed inline;