Remove tmp.
[gnus] / lisp / mml.el
index de159bd..db11fa8 100644 (file)
@@ -1,5 +1,5 @@
 ;;; mml.el --- A package for parsing and validating MML documents
-;; Copyright (C) 1998,99 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; This file is part of GNU Emacs.
 (require 'mm-bodies)
 (require 'mm-encode)
 (require 'mm-decode)
+(eval-when-compile 'cl)
 
 (eval-and-compile
-  (autoload 'message-make-message-id "message"))
+  (autoload 'message-make-message-id "message")
+  (autoload 'gnus-setup-posting-charset "gnus-msg")
+  (autoload 'message-fetch-field "message")
+  (autoload 'message-posting-charset "message"))
 
-(defvar mml-generate-multipart-alist
-  '(("signed" . rfc2015-generate-signed-multipart)
-    ("encrypted" . rfc2015-generate-encrypted-multipart))
+(defvar mml-generate-multipart-alist nil
   "*Alist of multipart generation functions.
-
 Each entry has the form (NAME . FUNCTION), where
-NAME: is a string containing the name of the part (without the 
+NAME is a string containing the name of the part (without the 
 leading \"/multipart/\"),
-FUNCTION: is a Lisp function which is called to generate the part.
+FUNCTION is a Lisp function which is called to generate the part.
 
 The Lisp function has to supply the appropriate MIME headers and the
 contents of this part.")
@@ -64,6 +65,27 @@ suggestion each time.  The function is called with one parameter,
 which is a number that says how many times the function has been
 called for this message.")
 
+(defvar mml-confirmation-set nil
+  "A list of symbols, each of which disables some warning.
+`unknown-encoding': always send messages contain characters with
+unknown encoding; `use-ascii': always use ASCII for those characters
+with unknown encoding; `multipart': always send messages with more than
+one charsets.")
+
+(defvar mml-generate-default-type "text/plain")
+
+(defvar mml-buffer-list nil)
+
+(defun mml-generate-new-buffer (name) 
+  (let ((buf (generate-new-buffer name)))
+    (push buf mml-buffer-list)
+    buf))
+
+(defun mml-destroy-buffers ()
+  (let (kill-buffer-hook)
+    (mapcar 'kill-buffer mml-buffer-list)
+    (setq mml-buffer-list nil)))
+
 (defun mml-parse ()
   "Parse the current buffer as an MML document."
   (goto-char (point-min))
@@ -76,7 +98,7 @@ called for this message.")
 
 (defun mml-parse-1 ()
   "Parse the current buffer as an MML document."
-  (let (struct tag point contents charsets warn)
+  (let (struct tag point contents charsets warn use-ascii no-markup-p)
     (while (and (not (eobp))
                (not (looking-at "<#/multipart")))
       (cond
@@ -86,23 +108,40 @@ called for this message.")
        (push (nconc (mml-read-tag) (list (cons 'contents (mml-read-part))))
              struct))
        (t
-       (if (looking-at "<#part")
-           (setq tag (mml-read-tag))
+       (if (or (looking-at "<#part") (looking-at "<#mml"))
+           (setq tag (mml-read-tag)
+                 no-markup-p nil
+                 warn nil)
          (setq tag (list 'part '(type . "text/plain"))
+               no-markup-p t
                warn t))
        (setq point (point)
-             contents (mml-read-part)
+             contents (mml-read-part (eq 'mml (car tag)))
              charsets (mm-find-mime-charset-region point (point)))
+       (when (memq nil charsets)
+         (if (or (memq 'unknown-encoding mml-confirmation-set)
+                 (y-or-n-p
+                  "Warning: You message contains characters with unknown encoding. Really send?"))
+             (if (setq use-ascii 
+                       (or (memq 'use-ascii mml-confirmation-set)
+                           (y-or-n-p "Use ASCII as charset?")))
+                 (setq charsets (delq nil charsets))
+               (setq warn nil))
+           (error "Edit your message to remove those characters")))
        (if (< (length charsets) 2)
-           (push (nconc tag (list (cons 'contents contents)))
-                 struct)
+           (if (or (not no-markup-p)
+                   (string-match "[^ \t\r\n]" contents))
+               ;; Don't create blank parts.
+               (push (nconc tag (list (cons 'contents contents)))
+                     struct))
          (let ((nstruct (mml-parse-singlepart-with-multiple-charsets
-                         tag point (point))))
+                         tag point (point) use-ascii)))
            (when (and warn
+                      (not (memq 'multipart mml-confirmation-set))
                       (not
                        (y-or-n-p
                         (format
-                         "Warning: Your message contains %d parts.  Really send? "
+                         "Warning: Your message contains more than %d parts.  Really send? "
                          (length nstruct)))))
              (error "Edit your message to use only one charset"))
            (setq struct (nconc nstruct struct)))))))
@@ -110,56 +149,63 @@ called for this message.")
       (forward-line 1))
     (nreverse struct)))
 
-(defun mml-parse-singlepart-with-multiple-charsets (orig-tag beg end)
+(defun mml-parse-singlepart-with-multiple-charsets 
+  (orig-tag beg end &optional use-ascii)
   (save-excursion
-    (narrow-to-region beg end)
-    (goto-char (point-min))
-    (let ((current (mm-mime-charset (char-charset (following-char))))
-         charset struct space newline paragraph)
-      (while (not (eobp))
-       (cond
-        ;; The charset remains the same.
-        ((or (eq (setq charset (mm-mime-charset
-                                (char-charset (following-char)))) 'us-ascii)
-             (eq charset current)))
-        ;; The initial charset was ascii.
-        ((eq current 'us-ascii)
-         (setq current charset
-               space nil
-               newline nil
-               paragraph nil))
-        ;; We have a change in charsets.
-        (t
-         (push (append
-                orig-tag
-                (list (cons 'contents
-                            (buffer-substring-no-properties
-                             beg (or paragraph newline space (point))))))
-               struct)
-         (setq beg (or paragraph newline space (point))
-               current charset
-               space nil
-               newline nil
-               paragraph nil)))
-       ;; Compute places where it might be nice to break the part.
-       (cond
-        ((memq (following-char) '(?  ?\t))
-         (setq space (1+ (point))))
-        ((eq (following-char) ?\n)
-         (setq newline (1+ (point))))
-        ((and (eq (following-char) ?\n)
-              (not (bobp))
-              (eq (char-after (1- (point))) ?\n))
-         (setq paragraph (point))))
-       (forward-char 1))
-      ;; Do the final part.
-      (unless (= beg (point))
-       (push (append orig-tag
-                     (list (cons 'contents
-                                 (buffer-substring-no-properties
-                                  beg (point)))))
-             struct))
-      struct)))
+    (save-restriction
+      (narrow-to-region beg end)
+      (goto-char (point-min))
+      (let ((current (or (mm-mime-charset (mm-charset-after))
+                        (and use-ascii 'us-ascii)))
+           charset struct space newline paragraph)
+       (while (not (eobp))
+         (setq charset (mm-mime-charset (mm-charset-after)))
+         (cond
+          ;; The charset remains the same.
+          ((eq charset 'us-ascii))
+          ((or (and use-ascii (not charset))
+               (eq charset current))
+           (setq space nil
+                 newline nil
+                 paragraph nil))
+          ;; The initial charset was ascii.
+          ((eq current 'us-ascii)
+           (setq current charset
+                 space nil
+                 newline nil
+                 paragraph nil))
+          ;; We have a change in charsets.
+          (t
+           (push (append
+                  orig-tag
+                  (list (cons 'contents
+                              (buffer-substring-no-properties
+                               beg (or paragraph newline space (point))))))
+                 struct)
+           (setq beg (or paragraph newline space (point))
+                 current charset
+                 space nil
+                 newline nil
+                 paragraph nil)))
+         ;; Compute places where it might be nice to break the part.
+         (cond
+          ((memq (following-char) '(?  ?\t))
+           (setq space (1+ (point))))
+          ((and (eq (following-char) ?\n)
+                (not (bobp))
+                (eq (char-after (1- (point))) ?\n))
+           (setq paragraph (point)))
+          ((eq (following-char) ?\n)
+           (setq newline (1+ (point)))))
+         (forward-char 1))
+       ;; Do the final part.
+       (unless (= beg (point))
+         (push (append orig-tag
+                       (list (cons 'contents
+                                   (buffer-substring-no-properties
+                                    beg (point)))))
+               struct))
+       struct))))
 
 (defun mml-read-tag ()
   "Read a tag and return the contents."
@@ -182,22 +228,32 @@ called for this message.")
     (skip-chars-forward " \t\n")
     (cons (intern name) (nreverse contents))))
 
-(defun mml-read-part ()
-  "Return the buffer up till the next part, multipart or closing part or multipart."
-  (let ((beg (point)))
+(defun mml-read-part (&optional mml)
+  "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.
     (when (looking-at "[ \t]*\n")
       (forward-line 1))
-    (if (re-search-forward
-        "<#\\(/\\)?\\(multipart\\|part\\|external\\)." nil t)
-       (prog1
-           (buffer-substring-no-properties beg (match-beginning 0))
-         (if (or (not (match-beginning 1))
-                 (equal (match-string 2) "multipart"))
-             (goto-char (match-beginning 0))
-           (when (looking-at "[ \t]*\n")
-             (forward-line 1))))
-      (buffer-substring-no-properties beg (goto-char (point-max))))))
+    (if mml
+       (progn
+         (while (and (> count 0) (not (eobp)))
+           (if (re-search-forward "<#\\(/\\)?mml." nil t)
+               (setq count (+ count (if (match-beginning 1) -1 1)))
+             (goto-char (point-max))))
+         (buffer-substring-no-properties beg (if (> count 0) 
+                                                 (point)
+                                               (match-beginning 0))))
+      (if (re-search-forward
+          "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t)
+         (prog1
+             (buffer-substring-no-properties beg (match-beginning 0))
+           (if (or (not (match-beginning 1))
+                   (equal (match-string 2) "multipart"))
+               (goto-char (match-beginning 0))
+             (when (looking-at "[ \t]*\n")
+               (forward-line 1))))
+       (buffer-substring-no-properties beg (goto-char (point-max)))))))
 
 (defvar mml-boundary nil)
 (defvar mml-base-boundary "-=-=")
@@ -206,7 +262,7 @@ called for this message.")
 (defun mml-generate-mime ()
   "Generate a MIME message based on the current MML document."
   (let ((cont (mml-parse))
-       (mml-multipart-number 0))
+       (mml-multipart-number mml-multipart-number))
     (if (not cont)
        nil
       (with-temp-buffer
@@ -219,7 +275,7 @@ called for this message.")
 
 (defun mml-generate-mime-1 (cont)
   (cond
-   ((eq (car cont) 'part)
+   ((or (eq (car cont) 'part) (eq (car cont) 'mml))
     (let (coded encoding charset filename type)
       (setq type (or (cdr (assq 'type cont)) "text/plain"))
       (if (member (car (split-string type "/")) '("text" "message"))
@@ -230,6 +286,8 @@ called for this message.")
             ((and (setq filename (cdr (assq 'filename cont)))
                   (not (equal (cdr (assq 'nofile cont)) "yes")))
              (mm-insert-file-contents filename))
+            ((eq 'mml (car cont))
+             (insert (cdr (assq 'contents cont))))
             (t
              (save-restriction
                (narrow-to-region (point) (point))
@@ -237,12 +295,26 @@ called for this message.")
                ;; Remove quotes from quoted tags.
                (goto-char (point-min))
                (while (re-search-forward
-                       "<#!+/?\\(part\\|multipart\\|external\\)" nil t)
+                       "<#!+/?\\(part\\|multipart\\|external\\|mml\\)" nil t)
                  (delete-region (+ (match-beginning 0) 2)
                                 (+ (match-beginning 0) 3))))))
-           (setq charset (mm-encode-body))
-           (setq encoding (mm-body-encoding charset 
-                                            (cdr (assq 'encoding cont))))
+           (cond 
+            ((eq (car cont) 'mml)
+             (let ((mml-boundary (funcall mml-boundary-function
+                                          (incf mml-multipart-number)))
+                   (mml-generate-default-type "text/plain"))
+               (mml-to-mime))
+             (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))))
+            ((string= (car (split-string type "/")) "message")
+             (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))))
+            (t 
+             (setq charset (mm-encode-body))
+             (setq encoding (mm-body-encoding
+                             charset (cdr (assq 'encoding cont))))))
            (setq coded (buffer-string)))
        (mm-with-unibyte-buffer
          (cond
@@ -294,6 +366,9 @@ called for this message.")
     (insert "\n"))
    ((eq (car cont) 'multipart)
     (let* ((type (or (cdr (assq 'type cont)) "mixed"))
+          (mml-generate-default-type (if (equal type "digest")
+                                         "message/rfc822"
+                                       "text/plain"))
            (handler (assoc type mml-generate-multipart-alist)))
       (if handler
           (funcall (cdr handler) cont)
@@ -301,6 +376,7 @@ called for this message.")
         (let ((mml-boundary (mml-compute-boundary cont)))
           (insert (format "Content-Type: multipart/%s; boundary=\"%s\"\n"
                           type mml-boundary))
+         ;; Skip `multipart' and `type' elements.
           (setq cont (cddr cont))
           (while cont
             (insert "\n--" mml-boundary "\n")
@@ -362,7 +438,7 @@ called for this message.")
           cont '(name access-type expiration size permission)))
     (when (or charset
              parameters
-             (not (equal type "text/plain")))
+             (not (equal type mml-generate-default-type)))
       (when (consp charset)
        (error
         "Can't encode a part with several charsets."))
@@ -451,27 +527,43 @@ called for this message.")
     (if (stringp (car handles))
        (mml-insert-mime handles)
       (mml-insert-mime handles t))
-    (mm-destroy-parts handles)))
+    (mm-destroy-parts handles))
+  (save-restriction
+    (message-narrow-to-head)
+    ;; Remove them, they are confusing.
+    (message-remove-header "Content-Type")
+    (message-remove-header "MIME-Version")
+    (message-remove-header "Content-Transfer-Encoding")))
 
 (defun mml-to-mime ()
   "Translate the current buffer from MML to MIME."
   (message-encode-message-body)
   (save-restriction
     (message-narrow-to-headers-or-head)
-    (mail-encode-encoded-word-buffer)))
+    (let ((mail-parse-charset message-default-charset))
+      (mail-encode-encoded-word-buffer))))
 
 (defun mml-insert-mime (handle &optional no-markup)
-  (let (textp buffer)
+  (let (textp buffer mmlp)
     ;; Determine type and stuff.
     (unless (stringp (car handle))
-      (unless (setq textp (equal (mm-handle-media-supertype handle)
-                                "text"))
+      (unless (setq textp (equal (mm-handle-media-supertype handle) "text"))
        (save-excursion
-         (set-buffer (setq buffer (generate-new-buffer " *mml*")))
-         (mm-insert-part handle))))
-    (unless no-markup
-      (mml-insert-mml-markup handle buffer textp))
+         (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)))))
+    (if mmlp
+       (mml-insert-mml-markup handle nil t t)
+      (unless (and no-markup
+                  (equal (mm-handle-media-type handle) "text/plain"))
+       (mml-insert-mml-markup handle buffer textp)))
     (cond
+     (mmlp 
+      (insert-buffer buffer)
+      (goto-char (point-max))
+      (insert "<#/mml>\n"))
      ((stringp (car handle))
       (mapcar 'mml-insert-mime (cdr handle))
       (insert "<#/multipart>\n"))
@@ -484,12 +576,14 @@ called for this message.")
      (t
       (insert "<#/part>\n")))))
 
-(defun mml-insert-mml-markup (handle &optional buffer nofile)
+(defun mml-insert-mml-markup (handle &optional buffer nofile mmlp)
   "Take a MIME handle and insert an MML tag."
   (if (stringp (car handle))
       (insert "<#multipart type=" (mm-handle-media-subtype handle)
              ">\n")
-    (insert "<#part type=" (mm-handle-media-type handle))
+    (if mmlp
+       (insert "<#mml type=" (mm-handle-media-type handle))
+      (insert "<#part type=" (mm-handle-media-type handle)))
     (dolist (elem (append (cdr (mm-handle-type handle))
                          (cdr (mm-handle-disposition handle))))
       (insert " " (symbol-name (car elem)) "=\"" (cdr elem) "\""))
@@ -596,10 +690,9 @@ called for this message.")
                  (format "Content type (default %s): " default)
                  (mapcar
                   'list
-                  (delete-duplicates
+                  (mm-delete-duplicates
                    (nconc
-                    (mapcar (lambda (m) (cdr m))
-                            mailcap-mime-extensions)
+                    (mapcar 'cdr mailcap-mime-extensions)
                     (apply
                      'nconc
                      (mapcar
@@ -613,8 +706,7 @@ called for this message.")
                                        nil
                                      type)))
                                (cdr l))))
-                      mailcap-mime-data)))
-                   :test 'equal)))))
+                      mailcap-mime-data))))))))
     (if (not (equal string ""))
        string
       default)))
