(with-syntax-table): XEmacs bug has been fixed. Add URL.
[gnus] / lisp / mml.el
index f6e728b..5ff55db 100644 (file)
@@ -1,7 +1,7 @@
 ;;; mml.el --- A package for parsing and validating MML documents
 
 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005 Free Software Foundation, Inc.
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; This file is part of GNU Emacs.
   (autoload 'gnus-make-local-hook "gnus-util")
   (autoload 'message-fetch-field "message")
   (autoload 'message-mark-active-p "message")
+  (autoload 'message-info "message")
   (autoload 'fill-flowed-encode "flow-fill")
-  (autoload 'message-posting-charset "message"))
-
-(eval-when-compile
+  (autoload 'message-posting-charset "message")
   (autoload 'dnd-get-local-file-name "dnd"))
 
+(defvar gnus-article-mime-handles)
+(defvar gnus-mouse-2)
+(defvar gnus-newsrc-hashtb)
+(defvar message-default-charset)
+(defvar message-deletable-headers)
+(defvar message-options)
+(defvar message-posting-charset)
+(defvar message-required-mail-headers)
+(defvar message-required-news-headers)
+(defvar dnd-protocol-alist)
+
 (defcustom mml-content-type-parameters
   '(name access-type expiration size permission format)
   "*A list of acceptable parameters in MML tag.
@@ -60,6 +70,46 @@ These parameters are generated in Content-Disposition header if exists."
   :type '(repeat (symbol :tag "Parameter"))
   :group 'message)
 
+(defcustom mml-content-disposition-alist
+  '((text (rtf . "attachment") (t . "inline"))
+    (t . "attachment"))
+  "Alist of MIME types or regexps matching file names and default dispositions.
+Each element should be one of the following three forms:
+
+  (REGEXP . DISPOSITION)
+  (SUPERTYPE (SUBTYPE . DISPOSITION) (SUBTYPE . DISPOSITION)...)
+  (TYPE . DISPOSITION)
+
+Where REGEXP is a string which matches the file name (if any) of an
+attachment, SUPERTYPE, SUBTYPE and TYPE should be symbols which are a
+MIME supertype (e.g., text), a MIME subtype (e.g., plain) and a MIME
+type (e.g., text/plain) respectively, and DISPOSITION should be either
+the string \"attachment\" or the string \"inline\".  The value t for
+SUPERTYPE, SUBTYPE or TYPE matches any of those types.  The first
+match found will be used."
+  :version "23.0" ;; No Gnus
+  :type (let ((dispositions '(radio :format "DISPOSITION: %v"
+                                   :value "attachment"
+                                   (const :format "%v " "attachment")
+                                   (const :format "%v\n" "inline"))))
+         `(repeat
+           :offset 0
+           (choice :format "%[Value Menu%]%v"
+                   (cons :tag "(REGEXP . DISPOSITION)" :extra-offset 4
+                         (regexp :tag "REGEXP" :value ".*")
+                         ,dispositions)
+                   (cons :tag "(SUPERTYPE (SUBTYPE . DISPOSITION)...)"
+                         :indent 0
+                         (symbol :tag "    SUPERTYPE" :value text)
+                         (repeat :format "%v%i\n" :offset 0 :extra-offset 4
+                                 (cons :format "%v" :extra-offset 5
+                                       (symbol :tag "SUBTYPE" :value t)
+                                       ,dispositions)))
+                   (cons :tag "(TYPE . DISPOSITION)" :extra-offset 4
+                         (symbol :tag "TYPE" :value t)
+                         ,dispositions))))
+  :group 'message)
+
 (defcustom mml-insert-mime-headers-always nil
   "If non-nil, always put Content-Type: text/plain at top of empty parts.
 It is necessary to work against a bug in certain clients."
@@ -495,9 +545,9 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
            (mm-with-unibyte-buffer
              (cond
               ((cdr (assq 'buffer cont))
-               (insert (with-current-buffer (cdr (assq 'buffer cont))
-                         (mm-with-unibyte-current-buffer
-                           (buffer-string)))))
+               (insert (mm-string-as-unibyte
+                        (with-current-buffer (cdr (assq 'buffer cont))
+                          (buffer-string)))))
               ((and filename
                     (not (equal (cdr (assq 'nofile cont)) "yes")))
                (let ((coding-system-for-read mm-binary-coding-system))
@@ -507,7 +557,15 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                                 (mm-find-buffer-file-coding-system
                                  filename)))))
               (t
-               (insert (cdr (assq 'contents cont)))))
+               (let ((contents (cdr (assq 'contents cont))))
+                 (if (if (featurep 'xemacs)
+                         (string-match "[^\000-\377]" contents)
+                       (mm-multibyte-string-p contents))
+                     (progn
+                       (mm-enable-multibyte)
+                       (insert contents)
+                       (setq charset (mm-encode-body charset)))
+                   (insert contents)))))
              (setq encoding (mm-encode-buffer type)
                    coded (mm-string-as-multibyte (buffer-string))))
            (mml-insert-mime-headers cont type charset encoding nil)
@@ -578,7 +636,8 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                  ;; Skip `multipart' and attributes.
                  (when (and (consp part) (consp (cdr part)))
                    (insert "\n--" mml-boundary "\n")
-                   (mml-generate-mime-1 part))))
+                   (mml-generate-mime-1 part)
+                   (goto-char (point-max)))))
              (insert "\n--" mml-boundary "--\n")))))
        (t
        (error "Invalid element: %S" cont)))
