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