@@ -636,7 +728,7 @@ called for this message.")
       (goto-char (point-min))
       ;; Quote parts.
       (while (re-search-forward
-             "<#/?!*\\(multipart\\|part\\|external\\)" nil t)
+             "<#/?!*\\(multipart\\|part\\|external\\|mml\\)" nil t)
        ;; Insert ! after the #.
        (goto-char (+ (match-beginning 0) 2))
        (insert "!")))))
@@ -651,7 +743,7 @@ called for this message.")
          (value (pop plist)))
       (when value
        ;; Quote VALUE if it contains suspicious characters.
-       (when (string-match "[\"\\~/* \t\n]" value)
+       (when (string-match "[\"'\\~/*;() \t\n]" value)
          (setq value (prin1-to-string value)))
        (insert (format " %s=%s" key value)))))
   (insert ">\n"))
@@ -706,9 +798,9 @@ TYPE is the MIME type to use."
 
 (defun mml-insert-multipart (&optional type)
   (interactive (list (completing-read "Multipart type (default mixed): "
-                    '(("mixed") ("alternative") ("digest") ("parallel")
-                      ("signed") ("encrypted"))
-                    nil nil "mixed")))
+                                     '(("mixed") ("alternative") ("digest") ("parallel")
+                                       ("signed") ("encrypted"))
+                                     nil nil "mixed")))
   (or type
       (setq type "mixed"))
   (mml-insert-empty-tag "multipart" 'type type)
@@ -721,26 +813,33 @@ TYPE is the MIME type to use."
   (forward-line -1))
 
 (defun mml-preview (&optional raw)
- "Display current buffer with Gnus, in a new buffer.
 "Display current buffer with Gnus, in a new buffer.
 If RAW, don't highlight the article."
- (interactive "P")
- (let ((buf (current-buffer)))
-   (switch-to-buffer (get-buffer-create 
-                     (concat (if raw "*Raw MIME preview of "
-                               "*MIME preview of ") (buffer-name))))
-   (erase-buffer)
-   (insert-buffer buf)
-   (if (re-search-forward
-       (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
-       (replace-match "\n"))
-   (mml-to-mime)
-   (unless raw
-     (run-hooks 'gnus-article-decode-hook)
-     (let ((gnus-newsgroup-name "dummy"))
-      (gnus-article-prepare-display)))
-   (fundamental-mode)
-   (setq buffer-read-only t)
-   (goto-char (point-min))))
+  (interactive "P")
+  (let ((buf (current-buffer))
+       (message-posting-charset (or (gnus-setup-posting-charset 
+                                     (save-restriction
+                                       (message-narrow-to-headers-or-head)
+                                       (message-fetch-field "Newsgroups")))
+                                    message-posting-charset)))
+    (switch-to-buffer (get-buffer-create 
+                      (concat (if raw "*Raw MIME preview of "
+                                "*MIME preview of ") (buffer-name))))
+    (erase-buffer)
+    (insert-buffer buf)
+    (if (re-search-forward
+        (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
+       (replace-match "\n"))
+    (mml-to-mime)
+    (if raw
+       (mm-disable-multibyte)
+      (let ((gnus-newsgroup-charset (car message-posting-charset)))
+       (run-hooks 'gnus-article-decode-hook)
+       (let ((gnus-newsgroup-name "dummy"))
+         (gnus-article-prepare-display))))
+    (fundamental-mode)
+    (setq buffer-read-only t)
+    (goto-char (point-min))))
 
 (defun mml-validate ()
   "Validate the current MML document."