(mml-generate-mime): Don't bug out if you don't have libxml.
[gnus] / lisp / mml.el
index 9e97e4a..168fe49 100644 (file)
@@ -1,25 +1,22 @@
 ;;; mml.el --- A package for parsing and validating MML documents
 
-;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005 Free Software Foundation, Inc.
+;; Copyright (C) 1998-2014 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 (require 'mm-decode)
 (require 'mml-sec)
 (eval-when-compile (require 'cl))
-
-(eval-and-compile
-  (autoload 'message-make-message-id "message")
-  (autoload 'gnus-setup-posting-charset "gnus-msg")
-  (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 'dnd-get-local-file-name "dnd"))
+  (when (featurep 'xemacs)
+    (require 'easy-mmode))) ; for `define-minor-mode'
+
+(autoload 'message-make-message-id "message")
+(declare-function gnus-setup-posting-charset "gnus-msg" (group))
+(autoload 'gnus-make-local-hook "gnus-util")
+(autoload 'gnus-completing-read "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")
+(autoload 'dnd-get-local-file-name "dnd")
+
+(autoload 'message-options-set    "message")
+(autoload 'message-narrow-to-head "message")
+(autoload 'message-in-body-p      "message")
+(autoload 'message-mail-p         "message")
 
 (defvar gnus-article-mime-handles)
 (defvar gnus-mouse-2)
@@ -54,6 +57,8 @@
 (defvar message-posting-charset)
 (defvar message-required-mail-headers)
 (defvar message-required-news-headers)
+(defvar dnd-protocol-alist)
+(defvar mml-dnd-protocol-alist)
 
 (defcustom mml-content-type-parameters
   '(name access-type expiration size permission format)
@@ -71,10 +76,58 @@ These parameters are generated in Content-Disposition header if exists."
   :type '(repeat (symbol :tag "Parameter"))
   :group 'message)
 
-(defcustom mml-insert-mime-headers-always nil
+(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.1" ;; 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 t
   "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."
-  :version "22.1"
+  :version "24.1"
+  :type 'boolean
+  :group 'message)
+
+(defcustom mml-enable-flowed t
+  "If non-nil, enable format=flowed usage when encoding a message.
+This is only performed when filling on text/plain with hard
+newlines in the text."
+  :version "24.1"
   :type 'boolean
   :group 'message)
 
@@ -179,7 +232,10 @@ part.  This is for the internal use, you should never modify the value.")
        (let* (secure-mode
               (taginfo (mml-read-tag))
               (keyfile (cdr (assq 'keyfile taginfo)))
-              (certfile (cdr (assq 'certfile taginfo)))
+              (certfiles (delq nil (mapcar (lambda (tag)
+                                             (if (eq (car-safe tag) 'certfile)
+                                                 (cdr tag)))
+                                           taginfo)))
               (recipients (cdr (assq 'recipients taginfo)))
               (sender (cdr (assq 'sender taginfo)))
               (location (cdr (assq 'tag-location taginfo)))
@@ -205,8 +261,10 @@ part.  This is for the internal use, you should never modify the value.")
                                 ,@tags
                                 ,(if keyfile "keyfile")
                                 ,keyfile
-                                ,(if certfile "certfile")
-                                ,certfile
+                                ,@(apply #'append
+                                         (mapcar (lambda (certfile)
+                                                   (list "certfile" certfile))
+                                                 certfiles))
                                 ,(if recipients "recipients")
                                 ,recipients
                                 ,(if sender "sender")
@@ -346,8 +404,8 @@ A message part needs to be split into %d charset parts.  Really send? "
       (skip-chars-forward "= \t\n")
       (setq val (buffer-substring-no-properties
                 (point) (progn (forward-sexp 1) (point))))
-      (when (string-match "^\"\\(.*\\)\"$" val)
-       (setq val (match-string 1 val)))
+      (when (string-match "\\`\"" val)
+       (setq val (read val))) ;; inverse of prin1 in mml-insert-tag
       (push (cons (intern elem) val) contents)
       (skip-chars-forward " \t\n"))
     (goto-char (match-end 0))
@@ -399,20 +457,82 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
 (defvar mml-boundary nil)
 (defvar mml-base-boundary "-=-=")
 (defvar mml-multipart-number 0)
+(defvar mml-inhibit-compute-boundary nil)
+
+(declare-function libxml-parse-html-region "xml.c"
+                 (start end &optional base-url))
 
-(defun mml-generate-mime ()
-  "Generate a MIME message based on the current MML document."
+(defun mml-generate-mime (&optional multipart-type)
+  "Generate a MIME message based on the current MML document.
+MULTIPART-TYPE defaults to \"mixed\", but can also
+be \"related\" or \"alternate\"."
   (let ((cont (mml-parse))
-       (mml-multipart-number mml-multipart-number))
+       (mml-multipart-number mml-multipart-number)
+       (options message-options))
     (if (not cont)
        nil
-      (with-temp-buffer
-       (if (and (consp (car cont))
-                (= (length cont) 1))
-           (mml-generate-mime-1 (car cont))
-         (mml-generate-mime-1 (nconc (list 'multipart '(type . "mixed"))
-                                     cont)))
-       (buffer-string)))))
+      (when (and (consp (car cont))
+                (= (length cont) 1)
+                (fboundp 'libxml-parse-html-region)
+                (equal (cdr (assq 'type (car cont))) "text/html"))
+       (setq cont (mml-expand-html-into-multipart-related (car cont))))
+      (prog1
+         (mm-with-multibyte-buffer
+           (setq message-options options)
+           (cond
+            ((and (consp (car cont))
+                  (= (length cont) 1))
+             (mml-generate-mime-1 (car cont)))
+            ((eq (car cont) 'multipart)
+             (mml-generate-mime-1 cont))
+            (t
+             (mml-generate-mime-1
+              (nconc (list 'multipart (cons 'type (or multipart-type "mixed")))
+                     cont))))
+           (setq options message-options)
+           (buffer-string))
+       (setq message-options options)))))
+
+(defun mml-expand-html-into-multipart-related (cont)
+  (let ((new-parts nil)
+       (cid 1))
+    (mm-with-multibyte-buffer
+      (insert (cdr (assq 'contents cont)))
+      (goto-char (point-min))
+      (with-syntax-table mml-syntax-table
+       (while (re-search-forward "<img\\b" nil t)
+         (goto-char (match-beginning 0))
+         (let* ((start (point))
+                (img (nth 2
+                          (nth 2
+                               (libxml-parse-html-region
+                                (point) (progn (forward-sexp) (point))))))
+                (end (point))
+                (parsed (url-generic-parse-url (cdr (assq 'src (cadr img))))))
+           (when (and (null (url-type parsed))
+                      (url-filename parsed)
+                      (file-exists-p (url-filename parsed)))
+             (goto-char start)
+             (when (search-forward (url-filename parsed) end t)
+               (let ((cid (format "fsf.%d" cid)))
+                 (replace-match (concat "cid:" cid) t t)
+                 (push (list cid (url-filename parsed)) new-parts))
+               (setq cid (1+ cid)))))))
+      ;; We have local images that we want to include.
+      (if (not new-parts)
+         (list cont)
+       (setcdr (assq 'contents cont) (buffer-string))
+       (setq cont
+             (nconc (list 'multipart (cons 'type "related"))
+                    (list cont)))
+       (dolist (new-part (nreverse new-parts))
+         (setq cont
+               (nconc cont
+                      (list `(part (type . "image/png")
+                                   (filename . ,(nth 1 new-part))
+                                   (id . ,(concat "<" (nth 0 new-part)
+                                                  ">")))))))
+       cont))))
 
 (defun mml-generate-mime-1 (cont)
   (let ((mm-use-ultra-safe-encoding
@@ -436,7 +556,12 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                 (setq charset nil
                       coding nil))
                (charset
-                (setq charset (intern (downcase charset)))))
+                ;; The value of `charset' might be a bogus alias that
+                ;; `mm-charset-synonym-alist' provides, like `utf8',
+                ;; so we prefer the MIME charset that Emacs knows for
+                ;; the coding system `coding'.
+                (setq charset (or (mm-coding-system-to-mime-charset coding)
+                                  (intern (downcase charset))))))
          (if (and (not raw)
                   (member (car (split-string type "/")) '("text" "message")))
              (progn
@@ -457,7 +582,7 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                      ;; Remove quotes from quoted tags.
                      (goto-char (point-min))
                      (while (re-search-forward
-                             "<#!+/?\\(part\\|multipart\\|external\\|mml\\)"
+                             "<#!+/?\\(part\\|multipart\\|external\\|mml\\|secure\\)"
                              nil t)
                        (delete-region (+ (match-beginning 0) 2)
                                       (+ (match-beginning 0) 3))))))
@@ -469,7 +594,11 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                          ;; `m-g-d-t' will be bound to "message/rfc822"
                          ;; when encoding an article to be forwarded.
                          (mml-generate-default-type "text/plain"))
-                     (mml-to-mime))
+                     (mml-to-mime)
+                     ;; Update handle so mml-compute-boundary can
+                     ;; detect collisions with the nested parts.
+                     (unless mml-inhibit-compute-boundary
+                       (setcdr (assoc 'contents cont) (buffer-string))))
                    (let ((mm-7bit-chars (concat mm-7bit-chars "\x1b")))
                      ;; ignore 0x1b, it is part of iso-2022-jp
                      (setq encoding (mm-body-7-or-8))))
@@ -483,7 +612,8 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                    ;; in the mml tag or it says "flowed" and there
                    ;; actually are hard newlines in the text.
                    (let (use-hard-newlines)
-                     (when (and (string= type "text/plain")
+                     (when (and mml-enable-flowed
+                                 (string= type "text/plain")
                                 (not (string= (cdr (assq 'sign cont)) "pgp"))
                                 (or (null (assq 'format cont))
                                     (string= (cdr (assq 'format cont))
@@ -496,7 +626,13 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                        ;; insert a "; format=flowed" string unless the
                        ;; user has already specified it.
                        (setq flowed (null (assq 'format cont)))))
-                   (setq charset (mm-encode-body charset))
+                   ;; Prefer `utf-8' for text/calendar parts.
+                   (if (or charset
+                           (not (string= type "text/calendar")))
+                       (setq charset (mm-encode-body charset))
+                     (let ((mm-coding-system-priorities
+                            (cons 'utf-8 mm-coding-system-priorities)))
+                       (setq charset (mm-encode-body))))
                    (setq encoding (mm-body-encoding
                                    charset (cdr (assq 'encoding cont))))))
                  (setq coded (buffer-string)))
@@ -506,9 +642,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))
@@ -518,13 +654,22 @@ 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)))))
-             (setq encoding (mm-encode-buffer type)
+               (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)
+                       (unless raw
+                         (setq charset (mm-encode-body charset))))
+                   (insert contents)))))
+             (if (setq encoding (cdr (assq 'encoding cont)))
+                 (setq encoding (intern (downcase encoding))))
+             (setq encoding (mm-encode-buffer type encoding)
                    coded (mm-string-as-multibyte (buffer-string))))
            (mml-insert-mime-headers cont type charset encoding nil)
-           (insert "\n")
-           (mm-with-unibyte-current-buffer
-             (insert coded)))))
+           (insert "\n" coded))))
        ((eq (car cont) 'external)
        (insert "Content-Type: message/external-body")
        (let ((parameters (mml-parameter-string
@@ -589,7 +734,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)))
@@ -623,34 +769,30 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
   "Return a unique boundary that does not exist in CONT."
   (let ((mml-boundary (funcall mml-boundary-function
                               (incf mml-multipart-number))))
-    ;; This function tries again and again until it has found
-    ;; a unique boundary.
-    (while (not (catch 'not-unique
-                 (mml-compute-boundary-1 cont))))
+    (unless mml-inhibit-compute-boundary
+      ;; This function tries again and again until it has found
+      ;; a unique boundary.
+      (while (not (catch 'not-unique
+                   (mml-compute-boundary-1 cont)))))
     mml-boundary))
 
 (defun mml-compute-boundary-1 (cont)
-  (let (filename)
-    (cond
-     ((eq (car cont) 'part)
-      (with-temp-buffer
-       (cond
-        ((cdr (assq 'buffer cont))
-         (insert-buffer-substring (cdr (assq 'buffer cont))))
-        ((and (setq filename (cdr (assq 'filename cont)))
-              (not (equal (cdr (assq 'nofile cont)) "yes")))
-         (mm-insert-file-contents filename nil nil nil nil t))
-        (t
-         (insert (cdr (assq 'contents cont)))))
-       (goto-char (point-min))
-       (when (re-search-forward (concat "^--" (regexp-quote mml-boundary))
-                                nil t)
-         (setq mml-boundary (funcall mml-boundary-function
-                                     (incf mml-multipart-number)))
-         (throw 'not-unique nil))))
-     ((eq (car cont) 'multipart)
-      (mapc 'mml-compute-boundary-1 (cddr cont))))
-    t))
+  (cond
+   ((member (car cont) '(part mml))
+    (mm-with-multibyte-buffer
+      (let ((mml-inhibit-compute-boundary t)
+           (mml-multipart-number 0)
+           mml-sign-alist mml-encrypt-alist)
+       (mml-generate-mime-1 cont))
+      (goto-char (point-min))
+      (when (re-search-forward (concat "^--" (regexp-quote mml-boundary))
+                              nil t)
+       (setq mml-boundary (funcall mml-boundary-function
+                                   (incf mml-multipart-number)))
+       (throw 'not-unique nil))))
+   ((eq (car cont) 'multipart)
+    (mapc 'mml-compute-boundary-1 (cddr cont))))
+  t)
 
 (defun mml-make-boundary (number)
   (concat (make-string (% number 60) ?=)
@@ -659,6 +801,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
@@ -674,10 +840,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))
@@ -689,7 +855,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))
@@ -697,8 +865,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 "")
@@ -725,9 +896,9 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
         (mail-header-encode-parameter
          (symbol-name type) value))))))
 
-(eval-when-compile
-  (defvar ange-ftp-name-format)
-  (defvar efs-path-regexp))
+(defvar ange-ftp-name-format)
+(defvar efs-path-regexp)
+
 (defun mml-parse-file-name (path)
   (if (if (boundp 'efs-path-regexp)
          (string-match efs-path-regexp path)
@@ -749,6 +920,10 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
 ;;; Transforming MIME to MML
 ;;;
 
+;; message-narrow-to-head autoloads message.
+(declare-function message-remove-header "message"
+                  (header &optional is-regexp first reverse))
+
 (defun mime-to-mml (&optional handles)
   "Translate the current buffer (which should be a message) into MML.
 If HANDLES is non-nil, use it instead reparsing the buffer."
@@ -774,28 +949,43 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     (message-remove-header "Content-Disposition")
     (message-remove-header "Content-Transfer-Encoding")))
 
+(autoload 'message-encode-message-body "message")
+(declare-function message-narrow-to-headers-or-head "message" ())
+
+;;;###autoload
 (defun mml-to-mime ()
   "Translate the current buffer from MML to MIME."
-  (message-encode-message-body)
+  ;; `message-encode-message-body' will insert an encoded Content-Description
+  ;; header in the message header if the body contains a single part
+  ;; that is specified by a user with a MML tag containing a description
+  ;; token.  So, we encode the message header first to prevent the encoded
+  ;; Content-Description header from being encoded again.
   (save-restriction
     (message-narrow-to-headers-or-head)
     ;; Skip past any From_ headers.
     (while (looking-at "From ")
       (forward-line 1))
     (let ((mail-parse-charset message-default-charset))
-      (mail-encode-encoded-word-buffer))))
+      (mail-encode-encoded-word-buffer)))
+  (message-encode-message-body))
 
 (defun mml-insert-mime (handle &optional no-markup)
   (let (textp buffer mmlp)
     ;; Determine type and stuff.
     (unless (stringp (car handle))
       (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)
-         (if (setq mmlp (equal (mm-handle-media-type handle)
-                               "message/rfc822"))
-             (mime-to-mml)))))
+       (with-current-buffer (setq buffer (mml-generate-new-buffer " *mml*"))
+         (if (eq (mail-content-type-get (mm-handle-type handle) 'charset)
+                 'gnus-decoded)
+             ;; A part that mm-uu dissected from a non-MIME message
+             ;; because of `gnus-article-emulate-mime'.
+             (progn
+               (mm-enable-multibyte)
+               (insert-buffer-substring (mm-handle-buffer handle)))
+           (mm-insert-part handle 'no-cache)
+           (if (setq mmlp (equal (mm-handle-media-type handle)
+                                 "message/rfc822"))
+               (mime-to-mml))))))
     (if mmlp
        (mml-insert-mml-markup handle nil t t)
       (unless (and no-markup
@@ -807,7 +997,7 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
       (goto-char (point-max))
       (insert "<#/mml>\n"))
      ((stringp (car handle))
-      (mapcar 'mml-insert-mime (cdr handle))
+      (mapc 'mml-insert-mime (cdr handle))
       (insert "<#/multipart>\n"))
      (textp
       (let ((charset (mail-content-type-get
@@ -851,14 +1041,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
@@ -915,10 +1111,34 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
         '(:help "Attach a file at point"))]
     ["Attach Buffer..." mml-attach-buffer
      ,@(if (featurep 'xemacs) '(t)
-        '(:help "Attach a buffer to the outgoing MIME message"))]
+        '(:help "Attach a buffer to the outgoing message"))]
     ["Attach External..." mml-attach-external
      ,@(if (featurep 'xemacs) '(t)
-        '(:help "Attach reference to file"))]
+        '(:help "Attach reference to an external file"))]
+    ;; FIXME: Is it possible to do this without using
+    ;; `gnus-gcc-externalize-attachments'?
+    ["Externalize Attachments"
+     (lambda ()
+       (interactive)
+       (if (not (and (boundp 'gnus-gcc-externalize-attachments)
+                    (memq gnus-gcc-externalize-attachments
+                          '(all t nil))))
+          ;; Stupid workaround for XEmacs not honoring :visible.
+          (message "Can't handle this value of `gnus-gcc-externalize-attachments'")
+        (setq gnus-gcc-externalize-attachments
+              (not gnus-gcc-externalize-attachments))
+        (message "gnus-gcc-externalize-attachments is `%s'."
+                 gnus-gcc-externalize-attachments)))
+     ;; XEmacs barfs on :visible.
+     ,@(if (featurep 'xemacs) nil
+        '(:visible (and (boundp 'gnus-gcc-externalize-attachments)
+                        (memq gnus-gcc-externalize-attachments
+                              '(all t nil)))))
+     :style toggle
+     :selected gnus-gcc-externalize-attachments
+     ,@(if (featurep 'xemacs) nil
+        '(:help "Save attachments as external parts in Gcc copies"))]
+    "----"
     ;;
     ("Change Security Method"
      ["PGP/MIME"
@@ -946,6 +1166,10 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     ["Encrypt/Sign off" mml-unsecure-message
      ,@(if (featurep 'xemacs) '(t)
         '(:help "Don't Encrypt/Sign Message"))]
+    ;; Do we have separate encrypt and encrypt/sign commands for parts?
+    ["Sign Part" mml-secure-sign t]
+    ["Encrypt Part" mml-secure-encrypt t]
+    "----"
     ;; Maybe we could remove these, because people who write MML most probably
     ;; don't use the menu:
     ["Insert Part..." mml-insert-part
@@ -953,9 +1177,6 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     ["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 in region" mml-quote-region
      :active (message-mark-active-p)
@@ -967,39 +1188,49 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     ["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))
+    ["PGG manual" (lambda () (interactive) (message-info mml2015-use))
+     ;; XEmacs barfs on :visible.
+     ,@(if (featurep 'xemacs) nil
+        '(:visible (and (boundp 'mml2015-use) (equal mml2015-use 'pgg))))
      ,@(if (featurep 'xemacs) '(t)
-        '(:help "Display the PGG manual"))]))
-
-(defvar mml-mode nil
-  "Minor mode for editing MML.")
+        '(:help "Display the PGG manual"))]
+    ["EasyPG manual" (lambda () (interactive) (require 'mml2015) (message-info mml2015-use))
+     ;; XEmacs barfs on :visible.
+     ,@(if (featurep 'xemacs) nil
+        '(:visible (and (boundp 'mml2015-use) (equal mml2015-use 'epg))))
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Display the EasyPG manual"))]))
 
-(defun mml-mode (&optional arg)
+(define-minor-mode mml-mode
   "Minor mode for editing MML.
 MML is the MIME Meta Language, a minor mode for composing MIME articles.
 See Info node `(emacs-mime)Composing'.
 
 \\{mml-mode-map}"
-  (interactive "P")
-  (when (set (make-local-variable 'mml-mode)
-            (if (null arg) (not mml-mode)
-              (> (prefix-numeric-value arg) 0)))
-    (add-minor-mode 'mml-mode " MML" mml-mode-map)
+  :lighter " MML" :keymap mml-mode-map
+  (when mml-mode
     (easy-menu-add mml-menu mml-mode-map)
     (when (boundp 'dnd-protocol-alist)
       (set (make-local-variable 'dnd-protocol-alist)
-          (append mml-dnd-protocol-alist
-                  (symbol-value 'dnd-protocol-alist))))
-    (run-hooks 'mml-mode-hook)))
+          (append mml-dnd-protocol-alist dnd-protocol-alist)))))
 
 ;;;
 ;;; Helper functions for reading MIME stuff from the minibuffer and
 ;;; 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.1" ;; 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)
@@ -1010,7 +1241,11 @@ See Info node `(emacs-mime)Composing'.
       (error "Permission denied: %s" file))
     file))
 
+(declare-function mailcap-parse-mimetypes "mailcap" (&optional path force))
+(declare-function mailcap-mime-types "mailcap" ())
+
 (defun mml-minibuffer-read-type (name &optional default)
+  (require 'mailcap)
   (mailcap-parse-mimetypes)
   (let* ((default (or default
                      (mm-default-file-encoding name)
@@ -1018,29 +1253,27 @@ See Info node `(emacs-mime)Composing'.
                      ;; looks like, and offer text/plain if it looks
                      ;; like text/plain.
                      "application/octet-stream"))
-        (string (completing-read
-                 (format "Content type (default %s): " default)
-                 (mapcar 'list (mailcap-mime-types)))))
+        (string (gnus-completing-read
+                 "Content type"
+                 (mailcap-mime-types)
+                  nil nil nil default)))
     (if (not (equal string ""))
        string
       default)))
 
-(defun mml-minibuffer-read-description ()
-  (let ((description (read-string "One line description: ")))
+(defun mml-minibuffer-read-description (&optional default)
+  (let ((description (read-string "One line description: " default)))
     (when (string-match "\\`[ \t]*\\'" description)
       (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")))
-  (let ((disposition (completing-read
-                      (format "Disposition (default %s): " default)
-                      '(("attachment") ("inline") (""))
-                      nil t nil nil default)))
+(defun mml-minibuffer-read-disposition (type &optional default filename)
+  (unless default
+    (setq default (mml-content-disposition type filename)))
+  (let ((disposition (gnus-completing-read
+                     "Disposition"
+                     '("attachment" "inline")
+                     t nil nil default)))
     (if (not (equal disposition ""))
        disposition
       default)))
@@ -1056,7 +1289,7 @@ See Info node `(emacs-mime)Composing'.
       (goto-char (point-min))
       ;; Quote parts.
       (while (re-search-forward
-             "<#!*/?\\(multipart\\|part\\|external\\|mml\\)" nil t)
+             "<#!*/?\\(multipart\\|part\\|external\\|mml\\|secure\\)" nil t)
        ;; Insert ! after the #.
        (goto-char (+ (match-beginning 0) 2))
        (insert "!")))))
@@ -1096,7 +1329,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
@@ -1107,20 +1340,22 @@ If it is a list, valid members are `type', `description' and
 don't ask for options.  If it is t, ask the user whether or not
 to specify options."
   :type '(choice
-         (const :tag "Non" nil)
+         (const :tag "None" nil)
          (const :tag "Query" t)
          (list :value (type description disposition)
           (set :inline t
                (const type)
                (const description)
                (const disposition))))
-  :version "23.0" ;; No Gnus
+  :version "22.1" ;; Gnus 5.10.9
   :group 'message)
 
+;;;###autoload
 (defun mml-attach-file (file &optional type description disposition)
   "Attach a file to the outgoing MIME message.
 The file is not inserted or encoded until you send the message with
-`\\[message-send-and-exit]' or `\\[message-send]'.
+`\\[message-send-and-exit]' or `\\[message-send]' in Message mode,
+or `\\[mail-send-and-exit]' or `\\[mail-send]' in Mail mode.
 
 FILE is the name of the file to attach.  TYPE is its
 content-type, a string of the form \"type/subtype\".  DESCRIPTION
@@ -1132,15 +1367,27 @@ body) or \"attachment\" (separate from the body)."
    (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)))
-  (save-excursion
-    (unless (message-in-body-p) (goto-char (point-max)))
+  ;; If in the message header, attach at the end and leave point unchanged.
+  (let ((head (unless (message-in-body-p) (point))))
+    (if head (goto-char (point-max)))
     (mml-insert-empty-tag 'part
                          'type type
-                         'filename file
+                         ;; icicles redefines read-file-name and returns a
+                         ;; string w/ text properties :-/
+                         'filename (mm-substring-no-properties file)
                          'disposition (or disposition "attachment")
-                         'description description)))
+                         'description description)
+    ;; When using Mail mode, make sure it does the mime encoding
+    ;; when you send the message.
+    (or (eq mail-user-agent 'message-user-agent)
+       (setq mail-encode-mml t))
+    (when head
+      (unless (pos-visible-in-window-p)
+       (message "The file \"%s\" has been attached at the end of the message"
+                (file-name-nondirectory file)))
+      (goto-char head))))
 
 (defun mml-dnd-attach-file (uri action)
   "Attach a drag and drop file.
@@ -1163,22 +1410,35 @@ 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)
+(defun mml-attach-buffer (buffer &optional type description disposition)
   "Attach a buffer to the outgoing MIME message.
-See `mml-attach-file' for details of operation."
+BUFFER is the name of the buffer to attach.  See
+`mml-attach-file' for details of operation."
   (interactive
    (let* ((buffer (read-buffer "Attach buffer: "))
          (type (mml-minibuffer-read-type buffer "text/plain"))
-         (description (mml-minibuffer-read-description)))
-     (list buffer type description)))
-  (save-excursion
-    (unless (message-in-body-p) (goto-char (point-max)))
+         (description (mml-minibuffer-read-description))
+         (disposition (mml-minibuffer-read-disposition type nil)))
+     (list buffer type description disposition)))
+  ;; If in the message header, attach at the end and leave point unchanged.
+  (let ((head (unless (message-in-body-p) (point))))
+    (if head (goto-char (point-max)))
     (mml-insert-empty-tag 'part 'type type 'buffer buffer
-                         'disposition "attachment"
-                         'description description)))
+                         'disposition disposition
+                         'description description)
+    ;; When using Mail mode, make sure it does the mime encoding
+    ;; when you send the message.
+    (or (eq mail-user-agent 'message-user-agent)
+       (setq mail-encode-mml t))
+    (when head
+      (unless (pos-visible-in-window-p)
+       (message
+        "The buffer \"%s\" has been attached at the end of the message"
+        buffer))
+      (goto-char head))))
 
 (defun mml-attach-external (file &optional type description)
   "Attach an external file into the buffer.
@@ -1189,26 +1449,54 @@ TYPE is the MIME type to use."
          (type (mml-minibuffer-read-type file))
          (description (mml-minibuffer-read-description)))
      (list file type description)))
-  (save-excursion
-    (unless (message-in-body-p) (goto-char (point-max)))
+  ;; If in the message header, attach at the end and leave point unchanged.
+  (let ((head (unless (message-in-body-p) (point))))
+    (if head (goto-char (point-max)))
     (mml-insert-empty-tag 'external 'type type 'name file
-                         'disposition "attachment" 'description description)))
+                         'disposition "attachment" 'description description)
+    ;; When using Mail mode, make sure it does the mime encoding
+    ;; when you send the message.
+    (or (eq mail-user-agent 'message-user-agent)
+       (setq mail-encode-mml t))
+    (when head
+      (unless (pos-visible-in-window-p)
+       (message "The file \"%s\" has been attached at the end of the message"
+                (file-name-nondirectory file)))
+      (goto-char head))))
 
 (defun mml-insert-multipart (&optional type)
-  (interactive (list (completing-read "Multipart type (default mixed): "
-                                     '(("mixed") ("alternative") ("digest") ("parallel")
-                                       ("signed") ("encrypted"))
-                                     nil nil "mixed")))
+  (interactive (if (message-in-body-p)
+                  (list (gnus-completing-read "Multipart type"
+                                               '("mixed" "alternative"
+                                                 "digest" "parallel"
+                                                 "signed" "encrypted")
+                                               nil "mixed"))
+                (error "Use this command in the message body")))
   (or type
       (setq type "mixed"))
   (mml-insert-empty-tag "multipart" 'type type)
+  ;; When using Mail mode, make sure it does the mime encoding
+  ;; when you send the message.
+  (or (eq mail-user-agent 'message-user-agent)
+      (setq mail-encode-mml t))
   (forward-line -1))
 
 (defun mml-insert-part (&optional type)
-  (interactive
-   (list (mml-minibuffer-read-type "")))
+  (interactive (if (message-in-body-p)
+                  (list (mml-minibuffer-read-type ""))
+                (error "Use this command in the message body")))
+  ;; When using Mail mode, make sure it does the mime encoding
+  ;; when you send the message.
+  (or (eq mail-user-agent 'message-user-agent)
+      (setq mail-encode-mml t))
   (mml-insert-tag 'part 'type type 'disposition "inline")
-  (forward-line -1))
+  (save-excursion
+    (mml-insert-tag '/part)))
+
+(declare-function message-subscribed-p "message" ())
+(declare-function message-make-mail-followup-to "message"
+                  (&optional only-show-subscribed))
+(declare-function message-position-on-field "message" (header &rest afters))
 
 (defun mml-preview-insert-mail-followup-to ()
   "Insert a Mail-Followup-To header before previewing an article.
@@ -1222,11 +1510,22 @@ Should be adopted if code in `message-send-mail' is changed."
 
 (defvar mml-preview-buffer nil)
 
+(autoload 'gnus-make-hashtable "gnus-util")
+(autoload 'widget-button-press "wid-edit" nil t)
+(declare-function widget-event-point "wid-edit" (event))
+;; If gnus-buffer-configuration is bound this is loaded.
+(declare-function gnus-configure-windows "gnus-win" (setting &optional force))
+;; Called after message-mail-p, which autoloads message.
+(declare-function message-news-p                "message" ())
+(declare-function message-options-set-recipient "message" ())
+(declare-function message-generate-headers      "message" (headers))
+(declare-function message-sort-headers          "message" ())
+
 (defun mml-preview (&optional raw)
   "Display current buffer with Gnus, in a new buffer.
 If RAW, display a raw encoded MIME message.
 
-The window layout for the preview buffer is controled by the variables
+The window layout for the preview buffer is controlled 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."
@@ -1234,8 +1533,10 @@ or the `pop-to-buffer' function."
   (setq mml-preview-buffer (generate-new-buffer
                            (concat (if raw "*Raw MIME preview of "
                                      "*MIME preview of ") (buffer-name))))
+  (require 'gnus-msg)                ; for gnus-setup-posting-charset
   (save-excursion
     (let* ((buf (current-buffer))
+          (article-editing (eq major-mode 'gnus-article-edit-mode))
           (message-options message-options)
           (message-this-is-mail (message-mail-p))
           (message-this-is-news (message-news-p))
@@ -1255,15 +1556,19 @@ or the `pop-to-buffer' function."
       (mml-preview-insert-mail-followup-to)
       (let ((message-deletable-headers (if (message-news-p)
                                           nil
-                                        message-deletable-headers)))
+                                        message-deletable-headers))
+           (mail-header-separator (if article-editing
+                                      ""
+                                    mail-header-separator)))
        (message-generate-headers
         (copy-sequence (if (message-news-p)
                            message-required-news-headers
-                         message-required-mail-headers))))
-      (if (re-search-forward
-          (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
-         (replace-match "\n"))
-      (let ((mail-header-separator ""));; mail-header-separator is removed.
+                         message-required-mail-headers)))
+       (unless article-editing
+         (if (re-search-forward
+              (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
+             (replace-match "\n"))
+         (setq mail-header-separator ""))
        (message-sort-headers)
        (mml-to-mime))
       (if raw
@@ -1274,7 +1579,8 @@ or the `pop-to-buffer' function."
              (mm-disable-multibyte)
              (insert s)))
        (let ((gnus-newsgroup-charset (car message-posting-charset))
-             gnus-article-prepare-hook gnus-original-article-buffer)
+             gnus-article-prepare-hook gnus-original-article-buffer
+             gnus-displaying-mime)
          (run-hooks 'gnus-article-decode-hook)
          (let ((gnus-newsgroup-name "dummy")
                (gnus-newsrc-hashtb (or gnus-newsrc-hashtb
@@ -1297,6 +1603,8 @@ or the `pop-to-buffer' function."
                     (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 (not (mm-special-display-p (buffer-name mml-preview-buffer)))
           (boundp 'gnus-buffer-configuration)
@@ -1349,5 +1657,4 @@ or the `pop-to-buffer' function."
 
 (provide 'mml)
 
-;;; arch-tag: 583c96cf-1ffe-451b-a5e5-4733ae9ddd12
 ;;; mml.el ends here