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