1 ;;; mml.el --- A package for parsing and validating MML documents
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;; 2005 Free Software Foundation, Inc.
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
33 (eval-when-compile (require 'cl
))
36 (autoload 'message-make-message-id
"message")
37 (autoload 'gnus-setup-posting-charset
"gnus-msg")
38 (autoload 'gnus-add-minor-mode
"gnus-ems")
39 (autoload 'gnus-make-local-hook
"gnus-util")
40 (autoload 'message-fetch-field
"message")
41 (autoload 'fill-flowed-encode
"flow-fill")
42 (autoload 'message-posting-charset
"message"))
44 (defvar gnus-article-mime-handles
)
46 (defvar gnus-newsrc-hashtb
)
47 (defvar message-default-charset
)
48 (defvar message-deletable-headers
)
49 (defvar message-options
)
50 (defvar message-posting-charset
)
51 (defvar message-required-mail-headers
)
52 (defvar message-required-news-headers
)
54 (defcustom mml-content-type-parameters
55 '(name access-type expiration size permission format
)
56 "*A list of acceptable parameters in MML tag.
57 These parameters are generated in Content-Type header if exists."
59 :type
'(repeat (symbol :tag
"Parameter"))
62 (defcustom mml-content-disposition-parameters
63 '(filename creation-date modification-date read-date
)
64 "*A list of acceptable parameters in MML tag.
65 These parameters are generated in Content-Disposition header if exists."
67 :type
'(repeat (symbol :tag
"Parameter"))
70 (defcustom mml-insert-mime-headers-always nil
71 "If non-nil, always put Content-Type: text/plain at top of empty parts.
72 It is necessary to work against a bug in certain clients."
77 (defvar mml-tweak-type-alist nil
78 "A list of (TYPE . FUNCTION) for tweaking MML parts.
79 TYPE is a string containing a regexp to match the MIME type. FUNCTION
80 is a Lisp function which is called with the MML handle to tweak the
81 part. This variable is used only when no TWEAK parameter exists in
84 (defvar mml-tweak-function-alist nil
85 "A list of (NAME . FUNCTION) for tweaking MML parts.
86 NAME is a string containing the name of the TWEAK parameter in the MML
87 handle. FUNCTION is a Lisp function which is called with the MML
88 handle to tweak the part.")
90 (defvar mml-tweak-sexp-alist
91 '((mml-externalize-attachments . mml-tweak-externalize-attachments
))
92 "A list of (SEXP . FUNCTION) for tweaking MML parts.
93 SEXP is an s-expression. If the evaluation of SEXP is non-nil, FUNCTION
94 is called. FUNCTION is a Lisp function which is called with the MML
95 handle to tweak the part.")
97 (defvar mml-externalize-attachments nil
98 "*If non-nil, local-file attachments are generated as external parts.")
100 (defvar mml-generate-multipart-alist nil
101 "*Alist of multipart generation functions.
102 Each entry has the form (NAME . FUNCTION), where
103 NAME is a string containing the name of the part (without the
104 leading \"/multipart/\"),
105 FUNCTION is a Lisp function which is called to generate the part.
107 The Lisp function has to supply the appropriate MIME headers and the
108 contents of this part.")
110 (defvar mml-syntax-table
111 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table
)))
112 (modify-syntax-entry ?
\\ "/" table
)
113 (modify-syntax-entry ?
< "(" table
)
114 (modify-syntax-entry ?
> ")" table
)
115 (modify-syntax-entry ?
@ "w" table
)
116 (modify-syntax-entry ?
/ "w" table
)
117 (modify-syntax-entry ?
= " " table
)
118 (modify-syntax-entry ?
* " " table
)
119 (modify-syntax-entry ?\
; " " table)
120 (modify-syntax-entry ?
\' " " table
)
123 (defvar mml-boundary-function
'mml-make-boundary
124 "A function called to suggest a boundary.
125 The function may be called several times, and should try to make a new
126 suggestion each time. The function is called with one parameter,
127 which is a number that says how many times the function has been
128 called for this message.")
130 (defvar mml-confirmation-set nil
131 "A list of symbols, each of which disables some warning.
132 `unknown-encoding': always send messages contain characters with
133 unknown encoding; `use-ascii': always use ASCII for those characters
134 with unknown encoding; `multipart': always send messages with more than
137 (defvar mml-generate-default-type
"text/plain"
138 "Content type by which the Content-Type header can be omitted.
139 The Content-Type header will not be put in the MIME part if the type
140 equals the value and there's no parameter (e.g. charset, format, etc.)
141 and `mml-insert-mime-headers-always' is nil. The value will be bound
142 to \"message/rfc822\" when encoding an article to be forwarded as a MIME
143 part. This is for the internal use, you should never modify the value.")
145 (defvar mml-buffer-list nil
)
147 (defun mml-generate-new-buffer (name)
148 (let ((buf (generate-new-buffer name
)))
149 (push buf mml-buffer-list
)
152 (defun mml-destroy-buffers ()
153 (let (kill-buffer-hook)
154 (mapcar 'kill-buffer mml-buffer-list
)
155 (setq mml-buffer-list nil
)))
158 "Parse the current buffer as an MML document."
160 (goto-char (point-min))
161 (let ((table (syntax-table)))
164 (set-syntax-table mml-syntax-table
)
166 (set-syntax-table table
)))))
168 (defun mml-parse-1 ()
169 "Parse the current buffer as an MML document."
170 (let (struct tag point contents charsets warn use-ascii no-markup-p raw
)
171 (while (and (not (eobp))
172 (not (looking-at "<#/multipart")))
174 ((looking-at "<#secure")
175 ;; The secure part is essentially a meta-meta tag, which
176 ;; expands to either a part tag if there are no other parts in
177 ;; the document or a multipart tag if there are other parts
178 ;; included in the message
180 (taginfo (mml-read-tag))
181 (recipients (cdr (assq 'recipients taginfo
)))
182 (sender (cdr (assq 'sender taginfo
)))
183 (location (cdr (assq 'tag-location taginfo
)))
184 (mode (cdr (assq 'mode taginfo
)))
185 (method (cdr (assq 'method taginfo
)))
190 "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t
)
191 (setq secure-mode
"multipart")
192 (setq secure-mode
"part")))
195 (re-search-forward "<#secure[^\n]*>\n"))
196 (delete-region (match-beginning 0) (match-end 0))
197 (cond ((string= mode
"sign")
198 (setq tags
(list "sign" method
)))
199 ((string= mode
"encrypt")
200 (setq tags
(list "encrypt" method
)))
201 ((string= mode
"signencrypt")
202 (setq tags
(list "sign" method
"encrypt" method
))))
203 (eval `(mml-insert-tag ,secure-mode
205 ,(if recipients
"recipients")
207 ,(if sender
"sender")
210 (goto-char location
)))
211 ((looking-at "<#multipart")
212 (push (nconc (mml-read-tag) (mml-parse-1)) struct
))
213 ((looking-at "<#external")
214 (push (nconc (mml-read-tag) (list (cons 'contents
(mml-read-part))))
217 (if (or (looking-at "<#part") (looking-at "<#mml"))
218 (setq tag
(mml-read-tag)
221 (setq tag
(list 'part
'(type .
"text/plain"))
224 (setq raw
(cdr (assq 'raw tag
))
226 contents
(mml-read-part (eq 'mml
(car tag
)))
231 (intern (downcase (cdr (assq 'charset tag
))))))
233 (mm-find-mime-charset-region point
(point)
235 (when (and (not raw
) (memq nil charsets
))
236 (if (or (memq 'unknown-encoding mml-confirmation-set
)
237 (message-options-get 'unknown-encoding
)
239 Message contains characters with unknown encoding. Really send? ")
240 (message-options-set 'unknown-encoding t
)))
242 (or (memq 'use-ascii mml-confirmation-set
)
243 (message-options-get 'use-ascii
)
244 (and (y-or-n-p "Use ASCII as charset? ")
245 (message-options-set 'use-ascii t
))))
246 (setq charsets
(delq nil charsets
))
248 (error "Edit your message to remove those characters")))
251 (< (length charsets
) 2))
252 (if (or (not no-markup-p
)
253 (string-match "[^ \t\r\n]" contents
))
254 ;; Don't create blank parts.
255 (push (nconc tag
(list (cons 'contents contents
)))
257 (let ((nstruct (mml-parse-singlepart-with-multiple-charsets
258 tag point
(point) use-ascii
)))
260 (not (memq 'multipart mml-confirmation-set
))
261 (not (message-options-get 'multipart
))
262 (not (and (y-or-n-p (format "\
263 A message part needs to be split into %d charset parts. Really send? "
265 (message-options-set 'multipart t
))))
266 (error "Edit your message to use only one charset"))
267 (setq struct
(nconc nstruct struct
)))))))
272 (defun mml-parse-singlepart-with-multiple-charsets
273 (orig-tag beg end
&optional use-ascii
)
276 (narrow-to-region beg end
)
277 (goto-char (point-min))
278 (let ((current (or (mm-mime-charset (mm-charset-after))
279 (and use-ascii
'us-ascii
)))
280 charset struct space newline paragraph
)
282 (setq charset
(mm-mime-charset (mm-charset-after)))
284 ;; The charset remains the same.
285 ((eq charset
'us-ascii
))
286 ((or (and use-ascii
(not charset
))
287 (eq charset current
))
291 ;; The initial charset was ascii.
292 ((eq current
'us-ascii
)
293 (setq current charset
297 ;; We have a change in charsets.
301 (list (cons 'contents
302 (buffer-substring-no-properties
303 beg
(or paragraph newline space
(point))))))
305 (setq beg
(or paragraph newline space
(point))
310 ;; Compute places where it might be nice to break the part.
312 ((memq (following-char) '(? ?
\t))
313 (setq space
(1+ (point))))
314 ((and (eq (following-char) ?
\n)
316 (eq (char-after (1- (point))) ?
\n))
317 (setq paragraph
(point)))
318 ((eq (following-char) ?
\n)
319 (setq newline
(1+ (point)))))
321 ;; Do the final part.
322 (unless (= beg
(point))
323 (push (append orig-tag
324 (list (cons 'contents
325 (buffer-substring-no-properties
330 (defun mml-read-tag ()
331 "Read a tag and return the contents."
332 (let ((orig-point (point))
333 contents name elem val
)
335 (setq name
(buffer-substring-no-properties
336 (point) (progn (forward-sexp 1) (point))))
337 (skip-chars-forward " \t\n")
338 (while (not (looking-at ">[ \t]*\n?"))
339 (setq elem
(buffer-substring-no-properties
340 (point) (progn (forward-sexp 1) (point))))
341 (skip-chars-forward "= \t\n")
342 (setq val
(buffer-substring-no-properties
343 (point) (progn (forward-sexp 1) (point))))
344 (when (string-match "^\"\\(.*\\)\"$" val
)
345 (setq val
(match-string 1 val
)))
346 (push (cons (intern elem
) val
) contents
)
347 (skip-chars-forward " \t\n"))
348 (goto-char (match-end 0))
349 ;; Don't skip the leading space.
350 ;;(skip-chars-forward " \t\n")
351 ;; Put the tag location into the returned contents
352 (setq contents
(append (list (cons 'tag-location orig-point
)) contents
))
353 (cons (intern name
) (nreverse contents
))))
355 (defun mml-buffer-substring-no-properties-except-hard-newlines (start end
)
356 (let ((str (buffer-substring-no-properties start end
))
357 (bufstart start
) tmp
)
358 (while (setq tmp
(text-property-any start end
'hard
't
))
359 (set-text-properties (- tmp bufstart
) (- tmp bufstart -
1)
361 (setq start
(1+ tmp
)))
364 (defun mml-read-part (&optional mml
)
365 "Return the buffer up till the next part, multipart or closing part or multipart.
366 If MML is non-nil, return the buffer up till the correspondent mml tag."
367 (let ((beg (point)) (count 1))
368 ;; If the tag ended at the end of the line, we go to the next line.
369 (when (looking-at "[ \t]*\n")
373 (while (and (> count
0) (not (eobp)))
374 (if (re-search-forward "<#\\(/\\)?mml." nil t
)
375 (setq count
(+ count
(if (match-beginning 1) -
1 1)))
376 (goto-char (point-max))))
377 (mml-buffer-substring-no-properties-except-hard-newlines
380 (match-beginning 0))))
381 (if (re-search-forward
382 "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t
)
384 (mml-buffer-substring-no-properties-except-hard-newlines
385 beg
(match-beginning 0))
386 (if (or (not (match-beginning 1))
387 (equal (match-string 2) "multipart"))
388 (goto-char (match-beginning 0))
389 (when (looking-at "[ \t]*\n")
391 (mml-buffer-substring-no-properties-except-hard-newlines
392 beg
(goto-char (point-max)))))))
394 (defvar mml-boundary nil
)
395 (defvar mml-base-boundary
"-=-=")
396 (defvar mml-multipart-number
0)
398 (defun mml-generate-mime ()
399 "Generate a MIME message based on the current MML document."
400 (let ((cont (mml-parse))
401 (mml-multipart-number mml-multipart-number
))
405 (if (and (consp (car cont
))
407 (mml-generate-mime-1 (car cont
))
408 (mml-generate-mime-1 (nconc (list 'multipart
'(type .
"mixed"))
412 (defun mml-generate-mime-1 (cont)
413 (let ((mm-use-ultra-safe-encoding
414 (or mm-use-ultra-safe-encoding
(assq 'sign cont
))))
416 (narrow-to-region (point) (point))
417 (mml-tweak-part cont
)
419 ((or (eq (car cont
) 'part
) (eq (car cont
) 'mml
))
420 (let* ((raw (cdr (assq 'raw cont
)))
421 (filename (cdr (assq 'filename cont
)))
422 (type (or (cdr (assq 'type cont
))
424 (or (mm-default-file-encoding filename
)
425 "application/octet-stream")
427 coded encoding charset flowed
)
429 (member (car (split-string type
"/")) '("text" "message")))
432 (setq charset
(mm-charset-to-coding-system
433 (cdr (assq 'charset cont
))))
434 (when (eq charset
'ascii
)
437 ((cdr (assq 'buffer cont
))
438 (insert-buffer-substring (cdr (assq 'buffer cont
))))
440 (not (equal (cdr (assq 'nofile cont
)) "yes")))
441 (let ((coding-system-for-read charset
))
442 (mm-insert-file-contents filename
)))
443 ((eq 'mml
(car cont
))
444 (insert (cdr (assq 'contents cont
))))
447 (narrow-to-region (point) (point))
448 (insert (cdr (assq 'contents cont
)))
449 ;; Remove quotes from quoted tags.
450 (goto-char (point-min))
451 (while (re-search-forward
452 "<#!+/?\\(part\\|multipart\\|external\\|mml\\)"
454 (delete-region (+ (match-beginning 0) 2)
455 (+ (match-beginning 0) 3))))))
457 ((eq (car cont
) 'mml
)
458 (let ((mml-boundary (mml-compute-boundary cont
))
459 ;; It is necessary for the case where this
460 ;; function is called recursively since
461 ;; `m-g-d-t' will be bound to "message/rfc822"
462 ;; when encoding an article to be forwarded.
463 (mml-generate-default-type "text/plain"))
465 (let ((mm-7bit-chars (concat mm-7bit-chars
"\x1b")))
466 ;; ignore 0x1b, it is part of iso-2022-jp
467 (setq encoding
(mm-body-7-or-8))))
468 ((string= (car (split-string type
"/")) "message")
469 (let ((mm-7bit-chars (concat mm-7bit-chars
"\x1b")))
470 ;; ignore 0x1b, it is part of iso-2022-jp
471 (setq encoding
(mm-body-7-or-8))))
473 ;; Only perform format=flowed filling on text/plain
474 ;; parts where there either isn't a format parameter
475 ;; in the mml tag or it says "flowed" and there
476 ;; actually are hard newlines in the text.
477 (let (use-hard-newlines)
478 (when (and (string= type
"text/plain")
479 (not (string= (cdr (assq 'sign cont
)) "pgp"))
480 (or (null (assq 'format cont
))
481 (string= (cdr (assq 'format cont
))
483 (setq use-hard-newlines
485 (point-min) (point-max) 'hard
't
)))
487 ;; Indicate that `mml-insert-mime-headers' should
488 ;; insert a "; format=flowed" string unless the
489 ;; user has already specified it.
490 (setq flowed
(null (assq 'format cont
)))))
491 (setq charset
(mm-encode-body charset
))
492 (setq encoding
(mm-body-encoding
493 charset
(cdr (assq 'encoding cont
))))))
494 (setq coded
(buffer-string)))
495 (mml-insert-mime-headers cont type charset encoding flowed
)
498 (mm-with-unibyte-buffer
500 ((cdr (assq 'buffer cont
))
501 (insert (with-current-buffer (cdr (assq 'buffer cont
))
502 (mm-with-unibyte-current-buffer
505 (not (equal (cdr (assq 'nofile cont
)) "yes")))
506 (let ((coding-system-for-read mm-binary-coding-system
))
507 (mm-insert-file-contents filename nil nil nil nil t
)))
509 (insert (cdr (assq 'contents cont
)))))
510 (setq encoding
(mm-encode-buffer type
)
511 coded
(mm-string-as-multibyte (buffer-string))))
512 (mml-insert-mime-headers cont type charset encoding nil
)
514 (mm-with-unibyte-current-buffer
516 ((eq (car cont
) 'external
)
517 (insert "Content-Type: message/external-body")
518 (let ((parameters (mml-parameter-string
519 cont
'(expiration size permission
)))
520 (name (cdr (assq 'name cont
)))
521 (url (cdr (assq 'url cont
))))
523 (setq name
(mml-parse-file-name name
))
525 (mml-insert-parameter
526 (mail-header-encode-parameter "name" name
)
527 "access-type=local-file")
528 (mml-insert-parameter
529 (mail-header-encode-parameter
530 "name" (file-name-nondirectory (nth 2 name
)))
531 (mail-header-encode-parameter "site" (nth 1 name
))
532 (mail-header-encode-parameter
533 "directory" (file-name-directory (nth 2 name
))))
534 (mml-insert-parameter
535 (concat "access-type="
536 (if (member (nth 0 name
) '("ftp@" "anonymous@"))
540 (mml-insert-parameter
541 (mail-header-encode-parameter "url" url
)
544 (mml-insert-parameter-string
545 cont
'(expiration size permission
)))
547 (insert "Content-Type: "
548 (or (cdr (assq 'type cont
))
550 (or (mm-default-file-encoding name
)
551 "application/octet-stream")
554 (insert "Content-ID: " (message-make-message-id) "\n")
555 (insert "Content-Transfer-Encoding: "
556 (or (cdr (assq 'encoding cont
)) "binary"))
558 (insert (or (cdr (assq 'contents cont
))))
560 ((eq (car cont
) 'multipart
)
561 (let* ((type (or (cdr (assq 'type cont
)) "mixed"))
562 (mml-generate-default-type (if (equal type
"digest")
565 (handler (assoc type mml-generate-multipart-alist
)))
567 (funcall (cdr handler
) cont
)
568 ;; No specific handler. Use default one.
569 (let ((mml-boundary (mml-compute-boundary cont
)))
570 (insert (format "Content-Type: multipart/%s; boundary=\"%s\""
572 (if (cdr (assq 'start cont
))
573 (format "; start=\"%s\"\n" (cdr (assq 'start cont
)))
575 (let ((cont cont
) part
)
576 (while (setq part
(pop cont
))
577 ;; Skip `multipart' and attributes.
578 (when (and (consp part
) (consp (cdr part
)))
579 (insert "\n--" mml-boundary
"\n")
580 (mml-generate-mime-1 part
))))
581 (insert "\n--" mml-boundary
"--\n")))))
583 (error "Invalid element: %S" cont
)))
584 ;; handle sign & encrypt tags in a semi-smart way.
585 (let ((sign-item (assoc (cdr (assq 'sign cont
)) mml-sign-alist
))
586 (encrypt-item (assoc (cdr (assq 'encrypt cont
))
589 (when (or sign-item encrypt-item
)
590 (when (setq sender
(cdr (assq 'sender cont
)))
591 (message-options-set 'mml-sender sender
)
592 (message-options-set 'message-sender sender
))
593 (if (setq recipients
(cdr (assq 'recipients cont
)))
594 (message-options-set 'message-recipients recipients
))
595 (let ((style (mml-signencrypt-style
596 (first (or sign-item encrypt-item
)))))
597 ;; check if: we're both signing & encrypting, both methods
598 ;; are the same (why would they be different?!), and that
599 ;; the signencrypt style allows for combined operation.
600 (if (and sign-item encrypt-item
(equal (first sign-item
)
601 (first encrypt-item
))
602 (equal style
'combined
))
603 (funcall (nth 1 encrypt-item
) cont t
)
604 ;; otherwise, revert to the old behavior.
606 (funcall (nth 1 sign-item
) cont
))
608 (funcall (nth 1 encrypt-item
) cont
)))))))))
610 (defun mml-compute-boundary (cont)
611 "Return a unique boundary that does not exist in CONT."
612 (let ((mml-boundary (funcall mml-boundary-function
613 (incf mml-multipart-number
))))
614 ;; This function tries again and again until it has found
615 ;; a unique boundary.
616 (while (not (catch 'not-unique
617 (mml-compute-boundary-1 cont
))))
620 (defun mml-compute-boundary-1 (cont)
623 ((eq (car cont
) 'part
)
626 ((cdr (assq 'buffer cont
))
627 (insert-buffer-substring (cdr (assq 'buffer cont
))))
628 ((and (setq filename
(cdr (assq 'filename cont
)))
629 (not (equal (cdr (assq 'nofile cont
)) "yes")))
630 (mm-insert-file-contents filename nil nil nil nil t
))
632 (insert (cdr (assq 'contents cont
)))))
633 (goto-char (point-min))
634 (when (re-search-forward (concat "^--" (regexp-quote mml-boundary
))
636 (setq mml-boundary
(funcall mml-boundary-function
637 (incf mml-multipart-number
)))
638 (throw 'not-unique nil
))))
639 ((eq (car cont
) 'multipart
)
640 (mapcar 'mml-compute-boundary-1
(cddr cont
))))
643 (defun mml-make-boundary (number)
644 (concat (make-string (% number
60) ?
=)
650 (defun mml-insert-mime-headers (cont type charset encoding flowed
)
651 (let (parameters id disposition description
)
653 (mml-parameter-string
654 cont mml-content-type-parameters
))
658 (not (equal type mml-generate-default-type
))
659 mml-insert-mime-headers-always
)
660 (when (consp charset
)
662 "Can't encode a part with several charsets"))
663 (insert "Content-Type: " type
)
665 (insert "; " (mail-header-encode-parameter
666 "charset" (symbol-name charset
))))
668 (insert "; format=flowed"))
670 (mml-insert-parameter-string
671 cont mml-content-type-parameters
))
673 (when (setq id
(cdr (assq 'id cont
)))
674 (insert "Content-ID: " id
"\n"))
676 (mml-parameter-string
677 cont mml-content-disposition-parameters
))
678 (when (or (setq disposition
(cdr (assq 'disposition cont
)))
680 (insert "Content-Disposition: " (or disposition
"inline"))
682 (mml-insert-parameter-string
683 cont mml-content-disposition-parameters
))
685 (unless (eq encoding
'7bit
)
686 (insert (format "Content-Transfer-Encoding: %s\n" encoding
)))
687 (when (setq description
(cdr (assq 'description cont
)))
688 (insert "Content-Description: "
689 (mail-encode-encoded-word-string description
) "\n"))))
691 (defun mml-parameter-string (cont types
)
694 (while (setq type
(pop types
))
695 (when (setq value
(cdr (assq type cont
)))
696 ;; Strip directory component from the filename parameter.
697 (when (eq type
'filename
)
698 (setq value
(file-name-nondirectory value
)))
699 (setq string
(concat string
"; "
700 (mail-header-encode-parameter
701 (symbol-name type
) value
)))))
702 (when (not (zerop (length string
)))
705 (defun mml-insert-parameter-string (cont types
)
707 (while (setq type
(pop types
))
708 (when (setq value
(cdr (assq type cont
)))
709 ;; Strip directory component from the filename parameter.
710 (when (eq type
'filename
)
711 (setq value
(file-name-nondirectory value
)))
712 (mml-insert-parameter
713 (mail-header-encode-parameter
714 (symbol-name type
) value
))))))
717 (defvar ange-ftp-name-format
)
718 (defvar efs-path-regexp
))
719 (defun mml-parse-file-name (path)
720 (if (if (boundp 'efs-path-regexp
)
721 (string-match efs-path-regexp path
)
722 (if (boundp 'ange-ftp-name-format
)
723 (string-match (car ange-ftp-name-format
) path
)))
724 (list (match-string 1 path
) (match-string 2 path
)
725 (substring path
(1+ (match-end 2))))
728 (defun mml-insert-buffer (buffer)
729 "Insert BUFFER at point and quote any MML markup."
731 (narrow-to-region (point) (point))
732 (insert-buffer-substring buffer
)
733 (mml-quote-region (point-min) (point-max))
734 (goto-char (point-max))))
737 ;;; Transforming MIME to MML
740 (defun mime-to-mml (&optional handles
)
741 "Translate the current buffer (which should be a message) into MML.
742 If HANDLES is non-nil, use it instead reparsing the buffer."
743 ;; First decode the head.
745 (message-narrow-to-head)
746 (let ((rfc2047-quote-decoded-words-containing-tspecials t
))
747 (mail-decode-encoded-word-region (point-min) (point-max))))
749 (setq handles
(mm-dissect-buffer t
)))
750 (goto-char (point-min))
751 (search-forward "\n\n" nil t
)
752 (delete-region (point) (point-max))
753 (if (stringp (car handles
))
754 (mml-insert-mime handles
)
755 (mml-insert-mime handles t
))
756 (mm-destroy-parts handles
)
758 (message-narrow-to-head)
759 ;; Remove them, they are confusing.
760 (message-remove-header "Content-Type")
761 (message-remove-header "MIME-Version")
762 (message-remove-header "Content-Disposition")
763 (message-remove-header "Content-Transfer-Encoding")))
765 (defun mml-to-mime ()
766 "Translate the current buffer from MML to MIME."
767 (message-encode-message-body)
769 (message-narrow-to-headers-or-head)
770 ;; Skip past any From_ headers.
771 (while (looking-at "From ")
773 (let ((mail-parse-charset message-default-charset
))
774 (mail-encode-encoded-word-buffer))))
776 (defun mml-insert-mime (handle &optional no-markup
)
777 (let (textp buffer mmlp
)
778 ;; Determine type and stuff.
779 (unless (stringp (car handle
))
780 (unless (setq textp
(equal (mm-handle-media-supertype handle
) "text"))
782 (set-buffer (setq buffer
(mml-generate-new-buffer " *mml*")))
783 (mm-insert-part handle
)
784 (if (setq mmlp
(equal (mm-handle-media-type handle
)
788 (mml-insert-mml-markup handle nil t t
)
789 (unless (and no-markup
790 (equal (mm-handle-media-type handle
) "text/plain"))
791 (mml-insert-mml-markup handle buffer textp
)))
794 (insert-buffer-substring buffer
)
795 (goto-char (point-max))
796 (insert "<#/mml>\n"))
797 ((stringp (car handle
))
798 (mapcar 'mml-insert-mime
(cdr handle
))
799 (insert "<#/multipart>\n"))
801 (let ((charset (mail-content-type-get
802 (mm-handle-type handle
) 'charset
))
804 (if (eq charset
'gnus-decoded
)
805 (mm-insert-part handle
)
806 (insert (mm-decode-string (mm-get-part handle
) charset
)))
807 (mml-quote-region start
(point)))
808 (goto-char (point-max)))
810 (insert "<#/part>\n")))))
812 (defun mml-insert-mml-markup (handle &optional buffer nofile mmlp
)
813 "Take a MIME handle and insert an MML tag."
814 (if (stringp (car handle
))
816 (insert "<#multipart type=" (mm-handle-media-subtype handle
))
817 (let ((start (mm-handle-multipart-ctl-parameter handle
'start
)))
819 (insert " start=\"" start
"\"")))
822 (insert "<#mml type=" (mm-handle-media-type handle
))
823 (insert "<#part type=" (mm-handle-media-type handle
)))
824 (dolist (elem (append (cdr (mm-handle-type handle
))
825 (cdr (mm-handle-disposition handle
))))
826 (unless (symbolp (cdr elem
))
827 (insert " " (symbol-name (car elem
)) "=\"" (cdr elem
) "\"")))
828 (when (mm-handle-id handle
)
829 (insert " id=\"" (mm-handle-id handle
) "\""))
830 (when (mm-handle-disposition handle
)
831 (insert " disposition=" (car (mm-handle-disposition handle
))))
833 (insert " buffer=\"" (buffer-name buffer
) "\""))
835 (insert " nofile=yes"))
836 (when (mm-handle-description handle
)
837 (insert " description=\"" (mm-handle-description handle
) "\""))
840 (defun mml-insert-parameter (&rest parameters
)
841 "Insert PARAMETERS in a nice way."
842 (dolist (param parameters
)
844 (let ((point (point)))
846 (when (> (current-column) 71)
852 ;;; Mode for inserting and editing MML forms
856 (let ((sign (make-sparse-keymap))
857 (encrypt (make-sparse-keymap))
858 (signpart (make-sparse-keymap))
859 (encryptpart (make-sparse-keymap))
860 (map (make-sparse-keymap))
861 (main (make-sparse-keymap)))
862 (define-key sign
"p" 'mml-secure-message-sign-pgpmime
)
863 (define-key sign
"o" 'mml-secure-message-sign-pgp
)
864 (define-key sign
"s" 'mml-secure-message-sign-smime
)
865 (define-key signpart
"p" 'mml-secure-sign-pgpmime
)
866 (define-key signpart
"o" 'mml-secure-sign-pgp
)
867 (define-key signpart
"s" 'mml-secure-sign-smime
)
868 (define-key encrypt
"p" 'mml-secure-message-encrypt-pgpmime
)
869 (define-key encrypt
"o" 'mml-secure-message-encrypt-pgp
)
870 (define-key encrypt
"s" 'mml-secure-message-encrypt-smime
)
871 (define-key encryptpart
"p" 'mml-secure-encrypt-pgpmime
)
872 (define-key encryptpart
"o" 'mml-secure-encrypt-pgp
)
873 (define-key encryptpart
"s" 'mml-secure-encrypt-smime
)
874 (define-key map
"\C-n" 'mml-unsecure-message
)
875 (define-key map
"f" 'mml-attach-file
)
876 (define-key map
"b" 'mml-attach-buffer
)
877 (define-key map
"e" 'mml-attach-external
)
878 (define-key map
"q" 'mml-quote-region
)
879 (define-key map
"m" 'mml-insert-multipart
)
880 (define-key map
"p" 'mml-insert-part
)
881 (define-key map
"v" 'mml-validate
)
882 (define-key map
"P" 'mml-preview
)
883 (define-key map
"s" sign
)
884 (define-key map
"S" signpart
)
885 (define-key map
"c" encrypt
)
886 (define-key map
"C" encryptpart
)
887 ;;(define-key map "n" 'mml-narrow-to-part)
888 ;; `M-m' conflicts with `back-to-indentation'.
889 ;; (define-key main "\M-m" map)
890 (define-key main
"\C-c\C-m" map
)
894 mml-menu mml-mode-map
""
896 ["Attach File..." mml-attach-file
897 ,@(if (featurep 'xemacs
) '(t)
898 '(:help
"Attach a file at point"))]
899 ["Attach Buffer..." mml-attach-buffer t
]
900 ["Attach External..." mml-attach-external t
]
901 ["Insert Part..." mml-insert-part t
]
902 ["Insert Multipart..." mml-insert-multipart t
]
903 ["PGP/MIME Sign" mml-secure-message-sign-pgpmime t
]
904 ["PGP/MIME Encrypt" mml-secure-message-encrypt-pgpmime t
]
905 ["PGP Sign" mml-secure-message-sign-pgp t
]
906 ["PGP Encrypt" mml-secure-message-encrypt-pgp t
]
907 ["S/MIME Sign" mml-secure-message-sign-smime t
]
908 ["S/MIME Encrypt" mml-secure-message-encrypt-smime t
]
910 ["PGP/MIME Sign Part" mml-secure-sign-pgpmime t
]
911 ["PGP/MIME Encrypt Part" mml-secure-encrypt-pgpmime t
]
912 ["PGP Sign Part" mml-secure-sign-pgp t
]
913 ["PGP Encrypt Part" mml-secure-encrypt-pgp t
]
914 ["S/MIME Sign Part" mml-secure-sign-smime t
]
915 ["S/MIME Encrypt Part" mml-secure-encrypt-smime t
])
916 ["Encrypt/Sign off" mml-unsecure-message t
]
917 ;;["Narrow" mml-narrow-to-part t]
918 ["Quote MML" mml-quote-region t
]
919 ["Validate MML" mml-validate t
]
920 ["Preview" mml-preview t
]))
923 "Minor mode for editing MML.")
925 (defun mml-mode (&optional arg
)
926 "Minor mode for editing MML.
927 MML is the MIME Meta Language, a minor mode for composing MIME articles.
928 See Info node `(emacs-mime)Composing'.
932 (when (set (make-local-variable 'mml-mode
)
933 (if (null arg
) (not mml-mode
)
934 (> (prefix-numeric-value arg
) 0)))
935 (gnus-add-minor-mode 'mml-mode
" MML" mml-mode-map
)
936 (easy-menu-add mml-menu mml-mode-map
)
937 (run-hooks 'mml-mode-hook
)))
940 ;;; Helper functions for reading MIME stuff from the minibuffer and
941 ;;; inserting stuff to the buffer.
944 (defun mml-minibuffer-read-file (prompt)
945 (let* ((completion-ignored-extensions nil
)
946 (file (read-file-name prompt nil nil t
)))
947 ;; Prevent some common errors. This is inspired by similar code in
949 (when (file-directory-p file
)
950 (error "%s is a directory, cannot attach" file
))
951 (unless (file-exists-p file
)
952 (error "No such file: %s" file
))
953 (unless (file-readable-p file
)
954 (error "Permission denied: %s" file
))
957 (defun mml-minibuffer-read-type (name &optional default
)
958 (mailcap-parse-mimetypes)
959 (let* ((default (or default
960 (mm-default-file-encoding name
)
961 ;; Perhaps here we should check what the file
962 ;; looks like, and offer text/plain if it looks
964 "application/octet-stream"))
965 (string (completing-read
966 (format "Content type (default %s): " default
)
967 (mapcar 'list
(mailcap-mime-types)))))
968 (if (not (equal string
""))
972 (defun mml-minibuffer-read-description ()
973 (let ((description (read-string "One line description: ")))
974 (when (string-match "\\`[ \t]*\\'" description
)
975 (setq description nil
))
978 (defun mml-minibuffer-read-disposition (type &optional default
)
979 (unless default
(setq default
980 (if (and (string-match "\\`text/" type
)
981 (not (string-match "\\`text/rtf\\'" type
)))
984 (let ((disposition (completing-read
985 (format "Disposition (default %s): " default
)
986 '(("attachment") ("inline") (""))
987 nil t nil nil default
)))
988 (if (not (equal disposition
""))
992 (defun mml-quote-region (beg end
)
993 "Quote the MML tags in the region."
997 ;; Temporarily narrow the region to defend from changes
999 (narrow-to-region beg end
)
1000 (goto-char (point-min))
1002 (while (re-search-forward
1003 "<#!*/?\\(multipart\\|part\\|external\\|mml\\)" nil t
)
1004 ;; Insert ! after the #.
1005 (goto-char (+ (match-beginning 0) 2))
1008 (defun mml-insert-tag (name &rest plist
)
1009 "Insert an MML tag described by NAME and PLIST."
1010 (when (symbolp name
)
1011 (setq name
(symbol-name name
)))
1014 (let ((key (pop plist
))
1015 (value (pop plist
)))
1017 ;; Quote VALUE if it contains suspicious characters.
1018 (when (string-match "[\"'\\~/*;() \t\n]" value
)
1019 (setq value
(with-output-to-string
1020 (let (print-escape-nonascii)
1022 (insert (format " %s=%s" key value
)))))
1025 (defun mml-insert-empty-tag (name &rest plist
)
1026 "Insert an empty MML tag described by NAME and PLIST."
1027 (when (symbolp name
)
1028 (setq name
(symbol-name name
)))
1029 (apply #'mml-insert-tag name plist
)
1030 (insert "<#/" name
">\n"))
1032 ;;; Attachment functions.
1034 (defun mml-attach-file (file &optional type description disposition
)
1035 "Attach a file to the outgoing MIME message.
1036 The file is not inserted or encoded until you send the message with
1037 `\\[message-send-and-exit]' or `\\[message-send]'.
1039 FILE is the name of the file to attach. TYPE is its content-type, a
1040 string of the form \"type/subtype\". DESCRIPTION is a one-line
1041 description of the attachment."
1043 (let* ((file (mml-minibuffer-read-file "Attach file: "))
1044 (type (mml-minibuffer-read-type file
))
1045 (description (mml-minibuffer-read-description))
1046 (disposition (mml-minibuffer-read-disposition type
)))
1047 (list file type description disposition
)))
1048 (mml-insert-empty-tag 'part
1051 'disposition
(or disposition
"attachment")
1052 'description description
))
1054 (defun mml-attach-buffer (buffer &optional type description
)
1055 "Attach a buffer to the outgoing MIME message.
1056 See `mml-attach-file' for details of operation."
1058 (let* ((buffer (read-buffer "Attach buffer: "))
1059 (type (mml-minibuffer-read-type buffer
"text/plain"))
1060 (description (mml-minibuffer-read-description)))
1061 (list buffer type description
)))
1062 (mml-insert-empty-tag 'part
'type type
'buffer buffer
1063 'disposition
"attachment" 'description description
))
1065 (defun mml-attach-external (file &optional type description
)
1066 "Attach an external file into the buffer.
1067 FILE is an ange-ftp/efs specification of the part location.
1068 TYPE is the MIME type to use."
1070 (let* ((file (mml-minibuffer-read-file "Attach external file: "))
1071 (type (mml-minibuffer-read-type file
))
1072 (description (mml-minibuffer-read-description)))
1073 (list file type description
)))
1074 (mml-insert-empty-tag 'external
'type type
'name file
1075 'disposition
"attachment" 'description description
))
1077 (defun mml-insert-multipart (&optional type
)
1078 (interactive (list (completing-read "Multipart type (default mixed): "
1079 '(("mixed") ("alternative") ("digest") ("parallel")
1080 ("signed") ("encrypted"))
1083 (setq type
"mixed"))
1084 (mml-insert-empty-tag "multipart" 'type type
)
1087 (defun mml-insert-part (&optional type
)
1089 (list (mml-minibuffer-read-type "")))
1090 (mml-insert-tag 'part
'type type
'disposition
"inline")
1093 (defun mml-preview-insert-mail-followup-to ()
1094 "Insert a Mail-Followup-To header before previewing an article.
1095 Should be adopted if code in `message-send-mail' is changed."
1096 (when (and (message-mail-p)
1097 (message-subscribed-p)
1098 (not (mail-fetch-field "mail-followup-to"))
1099 (message-make-mail-followup-to))
1100 (message-position-on-field "Mail-Followup-To" "X-Draft-From")
1101 (insert (message-make-mail-followup-to))))
1103 (defun mml-preview (&optional raw
)
1104 "Display current buffer with Gnus, in a new buffer.
1105 If RAW, display a raw encoded MIME message."
1108 (let* ((buf (current-buffer))
1109 (message-options message-options
)
1110 (message-this-is-mail (message-mail-p))
1111 (message-this-is-news (message-news-p))
1112 (message-posting-charset (or (gnus-setup-posting-charset
1114 (message-narrow-to-headers-or-head)
1115 (message-fetch-field "Newsgroups")))
1116 message-posting-charset
)))
1117 (message-options-set-recipient)
1118 (pop-to-buffer (generate-new-buffer
1119 (concat (if raw
"*Raw MIME preview of "
1120 "*MIME preview of ") (buffer-name))))
1121 (when (boundp 'gnus-buffers
)
1122 (push (current-buffer) gnus-buffers
))
1124 (insert-buffer-substring buf
)
1125 (mml-preview-insert-mail-followup-to)
1126 (let ((message-deletable-headers (if (message-news-p)
1128 message-deletable-headers
)))
1129 (message-generate-headers
1130 (copy-sequence (if (message-news-p)
1131 message-required-news-headers
1132 message-required-mail-headers
))))
1133 (if (re-search-forward
1134 (concat "^" (regexp-quote mail-header-separator
) "\n") nil t
)
1135 (replace-match "\n"))
1136 (let ((mail-header-separator ""));; mail-header-separator is removed.
1139 (when (fboundp 'set-buffer-multibyte
)
1140 (let ((s (buffer-string)))
1141 ;; Insert the content into unibyte buffer.
1143 (mm-disable-multibyte)
1145 (let ((gnus-newsgroup-charset (car message-posting-charset
))
1146 gnus-article-prepare-hook gnus-original-article-buffer
)
1147 (run-hooks 'gnus-article-decode-hook
)
1148 (let ((gnus-newsgroup-name "dummy")
1149 (gnus-newsrc-hashtb (or gnus-newsrc-hashtb
1150 (gnus-make-hashtable 5))))
1151 (gnus-article-prepare-display))))
1152 ;; Disable article-mode-map.
1154 (gnus-make-local-hook 'kill-buffer-hook
)
1155 (add-hook 'kill-buffer-hook
1157 (mm-destroy-parts gnus-article-mime-handles
)) nil t
)
1158 (setq buffer-read-only t
)
1159 (local-set-key "q" (lambda () (interactive) (kill-buffer nil
)))
1160 (local-set-key "=" (lambda () (interactive) (delete-other-windows)))
1164 (widget-button-press (point))))
1165 (local-set-key gnus-mouse-2
1168 (widget-button-press (widget-event-point event
) event
)))
1169 (goto-char (point-min)))))
1171 (defun mml-validate ()
1172 "Validate the current MML document."
1176 (defun mml-tweak-part (cont)
1178 (let ((tweak (cdr (assq 'tweak cont
)))
1183 (or (cdr (assoc tweak mml-tweak-function-alist
))
1185 (mml-tweak-type-alist
1186 (let ((alist mml-tweak-type-alist
)
1187 (type (or (cdr (assq 'type cont
)) "text/plain")))
1189 (if (string-match (caar alist
) type
)
1190 (setq func
(cdar alist
)
1192 (setq alist
(cdr alist
)))))))
1196 (let ((alist mml-tweak-sexp-alist
))
1198 (if (eval (caar alist
))
1199 (funcall (cdar alist
) cont
))
1200 (setq alist
(cdr alist
)))))
1203 (defun mml-tweak-externalize-attachments (cont)
1204 "Tweak attached files as external parts."
1205 (let (filename-cons)
1206 (when (and (eq (car cont
) 'part
)
1207 (not (cdr (assq 'buffer cont
)))
1208 (and (setq filename-cons
(assq 'filename cont
))
1209 (not (equal (cdr (assq 'nofile cont
)) "yes"))))
1210 (setcar cont
'external
)
1211 (setcar filename-cons
'name
))))
1215 ;;; arch-tag: 583c96cf-1ffe-451b-a5e5-4733ae9ddd12
1216 ;;; mml.el ends here