@@ -648,6 +707,30 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
            "")
          mml-base-boundary))
 
+(defun mml-content-disposition (type &optional filename)
+  "Return a default disposition name suitable to TYPE or FILENAME."
+  (let ((defs mml-content-disposition-alist)
+       disposition def types)
+    (while (and (not disposition) defs)
+      (setq def (pop defs))
+      (cond ((stringp (car def))
+            (when (and filename
+                       (string-match (car def) filename))
+              (setq disposition (cdr def))))
+           ((consp (cdr def))
+            (when (string= (car (setq types (split-string type "/")))
+                           (car def))
+              (setq type (cadr types)
+                    types (cdr def))
+              (while (and (not disposition) types)
+                (setq def (pop types))
+                (when (or (eq (car def) t) (string= type (car def)))
+                  (setq disposition (cdr def))))))
+           (t
+            (when (or (eq (car def) t) (string= type (car def)))
+              (setq disposition (cdr def))))))
+    (or disposition "attachment")))
+
 (defun mml-insert-mime-headers (cont type charset encoding flowed)
   (let (parameters id disposition description)
     (setq parameters
@@ -663,10 +746,10 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
         "Can't encode a part with several charsets"))
       (insert "Content-Type: " type)
       (when charset
-       (insert "; " (mail-header-encode-parameter
-                     "charset" (symbol-name charset))))
+       (mml-insert-parameter
+        (mail-header-encode-parameter "charset" (symbol-name charset))))
       (when flowed
-       (insert "; format=flowed"))
+       (mml-insert-parameter "format=flowed"))
       (when parameters
        (mml-insert-parameter-string
         cont mml-content-type-parameters))
