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