(mml-preview): Remove mail-header-separator before encoding.
[gnus] / lisp / mml.el
1 ;;; mml.el --- A package for parsing and validating MML documents
2 ;; Copyright (C) 1998,99 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; This file is part of GNU Emacs.
6
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
10 ;; any later version.
11
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
21
22 ;;; Commentary:
23
24 ;;; Code:
25
26 (require 'mm-util)
27 (require 'mm-bodies)
28 (require 'mm-encode)
29 (require 'mm-decode)
30
31 (eval-and-compile
32   (autoload 'message-make-message-id "message"))
33
34 (defvar mml-generate-multipart-alist
35   '(("signed" . rfc2015-generate-signed-multipart)
36     ("encrypted" . rfc2015-generate-encrypted-multipart))
37   "*Alist of multipart generation functions.
38
39 Each entry has the form (NAME . FUNCTION), where
40 NAME: is a string containing the name of the part (without the 
41 leading \"/multipart/\"),
42 FUNCTION: is a Lisp function which is called to generate the part.
43
44 The Lisp function has to supply the appropriate MIME headers and the
45 contents of this part.")
46
47 (defvar mml-syntax-table
48   (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
49     (modify-syntax-entry ?\\ "/" table)
50     (modify-syntax-entry ?< "(" table)
51     (modify-syntax-entry ?> ")" table)
52     (modify-syntax-entry ?@ "w" table)
53     (modify-syntax-entry ?/ "w" table)
54     (modify-syntax-entry ?= " " table)
55     (modify-syntax-entry ?* " " table)
56     (modify-syntax-entry ?\; " " table)
57     (modify-syntax-entry ?\' " " table)
58     table))
59
60 (defun mml-parse ()
61   "Parse the current buffer as an MML document."
62   (goto-char (point-min))
63   (let ((table (syntax-table)))
64     (unwind-protect
65         (progn
66           (set-syntax-table mml-syntax-table)
67           (mml-parse-1))
68       (set-syntax-table table))))
69
70 (defun mml-parse-1 ()
71   "Parse the current buffer as an MML document."
72   (let (struct tag point contents charsets warn)
73     (while (and (not (eobp))
74                 (not (looking-at "<#/multipart")))
75       (cond
76        ((looking-at "<#multipart")
77         (push (nconc (mml-read-tag) (mml-parse-1)) struct))
78        ((looking-at "<#external")
79         (push (nconc (mml-read-tag) (list (cons 'contents (mml-read-part))))
80               struct))
81        (t
82         (if (looking-at "<#part")
83             (setq tag (mml-read-tag))
84           (setq tag (list 'part '(type . "text/plain"))
85                 warn t))
86         (setq point (point)
87               contents (mml-read-part)
88               charsets (mm-find-mime-charset-region point (point)))
89         (if (< (length charsets) 2)
90             (push (nconc tag (list (cons 'contents contents)))
91                   struct)
92           (let ((nstruct (mml-parse-singlepart-with-multiple-charsets
93                           tag point (point))))
94             (when (and warn
95                        (not
96                         (y-or-n-p
97                          (format
98                           "Warning: Your message contains %d parts.  Really send? "
99                           (length nstruct)))))
100               (error "Edit your message to use only one charset"))
101             (setq struct (nconc nstruct struct)))))))
102     (unless (eobp)
103       (forward-line 1))
104     (nreverse struct)))
105
106 (defun mml-parse-singlepart-with-multiple-charsets (orig-tag beg end)
107   (save-excursion
108     (narrow-to-region beg end)
109     (goto-char (point-min))
110     (let ((current (mm-mime-charset (char-charset (following-char))))
111           charset struct space newline paragraph)
112       (while (not (eobp))
113         (cond
114          ;; The charset remains the same.
115          ((or (eq (setq charset (mm-mime-charset
116                                  (char-charset (following-char)))) 'us-ascii)
117               (eq charset current)))
118          ;; The initial charset was ascii.
119          ((eq current 'us-ascii)
120           (setq current charset
121                 space nil
122                 newline nil
123                 paragraph nil))
124          ;; We have a change in charsets.
125          (t
126           (push (append
127                  orig-tag
128                  (list (cons 'contents
129                              (buffer-substring-no-properties
130                               beg (or paragraph newline space (point))))))
131                 struct)
132           (setq beg (or paragraph newline space (point))
133                 current charset
134                 space nil
135                 newline nil
136                 paragraph nil)))
137         ;; Compute places where it might be nice to break the part.
138         (cond
139          ((memq (following-char) '(?  ?\t))
140           (setq space (1+ (point))))
141          ((eq (following-char) ?\n)
142           (setq newline (1+ (point))))
143          ((and (eq (following-char) ?\n)
144                (not (bobp))
145                (eq (char-after (1- (point))) ?\n))
146           (setq paragraph (point))))
147         (forward-char 1))
148       ;; Do the final part.
149       (unless (= beg (point))
150         (push (append orig-tag
151                       (list (cons 'contents
152                                   (buffer-substring-no-properties
153                                    beg (point)))))
154               struct))
155       struct)))
156
157 (defun mml-read-tag ()
158   "Read a tag and return the contents."
159   (let (contents name elem val)
160     (forward-char 2)
161     (setq name (buffer-substring-no-properties
162                 (point) (progn (forward-sexp 1) (point))))
163     (skip-chars-forward " \t\n")
164     (while (not (looking-at ">"))
165       (setq elem (buffer-substring-no-properties
166                   (point) (progn (forward-sexp 1) (point))))
167       (skip-chars-forward "= \t\n")
168       (setq val (buffer-substring-no-properties
169                  (point) (progn (forward-sexp 1) (point))))
170       (when (string-match "^\"\\(.*\\)\"$" val)
171         (setq val (match-string 1 val)))
172       (push (cons (intern elem) val) contents)
173       (skip-chars-forward " \t\n"))
174     (forward-char 1)
175     (skip-chars-forward " \t\n")
176     (cons (intern name) (nreverse contents))))
177
178 (defun mml-read-part ()
179   "Return the buffer up till the next part, multipart or closing part or multipart."
180   (let ((beg (point)))
181     ;; If the tag ended at the end of the line, we go to the next line.
182     (when (looking-at "[ \t]*\n")
183       (forward-line 1))
184     (if (re-search-forward
185          "<#\\(/\\)?\\(multipart\\|part\\|external\\)." nil t)
186         (prog1
187             (buffer-substring-no-properties beg (match-beginning 0))
188           (if (or (not (match-beginning 1))
189                   (equal (match-string 2) "multipart"))
190               (goto-char (match-beginning 0))
191             (when (looking-at "[ \t]*\n")
192               (forward-line 1))))
193       (buffer-substring-no-properties beg (goto-char (point-max))))))
194
195 (defvar mml-boundary nil)
196 (defvar mml-base-boundary "-=-=")
197 (defvar mml-multipart-number 0)
198
199 (defun mml-generate-mime ()
200   "Generate a MIME message based on the current MML document."
201   (let ((cont (mml-parse))
202         (mml-multipart-number 0))
203     (if (not cont)
204         nil
205       (with-temp-buffer
206         (if (and (consp (car cont))
207                  (= (length cont) 1))
208             (mml-generate-mime-1 (car cont))
209           (mml-generate-mime-1 (nconc (list 'multipart '(type . "mixed"))
210                                       cont)))
211         (buffer-string)))))
212
213 (defun mml-generate-mime-1 (cont)
214   (cond
215    ((eq (car cont) 'part)
216     (let (coded encoding charset filename type)
217       (setq type (or (cdr (assq 'type cont)) "text/plain"))
218       (if (member (car (split-string type "/")) '("text" "message"))
219           (with-temp-buffer
220             (cond
221              ((cdr (assq 'buffer cont))
222               (insert-buffer-substring (cdr (assq 'buffer cont))))
223              ((and (setq filename (cdr (assq 'filename cont)))
224                    (not (equal (cdr (assq 'nofile cont)) "yes")))
225               (mm-insert-file-contents filename))
226              (t
227               (save-restriction
228                 (narrow-to-region (point) (point))
229                 (insert (cdr (assq 'contents cont)))
230                 ;; Remove quotes from quoted tags.
231                 (goto-char (point-min))
232                 (while (re-search-forward
233                         "<#!+/?\\(part\\|multipart\\|external\\)" nil t)
234                   (delete-region (+ (match-beginning 0) 2)
235                                  (+ (match-beginning 0) 3))))))
236             (setq charset (mm-encode-body))
237             (setq encoding (mm-body-encoding charset))
238             (setq coded (buffer-string)))
239         (mm-with-unibyte-buffer
240           (cond
241            ((cdr (assq 'buffer cont))
242             (insert-buffer-substring (cdr (assq 'buffer cont))))
243            ((and (setq filename (cdr (assq 'filename cont)))
244                  (not (equal (cdr (assq 'nofile cont)) "yes")))
245             (let ((coding-system-for-read mm-binary-coding-system))
246               (mm-insert-file-contents filename nil nil nil nil t)))
247            (t
248             (insert (cdr (assq 'contents cont)))))
249           (setq encoding (mm-encode-buffer type)
250                 coded (buffer-string))))
251       (mml-insert-mime-headers cont type charset encoding)
252       (insert "\n")
253       (insert coded)))
254    ((eq (car cont) 'external)
255     (insert "Content-Type: message/external-body")
256     (let ((parameters (mml-parameter-string
257                        cont '(expiration size permission)))
258           (name (cdr (assq 'name cont))))
259       (when name
260         (setq name (mml-parse-file-name name))
261         (if (stringp name)
262             (mml-insert-parameter
263              (mail-header-encode-parameter "name" name)
264              "access-type=local-file")
265           (mml-insert-parameter
266            (mail-header-encode-parameter
267             "name" (file-name-nondirectory (nth 2 name)))
268            (mail-header-encode-parameter "site" (nth 1 name))
269            (mail-header-encode-parameter
270             "directory" (file-name-directory (nth 2 name))))
271           (mml-insert-parameter
272            (concat "access-type="
273                    (if (member (nth 0 name) '("ftp@" "anonymous@"))
274                        "anon-ftp"
275                      "ftp")))))      
276       (when parameters
277         (mml-insert-parameter-string
278          cont '(expiration size permission))))
279     (insert "\n\n")
280     (insert "Content-Type: " (cdr (assq 'type cont)) "\n")
281     (insert "Content-ID: " (message-make-message-id) "\n")
282     (insert "Content-Transfer-Encoding: "
283             (or (cdr (assq 'encoding cont)) "binary"))
284     (insert "\n\n")
285     (insert (or (cdr (assq 'contents cont))))
286     (insert "\n"))
287    ((eq (car cont) 'multipart)
288     (let* ((type (or (cdr (assq 'type cont)) "mixed"))
289            (handler (assoc type mml-generate-multipart-alist)))
290       (if handler
291           (funcall (cdr handler) cont)
292         ;; No specific handler.  Use default one.
293         (let ((mml-boundary (mml-compute-boundary cont)))
294           (insert (format "Content-Type: multipart/%s; boundary=\"%s\"\n"
295                           type mml-boundary))
296           (insert "\n")
297           (setq cont (cddr cont))
298           (while cont
299             (insert "\n--" mml-boundary "\n")
300             (mml-generate-mime-1 (pop cont)))
301           (insert "\n--" mml-boundary "--\n")))))
302    (t
303     (error "Invalid element: %S" cont))))
304
305 (defun mml-compute-boundary (cont)
306   "Return a unique boundary that does not exist in CONT."
307   (let ((mml-boundary (mml-make-boundary)))
308     ;; This function tries again and again until it has found
309     ;; a unique boundary.
310     (while (not (catch 'not-unique
311                   (mml-compute-boundary-1 cont))))
312     mml-boundary))
313
314 (defun mml-compute-boundary-1 (cont)
315   (let (filename)
316     (cond
317      ((eq (car cont) 'part)
318       (with-temp-buffer
319         (cond
320          ((cdr (assq 'buffer cont))
321           (insert-buffer-substring (cdr (assq 'buffer cont))))
322          ((and (setq filename (cdr (assq 'filename cont)))
323                (not (equal (cdr (assq 'nofile cont)) "yes")))
324           (mm-insert-file-contents filename))
325          (t
326           (insert (cdr (assq 'contents cont)))))
327         (goto-char (point-min))
328         (when (re-search-forward (concat "^--" (regexp-quote mml-boundary))
329                                  nil t)
330           (setq mml-boundary (mml-make-boundary))
331           (throw 'not-unique nil))))
332      ((eq (car cont) 'multipart)
333       (mapcar 'mml-compute-boundary-1 (cddr cont))))
334     t))
335
336 (defun mml-make-boundary ()
337   (concat (make-string (% (incf mml-multipart-number) 60) ?=)
338           (if (> mml-multipart-number 17)
339               (format "%x" mml-multipart-number)
340             "")
341           mml-base-boundary))
342
343 (defun mml-make-string (num string)
344   (let ((out ""))
345     (while (not (zerop (decf num)))
346       (setq out (concat out string)))
347     out))
348
349 (defun mml-insert-mime-headers (cont type charset encoding)
350   (let (parameters disposition description)
351     (setq parameters
352           (mml-parameter-string
353            cont '(name access-type expiration size permission)))
354     (when (or charset
355               parameters
356               (not (equal type "text/plain")))
357       (when (consp charset)
358         (error
359          "Can't encode a part with several charsets."))
360       (insert "Content-Type: " type)
361       (when charset
362         (insert "; " (mail-header-encode-parameter
363                       "charset" (symbol-name charset))))
364       (when parameters
365         (mml-insert-parameter-string
366          cont '(name access-type expiration size permission)))
367       (insert "\n"))
368     (setq parameters
369           (mml-parameter-string
370            cont '(filename creation-date modification-date read-date)))
371     (when (or (setq disposition (cdr (assq 'disposition cont)))
372               parameters)
373       (insert "Content-Disposition: " (or disposition "inline"))
374       (when parameters
375         (mml-insert-parameter-string
376          cont '(filename creation-date modification-date read-date)))
377       (insert "\n"))
378     (unless (eq encoding '7bit)
379       (insert (format "Content-Transfer-Encoding: %s\n" encoding)))
380     (when (setq description (cdr (assq 'description cont)))
381       (insert "Content-Description: "
382               (mail-encode-encoded-word-string description) "\n"))))
383
384 (defun mml-parameter-string (cont types)
385   (let ((string "")
386         value type)
387     (while (setq type (pop types))
388       (when (setq value (cdr (assq type cont)))
389         ;; Strip directory component from the filename parameter.
390         (when (eq type 'filename)
391           (setq value (file-name-nondirectory value)))
392         (setq string (concat string "; "
393                              (mail-header-encode-parameter
394                               (symbol-name type) value)))))
395     (when (not (zerop (length string)))
396       string)))
397
398 (defun mml-insert-parameter-string (cont types)
399   (let (value type)
400     (while (setq type (pop types))
401       (when (setq value (cdr (assq type cont)))
402         ;; Strip directory component from the filename parameter.
403         (when (eq type 'filename)
404           (setq value (file-name-nondirectory value)))
405         (mml-insert-parameter
406          (mail-header-encode-parameter
407           (symbol-name type) value))))))
408
409 (defvar ange-ftp-path-format)
410 (defvar efs-path-regexp)
411 (defun mml-parse-file-name (path)
412   (if (if (boundp 'efs-path-regexp)
413           (string-match efs-path-regexp path)
414         (if (boundp 'ange-ftp-path-format)
415             (string-match (car ange-ftp-path-format))))
416       (list (match-string 1 path) (match-string 2 path)
417             (substring path (1+ (match-end 2))))
418     path))
419
420 (defun mml-insert-buffer (buffer)
421   "Insert BUFFER at point and quote any MML markup."
422   (save-restriction
423     (narrow-to-region (point) (point))
424     (insert-buffer-substring buffer)
425     (mml-quote-region (point-min) (point-max))
426     (goto-char (point-max))))
427
428 ;;;
429 ;;; Transforming MIME to MML
430 ;;;
431
432 (defun mime-to-mml ()
433   "Translate the current buffer (which should be a message) into MML."
434   ;; First decode the head.
435   (save-restriction
436     (message-narrow-to-head)
437     (mail-decode-encoded-word-region (point-min) (point-max)))
438   (let ((handles (mm-dissect-buffer t)))
439     (goto-char (point-min))
440     (search-forward "\n\n" nil t)
441     (delete-region (point) (point-max))
442     (if (stringp (car handles))
443         (mml-insert-mime handles)
444       (mml-insert-mime handles t))
445     (mm-destroy-parts handles)))
446
447 (defun mml-to-mime ()
448   "Translate the current buffer from MML to MIME."
449   (message-encode-message-body)
450   (save-restriction
451     (message-narrow-to-headers-or-head)
452     (mail-encode-encoded-word-buffer)))
453
454 (defun mml-insert-mime (handle &optional no-markup)
455   (let (textp buffer)
456     ;; Determine type and stuff.
457     (unless (stringp (car handle))
458       (unless (setq textp (equal (mm-handle-media-supertype handle)
459                                  "text"))
460         (save-excursion
461           (set-buffer (setq buffer (generate-new-buffer " *mml*")))
462           (mm-insert-part handle))))
463     (unless no-markup
464       (mml-insert-mml-markup handle buffer textp))
465     (cond
466      ((stringp (car handle))
467       (mapcar 'mml-insert-mime (cdr handle))
468       (insert "<#/multipart>\n"))
469      (textp
470       (let ((text (mm-get-part handle))
471             (charset (mail-content-type-get
472                       (mm-handle-type handle) 'charset)))
473         (insert (mm-decode-string text charset)))
474       (goto-char (point-max)))
475      (t
476       (insert "<#/part>\n")))))
477
478 (defun mml-insert-mml-markup (handle &optional buffer nofile)
479   "Take a MIME handle and insert an MML tag."
480   (if (stringp (car handle))
481       (insert "<#multipart type=" (mm-handle-media-subtype handle)
482               ">\n")
483     (insert "<#part type=" (mm-handle-media-type handle))
484     (dolist (elem (append (cdr (mm-handle-type handle))
485                           (cdr (mm-handle-disposition handle))))
486       (insert " " (symbol-name (car elem)) "=\"" (cdr elem) "\""))
487     (when (mm-handle-disposition handle)
488       (insert " disposition=" (car (mm-handle-disposition handle))))
489     (when buffer
490       (insert " buffer=\"" (buffer-name buffer) "\""))
491     (when nofile
492       (insert " nofile=yes"))
493     (when (mm-handle-description handle)
494       (insert " description=\"" (mm-handle-description handle) "\""))
495     (insert ">\n")))
496
497 (defun mml-insert-parameter (&rest parameters)
498   "Insert PARAMETERS in a nice way."
499   (dolist (param parameters)
500     (insert ";")
501     (let ((point (point)))
502       (insert " " param)
503       (when (> (current-column) 71)
504         (goto-char point)
505         (insert "\n ")
506         (end-of-line)))))
507
508 ;;;
509 ;;; Mode for inserting and editing MML forms
510 ;;;
511
512 (defvar mml-mode-map
513   (let ((map (make-sparse-keymap))
514         (main (make-sparse-keymap)))
515     (define-key map "f" 'mml-attach-file)
516     (define-key map "b" 'mml-attach-buffer)
517     (define-key map "e" 'mml-attach-external)
518     (define-key map "q" 'mml-quote-region)
519     (define-key map "m" 'mml-insert-multipart)
520     (define-key map "p" 'mml-insert-part)
521     (define-key map "v" 'mml-validate)
522     (define-key map "P" 'mml-preview)
523     (define-key map "n" 'mml-narrow-to-part)
524     (define-key main "\M-m" map)
525     main))
526
527 (easy-menu-define
528  mml-menu mml-mode-map ""
529  '("MML"
530    ("Attach"
531     ["File" mml-attach-file t]
532     ["Buffer" mml-attach-buffer t]
533     ["External" mml-attach-external t])
534    ("Insert"
535     ["Multipart" mml-insert-multipart t]
536     ["Part" mml-insert-part t])
537    ["Narrow" mml-narrow-to-part t]
538    ["Quote" mml-quote-region t]
539    ["Validate" mml-validate t]
540    ["Preview" mml-preview t]))
541
542 (defvar mml-mode nil
543   "Minor mode for editing MML.")
544
545 (defun mml-mode (&optional arg)
546   "Minor mode for editing MML.
547
548 \\{mml-mode-map}"
549   (interactive "P")
550   (if (not (set (make-local-variable 'mml-mode)
551                 (if (null arg) (not mml-mode)
552                   (> (prefix-numeric-value arg) 0))))
553       nil
554     (set (make-local-variable 'mml-mode) t)
555     (unless (assq 'mml-mode minor-mode-alist)
556       (push `(mml-mode " MML") minor-mode-alist))
557     (unless (assq 'mml-mode minor-mode-map-alist)
558       (push (cons 'mml-mode mml-mode-map)
559             minor-mode-map-alist)))
560   (run-hooks 'mml-mode-hook))
561
562 ;;;
563 ;;; Helper functions for reading MIME stuff from the minibuffer and
564 ;;; inserting stuff to the buffer.
565 ;;;
566
567 (defun mml-minibuffer-read-file (prompt)
568   (let ((file (read-file-name prompt nil nil t)))
569     ;; Prevent some common errors.  This is inspired by similar code in
570     ;; VM.
571     (when (file-directory-p file)
572       (error "%s is a directory, cannot attach" file))
573     (unless (file-exists-p file)
574       (error "No such file: %s" file))
575     (unless (file-readable-p file)
576       (error "Permission denied: %s" file))
577     file))
578
579 (defun mml-minibuffer-read-type (name &optional default)
580   (let* ((default (or default
581                       (mm-default-file-encoding name)
582                       ;; Perhaps here we should check what the file
583                       ;; looks like, and offer text/plain if it looks
584                       ;; like text/plain.
585                       "application/octet-stream"))
586          (string (completing-read
587                   (format "Content type (default %s): " default)
588                   (mapcar
589                    'list
590                    (delete-duplicates
591                     (nconc
592                      (mapcar (lambda (m) (cdr m))
593                              mailcap-mime-extensions)
594                      (apply
595                       'nconc
596                       (mapcar
597                        (lambda (l)
598                          (delq nil
599                                (mapcar
600                                 (lambda (m)
601                                   (let ((type (cdr (assq 'type (cdr m)))))
602                                     (if (equal (cadr (split-string type "/"))
603                                                "*")
604                                         nil
605                                       type)))
606                                 (cdr l))))
607                        mailcap-mime-data)))
608                     :test 'equal)))))
609     (if (not (equal string ""))
610         string
611       default)))
612
613 (defun mml-minibuffer-read-description ()
614   (let ((description (read-string "One line description: ")))
615     (when (string-match "\\`[ \t]*\\'" description)
616       (setq description nil))
617     description))
618
619 (defun mml-quote-region (beg end)
620   "Quote the MML tags in the region."
621   (interactive "r")
622   (save-excursion
623     (save-restriction
624       ;; Temporarily narrow the region to defend from changes
625       ;; invalidating END.
626       (narrow-to-region beg end)
627       (goto-char (point-min))
628       ;; Quote parts.
629       (while (re-search-forward
630               "<#/?!*\\(multipart\\|part\\|external\\)" nil t)
631         (goto-char (match-beginning 1))
632         (insert "!")))))
633
634 (defun mml-insert-tag (name &rest plist)
635   "Insert an MML tag described by NAME and PLIST."
636   (when (symbolp name)
637     (setq name (symbol-name name)))
638   (insert "<#" name)
639   (while plist
640     (let ((key (pop plist))
641           (value (pop plist)))
642       (when value
643         ;; Quote VALUE if it contains suspicious characters.
644         (when (string-match "[\"\\~/* \t\n]" value)
645           (setq value (prin1-to-string value)))
646         (insert (format " %s=%s" key value)))))
647   (insert ">\n<#/" name ">\n"))
648
649 ;;; Attachment functions.
650
651 (defun mml-attach-file (file &optional type description)
652   "Attach a file to the outgoing MIME message.
653 The file is not inserted or encoded until you send the message with
654 `\\[message-send-and-exit]' or `\\[message-send]'.
655
656 FILE is the name of the file to attach.  TYPE is its content-type, a
657 string of the form \"type/subtype\".  DESCRIPTION is a one-line
658 description of the attachment."
659   (interactive
660    (let* ((file (mml-minibuffer-read-file "Attach file: "))
661           (type (mml-minibuffer-read-type file))
662           (description (mml-minibuffer-read-description)))
663      (list file type description)))
664   (mml-insert-tag 'part 'type type 'filename file 'disposition "attachment"
665                   'description description))
666
667 (defun mml-attach-buffer (buffer &optional type description)
668   "Attach a buffer to the outgoing MIME message.
669 See `mml-attach-file' for details of operation."
670   (interactive
671    (let* ((buffer (read-buffer "Attach buffer: "))
672           (type (mml-minibuffer-read-type buffer "text/plain"))
673           (description (mml-minibuffer-read-description)))
674      (list buffer type description)))
675   (mml-insert-tag 'part 'type type 'buffer buffer 'disposition "attachment"
676                   'description description))
677
678 (defun mml-attach-external (file &optional type description)
679   "Attach an external file into the buffer.
680 FILE is an ange-ftp/efs specification of the part location.
681 TYPE is the MIME type to use."
682   (interactive
683    (let* ((file (mml-minibuffer-read-file "Attach external file: "))
684           (type (mml-minibuffer-read-type file))
685           (description (mml-minibuffer-read-description)))
686      (list file type description)))
687   (mml-insert-tag 'external 'type type 'name file 'disposition "attachment"
688                   'description description))
689
690 (defun mml-insert-multipart (&optional type)
691   (interactive (list (completing-read "Multipart type (default mixed): "
692                      '(("mixed") ("alternative") ("digest") ("parallel")
693                        ("signed") ("encrypted"))
694                      nil nil "mixed")))
695   (or type
696       (setq type "mixed"))
697   (mml-insert-tag "multipart" 'type type)
698   (forward-line -1))
699
700 (defun mml-preview (&optional raw)
701  "Display current buffer with Gnus, in a new buffer.
702 If RAW, don't highlight the article."
703  (interactive "P")
704  (let ((buf (current-buffer)))
705    (switch-to-buffer (get-buffer-create 
706                      (concat (if raw "*Raw MIME preview of "
707                                "*MIME preview of ") (buffer-name))))
708    (erase-buffer)
709    (insert-buffer buf)
710    (if (re-search-forward
711         (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
712        (replace-match "\n"))
713    (mml-to-mime)
714    (unless raw
715      (run-hooks 'gnus-article-decode-hook)
716      (let ((gnus-newsgroup-name "dummy"))
717       (gnus-article-prepare-display)))
718    (fundamental-mode)
719    (setq buffer-read-only t)
720    (goto-char (point-min))))
721
722 (defun mml-validate ()
723   "Validate the current MML document."
724   (interactive)
725   (mml-parse))
726
727 (provide 'mml)
728
729 ;;; mml.el ends here