@@ -678,7 +761,9 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
           cont mml-content-disposition-parameters))
     (when (or (setq disposition (cdr (assq 'disposition cont)))
              parameters)
-      (insert "Content-Disposition: " (or disposition "inline"))
+      (insert "Content-Disposition: "
+             (or disposition
+                 (mml-content-disposition type (cdr (assq 'filename cont)))))
       (when parameters
        (mml-insert-parameter-string
         cont mml-content-disposition-parameters))
@@ -686,8 +771,11 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
     (unless (eq encoding '7bit)
       (insert (format "Content-Transfer-Encoding: %s\n" encoding)))
     (when (setq description (cdr (assq 'description cont)))
-      (insert "Content-Description: "
-             (mail-encode-encoded-word-string description) "\n"))))
+      (insert "Content-Description: ")
+      (setq description (prog1
+                           (point)
+                         (insert description "\n")))
+      (mail-encode-encoded-word-region description (point)))))
 
 (defun mml-parameter-string (cont types)
   (let ((string "")
@@ -781,7 +869,7 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
       (unless (setq textp (equal (mm-handle-media-supertype handle) "text"))
        (save-excursion
          (set-buffer (setq buffer (mml-generate-new-buffer " *mml*")))
-         (mm-insert-part handle)
+         (mm-insert-part handle 'no-cache)
          (if (setq mmlp (equal (mm-handle-media-type handle)
                                "message/rfc822"))
              (mime-to-mml)))))
@@ -840,14 +928,20 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
 
 (defun mml-insert-parameter (&rest parameters)
   "Insert PARAMETERS in a nice way."
-  (dolist (param parameters)
-    (insert ";")
-    (let ((point (point)))
+  (let (start end)
+    (dolist (param parameters)
+      (insert ";")
+      (setq start (point))
       (insert " " param)
-      (when (> (current-column) 71)
-       (goto-char point)
-       (insert "\n")
-       (end-of-line)))))
+      (setq end (point))
+      (goto-char start)
+      (end-of-line)
+      (if (> (current-column) 76)
+         (progn
+           (goto-char start)
+           (insert "\n")
+           (goto-char (1+ end)))
+       (goto-char end)))))
 
 ;;;
 ;;; Mode for inserting and editing MML forms
@@ -860,6 +954,11 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
        (encryptpart (make-sparse-keymap))
        (map (make-sparse-keymap))
        (main (make-sparse-keymap)))
+    (define-key map "\C-s" 'mml-secure-message-sign)
+    (define-key map "\C-c" 'mml-secure-message-encrypt)
+    (define-key map "\C-e" 'mml-secure-message-sign-encrypt)
+    (define-key map "\C-p\C-s" 'mml-secure-sign)
+    (define-key map "\C-p\C-c" 'mml-secure-encrypt)
     (define-key sign "p" 'mml-secure-message-sign-pgpmime)
     (define-key sign "o" 'mml-secure-message-sign-pgp)
     (define-key sign "s" 'mml-secure-message-sign-smime)
@@ -897,31 +996,63 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     ["Attach File..." mml-attach-file
      ,@(if (featurep 'xemacs) '(t)
         '(:help "Attach a file at point"))]
-    ["Attach Buffer..." mml-attach-buffer t]
-    ["Attach External..." mml-attach-external t]
-    ["Insert Part..." mml-insert-part t]
-    ["Insert Multipart..." mml-insert-multipart t]
-    ["PGP/MIME Sign" mml-secure-message-sign-pgpmime t]
-    ["PGP/MIME Encrypt" mml-secure-message-encrypt-pgpmime t]
-    ["PGP Sign" mml-secure-message-sign-pgp t]
-    ["PGP Encrypt" mml-secure-message-encrypt-pgp t]
-    ["S/MIME Sign" mml-secure-message-sign-smime t]
-    ["S/MIME Encrypt" mml-secure-message-encrypt-smime t]
-    ("Secure MIME part"
-     ["PGP/MIME Sign Part" mml-secure-sign-pgpmime t]
-     ["PGP/MIME Encrypt Part" mml-secure-encrypt-pgpmime t]
-     ["PGP Sign Part" mml-secure-sign-pgp t]
-     ["PGP Encrypt Part" mml-secure-encrypt-pgp t]
-     ["S/MIME Sign Part" mml-secure-sign-smime t]
-     ["S/MIME Encrypt Part" mml-secure-encrypt-smime t])
-    ["Encrypt/Sign off" mml-unsecure-message t]
+    ["Attach Buffer..." mml-attach-buffer
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Attach a buffer to the outgoing MIME message"))]
+    ["Attach External..." mml-attach-external
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Attach reference to file"))]
+    ;;
+    ("Change Security Method"
+     ["PGP/MIME"
+      (lambda () (interactive) (setq mml-secure-method "pgpmime"))
+      ,@(if (featurep 'xemacs) nil
+         '(:help "Set Security Method to PGP/MIME"))
+      :style radio
+      :selected (equal mml-secure-method "pgpmime") ]
+     ["S/MIME"
+      (lambda () (interactive) (setq mml-secure-method "smime"))
+      ,@(if (featurep 'xemacs) nil
+         '(:help "Set Security Method to S/MIME"))
+      :style radio
+      :selected (equal mml-secure-method "smime") ]
+     ["Inline PGP"
+      (lambda () (interactive) (setq mml-secure-method "pgp"))
+      ,@(if (featurep 'xemacs) nil
+         '(:help "Set Security Method to inline PGP"))
+      :style radio
+      :selected (equal mml-secure-method "pgp") ] )
+    ;;
+    ["Sign Message" mml-secure-message-sign t]
+    ["Encrypt Message" mml-secure-message-encrypt t]
+    ["Sign and Encrypt Message" mml-secure-message-sign-encrypt t]
+    ["Encrypt/Sign off" mml-unsecure-message
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Don't Encrypt/Sign Message"))]
+    ;; Maybe we could remove these, because people who write MML most probably
+    ;; don't use the menu:
+    ["Insert Part..." mml-insert-part
+     :active (message-in-body-p)]
+    ["Insert Multipart..." mml-insert-multipart
+     :active (message-in-body-p)]
+    ;;
+    ;; Do we have separate encrypt and encrypt/sign commands for parts?
+    ["Sign Part" mml-secure-sign t]
+    ["Encrypt Part" mml-secure-encrypt t]
     ;;["Narrow" mml-narrow-to-part t]
-    ["Quote MML" mml-quote-region
+    ["Quote MML in region" mml-quote-region
      :active (message-mark-active-p)
      ,@(if (featurep 'xemacs) nil
         '(:help "Quote MML tags in region"))]
     ["Validate MML" mml-validate t]
-    ["Preview" mml-preview t]))
+    ["Preview" mml-preview t]
+    "----"
+    ["Emacs MIME manual" (lambda () (interactive) (message-info 4))
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Display the Emacs MIME manual"))]
+    ["PGG manual" (lambda () (interactive) (message-info 16))
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Display the PGG manual"))]))
 
 (defvar mml-mode nil
   "Minor mode for editing MML.")
@@ -948,9 +1079,18 @@ See Info node `(emacs-mime)Composing'.
 ;;; inserting stuff to the buffer.
 ;;;
 
+(defcustom mml-default-directory mm-default-directory
+  "The default directory where mml will find files.
+If not set, `default-directory' will be used."
+  :type '(choice directory (const :tag "Default" nil))
+  :version "23.0" ;; No Gnus
+  :group 'message)
+
 (defun mml-minibuffer-read-file (prompt)
   (let* ((completion-ignored-extensions nil)
-        (file (read-file-name prompt nil nil t)))
+        (file (read-file-name prompt
+                              (or mml-default-directory default-directory)
+                              nil t)))
     ;; Prevent some common errors.  This is inspired by similar code in
     ;; VM.
     (when (file-directory-p file)
@@ -982,16 +1122,13 @@ See Info node `(emacs-mime)Composing'.
       (setq description nil))
     description))
 
-(defun mml-minibuffer-read-disposition (type &optional default)
-  (unless default (setq default
-                        (if (and (string-match "\\`text/" type)
-                                 (not (string-match "\\`text/rtf\\'" type)))
-                            "inline"
-                          "attachment")))
+(defun mml-minibuffer-read-disposition (type &optional default filename)
+  (unless default
+    (setq default (mml-content-disposition type filename)))
   (let ((disposition (completing-read
-                      (format "Disposition (default %s): " default)
-                      '(("attachment") ("inline") (""))
-                      nil t nil nil default)))
+                     (format "Disposition (default %s): " default)
+                     '(("attachment") ("inline") (""))
+                     nil t nil nil default)))
     (if (not (equal disposition ""))
        disposition
       default)))
@@ -1047,7 +1184,7 @@ See `dnd-protocol-alist' for more information.  When nil, behave
 as in other buffers."
   :type '(choice (repeat (cons (regexp) (function)))
                 (const :tag "Behave as in other buffers" nil))
-  :version "23.0" ;; No Gnus
+  :version "22.1" ;; Gnus 5.10.9
   :group 'message)
 
 (defcustom mml-dnd-attach-options nil
@@ -1065,7 +1202,7 @@ to specify options."
                (const type)
                (const description)
                (const disposition))))
-  :version "23.0" ;; No Gnus
+  :version "22.1" ;; Gnus 5.10.9
   :group 'message)
 
 (defun mml-attach-file (file &optional type description disposition)
@@ -1073,20 +1210,25 @@ to specify options."
 The file is not inserted or encoded until you send the message with
 `\\[message-send-and-exit]' or `\\[message-send]'.
 
-FILE is the name of the file to attach.  TYPE is its content-type, a
-string of the form \"type/subtype\".  DESCRIPTION is a one-line
-description of the attachment."
+FILE is the name of the file to attach.  TYPE is its
+content-type, a string of the form \"type/subtype\".  DESCRIPTION
+is a one-line description of the attachment.  The DISPOSITION
+specifies how the attachment is intended to be displayed.  It can
+be either \"inline\" (displayed automatically within the message
+body) or \"attachment\" (separate from the body)."
   (interactive
    (let* ((file (mml-minibuffer-read-file "Attach file: "))
          (type (mml-minibuffer-read-type file))
          (description (mml-minibuffer-read-description))
-         (disposition (mml-minibuffer-read-disposition type)))
+         (disposition (mml-minibuffer-read-disposition type nil file)))
      (list file type description disposition)))
-  (mml-insert-empty-tag 'part
-                       'type type
-                       'filename file
-                       'disposition (or disposition "attachment")
-                       'description description))
+  (save-excursion
+    (unless (message-in-body-p) (goto-char (point-max)))
+    (mml-insert-empty-tag 'part
+                         'type type
+                         'filename file
+                         'disposition (or disposition "attachment")
+                         'description description)))
 
 (defun mml-dnd-attach-file (uri action)
   "Attach a drag and drop file.
@@ -1109,7 +1251,7 @@ Ask for type, description or disposition according to
        (when (memq 'description mml-dnd-attach-options)
          (setq description (mml-minibuffer-read-description)))
        (when (memq 'disposition mml-dnd-attach-options)
-         (setq disposition (mml-minibuffer-read-disposition type)))
+         (setq disposition (mml-minibuffer-read-disposition type nil file)))
        (mml-attach-file file type description disposition)))))
 
 (defun mml-attach-buffer (buffer &optional type description)
@@ -1120,8 +1262,11 @@ See `mml-attach-file' for details of operation."
          (type (mml-minibuffer-read-type buffer "text/plain"))
          (description (mml-minibuffer-read-description)))
      (list buffer type description)))
-  (mml-insert-empty-tag 'part 'type type 'buffer buffer
-                       'disposition "attachment" 'description description))
+  (save-excursion
+    (unless (message-in-body-p) (goto-char (point-max)))
+    (mml-insert-empty-tag 'part 'type type 'buffer buffer
+                         'disposition "attachment"
+                         'description description)))
 
 (defun mml-attach-external (file &optional type description)
   "Attach an external file into the buffer.
@@ -1132,8 +1277,10 @@ TYPE is the MIME type to use."
          (type (mml-minibuffer-read-type file))
          (description (mml-minibuffer-read-description)))
      (list file type description)))
-  (mml-insert-empty-tag 'external 'type type 'name file
-                       'disposition "attachment" 'description description))
+  (save-excursion
+    (unless (message-in-body-p) (goto-char (point-max)))
+    (mml-insert-empty-tag 'external 'type type 'name file
+                         'disposition "attachment" 'description description)))
 
 (defun mml-insert-multipart (&optional type)
   (interactive (list (completing-read "Multipart type (default mixed): "
@@ -1165,7 +1312,12 @@ Should be adopted if code in `message-send-mail' is changed."
 
 (defun mml-preview (&optional raw)
   "Display current buffer with Gnus, in a new buffer.
-If RAW, don't highlight the article."
+If RAW, display a raw encoded MIME message.
+
+The window layout for the preview buffer is controled by the variables
+`special-display-buffer-names', `special-display-regexps', or
+`gnus-buffer-configuration' (the first match made will be used),
+or the `pop-to-buffer' function."
   (interactive "P")
   (setq mml-preview-buffer (generate-new-buffer
                            (concat (if raw "*Raw MIME preview of "
@@ -1233,8 +1385,11 @@ If RAW, don't highlight the article."
                     (lambda (event)
                       (interactive "@e")
                       (widget-button-press (widget-event-point event) event)))
+      ;; FIXME: Buffer is in article mode, but most tool bar commands won't
+      ;; work.  Maybe only keep the following icons: search, print, quit
       (goto-char (point-min))))
-  (if (and (boundp 'gnus-buffer-configuration)
+  (if (and (not (mm-special-display-p (buffer-name mml-preview-buffer)))
+          (boundp 'gnus-buffer-configuration)
           (assq 'mml-preview gnus-buffer-configuration))
       (let ((gnus-message-buffer (current-buffer)))
        (gnus-configure-windows 'mml-preview))