Bugfixes for `customize-create-theme'.
[bpt/emacs.git] / lisp / gnus / mml.el
CommitLineData
23f87bed 1;;; mml.el --- A package for parsing and validating MML documents
e84b4b86 2
8abf1b22 3;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
114f9c96 4;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
c113de23
GM
5
6;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7;; This file is part of GNU Emacs.
8
5e809f55 9;; GNU Emacs is free software: you can redistribute it and/or modify
c113de23 10;; it under the terms of the GNU General Public License as published by
5e809f55
GM
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
c113de23
GM
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
5e809f55 16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
c113de23
GM
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
5e809f55 20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
c113de23
GM
21
22;;; Commentary:
23
24;;; Code:
25
f0b7f5a8 26;; For Emacs <22.2 and XEmacs.
5ab56288
GM
27(eval-and-compile
28 (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
29
c113de23
GM
30(require 'mm-util)
31(require 'mm-bodies)
32(require 'mm-encode)
33(require 'mm-decode)
23f87bed 34(require 'mml-sec)
160ff4e5 35(eval-when-compile (require 'cl))
d029b5d2
KY
36(eval-when-compile
37 (when (featurep 'xemacs)
38 (require 'easy-mmode))) ; for `define-minor-mode'
c113de23 39
8abf1b22 40(autoload 'message-make-message-id "message")
aa8f8277 41(declare-function gnus-setup-posting-charset "gnus-msg" (group))
8abf1b22 42(autoload 'gnus-make-local-hook "gnus-util")
229b59da 43(autoload 'gnus-completing-read "gnus-util")
8abf1b22
GM
44(autoload 'message-fetch-field "message")
45(autoload 'message-mark-active-p "message")
46(autoload 'message-info "message")
47(autoload 'fill-flowed-encode "flow-fill")
48(autoload 'message-posting-charset "message")
49(autoload 'dnd-get-local-file-name "dnd")
c113de23 50
541cbf8b
GM
51(autoload 'message-options-set "message")
52(autoload 'message-narrow-to-head "message")
53(autoload 'message-in-body-p "message")
54(autoload 'message-mail-p "message")
55
12710ba4
JB
56(defvar gnus-article-mime-handles)
57(defvar gnus-mouse-2)
58(defvar gnus-newsrc-hashtb)
59(defvar message-default-charset)
60(defvar message-deletable-headers)
61(defvar message-options)
62(defvar message-posting-charset)
63(defvar message-required-mail-headers)
64(defvar message-required-news-headers)
0565caeb 65(defvar dnd-protocol-alist)
9efa445f 66(defvar mml-dnd-protocol-alist)
12710ba4 67
23f87bed
MB
68(defcustom mml-content-type-parameters
69 '(name access-type expiration size permission format)
70 "*A list of acceptable parameters in MML tag.
71These parameters are generated in Content-Type header if exists."
bf247b6e 72 :version "22.1"
23f87bed
MB
73 :type '(repeat (symbol :tag "Parameter"))
74 :group 'message)
75
76(defcustom mml-content-disposition-parameters
77 '(filename creation-date modification-date read-date)
78 "*A list of acceptable parameters in MML tag.
79These parameters are generated in Content-Disposition header if exists."
bf247b6e 80 :version "22.1"
23f87bed
MB
81 :type '(repeat (symbol :tag "Parameter"))
82 :group 'message)
83
01c52d31
MB
84(defcustom mml-content-disposition-alist
85 '((text (rtf . "attachment") (t . "inline"))
86 (t . "attachment"))
87 "Alist of MIME types or regexps matching file names and default dispositions.
88Each element should be one of the following three forms:
89
90 (REGEXP . DISPOSITION)
91 (SUPERTYPE (SUBTYPE . DISPOSITION) (SUBTYPE . DISPOSITION)...)
92 (TYPE . DISPOSITION)
93
94Where REGEXP is a string which matches the file name (if any) of an
95attachment, SUPERTYPE, SUBTYPE and TYPE should be symbols which are a
96MIME supertype (e.g., text), a MIME subtype (e.g., plain) and a MIME
97type (e.g., text/plain) respectively, and DISPOSITION should be either
98the string \"attachment\" or the string \"inline\". The value t for
99SUPERTYPE, SUBTYPE or TYPE matches any of those types. The first
100match found will be used."
330f707b 101 :version "23.1" ;; No Gnus
01c52d31
MB
102 :type (let ((dispositions '(radio :format "DISPOSITION: %v"
103 :value "attachment"
104 (const :format "%v " "attachment")
105 (const :format "%v\n" "inline"))))
106 `(repeat
107 :offset 0
108 (choice :format "%[Value Menu%]%v"
109 (cons :tag "(REGEXP . DISPOSITION)" :extra-offset 4
110 (regexp :tag "REGEXP" :value ".*")
111 ,dispositions)
112 (cons :tag "(SUPERTYPE (SUBTYPE . DISPOSITION)...)"
113 :indent 0
114 (symbol :tag " SUPERTYPE" :value text)
115 (repeat :format "%v%i\n" :offset 0 :extra-offset 4
116 (cons :format "%v" :extra-offset 5
117 (symbol :tag "SUBTYPE" :value t)
118 ,dispositions)))
119 (cons :tag "(TYPE . DISPOSITION)" :extra-offset 4
120 (symbol :tag "TYPE" :value t)
121 ,dispositions))))
122 :group 'message)
123
1f609549 124(defcustom mml-insert-mime-headers-always t
23f87bed
MB
125 "If non-nil, always put Content-Type: text/plain at top of empty parts.
126It is necessary to work against a bug in certain clients."
1a10d421 127 :version "24.1"
23f87bed
MB
128 :type 'boolean
129 :group 'message)
130
131(defvar mml-tweak-type-alist nil
132 "A list of (TYPE . FUNCTION) for tweaking MML parts.
133TYPE is a string containing a regexp to match the MIME type. FUNCTION
134is a Lisp function which is called with the MML handle to tweak the
135part. This variable is used only when no TWEAK parameter exists in
136the MML handle.")
137
138(defvar mml-tweak-function-alist nil
139 "A list of (NAME . FUNCTION) for tweaking MML parts.
140NAME is a string containing the name of the TWEAK parameter in the MML
141handle. FUNCTION is a Lisp function which is called with the MML
142handle to tweak the part.")
143
144(defvar mml-tweak-sexp-alist
145 '((mml-externalize-attachments . mml-tweak-externalize-attachments))
146 "A list of (SEXP . FUNCTION) for tweaking MML parts.
147SEXP is an s-expression. If the evaluation of SEXP is non-nil, FUNCTION
148is called. FUNCTION is a Lisp function which is called with the MML
149handle to tweak the part.")
150
151(defvar mml-externalize-attachments nil
152 "*If non-nil, local-file attachments are generated as external parts.")
153
c113de23
GM
154(defvar mml-generate-multipart-alist nil
155 "*Alist of multipart generation functions.
156Each entry has the form (NAME . FUNCTION), where
a1506d29 157NAME is a string containing the name of the part (without the
c113de23
GM
158leading \"/multipart/\"),
159FUNCTION is a Lisp function which is called to generate the part.
160
161The Lisp function has to supply the appropriate MIME headers and the
162contents of this part.")
163
164(defvar mml-syntax-table
165 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
166 (modify-syntax-entry ?\\ "/" table)
167 (modify-syntax-entry ?< "(" table)
168 (modify-syntax-entry ?> ")" table)
169 (modify-syntax-entry ?@ "w" table)
170 (modify-syntax-entry ?/ "w" table)
171 (modify-syntax-entry ?= " " table)
172 (modify-syntax-entry ?* " " table)
173 (modify-syntax-entry ?\; " " table)
174 (modify-syntax-entry ?\' " " table)
175 table))
176
177(defvar mml-boundary-function 'mml-make-boundary
178 "A function called to suggest a boundary.
179The function may be called several times, and should try to make a new
180suggestion each time. The function is called with one parameter,
181which is a number that says how many times the function has been
182called for this message.")
183
184(defvar mml-confirmation-set nil
185 "A list of symbols, each of which disables some warning.
186`unknown-encoding': always send messages contain characters with
187unknown encoding; `use-ascii': always use ASCII for those characters
188with unknown encoding; `multipart': always send messages with more than
189one charsets.")
190
4b91459a
MB
191(defvar mml-generate-default-type "text/plain"
192 "Content type by which the Content-Type header can be omitted.
193The Content-Type header will not be put in the MIME part if the type
194equals the value and there's no parameter (e.g. charset, format, etc.)
195and `mml-insert-mime-headers-always' is nil. The value will be bound
196to \"message/rfc822\" when encoding an article to be forwarded as a MIME
197part. This is for the internal use, you should never modify the value.")
c113de23
GM
198
199(defvar mml-buffer-list nil)
200
a1506d29 201(defun mml-generate-new-buffer (name)
c113de23
GM
202 (let ((buf (generate-new-buffer name)))
203 (push buf mml-buffer-list)
204 buf))
205
206(defun mml-destroy-buffers ()
207 (let (kill-buffer-hook)
01c52d31 208 (mapc 'kill-buffer mml-buffer-list)
c113de23
GM
209 (setq mml-buffer-list nil)))
210
211(defun mml-parse ()
212 "Parse the current buffer as an MML document."
23f87bed
MB
213 (save-excursion
214 (goto-char (point-min))
01c52d31
MB
215 (with-syntax-table mml-syntax-table
216 (mml-parse-1))))
c113de23
GM
217
218(defun mml-parse-1 ()
219 "Parse the current buffer as an MML document."
220 (let (struct tag point contents charsets warn use-ascii no-markup-p raw)
221 (while (and (not (eobp))
222 (not (looking-at "<#/multipart")))
223 (cond
23f87bed
MB
224 ((looking-at "<#secure")
225 ;; The secure part is essentially a meta-meta tag, which
226 ;; expands to either a part tag if there are no other parts in
227 ;; the document or a multipart tag if there are other parts
228 ;; included in the message
229 (let* (secure-mode
230 (taginfo (mml-read-tag))
01c52d31 231 (keyfile (cdr (assq 'keyfile taginfo)))
a0abd4d6
DU
232 (certfiles (delq nil (mapcar (lambda (tag)
233 (if (eq (car-safe tag) 'certfile)
234 (cdr tag)))
235 taginfo)))
23f87bed
MB
236 (recipients (cdr (assq 'recipients taginfo)))
237 (sender (cdr (assq 'sender taginfo)))
238 (location (cdr (assq 'tag-location taginfo)))
239 (mode (cdr (assq 'mode taginfo)))
240 (method (cdr (assq 'method taginfo)))
241 tags)
242 (save-excursion
01c52d31
MB
243 (if (re-search-forward
244 "<#/?\\(multipart\\|part\\|external\\|mml\\)." nil t)
23f87bed
MB
245 (setq secure-mode "multipart")
246 (setq secure-mode "part")))
247 (save-excursion
248 (goto-char location)
249 (re-search-forward "<#secure[^\n]*>\n"))
250 (delete-region (match-beginning 0) (match-end 0))
251 (cond ((string= mode "sign")
252 (setq tags (list "sign" method)))
253 ((string= mode "encrypt")
254 (setq tags (list "encrypt" method)))
255 ((string= mode "signencrypt")
256 (setq tags (list "sign" method "encrypt" method))))
257 (eval `(mml-insert-tag ,secure-mode
258 ,@tags
01c52d31
MB
259 ,(if keyfile "keyfile")
260 ,keyfile
a0abd4d6
DU
261 ,@(apply #'append
262 (mapcar (lambda (certfile)
263 (list "certfile" certfile))
264 certfiles))
23f87bed
MB
265 ,(if recipients "recipients")
266 ,recipients
267 ,(if sender "sender")
268 ,sender))
269 ;; restart the parse
270 (goto-char location)))
c113de23
GM
271 ((looking-at "<#multipart")
272 (push (nconc (mml-read-tag) (mml-parse-1)) struct))
273 ((looking-at "<#external")
274 (push (nconc (mml-read-tag) (list (cons 'contents (mml-read-part))))
275 struct))
276 (t
277 (if (or (looking-at "<#part") (looking-at "<#mml"))
278 (setq tag (mml-read-tag)
279 no-markup-p nil
280 warn nil)
281 (setq tag (list 'part '(type . "text/plain"))
282 no-markup-p t
283 warn t))
284 (setq raw (cdr (assq 'raw tag))
285 point (point)
ce9401f3 286 contents (mml-read-part (eq 'mml (car tag)))
23f87bed
MB
287 charsets (cond
288 (raw nil)
289 ((assq 'charset tag)
290 (list
291 (intern (downcase (cdr (assq 'charset tag))))))
292 (t
f5490ddc
MB
293 (mm-find-mime-charset-region point (point)
294 mm-hack-charsets))))
c113de23
GM
295 (when (and (not raw) (memq nil charsets))
296 (if (or (memq 'unknown-encoding mml-confirmation-set)
23f87bed
MB
297 (message-options-get 'unknown-encoding)
298 (and (y-or-n-p "\
299Message contains characters with unknown encoding. Really send? ")
300 (message-options-set 'unknown-encoding t)))
a1506d29 301 (if (setq use-ascii
c113de23 302 (or (memq 'use-ascii mml-confirmation-set)
23f87bed
MB
303 (message-options-get 'use-ascii)
304 (and (y-or-n-p "Use ASCII as charset? ")
305 (message-options-set 'use-ascii t))))
c113de23
GM
306 (setq charsets (delq nil charsets))
307 (setq warn nil))
308 (error "Edit your message to remove those characters")))
309 (if (or raw
310 (eq 'mml (car tag))
311 (< (length charsets) 2))
312 (if (or (not no-markup-p)
313 (string-match "[^ \t\r\n]" contents))
314 ;; Don't create blank parts.
315 (push (nconc tag (list (cons 'contents contents)))
316 struct))
317 (let ((nstruct (mml-parse-singlepart-with-multiple-charsets
318 tag point (point) use-ascii)))
319 (when (and warn
320 (not (memq 'multipart mml-confirmation-set))
23f87bed
MB
321 (not (message-options-get 'multipart))
322 (not (and (y-or-n-p (format "\
2453b2e8 323A message part needs to be split into %d charset parts. Really send? "
23f87bed
MB
324 (length nstruct)))
325 (message-options-set 'multipart t))))
c113de23
GM
326 (error "Edit your message to use only one charset"))
327 (setq struct (nconc nstruct struct)))))))
328 (unless (eobp)
329 (forward-line 1))
330 (nreverse struct)))
331
a1506d29 332(defun mml-parse-singlepart-with-multiple-charsets
c113de23
GM
333 (orig-tag beg end &optional use-ascii)
334 (save-excursion
335 (save-restriction
336 (narrow-to-region beg end)
337 (goto-char (point-min))
338 (let ((current (or (mm-mime-charset (mm-charset-after))
339 (and use-ascii 'us-ascii)))
340 charset struct space newline paragraph)
341 (while (not (eobp))
342 (setq charset (mm-mime-charset (mm-charset-after)))
343 (cond
344 ;; The charset remains the same.
345 ((eq charset 'us-ascii))
346 ((or (and use-ascii (not charset))
347 (eq charset current))
348 (setq space nil
349 newline nil
350 paragraph nil))
351 ;; The initial charset was ascii.
352 ((eq current 'us-ascii)
353 (setq current charset
354 space nil
355 newline nil
356 paragraph nil))
357 ;; We have a change in charsets.
358 (t
359 (push (append
360 orig-tag
361 (list (cons 'contents
362 (buffer-substring-no-properties
363 beg (or paragraph newline space (point))))))
364 struct)
365 (setq beg (or paragraph newline space (point))
366 current charset
367 space nil
368 newline nil
369 paragraph nil)))
370 ;; Compute places where it might be nice to break the part.
371 (cond
372 ((memq (following-char) '(? ?\t))
373 (setq space (1+ (point))))
374 ((and (eq (following-char) ?\n)
375 (not (bobp))
376 (eq (char-after (1- (point))) ?\n))
377 (setq paragraph (point)))
378 ((eq (following-char) ?\n)
379 (setq newline (1+ (point)))))
380 (forward-char 1))
381 ;; Do the final part.
382 (unless (= beg (point))
383 (push (append orig-tag
384 (list (cons 'contents
385 (buffer-substring-no-properties
386 beg (point)))))
387 struct))
388 struct))))
389
390(defun mml-read-tag ()
391 "Read a tag and return the contents."
23f87bed
MB
392 (let ((orig-point (point))
393 contents name elem val)
c113de23
GM
394 (forward-char 2)
395 (setq name (buffer-substring-no-properties
396 (point) (progn (forward-sexp 1) (point))))
397 (skip-chars-forward " \t\n")
23f87bed 398 (while (not (looking-at ">[ \t]*\n?"))
c113de23
GM
399 (setq elem (buffer-substring-no-properties
400 (point) (progn (forward-sexp 1) (point))))
401 (skip-chars-forward "= \t\n")
402 (setq val (buffer-substring-no-properties
403 (point) (progn (forward-sexp 1) (point))))
4a44ff5f
KY
404 (when (string-match "\\`\"" val)
405 (setq val (read val))) ;; inverse of prin1 in mml-insert-tag
c113de23
GM
406 (push (cons (intern elem) val) contents)
407 (skip-chars-forward " \t\n"))
23f87bed
MB
408 (goto-char (match-end 0))
409 ;; Don't skip the leading space.
410 ;;(skip-chars-forward " \t\n")
411 ;; Put the tag location into the returned contents
412 (setq contents (append (list (cons 'tag-location orig-point)) contents))
c113de23
GM
413 (cons (intern name) (nreverse contents))))
414
23f87bed
MB
415(defun mml-buffer-substring-no-properties-except-hard-newlines (start end)
416 (let ((str (buffer-substring-no-properties start end))
417 (bufstart start) tmp)
418 (while (setq tmp (text-property-any start end 'hard 't))
419 (set-text-properties (- tmp bufstart) (- tmp bufstart -1)
420 '(hard t) str)
421 (setq start (1+ tmp)))
422 str))
423
c113de23
GM
424(defun mml-read-part (&optional mml)
425 "Return the buffer up till the next part, multipart or closing part or multipart.
426If MML is non-nil, return the buffer up till the correspondent mml tag."
427 (let ((beg (point)) (count 1))
23f87bed 428 ;; If the tag ended at the end of the line, we go to the next line.
c113de23
GM
429 (when (looking-at "[ \t]*\n")
430 (forward-line 1))
431 (if mml
432 (progn
433 (while (and (> count 0) (not (eobp)))
434 (if (re-search-forward "<#\\(/\\)?mml." nil t)
435 (setq count (+ count (if (match-beginning 1) -1 1)))
436 (goto-char (point-max))))
23f87bed
MB
437 (mml-buffer-substring-no-properties-except-hard-newlines
438 beg (if (> count 0)
439 (point)
440 (match-beginning 0))))
c113de23
GM
441 (if (re-search-forward
442 "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t)
443 (prog1
23f87bed
MB
444 (mml-buffer-substring-no-properties-except-hard-newlines
445 beg (match-beginning 0))
c113de23
GM
446 (if (or (not (match-beginning 1))
447 (equal (match-string 2) "multipart"))
448 (goto-char (match-beginning 0))
449 (when (looking-at "[ \t]*\n")
450 (forward-line 1))))
23f87bed
MB
451 (mml-buffer-substring-no-properties-except-hard-newlines
452 beg (goto-char (point-max)))))))
c113de23
GM
453
454(defvar mml-boundary nil)
455(defvar mml-base-boundary "-=-=")
456(defvar mml-multipart-number 0)
457
458(defun mml-generate-mime ()
459 "Generate a MIME message based on the current MML document."
460 (let ((cont (mml-parse))
461 (mml-multipart-number mml-multipart-number))
462 (if (not cont)
463 nil
7f22a765 464 (mm-with-multibyte-buffer
c113de23
GM
465 (if (and (consp (car cont))
466 (= (length cont) 1))
467 (mml-generate-mime-1 (car cont))
468 (mml-generate-mime-1 (nconc (list 'multipart '(type . "mixed"))
469 cont)))
470 (buffer-string)))))
471
472(defun mml-generate-mime-1 (cont)
23f87bed
MB
473 (let ((mm-use-ultra-safe-encoding
474 (or mm-use-ultra-safe-encoding (assq 'sign cont))))
475 (save-restriction
476 (narrow-to-region (point) (point))
477 (mml-tweak-part cont)
478 (cond
479 ((or (eq (car cont) 'part) (eq (car cont) 'mml))
4b91459a
MB
480 (let* ((raw (cdr (assq 'raw cont)))
481 (filename (cdr (assq 'filename cont)))
482 (type (or (cdr (assq 'type cont))
afea040a
MB
483 (if filename
484 (or (mm-default-file-encoding filename)
485 "application/octet-stream")
486 "text/plain")))
01c52d31
MB
487 (charset (cdr (assq 'charset cont)))
488 (coding (mm-charset-to-coding-system charset))
489 encoding flowed coded)
490 (cond ((eq coding 'ascii)
491 (setq charset nil
492 coding nil))
493 (charset
bf46b4d4
MB
494 ;; The value of `charset' might be a bogus alias that
495 ;; `mm-charset-synonym-alist' provides, like `utf8',
496 ;; so we prefer the MIME charset that Emacs knows for
497 ;; the coding system `coding'.
498 (setq charset (or (mm-coding-system-to-mime-charset coding)
499 (intern (downcase charset))))))
23f87bed
MB
500 (if (and (not raw)
501 (member (car (split-string type "/")) '("text" "message")))
502 (progn
503 (with-temp-buffer
23f87bed
MB
504 (cond
505 ((cdr (assq 'buffer cont))
506 (insert-buffer-substring (cdr (assq 'buffer cont))))
4b91459a 507 ((and filename
23f87bed 508 (not (equal (cdr (assq 'nofile cont)) "yes")))
01c52d31 509 (let ((coding-system-for-read coding))
23f87bed
MB
510 (mm-insert-file-contents filename)))
511 ((eq 'mml (car cont))
512 (insert (cdr (assq 'contents cont))))
513 (t
514 (save-restriction
515 (narrow-to-region (point) (point))
516 (insert (cdr (assq 'contents cont)))
517 ;; Remove quotes from quoted tags.
518 (goto-char (point-min))
519 (while (re-search-forward
520 "<#!+/?\\(part\\|multipart\\|external\\|mml\\)"
521 nil t)
522 (delete-region (+ (match-beginning 0) 2)
523 (+ (match-beginning 0) 3))))))
524 (cond
525 ((eq (car cont) 'mml)
526 (let ((mml-boundary (mml-compute-boundary cont))
4b91459a
MB
527 ;; It is necessary for the case where this
528 ;; function is called recursively since
529 ;; `m-g-d-t' will be bound to "message/rfc822"
530 ;; when encoding an article to be forwarded.
23f87bed 531 (mml-generate-default-type "text/plain"))
ee309757
KY
532 (mml-to-mime)
533 ;; Update handle so mml-compute-boundary can
534 ;; detect collisions with the nested parts.
535 (setcdr (assoc 'contents cont) (buffer-string)))
23f87bed
MB
536 (let ((mm-7bit-chars (concat mm-7bit-chars "\x1b")))
537 ;; ignore 0x1b, it is part of iso-2022-jp
538 (setq encoding (mm-body-7-or-8))))
539 ((string= (car (split-string type "/")) "message")
540 (let ((mm-7bit-chars (concat mm-7bit-chars "\x1b")))
541 ;; ignore 0x1b, it is part of iso-2022-jp
542 (setq encoding (mm-body-7-or-8))))
543 (t
544 ;; Only perform format=flowed filling on text/plain
545 ;; parts where there either isn't a format parameter
546 ;; in the mml tag or it says "flowed" and there
547 ;; actually are hard newlines in the text.
548 (let (use-hard-newlines)
549 (when (and (string= type "text/plain")
f4dd4ae8 550 (not (string= (cdr (assq 'sign cont)) "pgp"))
23f87bed
MB
551 (or (null (assq 'format cont))
552 (string= (cdr (assq 'format cont))
553 "flowed"))
554 (setq use-hard-newlines
555 (text-property-any
556 (point-min) (point-max) 'hard 't)))
557 (fill-flowed-encode)
558 ;; Indicate that `mml-insert-mime-headers' should
559 ;; insert a "; format=flowed" string unless the
560 ;; user has already specified it.
561 (setq flowed (null (assq 'format cont)))))
01c52d31
MB
562 ;; Prefer `utf-8' for text/calendar parts.
563 (if (or charset
564 (not (string= type "text/calendar")))
565 (setq charset (mm-encode-body charset))
566 (let ((mm-coding-system-priorities
567 (cons 'utf-8 mm-coding-system-priorities)))
568 (setq charset (mm-encode-body))))
23f87bed
MB
569 (setq encoding (mm-body-encoding
570 charset (cdr (assq 'encoding cont))))))
571 (setq coded (buffer-string)))
572 (mml-insert-mime-headers cont type charset encoding flowed)
573 (insert "\n")
574 (insert coded))
575 (mm-with-unibyte-buffer
576 (cond
577 ((cdr (assq 'buffer cont))
4573e0df
MB
578 (insert (mm-string-as-unibyte
579 (with-current-buffer (cdr (assq 'buffer cont))
580 (buffer-string)))))
4b91459a 581 ((and filename
23f87bed
MB
582 (not (equal (cdr (assq 'nofile cont)) "yes")))
583 (let ((coding-system-for-read mm-binary-coding-system))
01c52d31
MB
584 (mm-insert-file-contents filename nil nil nil nil t))
585 (unless charset
586 (setq charset (mm-coding-system-to-mime-charset
587 (mm-find-buffer-file-coding-system
588 filename)))))
23f87bed 589 (t
719120ef
MB
590 (let ((contents (cdr (assq 'contents cont))))
591 (if (if (featurep 'xemacs)
592 (string-match "[^\000-\377]" contents)
593 (mm-multibyte-string-p contents))
594 (progn
595 (mm-enable-multibyte)
596 (insert contents)
7f22a765 597 (unless raw
01c52d31 598 (setq charset (mm-encode-body charset))))
719120ef 599 (insert contents)))))
de0bdfe7
KY
600 (if (setq encoding (cdr (assq 'encoding cont)))
601 (setq encoding (intern (downcase encoding))))
602 (setq encoding (mm-encode-buffer type encoding)
23f87bed
MB
603 coded (mm-string-as-multibyte (buffer-string))))
604 (mml-insert-mime-headers cont type charset encoding nil)
7f22a765 605 (insert "\n" coded))))
23f87bed
MB
606 ((eq (car cont) 'external)
607 (insert "Content-Type: message/external-body")
608 (let ((parameters (mml-parameter-string
609 cont '(expiration size permission)))
610 (name (cdr (assq 'name cont)))
611 (url (cdr (assq 'url cont))))
612 (when name
613 (setq name (mml-parse-file-name name))
614 (if (stringp name)
615 (mml-insert-parameter
616 (mail-header-encode-parameter "name" name)
617 "access-type=local-file")
c113de23 618 (mml-insert-parameter
23f87bed
MB
619 (mail-header-encode-parameter
620 "name" (file-name-nondirectory (nth 2 name)))
621 (mail-header-encode-parameter "site" (nth 1 name))
622 (mail-header-encode-parameter
623 "directory" (file-name-directory (nth 2 name))))
624 (mml-insert-parameter
625 (concat "access-type="
626 (if (member (nth 0 name) '("ftp@" "anonymous@"))
627 "anon-ftp"
628 "ftp")))))
629 (when url
c113de23 630 (mml-insert-parameter
23f87bed
MB
631 (mail-header-encode-parameter "url" url)
632 "access-type=url"))
633 (when parameters
634 (mml-insert-parameter-string
4b91459a
MB
635 cont '(expiration size permission)))
636 (insert "\n\n")
637 (insert "Content-Type: "
638 (or (cdr (assq 'type cont))
afea040a
MB
639 (if name
640 (or (mm-default-file-encoding name)
641 "application/octet-stream")
642 "text/plain"))
4b91459a
MB
643 "\n")
644 (insert "Content-ID: " (message-make-message-id) "\n")
645 (insert "Content-Transfer-Encoding: "
646 (or (cdr (assq 'encoding cont)) "binary"))
647 (insert "\n\n")
648 (insert (or (cdr (assq 'contents cont))))
649 (insert "\n")))
23f87bed
MB
650 ((eq (car cont) 'multipart)
651 (let* ((type (or (cdr (assq 'type cont)) "mixed"))
652 (mml-generate-default-type (if (equal type "digest")
653 "message/rfc822"
654 "text/plain"))
655 (handler (assoc type mml-generate-multipart-alist)))
656 (if handler
657 (funcall (cdr handler) cont)
658 ;; No specific handler. Use default one.
659 (let ((mml-boundary (mml-compute-boundary cont)))
660 (insert (format "Content-Type: multipart/%s; boundary=\"%s\""
661 type mml-boundary)
662 (if (cdr (assq 'start cont))
663 (format "; start=\"%s\"\n" (cdr (assq 'start cont)))
664 "\n"))
665 (let ((cont cont) part)
666 (while (setq part (pop cont))
667 ;; Skip `multipart' and attributes.
668 (when (and (consp part) (consp (cdr part)))
669 (insert "\n--" mml-boundary "\n")
6203370b
MB
670 (mml-generate-mime-1 part)
671 (goto-char (point-max)))))
23f87bed
MB
672 (insert "\n--" mml-boundary "--\n")))))
673 (t
674 (error "Invalid element: %S" cont)))
675 ;; handle sign & encrypt tags in a semi-smart way.
676 (let ((sign-item (assoc (cdr (assq 'sign cont)) mml-sign-alist))
677 (encrypt-item (assoc (cdr (assq 'encrypt cont))
678 mml-encrypt-alist))
679 sender recipients)
680 (when (or sign-item encrypt-item)
681 (when (setq sender (cdr (assq 'sender cont)))
682 (message-options-set 'mml-sender sender)
683 (message-options-set 'message-sender sender))
684 (if (setq recipients (cdr (assq 'recipients cont)))
685 (message-options-set 'message-recipients recipients))
4b91459a
MB
686 (let ((style (mml-signencrypt-style
687 (first (or sign-item encrypt-item)))))
23f87bed
MB
688 ;; check if: we're both signing & encrypting, both methods
689 ;; are the same (why would they be different?!), and that
690 ;; the signencrypt style allows for combined operation.
691 (if (and sign-item encrypt-item (equal (first sign-item)
692 (first encrypt-item))
693 (equal style 'combined))
694 (funcall (nth 1 encrypt-item) cont t)
695 ;; otherwise, revert to the old behavior.
696 (when sign-item
697 (funcall (nth 1 sign-item) cont))
698 (when encrypt-item
699 (funcall (nth 1 encrypt-item) cont)))))))))
c113de23
GM
700
701(defun mml-compute-boundary (cont)
702 "Return a unique boundary that does not exist in CONT."
703 (let ((mml-boundary (funcall mml-boundary-function
704 (incf mml-multipart-number))))
705 ;; This function tries again and again until it has found
706 ;; a unique boundary.
707 (while (not (catch 'not-unique
708 (mml-compute-boundary-1 cont))))
709 mml-boundary))
710
711(defun mml-compute-boundary-1 (cont)
712 (let (filename)
713 (cond
ee309757 714 ((member (car cont) '(part mml))
c113de23
GM
715 (with-temp-buffer
716 (cond
717 ((cdr (assq 'buffer cont))
718 (insert-buffer-substring (cdr (assq 'buffer cont))))
719 ((and (setq filename (cdr (assq 'filename cont)))
720 (not (equal (cdr (assq 'nofile cont)) "yes")))
f4dd4ae8 721 (mm-insert-file-contents filename nil nil nil nil t))
c113de23
GM
722 (t
723 (insert (cdr (assq 'contents cont)))))
724 (goto-char (point-min))
725 (when (re-search-forward (concat "^--" (regexp-quote mml-boundary))
726 nil t)
727 (setq mml-boundary (funcall mml-boundary-function
728 (incf mml-multipart-number)))
729 (throw 'not-unique nil))))
730 ((eq (car cont) 'multipart)
01c52d31 731 (mapc 'mml-compute-boundary-1 (cddr cont))))
c113de23
GM
732 t))
733
734(defun mml-make-boundary (number)
735 (concat (make-string (% number 60) ?=)
736 (if (> number 17)
737 (format "%x" number)
738 "")
739 mml-base-boundary))
740
01c52d31
MB
741(defun mml-content-disposition (type &optional filename)
742 "Return a default disposition name suitable to TYPE or FILENAME."
743 (let ((defs mml-content-disposition-alist)
744 disposition def types)
745 (while (and (not disposition) defs)
746 (setq def (pop defs))
747 (cond ((stringp (car def))
748 (when (and filename
749 (string-match (car def) filename))
750 (setq disposition (cdr def))))
751 ((consp (cdr def))
752 (when (string= (car (setq types (split-string type "/")))
753 (car def))
754 (setq type (cadr types)
755 types (cdr def))
756 (while (and (not disposition) types)
757 (setq def (pop types))
758 (when (or (eq (car def) t) (string= type (car def)))
759 (setq disposition (cdr def))))))
760 (t
761 (when (or (eq (car def) t) (string= type (car def)))
762 (setq disposition (cdr def))))))
763 (or disposition "attachment")))
764
23f87bed
MB
765(defun mml-insert-mime-headers (cont type charset encoding flowed)
766 (let (parameters id disposition description)
c113de23
GM
767 (setq parameters
768 (mml-parameter-string
23f87bed 769 cont mml-content-type-parameters))
c113de23
GM
770 (when (or charset
771 parameters
23f87bed
MB
772 flowed
773 (not (equal type mml-generate-default-type))
774 mml-insert-mime-headers-always)
c113de23
GM
775 (when (consp charset)
776 (error
23f87bed 777 "Can't encode a part with several charsets"))
c113de23
GM
778 (insert "Content-Type: " type)
779 (when charset
c96ec15a
MB
780 (mml-insert-parameter
781 (mail-header-encode-parameter "charset" (symbol-name charset))))
23f87bed 782 (when flowed
c96ec15a 783 (mml-insert-parameter "format=flowed"))
c113de23
GM
784 (when parameters
785 (mml-insert-parameter-string
23f87bed 786 cont mml-content-type-parameters))
c113de23 787 (insert "\n"))
23f87bed
MB
788 (when (setq id (cdr (assq 'id cont)))
789 (insert "Content-ID: " id "\n"))
c113de23
GM
790 (setq parameters
791 (mml-parameter-string
23f87bed 792 cont mml-content-disposition-parameters))
c113de23
GM
793 (when (or (setq disposition (cdr (assq 'disposition cont)))
794 parameters)
01c52d31
MB
795 (insert "Content-Disposition: "
796 (or disposition
797 (mml-content-disposition type (cdr (assq 'filename cont)))))
c113de23
GM
798 (when parameters
799 (mml-insert-parameter-string
23f87bed 800 cont mml-content-disposition-parameters))
c113de23
GM
801 (insert "\n"))
802 (unless (eq encoding '7bit)
803 (insert (format "Content-Transfer-Encoding: %s\n" encoding)))
804 (when (setq description (cdr (assq 'description cont)))
c96ec15a
MB
805 (insert "Content-Description: ")
806 (setq description (prog1
807 (point)
808 (insert description "\n")))
809 (mail-encode-encoded-word-region description (point)))))
c113de23
GM
810
811(defun mml-parameter-string (cont types)
812 (let ((string "")
813 value type)
814 (while (setq type (pop types))
815 (when (setq value (cdr (assq type cont)))
816 ;; Strip directory component from the filename parameter.
817 (when (eq type 'filename)
818 (setq value (file-name-nondirectory value)))
819 (setq string (concat string "; "
820 (mail-header-encode-parameter
821 (symbol-name type) value)))))
822 (when (not (zerop (length string)))
823 string)))
824
825(defun mml-insert-parameter-string (cont types)
826 (let (value type)
827 (while (setq type (pop types))
828 (when (setq value (cdr (assq type cont)))
829 ;; Strip directory component from the filename parameter.
830 (when (eq type 'filename)
831 (setq value (file-name-nondirectory value)))
832 (mml-insert-parameter
833 (mail-header-encode-parameter
834 (symbol-name type) value))))))
835
9efa445f
DN
836(defvar ange-ftp-name-format)
837(defvar efs-path-regexp)
838
c113de23
GM
839(defun mml-parse-file-name (path)
840 (if (if (boundp 'efs-path-regexp)
841 (string-match efs-path-regexp path)
842 (if (boundp 'ange-ftp-name-format)
843 (string-match (car ange-ftp-name-format) path)))
844 (list (match-string 1 path) (match-string 2 path)
845 (substring path (1+ (match-end 2))))
846 path))
847
848(defun mml-insert-buffer (buffer)
849 "Insert BUFFER at point and quote any MML markup."
850 (save-restriction
851 (narrow-to-region (point) (point))
852 (insert-buffer-substring buffer)
853 (mml-quote-region (point-min) (point-max))
854 (goto-char (point-max))))
855
856;;;
857;;; Transforming MIME to MML
858;;;
859
541cbf8b
GM
860;; message-narrow-to-head autoloads message.
861(declare-function message-remove-header "message"
862 (header &optional is-regexp first reverse))
863
23f87bed
MB
864(defun mime-to-mml (&optional handles)
865 "Translate the current buffer (which should be a message) into MML.
866If HANDLES is non-nil, use it instead reparsing the buffer."
c113de23
GM
867 ;; First decode the head.
868 (save-restriction
869 (message-narrow-to-head)
270a576a
MB
870 (let ((rfc2047-quote-decoded-words-containing-tspecials t))
871 (mail-decode-encoded-word-region (point-min) (point-max))))
23f87bed
MB
872 (unless handles
873 (setq handles (mm-dissect-buffer t)))
874 (goto-char (point-min))
875 (search-forward "\n\n" nil t)
876 (delete-region (point) (point-max))
877 (if (stringp (car handles))
878 (mml-insert-mime handles)
879 (mml-insert-mime handles t))
880 (mm-destroy-parts handles)
c113de23
GM
881 (save-restriction
882 (message-narrow-to-head)
883 ;; Remove them, they are confusing.
884 (message-remove-header "Content-Type")
885 (message-remove-header "MIME-Version")
23f87bed 886 (message-remove-header "Content-Disposition")
c113de23
GM
887 (message-remove-header "Content-Transfer-Encoding")))
888
541cbf8b
GM
889(autoload 'message-encode-message-body "message")
890(declare-function message-narrow-to-headers-or-head "message" ())
891
c113de23
GM
892(defun mml-to-mime ()
893 "Translate the current buffer from MML to MIME."
4599d0ec
MB
894 ;; `message-encode-message-body' will insert an encoded Content-Description
895 ;; header in the message header if the body contains a single part
896 ;; that is specified by a user with a MML tag containing a description
897 ;; token. So, we encode the message header first to prevent the encoded
898 ;; Content-Description header from being encoded again.
c113de23
GM
899 (save-restriction
900 (message-narrow-to-headers-or-head)
23f87bed
MB
901 ;; Skip past any From_ headers.
902 (while (looking-at "From ")
903 (forward-line 1))
c113de23 904 (let ((mail-parse-charset message-default-charset))
4599d0ec
MB
905 (mail-encode-encoded-word-buffer)))
906 (message-encode-message-body))
c113de23
GM
907
908(defun mml-insert-mime (handle &optional no-markup)
909 (let (textp buffer mmlp)
910 ;; Determine type and stuff.
911 (unless (stringp (car handle))
912 (unless (setq textp (equal (mm-handle-media-supertype handle) "text"))
bbf52f1e 913 (with-current-buffer (setq buffer (mml-generate-new-buffer " *mml*"))
9cdff613
MB
914 (if (eq (mail-content-type-get (mm-handle-type handle) 'charset)
915 'gnus-decoded)
916 ;; A part that mm-uu dissected from a non-MIME message
917 ;; because of `gnus-article-emulate-mime'.
918 (progn
919 (mm-enable-multibyte)
920 (insert-buffer-substring (mm-handle-buffer handle)))
921 (mm-insert-part handle 'no-cache)
922 (if (setq mmlp (equal (mm-handle-media-type handle)
923 "message/rfc822"))
924 (mime-to-mml))))))
c113de23
GM
925 (if mmlp
926 (mml-insert-mml-markup handle nil t t)
927 (unless (and no-markup
928 (equal (mm-handle-media-type handle) "text/plain"))
929 (mml-insert-mml-markup handle buffer textp)))
930 (cond
a1506d29 931 (mmlp
23f87bed 932 (insert-buffer-substring buffer)
c113de23
GM
933 (goto-char (point-max))
934 (insert "<#/mml>\n"))
935 ((stringp (car handle))
01c52d31 936 (mapc 'mml-insert-mime (cdr handle))
c113de23
GM
937 (insert "<#/multipart>\n"))
938 (textp
23f87bed
MB
939 (let ((charset (mail-content-type-get
940 (mm-handle-type handle) 'charset))
941 (start (point)))
942 (if (eq charset 'gnus-decoded)
943 (mm-insert-part handle)
944 (insert (mm-decode-string (mm-get-part handle) charset)))
945 (mml-quote-region start (point)))
c113de23
GM
946 (goto-char (point-max)))
947 (t
948 (insert "<#/part>\n")))))
949
950(defun mml-insert-mml-markup (handle &optional buffer nofile mmlp)
951 "Take a MIME handle and insert an MML tag."
952 (if (stringp (car handle))
23f87bed
MB
953 (progn
954 (insert "<#multipart type=" (mm-handle-media-subtype handle))
955 (let ((start (mm-handle-multipart-ctl-parameter handle 'start)))
956 (when start
957 (insert " start=\"" start "\"")))
958 (insert ">\n"))
c113de23
GM
959 (if mmlp
960 (insert "<#mml type=" (mm-handle-media-type handle))
961 (insert "<#part type=" (mm-handle-media-type handle)))
962 (dolist (elem (append (cdr (mm-handle-type handle))
963 (cdr (mm-handle-disposition handle))))
23f87bed
MB
964 (unless (symbolp (cdr elem))
965 (insert " " (symbol-name (car elem)) "=\"" (cdr elem) "\"")))
966 (when (mm-handle-id handle)
967 (insert " id=\"" (mm-handle-id handle) "\""))
c113de23
GM
968 (when (mm-handle-disposition handle)
969 (insert " disposition=" (car (mm-handle-disposition handle))))
970 (when buffer
971 (insert " buffer=\"" (buffer-name buffer) "\""))
972 (when nofile
973 (insert " nofile=yes"))
974 (when (mm-handle-description handle)
975 (insert " description=\"" (mm-handle-description handle) "\""))
976 (insert ">\n")))
977
978(defun mml-insert-parameter (&rest parameters)
979 "Insert PARAMETERS in a nice way."
c96ec15a
MB
980 (let (start end)
981 (dolist (param parameters)
982 (insert ";")
983 (setq start (point))
c113de23 984 (insert " " param)
c96ec15a
MB
985 (setq end (point))
986 (goto-char start)
987 (end-of-line)
988 (if (> (current-column) 76)
989 (progn
990 (goto-char start)
991 (insert "\n")
992 (goto-char (1+ end)))
993 (goto-char end)))))
c113de23
GM
994
995;;;
996;;; Mode for inserting and editing MML forms
997;;;
998
999(defvar mml-mode-map
23f87bed
MB
1000 (let ((sign (make-sparse-keymap))
1001 (encrypt (make-sparse-keymap))
1002 (signpart (make-sparse-keymap))
1003 (encryptpart (make-sparse-keymap))
1004 (map (make-sparse-keymap))
c113de23 1005 (main (make-sparse-keymap)))
0565caeb
MB
1006 (define-key map "\C-s" 'mml-secure-message-sign)
1007 (define-key map "\C-c" 'mml-secure-message-encrypt)
1008 (define-key map "\C-e" 'mml-secure-message-sign-encrypt)
1009 (define-key map "\C-p\C-s" 'mml-secure-sign)
1010 (define-key map "\C-p\C-c" 'mml-secure-encrypt)
23f87bed
MB
1011 (define-key sign "p" 'mml-secure-message-sign-pgpmime)
1012 (define-key sign "o" 'mml-secure-message-sign-pgp)
1013 (define-key sign "s" 'mml-secure-message-sign-smime)
1014 (define-key signpart "p" 'mml-secure-sign-pgpmime)
1015 (define-key signpart "o" 'mml-secure-sign-pgp)
1016 (define-key signpart "s" 'mml-secure-sign-smime)
1017 (define-key encrypt "p" 'mml-secure-message-encrypt-pgpmime)
1018 (define-key encrypt "o" 'mml-secure-message-encrypt-pgp)
1019 (define-key encrypt "s" 'mml-secure-message-encrypt-smime)
1020 (define-key encryptpart "p" 'mml-secure-encrypt-pgpmime)
1021 (define-key encryptpart "o" 'mml-secure-encrypt-pgp)
1022 (define-key encryptpart "s" 'mml-secure-encrypt-smime)
1023 (define-key map "\C-n" 'mml-unsecure-message)
c113de23
GM
1024 (define-key map "f" 'mml-attach-file)
1025 (define-key map "b" 'mml-attach-buffer)
1026 (define-key map "e" 'mml-attach-external)
1027 (define-key map "q" 'mml-quote-region)
1028 (define-key map "m" 'mml-insert-multipart)
1029 (define-key map "p" 'mml-insert-part)
1030 (define-key map "v" 'mml-validate)
1031 (define-key map "P" 'mml-preview)
23f87bed
MB
1032 (define-key map "s" sign)
1033 (define-key map "S" signpart)
1034 (define-key map "c" encrypt)
1035 (define-key map "C" encryptpart)
c113de23 1036 ;;(define-key map "n" 'mml-narrow-to-part)
23f87bed
MB
1037 ;; `M-m' conflicts with `back-to-indentation'.
1038 ;; (define-key main "\M-m" map)
1039 (define-key main "\C-c\C-m" map)
c113de23
GM
1040 main))
1041
1042(easy-menu-define
f4bb9409 1043 mml-menu mml-mode-map ""
23f87bed
MB
1044 `("Attachments"
1045 ["Attach File..." mml-attach-file
1046 ,@(if (featurep 'xemacs) '(t)
1047 '(:help "Attach a file at point"))]
0565caeb
MB
1048 ["Attach Buffer..." mml-attach-buffer
1049 ,@(if (featurep 'xemacs) '(t)
8f7abae3 1050 '(:help "Attach a buffer to the outgoing message"))]
0565caeb
MB
1051 ["Attach External..." mml-attach-external
1052 ,@(if (featurep 'xemacs) '(t)
8f7abae3 1053 '(:help "Attach reference to an external file"))]
9b3ebcb6
MB
1054 ;; FIXME: Is it possible to do this without using
1055 ;; `gnus-gcc-externalize-attachments'?
1056 ["Externalize Attachments"
1057 (lambda ()
1058 (interactive)
1059 (if (not (and (boundp 'gnus-gcc-externalize-attachments)
1060 (memq gnus-gcc-externalize-attachments
1061 '(all t nil))))
1062 ;; Stupid workaround for XEmacs not honoring :visible.
1063 (message "Can't handle this value of `gnus-gcc-externalize-attachments'")
1064 (setq gnus-gcc-externalize-attachments
1065 (not gnus-gcc-externalize-attachments))
1066 (message "gnus-gcc-externalize-attachments is `%s'."
1067 gnus-gcc-externalize-attachments)))
1068 ;; XEmacs barfs on :visible.
1069 ,@(if (featurep 'xemacs) nil
1070 '(:visible (and (boundp 'gnus-gcc-externalize-attachments)
1071 (memq gnus-gcc-externalize-attachments
1072 '(all t nil)))))
1073 :style toggle
1074 :selected gnus-gcc-externalize-attachments
1075 ,@(if (featurep 'xemacs) nil
1076 '(:help "Save attachments as external parts in Gcc copies"))]
8f7abae3 1077 "----"
0565caeb
MB
1078 ;;
1079 ("Change Security Method"
1080 ["PGP/MIME"
1081 (lambda () (interactive) (setq mml-secure-method "pgpmime"))
1082 ,@(if (featurep 'xemacs) nil
1083 '(:help "Set Security Method to PGP/MIME"))
1084 :style radio
1085 :selected (equal mml-secure-method "pgpmime") ]
1086 ["S/MIME"
1087 (lambda () (interactive) (setq mml-secure-method "smime"))
1088 ,@(if (featurep 'xemacs) nil
1089 '(:help "Set Security Method to S/MIME"))
1090 :style radio
1091 :selected (equal mml-secure-method "smime") ]
1092 ["Inline PGP"
1093 (lambda () (interactive) (setq mml-secure-method "pgp"))
1094 ,@(if (featurep 'xemacs) nil
1095 '(:help "Set Security Method to inline PGP"))
1096 :style radio
1097 :selected (equal mml-secure-method "pgp") ] )
1098 ;;
1099 ["Sign Message" mml-secure-message-sign t]
1100 ["Encrypt Message" mml-secure-message-encrypt t]
1101 ["Sign and Encrypt Message" mml-secure-message-sign-encrypt t]
1102 ["Encrypt/Sign off" mml-unsecure-message
1103 ,@(if (featurep 'xemacs) '(t)
1104 '(:help "Don't Encrypt/Sign Message"))]
8f7abae3
MB
1105 ;; Do we have separate encrypt and encrypt/sign commands for parts?
1106 ["Sign Part" mml-secure-sign t]
1107 ["Encrypt Part" mml-secure-encrypt t]
1108 "----"
0565caeb
MB
1109 ;; Maybe we could remove these, because people who write MML most probably
1110 ;; don't use the menu:
1111 ["Insert Part..." mml-insert-part
1112 :active (message-in-body-p)]
1113 ["Insert Multipart..." mml-insert-multipart
1114 :active (message-in-body-p)]
1115 ;;
f4bb9409 1116 ;;["Narrow" mml-narrow-to-part t]
0565caeb
MB
1117 ["Quote MML in region" mml-quote-region
1118 :active (message-mark-active-p)
1119 ,@(if (featurep 'xemacs) nil
1120 '(:help "Quote MML tags in region"))]
23f87bed 1121 ["Validate MML" mml-validate t]
7347faa8
MB
1122 ["Preview" mml-preview t]
1123 "----"
1124 ["Emacs MIME manual" (lambda () (interactive) (message-info 4))
1125 ,@(if (featurep 'xemacs) '(t)
1126 '(:help "Display the Emacs MIME manual"))]
9b3ebcb6
MB
1127 ["PGG manual" (lambda () (interactive) (message-info mml2015-use))
1128 ;; XEmacs barfs on :visible.
1129 ,@(if (featurep 'xemacs) nil
7335e376 1130 '(:visible (and (boundp 'mml2015-use) (equal mml2015-use 'pgg))))
9b3ebcb6
MB
1131 ,@(if (featurep 'xemacs) '(t)
1132 '(:help "Display the PGG manual"))]
7335e376 1133 ["EasyPG manual" (lambda () (interactive) (require 'mml2015) (message-info mml2015-use))
9b3ebcb6
MB
1134 ;; XEmacs barfs on :visible.
1135 ,@(if (featurep 'xemacs) nil
7335e376 1136 '(:visible (and (boundp 'mml2015-use) (equal mml2015-use 'epg))))
7347faa8 1137 ,@(if (featurep 'xemacs) '(t)
9b3ebcb6 1138 '(:help "Display the EasyPG manual"))]))
c113de23 1139
bbf52f1e 1140(define-minor-mode mml-mode
c113de23 1141 "Minor mode for editing MML.
23f87bed
MB
1142MML is the MIME Meta Language, a minor mode for composing MIME articles.
1143See Info node `(emacs-mime)Composing'.
c113de23
GM
1144
1145\\{mml-mode-map}"
bbf52f1e
SM
1146 :lighter " MML" :keymap mml-mode-map
1147 (when mml-mode
23f87bed 1148 (easy-menu-add mml-menu mml-mode-map)
0565caeb
MB
1149 (when (boundp 'dnd-protocol-alist)
1150 (set (make-local-variable 'dnd-protocol-alist)
bbf52f1e 1151 (append mml-dnd-protocol-alist dnd-protocol-alist)))))
c113de23
GM
1152
1153;;;
1154;;; Helper functions for reading MIME stuff from the minibuffer and
1155;;; inserting stuff to the buffer.
1156;;;
1157
01c52d31
MB
1158(defcustom mml-default-directory mm-default-directory
1159 "The default directory where mml will find files.
1160If not set, `default-directory' will be used."
1161 :type '(choice directory (const :tag "Default" nil))
330f707b 1162 :version "23.1" ;; No Gnus
01c52d31
MB
1163 :group 'message)
1164
c113de23 1165(defun mml-minibuffer-read-file (prompt)
23f87bed 1166 (let* ((completion-ignored-extensions nil)
01c52d31
MB
1167 (file (read-file-name prompt
1168 (or mml-default-directory default-directory)
1169 nil t)))
23f87bed 1170 ;; Prevent some common errors. This is inspired by similar code in
c113de23
GM
1171 ;; VM.
1172 (when (file-directory-p file)
1173 (error "%s is a directory, cannot attach" file))
1174 (unless (file-exists-p file)
1175 (error "No such file: %s" file))
1176 (unless (file-readable-p file)
1177 (error "Permission denied: %s" file))
1178 file))
1179
aa8f8277
GM
1180(declare-function mailcap-parse-mimetypes "mailcap" (&optional path force))
1181(declare-function mailcap-mime-types "mailcap" ())
1182
c113de23 1183(defun mml-minibuffer-read-type (name &optional default)
aa8f8277 1184 (require 'mailcap)
c113de23
GM
1185 (mailcap-parse-mimetypes)
1186 (let* ((default (or default
1187 (mm-default-file-encoding name)
1188 ;; Perhaps here we should check what the file
1189 ;; looks like, and offer text/plain if it looks
1190 ;; like text/plain.
1191 "application/octet-stream"))
229b59da
G
1192 (string (gnus-completing-read
1193 "Content type"
1194 (mailcap-mime-types)
1195 nil nil nil default)))
c113de23
GM
1196 (if (not (equal string ""))
1197 string
1198 default)))
1199
1200(defun mml-minibuffer-read-description ()
1201 (let ((description (read-string "One line description: ")))
1202 (when (string-match "\\`[ \t]*\\'" description)
1203 (setq description nil))
1204 description))
1205
01c52d31
MB
1206(defun mml-minibuffer-read-disposition (type &optional default filename)
1207 (unless default
1208 (setq default (mml-content-disposition type filename)))
229b59da
G
1209 (let ((disposition (gnus-completing-read
1210 "Disposition"
1211 '("attachment" "inline")
1212 t nil nil default)))
23f87bed
MB
1213 (if (not (equal disposition ""))
1214 disposition
1215 default)))
1216
c113de23
GM
1217(defun mml-quote-region (beg end)
1218 "Quote the MML tags in the region."
1219 (interactive "r")
1220 (save-excursion
1221 (save-restriction
1222 ;; Temporarily narrow the region to defend from changes
1223 ;; invalidating END.
1224 (narrow-to-region beg end)
1225 (goto-char (point-min))
1226 ;; Quote parts.
1227 (while (re-search-forward
1228 "<#!*/?\\(multipart\\|part\\|external\\|mml\\)" nil t)
1229 ;; Insert ! after the #.
1230 (goto-char (+ (match-beginning 0) 2))
1231 (insert "!")))))
1232
1233(defun mml-insert-tag (name &rest plist)
1234 "Insert an MML tag described by NAME and PLIST."
1235 (when (symbolp name)
1236 (setq name (symbol-name name)))
1237 (insert "<#" name)
1238 (while plist
1239 (let ((key (pop plist))
1240 (value (pop plist)))
1241 (when value
1242 ;; Quote VALUE if it contains suspicious characters.
1243 (when (string-match "[\"'\\~/*;() \t\n]" value)
23f87bed
MB
1244 (setq value (with-output-to-string
1245 (let (print-escape-nonascii)
1246 (prin1 value)))))
c113de23
GM
1247 (insert (format " %s=%s" key value)))))
1248 (insert ">\n"))
1249
1250(defun mml-insert-empty-tag (name &rest plist)
1251 "Insert an empty MML tag described by NAME and PLIST."
1252 (when (symbolp name)
1253 (setq name (symbol-name name)))
1254 (apply #'mml-insert-tag name plist)
1255 (insert "<#/" name ">\n"))
1256
1257;;; Attachment functions.
1258
0565caeb
MB
1259(defcustom mml-dnd-protocol-alist
1260 '(("^file:///" . mml-dnd-attach-file)
1261 ("^file://" . dnd-open-file)
1262 ("^file:" . mml-dnd-attach-file))
1263 "The functions to call when a drop in `mml-mode' is made.
1264See `dnd-protocol-alist' for more information. When nil, behave
1265as in other buffers."
1266 :type '(choice (repeat (cons (regexp) (function)))
1267 (const :tag "Behave as in other buffers" nil))
1268 :version "22.1" ;; Gnus 5.10.9
1269 :group 'message)
1270
1271(defcustom mml-dnd-attach-options nil
1272 "Which options should be queried when attaching a file via drag and drop.
1273
1274If it is a list, valid members are `type', `description' and
1275`disposition'. `disposition' implies `type'. If it is nil,
1276don't ask for options. If it is t, ask the user whether or not
1277to specify options."
1278 :type '(choice
8f7abae3 1279 (const :tag "None" nil)
0565caeb
MB
1280 (const :tag "Query" t)
1281 (list :value (type description disposition)
1282 (set :inline t
1283 (const type)
1284 (const description)
1285 (const disposition))))
1286 :version "22.1" ;; Gnus 5.10.9
1287 :group 'message)
1288
23f87bed 1289(defun mml-attach-file (file &optional type description disposition)
c113de23
GM
1290 "Attach a file to the outgoing MIME message.
1291The file is not inserted or encoded until you send the message with
1292`\\[message-send-and-exit]' or `\\[message-send]'.
1293
7347faa8
MB
1294FILE is the name of the file to attach. TYPE is its
1295content-type, a string of the form \"type/subtype\". DESCRIPTION
1296is a one-line description of the attachment. The DISPOSITION
1297specifies how the attachment is intended to be displayed. It can
1298be either \"inline\" (displayed automatically within the message
1299body) or \"attachment\" (separate from the body)."
c113de23
GM
1300 (interactive
1301 (let* ((file (mml-minibuffer-read-file "Attach file: "))
1302 (type (mml-minibuffer-read-type file))
23f87bed 1303 (description (mml-minibuffer-read-description))
01c52d31 1304 (disposition (mml-minibuffer-read-disposition type nil file)))
23f87bed 1305 (list file type description disposition)))
29b647c5
MB
1306 ;; Don't move point if this command is invoked inside the message header.
1307 (let ((head (unless (message-in-body-p)
1308 (prog1
1309 (point)
1310 (goto-char (point-max))))))
1311 (mml-insert-empty-tag 'part
1312 'type type
1313 ;; icicles redefines read-file-name and returns a
1314 ;; string w/ text properties :-/
1315 'filename (mm-substring-no-properties file)
1316 'disposition (or disposition "attachment")
1317 'description description)
1318 (when head
1319 (unless (prog1
1320 (pos-visible-in-window-p)
1321 (goto-char head))
1322 (message "The file \"%s\" has been attached at the end of the message"
1323 (file-name-nondirectory file))))))
0565caeb
MB
1324
1325(defun mml-dnd-attach-file (uri action)
1326 "Attach a drag and drop file.
1327
1328Ask for type, description or disposition according to
1329`mml-dnd-attach-options'."
1330 (let ((file (dnd-get-local-file-name uri t)))
1331 (when (and file (file-regular-p file))
1332 (let ((mml-dnd-attach-options mml-dnd-attach-options)
1333 type description disposition)
1334 (setq mml-dnd-attach-options
1335 (when (and (eq mml-dnd-attach-options t)
1336 (not
1337 (y-or-n-p
1338 "Use default type, disposition and description? ")))
1339 '(type description disposition)))
1340 (when (or (memq 'type mml-dnd-attach-options)
1341 (memq 'disposition mml-dnd-attach-options))
1342 (setq type (mml-minibuffer-read-type file)))
1343 (when (memq 'description mml-dnd-attach-options)
1344 (setq description (mml-minibuffer-read-description)))
1345 (when (memq 'disposition mml-dnd-attach-options)
01c52d31 1346 (setq disposition (mml-minibuffer-read-disposition type nil file)))
0565caeb 1347 (mml-attach-file file type description disposition)))))
c113de23 1348
03c673c9 1349(defun mml-attach-buffer (buffer &optional type description disposition)
c113de23 1350 "Attach a buffer to the outgoing MIME message.
03c673c9
MB
1351BUFFER is the name of the buffer to attach. See
1352`mml-attach-file' for details of operation."
c113de23
GM
1353 (interactive
1354 (let* ((buffer (read-buffer "Attach buffer: "))
1355 (type (mml-minibuffer-read-type buffer "text/plain"))
03c673c9
MB
1356 (description (mml-minibuffer-read-description))
1357 (disposition (mml-minibuffer-read-disposition type nil)))
1358 (list buffer type description disposition)))
29b647c5
MB
1359 ;; Don't move point if this command is invoked inside the message header.
1360 (let ((head (unless (message-in-body-p)
1361 (prog1
1362 (point)
1363 (goto-char (point-max))))))
1364 (mml-insert-empty-tag 'part 'type type 'buffer buffer
1365 'disposition disposition
1366 'description description)
1367 (when head
1368 (unless (prog1
1369 (pos-visible-in-window-p)
1370 (goto-char head))
1371 (message
1372 "The buffer \"%s\" has been attached at the end of the message"
1373 buffer)))))
c113de23
GM
1374
1375(defun mml-attach-external (file &optional type description)
1376 "Attach an external file into the buffer.
1377FILE is an ange-ftp/efs specification of the part location.
1378TYPE is the MIME type to use."
1379 (interactive
1380 (let* ((file (mml-minibuffer-read-file "Attach external file: "))
1381 (type (mml-minibuffer-read-type file))
1382 (description (mml-minibuffer-read-description)))
1383 (list file type description)))
29b647c5
MB
1384 ;; Don't move point if this command is invoked inside the message header.
1385 (let ((head (unless (message-in-body-p)
1386 (prog1
1387 (point)
1388 (goto-char (point-max))))))
1389 (mml-insert-empty-tag 'external 'type type 'name file
1390 'disposition "attachment" 'description description)
1391 (when head
1392 (unless (prog1
1393 (pos-visible-in-window-p)
1394 (goto-char head))
1395 (message "The file \"%s\" has been attached at the end of the message"
1396 (file-name-nondirectory file))))))
c113de23
GM
1397
1398(defun mml-insert-multipart (&optional type)
83ccc32c 1399 (interactive (if (message-in-body-p)
229b59da
G
1400 (list (gnus-completing-read "Multipart type"
1401 '("mixed" "alternative"
1402 "digest" "parallel"
1403 "signed" "encrypted")
1404 nil "mixed"))
83ccc32c 1405 (error "Use this command in the message body")))
c113de23
GM
1406 (or type
1407 (setq type "mixed"))
1408 (mml-insert-empty-tag "multipart" 'type type)
1409 (forward-line -1))
1410
1411(defun mml-insert-part (&optional type)
83ccc32c
KY
1412 (interactive (if (message-in-body-p)
1413 (list (mml-minibuffer-read-type ""))
1414 (error "Use this command in the message body")))
1415 (mml-insert-tag 'part 'type type 'disposition "inline"))
c113de23 1416
541cbf8b
GM
1417(declare-function message-subscribed-p "message" ())
1418(declare-function message-make-mail-followup-to "message"
1419 (&optional only-show-subscribed))
1420(declare-function message-position-on-field "message" (header &rest afters))
1421
23f87bed
MB
1422(defun mml-preview-insert-mail-followup-to ()
1423 "Insert a Mail-Followup-To header before previewing an article.
1424Should be adopted if code in `message-send-mail' is changed."
1425 (when (and (message-mail-p)
1426 (message-subscribed-p)
1427 (not (mail-fetch-field "mail-followup-to"))
1428 (message-make-mail-followup-to))
1429 (message-position-on-field "Mail-Followup-To" "X-Draft-From")
1430 (insert (message-make-mail-followup-to))))
1431
01c52d31
MB
1432(defvar mml-preview-buffer nil)
1433
5ab56288
GM
1434(autoload 'gnus-make-hashtable "gnus-util")
1435(autoload 'widget-button-press "wid-edit" nil t)
1436(declare-function widget-event-point "wid-edit" (event))
1437;; If gnus-buffer-configuration is bound this is loaded.
1438(declare-function gnus-configure-windows "gnus-win" (setting &optional force))
541cbf8b
GM
1439;; Called after message-mail-p, which autoloads message.
1440(declare-function message-news-p "message" ())
1441(declare-function message-options-set-recipient "message" ())
1442(declare-function message-generate-headers "message" (headers))
1443(declare-function message-sort-headers "message" ())
5ab56288 1444
c113de23
GM
1445(defun mml-preview (&optional raw)
1446 "Display current buffer with Gnus, in a new buffer.
01c52d31
MB
1447If RAW, display a raw encoded MIME message.
1448
1449The window layout for the preview buffer is controled by the variables
1450`special-display-buffer-names', `special-display-regexps', or
1451`gnus-buffer-configuration' (the first match made will be used),
1452or the `pop-to-buffer' function."
c113de23 1453 (interactive "P")
01c52d31
MB
1454 (setq mml-preview-buffer (generate-new-buffer
1455 (concat (if raw "*Raw MIME preview of "
1456 "*MIME preview of ") (buffer-name))))
aa8f8277 1457 (require 'gnus-msg) ; for gnus-setup-posting-charset
23f87bed
MB
1458 (save-excursion
1459 (let* ((buf (current-buffer))
1460 (message-options message-options)
1461 (message-this-is-mail (message-mail-p))
1462 (message-this-is-news (message-news-p))
1463 (message-posting-charset (or (gnus-setup-posting-charset
1464 (save-restriction
1465 (message-narrow-to-headers-or-head)
1466 (message-fetch-field "Newsgroups")))
1467 message-posting-charset)))
1468 (message-options-set-recipient)
23f87bed 1469 (when (boundp 'gnus-buffers)
01c52d31
MB
1470 (push mml-preview-buffer gnus-buffers))
1471 (save-restriction
1472 (widen)
1473 (set-buffer mml-preview-buffer)
1474 (erase-buffer)
1475 (insert-buffer-substring buf))
23f87bed
MB
1476 (mml-preview-insert-mail-followup-to)
1477 (let ((message-deletable-headers (if (message-news-p)
1478 nil
1479 message-deletable-headers)))
1480 (message-generate-headers
1481 (copy-sequence (if (message-news-p)
1482 message-required-news-headers
1483 message-required-mail-headers))))
1484 (if (re-search-forward
1485 (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
1486 (replace-match "\n"))
1487 (let ((mail-header-separator ""));; mail-header-separator is removed.
01c52d31 1488 (message-sort-headers)
23f87bed
MB
1489 (mml-to-mime))
1490 (if raw
1491 (when (fboundp 'set-buffer-multibyte)
1492 (let ((s (buffer-string)))
1493 ;; Insert the content into unibyte buffer.
1494 (erase-buffer)
1495 (mm-disable-multibyte)
1496 (insert s)))
1497 (let ((gnus-newsgroup-charset (car message-posting-charset))
1498 gnus-article-prepare-hook gnus-original-article-buffer)
1499 (run-hooks 'gnus-article-decode-hook)
1500 (let ((gnus-newsgroup-name "dummy")
1501 (gnus-newsrc-hashtb (or gnus-newsrc-hashtb
1502 (gnus-make-hashtable 5))))
1503 (gnus-article-prepare-display))))
1504 ;; Disable article-mode-map.
1505 (use-local-map nil)
1506 (gnus-make-local-hook 'kill-buffer-hook)
1507 (add-hook 'kill-buffer-hook
1508 (lambda ()
1509 (mm-destroy-parts gnus-article-mime-handles)) nil t)
1510 (setq buffer-read-only t)
1511 (local-set-key "q" (lambda () (interactive) (kill-buffer nil)))
1512 (local-set-key "=" (lambda () (interactive) (delete-other-windows)))
1513 (local-set-key "\r"
1514 (lambda ()
1515 (interactive)
1516 (widget-button-press (point))))
1517 (local-set-key gnus-mouse-2
1518 (lambda (event)
1519 (interactive "@e")
1520 (widget-button-press (widget-event-point event) event)))
01c52d31
MB
1521 ;; FIXME: Buffer is in article mode, but most tool bar commands won't
1522 ;; work. Maybe only keep the following icons: search, print, quit
1523 (goto-char (point-min))))
1524 (if (and (not (mm-special-display-p (buffer-name mml-preview-buffer)))
1525 (boundp 'gnus-buffer-configuration)
1526 (assq 'mml-preview gnus-buffer-configuration))
1527 (let ((gnus-message-buffer (current-buffer)))
1528 (gnus-configure-windows 'mml-preview))
1529 (pop-to-buffer mml-preview-buffer)))
c113de23
GM
1530
1531(defun mml-validate ()
1532 "Validate the current MML document."
1533 (interactive)
1534 (mml-parse))
1535
23f87bed
MB
1536(defun mml-tweak-part (cont)
1537 "Tweak a MML part."
1538 (let ((tweak (cdr (assq 'tweak cont)))
1539 func)
1540 (cond
1541 (tweak
1542 (setq func
1543 (or (cdr (assoc tweak mml-tweak-function-alist))
1544 (intern tweak))))
1545 (mml-tweak-type-alist
1546 (let ((alist mml-tweak-type-alist)
1547 (type (or (cdr (assq 'type cont)) "text/plain")))
1548 (while alist
1549 (if (string-match (caar alist) type)
1550 (setq func (cdar alist)
1551 alist nil)
1552 (setq alist (cdr alist)))))))
1553 (if func
1554 (funcall func cont)
1555 cont)
1556 (let ((alist mml-tweak-sexp-alist))
1557 (while alist
1558 (if (eval (caar alist))
1559 (funcall (cdar alist) cont))
1560 (setq alist (cdr alist)))))
1561 cont)
1562
1563(defun mml-tweak-externalize-attachments (cont)
1564 "Tweak attached files as external parts."
1565 (let (filename-cons)
1566 (when (and (eq (car cont) 'part)
1567 (not (cdr (assq 'buffer cont)))
1568 (and (setq filename-cons (assq 'filename cont))
1569 (not (equal (cdr (assq 'nofile cont)) "yes"))))
1570 (setcar cont 'external)
1571 (setcar filename-cons 'name))))
1572
c113de23
GM
1573(provide 'mml)
1574
1575;;; mml.el ends here