* nnmail.el (nnmail-cache-insert): make sure that the
[gnus] / lisp / mml.el
index 1554513..e2b3d00 100644 (file)
@@ -1,5 +1,6 @@
 ;;; mml.el --- A package for parsing and validating MML documents
-;; Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
+;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; This file is part of GNU Emacs.
@@ -34,6 +35,7 @@
   (autoload 'message-make-message-id "message")
   (autoload 'gnus-setup-posting-charset "gnus-msg")
   (autoload 'gnus-add-minor-mode "gnus-ems")
+  (autoload 'gnus-make-local-hook "gnus-util")
   (autoload 'message-fetch-field "message")
   (autoload 'fill-flowed-encode "flow-fill")
   (autoload 'message-posting-charset "message"))
@@ -52,6 +54,12 @@ These parameters are generated in Content-Disposition header if exists."
   :type '(repeat (symbol :tag "Parameter"))
   :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."
+  :type 'boolean
+  :group 'message)
+
 (defvar mml-tweak-type-alist nil
   "A list of (TYPE . FUNCTION) for tweaking MML parts.
 TYPE is a string containing a regexp to match the MIME type.  FUNCTION
@@ -68,7 +76,7 @@ handle to tweak the part.")
 (defvar mml-tweak-sexp-alist
   '((mml-externalize-attachments . mml-tweak-externalize-attachments))
   "A list of (SEXP . FUNCTION) for tweaking MML parts.
-SEXP is a s-expression. If the evaluation of SEXP is non-nil, FUNCTION
+SEXP is an s-expression.  If the evaluation of SEXP is non-nil, FUNCTION
 is called.  FUNCTION is a Lisp function which is called with the MML
 handle to tweak the part.")
 
@@ -151,6 +159,7 @@ one charsets.")
        (let* (secure-mode
               (taginfo (mml-read-tag))
               (recipients (cdr (assq 'recipients taginfo)))
+              (sender (cdr (assq 'sender taginfo)))
               (location (cdr (assq 'tag-location taginfo)))
               (mode (cdr (assq 'mode taginfo)))
               (method (cdr (assq 'method taginfo)))
@@ -173,8 +182,10 @@ one charsets.")
                 (setq tags (list "sign" method "encrypt" method))))
          (eval `(mml-insert-tag ,secure-mode
                                 ,@tags
-                                ,(if recipients 'recipients)
-                                ,recipients))
+                                ,(if recipients "recipients")
+                                ,recipients
+                                ,(if sender "sender")
+                                ,sender))
          ;; restart the parse
          (goto-char location)))
        ((looking-at "<#multipart")
@@ -205,12 +216,12 @@ one charsets.")
          (if (or (memq 'unknown-encoding mml-confirmation-set)
                  (message-options-get 'unknown-encoding)
                  (and (y-or-n-p "\
-Message contains characters with unknown encoding.  Really send?")
+Message contains characters with unknown encoding.  Really send? ")
                       (message-options-set 'unknown-encoding t)))
              (if (setq use-ascii
                        (or (memq 'use-ascii mml-confirmation-set)
                            (message-options-get 'use-ascii)
-                           (and (y-or-n-p "Use ASCII as charset?")
+                           (and (y-or-n-p "Use ASCII as charset? ")
                                 (message-options-set 'use-ascii t))))
                  (setq charsets (delq nil charsets))
                (setq warn nil))
@@ -334,7 +345,7 @@ A message part needs to be split into %d charset parts.  Really send? "
   "Return the buffer up till the next part, multipart or closing part or multipart.
 If MML is non-nil, return the buffer up till the correspondent mml tag."
   (let ((beg (point)) (count 1))
-   ;; If the tag ended at the end of the line, we go to the next line.
+    ;; If the tag ended at the end of the line, we go to the next line.
     (when (looking-at "[ \t]*\n")
       (forward-line 1))
     (if mml
@@ -431,26 +442,26 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
                      ;; ignore 0x1b, it is part of iso-2022-jp
                      (setq encoding (mm-body-7-or-8))))
                   (t
+                   ;; Only perform format=flowed filling on text/plain
+                   ;; parts where there either isn't a format parameter
+                   ;; 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")
+                                (or (null (assq 'format cont))
+                                    (string= (cdr (assq 'format cont))
+                                             "flowed"))
+                                (setq use-hard-newlines
+                                      (text-property-any
+                                       (point-min) (point-max) 'hard 't)))
+                       (fill-flowed-encode)
+                       ;; Indicate that `mml-insert-mime-headers' should
+                       ;; insert a "; format=flowed" string unless the
+                       ;; user has already specified it.
+                       (setq flowed (null (assq 'format cont)))))
                    (setq charset (mm-encode-body charset))
                    (setq encoding (mm-body-encoding
                                    charset (cdr (assq 'encoding cont))))))
-                 ;; Only perform format=flowed filling on text/plain
-                 ;; parts where there either isn't a format parameter
-                 ;; 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")
-                              (or (null (assq 'format cont))
-                                  (string= (cdr (assq 'format cont))
-                                           "flowed"))
-                              (setq use-hard-newlines
-                                    (text-property-any
-                                     (point-min) (point-max) 'hard 't)))
-                     (fill-flowed-encode)
-                     ;; Indicate that `mml-insert-mime-headers' should
-                     ;; insert a "; format=flowed" string unless the
-                     ;; user has already specified it.
-                     (setq flowed (null (assq 'format cont)))))
                  (setq coded (buffer-string)))
                (mml-insert-mime-headers cont type charset encoding flowed)
                (insert "\n")
@@ -530,22 +541,30 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
              (insert "\n--" mml-boundary "--\n")))))
        (t
        (error "Invalid element: %S" cont)))
-      (let ((item (assoc (cdr (assq 'sign cont)) mml-sign-alist))
-           sender recipients)
-       (when item
-         (if (setq sender (cdr (assq 'sender cont)))
-             (message-options-set 'message-sender sender))
-         (if (setq recipients (cdr (assq 'recipients cont)))
-             (message-options-set 'message-recipients recipients))
-         (funcall (nth 1 item) cont)))
-      (let ((item (assoc (cdr (assq 'encrypt cont)) mml-encrypt-alist))
+      ;; handle sign & encrypt tags in a semi-smart way.
+      (let ((sign-item (assoc (cdr (assq 'sign cont)) mml-sign-alist))
+           (encrypt-item (assoc (cdr (assq 'encrypt cont))
+                                mml-encrypt-alist))
            sender recipients)
-       (when item
-         (if (setq sender (cdr (assq 'sender cont)))
-             (message-options-set 'message-sender sender))
+       (when (or sign-item encrypt-item)
+         (when (setq sender (cdr (assq 'sender cont)))
+           (message-options-set 'mml-sender sender)
+           (message-options-set 'message-sender sender))
          (if (setq recipients (cdr (assq 'recipients cont)))
              (message-options-set 'message-recipients recipients))
-         (funcall (nth 1 item) cont))))))
+         (let ((style (mml-signencrypt-style (first (or sign-item encrypt-item)))))
+           ;; check if: we're both signing & encrypting, both methods
+           ;; are the same (why would they be different?!), and that
+           ;; the signencrypt style allows for combined operation.
+           (if (and sign-item encrypt-item (equal (first sign-item)
+                                                  (first encrypt-item))
+                    (equal style 'combined))
+               (funcall (nth 1 encrypt-item) cont t)
+             ;; otherwise, revert to the old behavior.
+             (when sign-item
+               (funcall (nth 1 sign-item) cont))
+             (when encrypt-item
+               (funcall (nth 1 encrypt-item) cont)))))))))
 
 (defun mml-compute-boundary (cont)
   "Return a unique boundary that does not exist in CONT."
@@ -595,10 +614,11 @@ If MML is non-nil, return the buffer up till the correspondent mml tag."
     (when (or charset
              parameters
              flowed
-             (not (equal type mml-generate-default-type)))
+             (not (equal type mml-generate-default-type))
+             mml-insert-mime-headers-always)
       (when (consp charset)
        (error
-        "Can't encode a part with several charsets."))
+        "Can't encode a part with several charsets"))
       (insert "Content-Type: " type)
       (when charset
        (insert "; " (mail-header-encode-parameter
@@ -727,7 +747,7 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
        (mml-insert-mml-markup handle buffer textp)))
     (cond
      (mmlp
-      (insert-buffer buffer)
+      (insert-buffer-substring buffer)
       (goto-char (point-max))
       (insert "<#/mml>\n"))
      ((stringp (car handle))
@@ -735,10 +755,12 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
       (insert "<#/multipart>\n"))
      (textp
       (let ((charset (mail-content-type-get
-                     (mm-handle-type handle) 'charset)))
+                     (mm-handle-type handle) 'charset))
+           (start (point)))
        (if (eq charset 'gnus-decoded)
            (mm-insert-part handle)
-         (insert (mm-decode-string (mm-get-part handle) charset))))
+         (insert (mm-decode-string (mm-get-part handle) charset)))
+       (mml-quote-region start (point)))
       (goto-char (point-max)))
      (t
       (insert "<#/part>\n")))))
@@ -783,14 +805,22 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
 (defvar mml-mode-map
   (let ((sign (make-sparse-keymap))
        (encrypt (make-sparse-keymap))
+       (signpart (make-sparse-keymap))
+       (encryptpart (make-sparse-keymap))
        (map (make-sparse-keymap))
        (main (make-sparse-keymap)))
     (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)
+    (define-key signpart "p" 'mml-secure-sign-pgpmime)
+    (define-key signpart "o" 'mml-secure-sign-pgp)
+    (define-key signpart "s" 'mml-secure-sign-smime)
     (define-key encrypt "p" 'mml-secure-message-encrypt-pgpmime)
     (define-key encrypt "o" 'mml-secure-message-encrypt-pgp)
     (define-key encrypt "s" 'mml-secure-message-encrypt-smime)
+    (define-key encryptpart "p" 'mml-secure-encrypt-pgpmime)
+    (define-key encryptpart "o" 'mml-secure-encrypt-pgp)
+    (define-key encryptpart "s" 'mml-secure-encrypt-smime)
     (define-key map "\C-n" 'mml-unsecure-message)
     (define-key map "f" 'mml-attach-file)
     (define-key map "b" 'mml-attach-buffer)
@@ -801,7 +831,9 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
     (define-key map "v" 'mml-validate)
     (define-key map "P" 'mml-preview)
     (define-key map "s" sign)
+    (define-key map "S" signpart)
     (define-key map "c" encrypt)
+    (define-key map "C" encryptpart)
     ;;(define-key map "n" 'mml-narrow-to-part)
     ;; `M-m' conflicts with `back-to-indentation'.
     ;; (define-key main "\M-m" map)
@@ -811,19 +843,26 @@ If HANDLES is non-nil, use it instead reparsing the buffer."
 (easy-menu-define
   mml-menu mml-mode-map ""
   `("Attachments"
-    ["Attach File" mml-attach-file
+    ["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]
+    ["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]
     ;;["Narrow" mml-narrow-to-part t]
     ["Quote MML" mml-quote-region t]
@@ -853,8 +892,9 @@ See Info node `(emacs-mime)Composing'.
 ;;;
 
 (defun mml-minibuffer-read-file (prompt)
-  (let ((file (read-file-name prompt nil nil t)))
-   ;; Prevent some common errors.  This is inspired by similar code in
+  (let* ((completion-ignored-extensions nil)
+        (file (read-file-name prompt nil nil t)))
+    ;; Prevent some common errors.  This is inspired by similar code in
     ;; VM.
     (when (file-directory-p file)
       (error "%s is a directory, cannot attach" file))
@@ -912,7 +952,9 @@ See Info node `(emacs-mime)Composing'.
       (when value
        ;; Quote VALUE if it contains suspicious characters.
        (when (string-match "[\"'\\~/*;() \t\n]" value)
-         (setq value (prin1-to-string value)))
+         (setq value (with-output-to-string
+                       (let (print-escape-nonascii)
+                         (prin1 value)))))
        (insert (format " %s=%s" key value)))))
   (insert ">\n"))
 
@@ -980,6 +1022,16 @@ TYPE is the MIME type to use."
   (mml-insert-tag 'part 'type type 'disposition "inline")
   (forward-line -1))
 
+(defun mml-preview-insert-mail-followup-to ()
+  "Insert a Mail-Followup-To header before previewing an article.
+Should be adopted if code in `message-send-mail' is changed."
+  (when (and (message-mail-p)
+            (message-subscribed-p)
+            (not (mail-fetch-field "mail-followup-to"))
+            (message-make-mail-followup-to))
+    (message-position-on-field "Mail-Followup-To" "X-Draft-From")
+    (insert (message-make-mail-followup-to))))
+
 (defun mml-preview (&optional raw)
   "Display current buffer with Gnus, in a new buffer.
 If RAW, don't highlight the article."
@@ -987,6 +1039,7 @@ If RAW, don't highlight the article."
   (save-excursion
     (let* ((buf (current-buffer))
           (message-options message-options)
+          (message-this-is-mail (message-mail-p))
           (message-this-is-news (message-news-p))
           (message-posting-charset (or (gnus-setup-posting-charset
                                         (save-restriction
@@ -998,7 +1051,15 @@ If RAW, don't highlight the article."
                         (concat (if raw "*Raw MIME preview of "
                                   "*MIME preview of ") (buffer-name))))
       (erase-buffer)
-      (insert-buffer buf)
+      (insert-buffer-substring buf)
+      (mml-preview-insert-mail-followup-to)
+      (let ((message-deletable-headers (if (message-news-p)
+                                          nil
+                                        message-deletable-headers)))
+       (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"))
@@ -1014,12 +1075,27 @@ If RAW, don't highlight the article."
        (let ((gnus-newsgroup-charset (car message-posting-charset))
              gnus-article-prepare-hook gnus-original-article-buffer)
          (run-hooks 'gnus-article-decode-hook)
-         (let ((gnus-newsgroup-name "dummy"))
+         (let ((gnus-newsgroup-name "dummy")
+               (gnus-newsrc-hashtb (or gnus-newsrc-hashtb
+                                       (gnus-make-hashtable 5))))
            (gnus-article-prepare-display))))
       ;; Disable article-mode-map.
       (use-local-map nil)
+      (gnus-make-local-hook 'kill-buffer-hook)
+      (add-hook 'kill-buffer-hook
+               (lambda ()
+                 (mm-destroy-parts gnus-article-mime-handles)) nil t)
       (setq buffer-read-only t)
       (local-set-key "q" (lambda () (interactive) (kill-buffer nil)))
+      (local-set-key "=" (lambda () (interactive) (delete-other-windows)))
+      (local-set-key "\r"
+                    (lambda ()
+                      (interactive)
+                      (widget-button-press (point))))
+      (local-set-key gnus-mouse-2
+                    (lambda (event)
+                      (interactive "@e")
+                      (widget-button-press (widget-event-point event) event)))
       (goto-char (point-min)))))
 
 (defun mml-validate ()