;;; mml.el --- A package for parsing and validating MML documents
-;; Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+
+;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+;; 2005, 2006 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; This file is part of GNU Emacs.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Commentary:
(eval-and-compile
(autoload 'message-make-message-id "message")
(autoload 'gnus-setup-posting-charset "gnus-msg")
- (autoload 'gnus-add-minor-mode "gnus-ems")
+ (autoload 'gnus-make-local-hook "gnus-util")
(autoload 'message-fetch-field "message")
+ (autoload 'message-mark-active-p "message")
+ (autoload 'message-info "message")
(autoload 'fill-flowed-encode "flow-fill")
- (autoload 'message-posting-charset "message"))
+ (autoload 'message-posting-charset "message")
+ (autoload 'dnd-get-local-file-name "dnd"))
+
+(defvar gnus-article-mime-handles)
+(defvar gnus-mouse-2)
+(defvar gnus-newsrc-hashtb)
+(defvar message-default-charset)
+(defvar message-deletable-headers)
+(defvar message-options)
+(defvar message-posting-charset)
+(defvar message-required-mail-headers)
+(defvar message-required-news-headers)
+(defvar dnd-protocol-alist)
(defcustom mml-content-type-parameters
'(name access-type expiration size permission format)
"*A list of acceptable parameters in MML tag.
These parameters are generated in Content-Type header if exists."
+ :version "22.1"
:type '(repeat (symbol :tag "Parameter"))
:group 'message)
'(filename creation-date modification-date read-date)
"*A list of acceptable parameters in MML tag.
These parameters are generated in Content-Disposition header if exists."
+ :version "22.1"
:type '(repeat (symbol :tag "Parameter"))
:group 'message)
+(defcustom mml-insert-mime-headers-always nil
+ "If non-nil, always put Content-Type: text/plain at top of empty parts.
+It is necessary to work against a bug in certain clients."
+ :version "22.1"
+ :type 'boolean
+ :group 'message)
+
(defvar mml-tweak-type-alist nil
"A list of (TYPE . FUNCTION) for tweaking MML parts.
TYPE is a string containing a regexp to match the MIME type. FUNCTION
(defvar mml-tweak-sexp-alist
'((mml-externalize-attachments . mml-tweak-externalize-attachments))
"A list of (SEXP . FUNCTION) for tweaking MML parts.
-SEXP is a s-expression. If the evaluation of SEXP is non-nil, FUNCTION
+SEXP is an s-expression. If the evaluation of SEXP is non-nil, FUNCTION
is called. FUNCTION is a Lisp function which is called with the MML
handle to tweak the part.")
with unknown encoding; `multipart': always send messages with more than
one charsets.")
-(defvar mml-generate-default-type "text/plain")
+(defvar mml-generate-default-type "text/plain"
+ "Content type by which the Content-Type header can be omitted.
+The Content-Type header will not be put in the MIME part if the type
+equals the value and there's no parameter (e.g. charset, format, etc.)
+and `mml-insert-mime-headers-always' is nil. The value will be bound
+to \"message/rfc822\" when encoding an article to be forwarded as a MIME
+part. This is for the internal use, you should never modify the value.")
(defvar mml-buffer-list nil)
(defun mml-destroy-buffers ()
(let (kill-buffer-hook)
- (mapcar 'kill-buffer mml-buffer-list)
+ (mapc 'kill-buffer mml-buffer-list)
(setq mml-buffer-list nil)))
(defun mml-parse ()
"Parse the current buffer as an MML document."
(save-excursion
(goto-char (point-min))
- (let ((table (syntax-table)))
- (unwind-protect
- (progn
- (set-syntax-table mml-syntax-table)
- (mml-parse-1))
- (set-syntax-table table)))))
+ (with-syntax-table mml-syntax-table
+ (mml-parse-1))))
(defun mml-parse-1 ()
"Parse the current buffer as an MML document."
;; included in the message
(let* (secure-mode
(taginfo (mml-read-tag))
+ (keyfile (cdr (assq 'keyfile taginfo)))
+ (certfile (cdr (assq 'certfile taginfo)))
(recipients (cdr (assq 'recipients taginfo)))
- (location (cdr (assq 'tag-location taginfo))))
+ (sender (cdr (assq 'sender taginfo)))
+ (location (cdr (assq 'tag-location taginfo)))
+ (mode (cdr (assq 'mode taginfo)))
+ (method (cdr (assq 'method taginfo)))
+ tags)
(save-excursion
- (if
- (re-search-forward
- "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t)
+ (if (re-search-forward
+ "<#/?\\(multipart\\|part\\|external\\|mml\\)." nil t)
(setq secure-mode "multipart")
(setq secure-mode "part")))
(save-excursion
(goto-char location)
(re-search-forward "<#secure[^\n]*>\n"))
(delete-region (match-beginning 0) (match-end 0))
- (mml-insert-tag secure-mode
- (cdr (assq 'mode taginfo))
- (cdr (assq 'method taginfo))
- (and recipients 'recipients)
- recipients)
+ (cond ((string= mode "sign")
+ (setq tags (list "sign" method)))
+ ((string= mode "encrypt")
+ (setq tags (list "encrypt" method)))
+ ((string= mode "signencrypt")
+ (setq tags (list "sign" method "encrypt" method))))
+ (eval `(mml-insert-tag ,secure-mode
+ ,@tags
+ ,(if keyfile "keyfile")
+ ,keyfile
+ ,(if certfile "certfile")
+ ,certfile
+ ,(if recipients "recipients")
+ ,recipients
+ ,(if sender "sender")
+ ,sender))
;; restart the parse
(goto-char location)))
((looking-at "<#multipart")
(if (or (memq 'unknown-encoding mml-confirmation-set)
(message-options-get 'unknown-encoding)
(and (y-or-n-p "\
-Message contains characters with unknown encoding. Really send?")
+Message contains characters with unknown encoding. Really send? ")
(message-options-set 'unknown-encoding t)))
(if (setq use-ascii
(or (memq 'use-ascii mml-confirmation-set)
(message-options-get 'use-ascii)
- (and (y-or-n-p "Use ASCII as charset?")
+ (and (y-or-n-p "Use ASCII as charset? ")
(message-options-set 'use-ascii t))))
(setq charsets (delq nil charsets))
(setq warn nil))
"Return the buffer up till the next part, multipart or closing part or multipart.
If MML is non-nil, return the buffer up till the correspondent mml tag."
(let ((beg (point)) (count 1))
- ;; If the tag ended at the end of the line, we go to the next line.
+ ;; If the tag ended at the end of the line, we go to the next line.
(when (looking-at "[ \t]*\n")
(forward-line 1))
(if mml
(mml-tweak-part cont)
(cond
((or (eq (car cont) 'part) (eq (car cont) 'mml))
- (let ((raw (cdr (assq 'raw cont)))
- coded encoding charset filename type flowed)
- (setq type (or (cdr (assq 'type cont)) "text/plain"))
+ (let* ((raw (cdr (assq 'raw cont)))
+ (filename (cdr (assq 'filename cont)))
+ (type (or (cdr (assq 'type cont))
+ (if filename
+ (or (mm-default-file-encoding filename)
+ "application/octet-stream")
+ "text/plain")))
+ (charset (cdr (assq 'charset cont)))
+ (coding (mm-charset-to-coding-system charset))
+ encoding flowed coded)
+ (cond ((eq coding 'ascii)
+ (setq charset nil
+ coding nil))
+ (charset
+ (setq charset (intern (downcase charset)))))
(if (and (not raw)
(member (car (split-string type "/")) '("text" "message")))
(progn
(with-temp-buffer
- (setq charset (mm-charset-to-coding-system
- (cdr (assq 'charset cont))))
- (when (eq charset 'ascii)
- (setq charset nil))
(cond
((cdr (assq 'buffer cont))
(insert-buffer-substring (cdr (assq 'buffer cont))))
- ((and (setq filename (cdr (assq 'filename cont)))
+ ((and filename
(not (equal (cdr (assq 'nofile cont)) "yes")))
- (let ((coding-system-for-read charset))
+ (let ((coding-system-for-read coding))
(mm-insert-file-contents filename)))
((eq 'mml (car cont))
(insert (cdr (assq 'contents cont))))
(+ (match-beginning 0) 3))))))
(cond
((eq (car cont) 'mml)
- (let ((mml-boundary (funcall mml-boundary-function
- (incf mml-multipart-number)))
+ (let ((mml-boundary (mml-compute-boundary cont))
+ ;; It is necessary for the case where this
+ ;; function is called recursively since
+ ;; `m-g-d-t' will be bound to "message/rfc822"
+ ;; when encoding an article to be forwarded.
(mml-generate-default-type "text/plain"))
(mml-to-mime))
(let ((mm-7bit-chars (concat mm-7bit-chars "\x1b")))
;; ignore 0x1b, it is part of iso-2022-jp
(setq encoding (mm-body-7-or-8))))
(t
+ ;; Only perform format=flowed filling on text/plain
+ ;; parts where there either isn't a format parameter
+ ;; in the mml tag or it says "flowed" and there
+ ;; actually are hard newlines in the text.
+ (let (use-hard-newlines)
+ (when (and (string= type "text/plain")
+ (not (string= (cdr (assq 'sign cont)) "pgp"))
+ (or (null (assq 'format cont))
+ (string= (cdr (assq 'format cont))
+ "flowed"))
+ (setq use-hard-newlines
+ (text-property-any
+ (point-min) (point-max) 'hard 't)))
+ (fill-flowed-encode)
+ ;; Indicate that `mml-insert-mime-headers' should
+ ;; insert a "; format=flowed" string unless the
+ ;; user has already specified it.
+ (setq flowed (null (assq 'format cont)))))
(setq charset (mm-encode-body charset))
(setq encoding (mm-body-encoding
charset (cdr (assq 'encoding cont))))))
- ;; Only perform format=flowed filling on text/plain
- ;; parts where there either isn't a format parameter
- ;; in the mml tag or it says "flowed" and there
- ;; actually are hard newlines in the text.
- (let (use-hard-newlines)
- (when (and (string= type "text/plain")
- (or (null (assq 'format cont))
- (string= (cdr (assq 'format cont))
- "flowed"))
- (setq use-hard-newlines
- (text-property-any
- (point-min) (point-max) 'hard 't)))
- (fill-flowed-encode)
- ;; Indicate that `mml-insert-mime-headers' should
- ;; insert a "; format=flowed" string unless the
- ;; user has already specified it.
- (setq flowed (null (assq 'format cont)))))
(setq coded (buffer-string)))
(mml-insert-mime-headers cont type charset encoding flowed)
(insert "\n")
(mm-with-unibyte-buffer
(cond
((cdr (assq 'buffer cont))
- (insert-buffer-substring (cdr (assq 'buffer cont))))
- ((and (setq filename (cdr (assq 'filename cont)))
+ (insert (with-current-buffer (cdr (assq 'buffer cont))
+ (mm-with-unibyte-current-buffer
+ (buffer-string)))))
+ ((and filename
(not (equal (cdr (assq 'nofile cont)) "yes")))
(let ((coding-system-for-read mm-binary-coding-system))
- (mm-insert-file-contents filename nil nil nil nil t)))
+ (mm-insert-file-contents filename nil nil nil nil t))
+ (unless charset
+ (setq charset (mm-coding-system-to-mime-charset
+ (mm-find-buffer-file-coding-system
+ filename)))))
(t
- (insert (cdr (assq 'contents cont)))))
+ (let ((contents (cdr (assq 'contents cont))))
+ (if (if (featurep 'xemacs)
+ (string-match "[^\000-\377]" contents)
+ (mm-multibyte-string-p contents))
+ (progn
+ (mm-enable-multibyte)
+ (insert contents)
+ (setq charset (mm-encode-body charset)))
+ (insert contents)))))
(setq encoding (mm-encode-buffer type)
coded (mm-string-as-multibyte (buffer-string))))
(mml-insert-mime-headers cont type charset encoding nil)
"access-type=url"))
(when parameters
(mml-insert-parameter-string
- cont '(expiration size permission))))
- (insert "\n\n")
- (insert "Content-Type: " (cdr (assq 'type cont)) "\n")
- (insert "Content-ID: " (message-make-message-id) "\n")
- (insert "Content-Transfer-Encoding: "
- (or (cdr (assq 'encoding cont)) "binary"))
- (insert "\n\n")
- (insert (or (cdr (assq 'contents cont))))
- (insert "\n"))
+ cont '(expiration size permission)))
+ (insert "\n\n")
+ (insert "Content-Type: "
+ (or (cdr (assq 'type cont))
+ (if name
+ (or (mm-default-file-encoding name)
+ "application/octet-stream")
+ "text/plain"))
+ "\n")
+ (insert "Content-ID: " (message-make-message-id) "\n")
+ (insert "Content-Transfer-Encoding: "
+ (or (cdr (assq 'encoding cont)) "binary"))
+ (insert "\n\n")
+ (insert (or (cdr (assq 'contents cont))))
+ (insert "\n")))
((eq (car cont) 'multipart)
(let* ((type (or (cdr (assq 'type cont)) "mixed"))
(mml-generate-default-type (if (equal type "digest")
(funcall (cdr handler) cont)
;; No specific handler. Use default one.
(let ((mml-boundary (mml-compute-boundary cont)))
- (insert (format "Content-Type: multipart/%s; boundary=\"%s\"\n"
- type mml-boundary))
+ (insert (format "Content-Type: multipart/%s; boundary=\"%s\""
+ type mml-boundary)
+ (if (cdr (assq 'start cont))
+ (format "; start=\"%s\"\n" (cdr (assq 'start cont)))
+ "\n"))
(let ((cont cont) part)
(while (setq part (pop cont))
;; Skip `multipart' and attributes.
(when (and (consp part) (consp (cdr part)))
(insert "\n--" mml-boundary "\n")
- (mml-generate-mime-1 part))))
+ (mml-generate-mime-1 part)
+ (goto-char (point-max)))))
(insert "\n--" mml-boundary "--\n")))))
(t
(error "Invalid element: %S" cont)))
- (let ((item (assoc (cdr (assq 'sign cont)) mml-sign-alist))
+ ;; handle sign & encrypt tags in a semi-smart way.
+ (let ((sign-item (assoc (cdr (assq 'sign cont)) mml-sign-alist))
+ (encrypt-item (assoc (cdr (assq 'encrypt cont))
+ mml-encrypt-alist))
sender recipients)
- (when item
- (if (setq sender (cdr (assq 'sender cont)))
- (message-options-set 'message-sender sender))
+ (when (or sign-item encrypt-item)
+ (when (setq sender (cdr (assq 'sender cont)))
+ (message-options-set 'mml-sender sender)
+ (message-options-set 'message-sender sender))
(if (setq recipients (cdr (assq 'recipients cont)))
(message-options-set 'message-recipients recipients))
- (funcall (nth 1 item) cont)))
- (let ((item (assoc (cdr (assq 'encrypt cont)) mml-encrypt-alist))
- sender recipients)
- (when item
- (if (setq sender (cdr (assq 'sender cont)))
- (message-options-set 'message-sender sender))
- (if (setq recipients (cdr (assq 'recipients cont)))
- (message-options-set 'message-recipients recipients))
- (funcall (nth 1 item) cont))))))
+ (let ((style (mml-signencrypt-style
+ (first (or sign-item encrypt-item)))))
+ ;; check if: we're both signing & encrypting, both methods
+ ;; are the same (why would they be different?!), and that
+ ;; the signencrypt style allows for combined operation.
+ (if (and sign-item encrypt-item (equal (first sign-item)
+ (first encrypt-item))
+ (equal style 'combined))
+ (funcall (nth 1 encrypt-item) cont t)
+ ;; otherwise, revert to the old behavior.
+ (when sign-item
+ (funcall (nth 1 sign-item) cont))
+ (when encrypt-item
+ (funcall (nth 1 encrypt-item) cont)))))))))
(defun mml-compute-boundary (cont)
"Return a unique boundary that does not exist in CONT."
(insert-buffer-substring (cdr (assq 'buffer cont))))
((and (setq filename (cdr (assq 'filename cont)))
(not (equal (cdr (assq 'nofile cont)) "yes")))
- (mm-insert-file-contents filename))
+ (mm-insert-file-contents filename nil nil nil nil t))
(t
(insert (cdr (assq 'contents cont)))))
(goto-char (point-min))
(incf mml-multipart-number)))
(throw 'not-unique nil))))
((eq (car cont) 'multipart)
- (mapcar 'mml-compute-boundary-1 (cddr cont))))
+ (mapc 'mml-compute-boundary-1 (cddr cont))))
t))
(defun mml-make-boundary (number)
mml-base-boundary))
(defun mml-insert-mime-headers (cont type charset encoding flowed)
- (let (parameters disposition description)
+ (let (parameters id disposition description)
(setq parameters
(mml-parameter-string
cont mml-content-type-parameters))
(when (or charset
parameters
flowed
- (not (equal type mml-generate-default-type)))
+ (not (equal type mml-generate-default-type))
+ mml-insert-mime-headers-always)
(when (consp charset)
(error
- "Can't encode a part with several charsets."))
+ "Can't encode a part with several charsets"))
(insert "Content-Type: " type)
(when charset
- (insert "; " (mail-header-encode-parameter
- "charset" (symbol-name charset))))
+ (mml-insert-parameter
+ (mail-header-encode-parameter "charset" (symbol-name charset))))
(when flowed
- (insert "; format=flowed"))
+ (mml-insert-parameter "format=flowed"))
(when parameters
(mml-insert-parameter-string
cont mml-content-type-parameters))
(insert "\n"))
+ (when (setq id (cdr (assq 'id cont)))
+ (insert "Content-ID: " id "\n"))
(setq parameters
(mml-parameter-string
cont mml-content-disposition-parameters))
(unless (eq encoding '7bit)
(insert (format "Content-Transfer-Encoding: %s\n" encoding)))
(when (setq description (cdr (assq 'description cont)))
- (insert "Content-Description: "
- (mail-encode-encoded-word-string description) "\n"))))
+ (insert "Content-Description: ")
+ (setq description (prog1
+ (point)
+ (insert description "\n")))
+ (mail-encode-encoded-word-region description (point)))))
(defun mml-parameter-string (cont types)
(let ((string "")
;; First decode the head.
(save-restriction
(message-narrow-to-head)
- (mail-decode-encoded-word-region (point-min) (point-max)))
+ (let ((rfc2047-quote-decoded-words-containing-tspecials t))
+ (mail-decode-encoded-word-region (point-min) (point-max))))
(unless handles
(setq handles (mm-dissect-buffer t)))
(goto-char (point-min))
(unless (setq textp (equal (mm-handle-media-supertype handle) "text"))
(save-excursion
(set-buffer (setq buffer (mml-generate-new-buffer " *mml*")))
- (mm-insert-part handle)
+ (mm-insert-part handle 'no-cache)
(if (setq mmlp (equal (mm-handle-media-type handle)
"message/rfc822"))
(mime-to-mml)))))
(mml-insert-mml-markup handle buffer textp)))
(cond
(mmlp
- (insert-buffer buffer)
+ (insert-buffer-substring buffer)
(goto-char (point-max))
(insert "<#/mml>\n"))
((stringp (car handle))
(insert "<#/multipart>\n"))
(textp
(let ((charset (mail-content-type-get
- (mm-handle-type handle) 'charset)))
+ (mm-handle-type handle) 'charset))
+ (start (point)))
(if (eq charset 'gnus-decoded)
(mm-insert-part handle)
- (insert (mm-decode-string (mm-get-part handle) charset))))
+ (insert (mm-decode-string (mm-get-part handle) charset)))
+ (mml-quote-region start (point)))
(goto-char (point-max)))
(t
(insert "<#/part>\n")))))
(defun mml-insert-mml-markup (handle &optional buffer nofile mmlp)
"Take a MIME handle and insert an MML tag."
(if (stringp (car handle))
- (insert "<#multipart type=" (mm-handle-media-subtype handle)
- ">\n")
+ (progn
+ (insert "<#multipart type=" (mm-handle-media-subtype handle))
+ (let ((start (mm-handle-multipart-ctl-parameter handle 'start)))
+ (when start
+ (insert " start=\"" start "\"")))
+ (insert ">\n"))
(if mmlp
(insert "<#mml type=" (mm-handle-media-type handle))
(insert "<#part type=" (mm-handle-media-type handle)))
(cdr (mm-handle-disposition handle))))
(unless (symbolp (cdr elem))
(insert " " (symbol-name (car elem)) "=\"" (cdr elem) "\"")))
+ (when (mm-handle-id handle)
+ (insert " id=\"" (mm-handle-id handle) "\""))
(when (mm-handle-disposition handle)
(insert " disposition=" (car (mm-handle-disposition handle))))
(when buffer
(defun mml-insert-parameter (&rest parameters)
"Insert PARAMETERS in a nice way."
- (dolist (param parameters)
- (insert ";")
- (let ((point (point)))
+ (let (start end)
+ (dolist (param parameters)
+ (insert ";")
+ (setq start (point))
(insert " " param)
- (when (> (current-column) 71)
- (goto-char point)
- (insert "\n ")
- (end-of-line)))))
+ (setq end (point))
+ (goto-char start)
+ (end-of-line)
+ (if (> (current-column) 76)
+ (progn
+ (goto-char start)
+ (insert "\n")
+ (goto-char (1+ end)))
+ (goto-char end)))))
;;;
;;; Mode for inserting and editing MML forms
(defvar mml-mode-map
(let ((sign (make-sparse-keymap))
(encrypt (make-sparse-keymap))
+ (signpart (make-sparse-keymap))
+ (encryptpart (make-sparse-keymap))
(map (make-sparse-keymap))
(main (make-sparse-keymap)))
+ (define-key map "\C-s" 'mml-secure-message-sign)
+ (define-key map "\C-c" 'mml-secure-message-encrypt)
+ (define-key map "\C-e" 'mml-secure-message-sign-encrypt)
+ (define-key map "\C-p\C-s" 'mml-secure-sign)
+ (define-key map "\C-p\C-c" 'mml-secure-encrypt)
(define-key sign "p" 'mml-secure-message-sign-pgpmime)
(define-key sign "o" 'mml-secure-message-sign-pgp)
(define-key sign "s" 'mml-secure-message-sign-smime)
+ (define-key signpart "p" 'mml-secure-sign-pgpmime)
+ (define-key signpart "o" 'mml-secure-sign-pgp)
+ (define-key signpart "s" 'mml-secure-sign-smime)
(define-key encrypt "p" 'mml-secure-message-encrypt-pgpmime)
(define-key encrypt "o" 'mml-secure-message-encrypt-pgp)
(define-key encrypt "s" 'mml-secure-message-encrypt-smime)
+ (define-key encryptpart "p" 'mml-secure-encrypt-pgpmime)
+ (define-key encryptpart "o" 'mml-secure-encrypt-pgp)
+ (define-key encryptpart "s" 'mml-secure-encrypt-smime)
(define-key map "\C-n" 'mml-unsecure-message)
(define-key map "f" 'mml-attach-file)
(define-key map "b" 'mml-attach-buffer)
(define-key map "v" 'mml-validate)
(define-key map "P" 'mml-preview)
(define-key map "s" sign)
+ (define-key map "S" signpart)
(define-key map "c" encrypt)
+ (define-key map "C" encryptpart)
;;(define-key map "n" 'mml-narrow-to-part)
;; `M-m' conflicts with `back-to-indentation'.
;; (define-key main "\M-m" map)
(easy-menu-define
mml-menu mml-mode-map ""
`("Attachments"
- ["Attach File" mml-attach-file
+ ["Attach File..." mml-attach-file
,@(if (featurep 'xemacs) '(t)
'(:help "Attach a file at point"))]
- ["Attach Buffer" mml-attach-buffer t]
- ["Attach External" mml-attach-external t]
- ["Insert Part" mml-insert-part t]
- ["Insert Multipart" mml-insert-multipart t]
- ["PGP/MIME Sign" mml-secure-message-sign-pgpmime t]
- ["PGP/MIME Encrypt" mml-secure-message-encrypt-pgpmime t]
- ["PGP Sign" mml-secure-message-sign-pgp t]
- ["PGP Encrypt" mml-secure-message-encrypt-pgp t]
- ["S/MIME Sign" mml-secure-message-sign-smime t]
- ["S/MIME Encrypt" mml-secure-message-encrypt-smime t]
- ["Encrypt/Sign off" mml-unsecure-message t]
+ ["Attach Buffer..." mml-attach-buffer
+ ,@(if (featurep 'xemacs) '(t)
+ '(:help "Attach a buffer to the outgoing MIME message"))]
+ ["Attach External..." mml-attach-external
+ ,@(if (featurep 'xemacs) '(t)
+ '(:help "Attach reference to file"))]
+ ;;
+ ("Change Security Method"
+ ["PGP/MIME"
+ (lambda () (interactive) (setq mml-secure-method "pgpmime"))
+ ,@(if (featurep 'xemacs) nil
+ '(:help "Set Security Method to PGP/MIME"))
+ :style radio
+ :selected (equal mml-secure-method "pgpmime") ]
+ ["S/MIME"
+ (lambda () (interactive) (setq mml-secure-method "smime"))
+ ,@(if (featurep 'xemacs) nil
+ '(:help "Set Security Method to S/MIME"))
+ :style radio
+ :selected (equal mml-secure-method "smime") ]
+ ["Inline PGP"
+ (lambda () (interactive) (setq mml-secure-method "pgp"))
+ ,@(if (featurep 'xemacs) nil
+ '(:help "Set Security Method to inline PGP"))
+ :style radio
+ :selected (equal mml-secure-method "pgp") ] )
+ ;;
+ ["Sign Message" mml-secure-message-sign t]
+ ["Encrypt Message" mml-secure-message-encrypt t]
+ ["Sign and Encrypt Message" mml-secure-message-sign-encrypt t]
+ ["Encrypt/Sign off" mml-unsecure-message
+ ,@(if (featurep 'xemacs) '(t)
+ '(:help "Don't Encrypt/Sign Message"))]
+ ;; Maybe we could remove these, because people who write MML most probably
+ ;; don't use the menu:
+ ["Insert Part..." mml-insert-part
+ :active (message-in-body-p)]
+ ["Insert Multipart..." mml-insert-multipart
+ :active (message-in-body-p)]
+ ;;
+ ;; Do we have separate encrypt and encrypt/sign commands for parts?
+ ["Sign Part" mml-secure-sign t]
+ ["Encrypt Part" mml-secure-encrypt t]
;;["Narrow" mml-narrow-to-part t]
- ["Quote MML" mml-quote-region t]
+ ["Quote MML in region" mml-quote-region
+ :active (message-mark-active-p)
+ ,@(if (featurep 'xemacs) nil
+ '(:help "Quote MML tags in region"))]
["Validate MML" mml-validate t]
- ["Preview" mml-preview t]))
+ ["Preview" mml-preview t]
+ "----"
+ ["Emacs MIME manual" (lambda () (interactive) (message-info 4))
+ ,@(if (featurep 'xemacs) '(t)
+ '(:help "Display the Emacs MIME manual"))]
+ ["PGG manual" (lambda () (interactive) (message-info 16))
+ ,@(if (featurep 'xemacs) '(t)
+ '(:help "Display the PGG manual"))]))
(defvar mml-mode nil
"Minor mode for editing MML.")
(when (set (make-local-variable 'mml-mode)
(if (null arg) (not mml-mode)
(> (prefix-numeric-value arg) 0)))
- (gnus-add-minor-mode 'mml-mode " MML" mml-mode-map)
+ (add-minor-mode 'mml-mode " MML" mml-mode-map)
(easy-menu-add mml-menu mml-mode-map)
+ (when (boundp 'dnd-protocol-alist)
+ (set (make-local-variable 'dnd-protocol-alist)
+ (append mml-dnd-protocol-alist dnd-protocol-alist)))
(run-hooks 'mml-mode-hook)))
;;;
;;; inserting stuff to the buffer.
;;;
+(defcustom mml-default-directory mm-default-directory
+ "The default directory where mml will find files.
+If not set, `default-directory' will be used."
+ :type '(choice directory (const :tag "Default" nil))
+ :version "23.0" ;; No Gnus
+ :group 'message)
+
(defun mml-minibuffer-read-file (prompt)
- (let ((file (read-file-name prompt nil nil t)))
- ;; Prevent some common errors. This is inspired by similar code in
+ (let* ((completion-ignored-extensions nil)
+ (file (read-file-name prompt
+ (or mml-default-directory default-directory)
+ nil t)))
+ ;; Prevent some common errors. This is inspired by similar code in
;; VM.
(when (file-directory-p file)
(error "%s is a directory, cannot attach" file))
(setq description nil))
description))
+(defun mml-minibuffer-read-disposition (type &optional default)
+ (unless default (setq default
+ (if (and (string-match "\\`text/" type)
+ (not (string-match "\\`text/rtf\\'" type)))
+ "inline"
+ "attachment")))
+ (let ((disposition (completing-read
+ (format "Disposition (default %s): " default)
+ '(("attachment") ("inline") (""))
+ nil t nil nil default)))
+ (if (not (equal disposition ""))
+ disposition
+ default)))
+
(defun mml-quote-region (beg end)
"Quote the MML tags in the region."
(interactive "r")
(when value
;; Quote VALUE if it contains suspicious characters.
(when (string-match "[\"'\\~/*;() \t\n]" value)
- (setq value (prin1-to-string value)))
+ (setq value (with-output-to-string
+ (let (print-escape-nonascii)
+ (prin1 value)))))
(insert (format " %s=%s" key value)))))
(insert ">\n"))
;;; Attachment functions.
-(defun mml-attach-file (file &optional type description)
+(defcustom mml-dnd-protocol-alist
+ '(("^file:///" . mml-dnd-attach-file)
+ ("^file://" . dnd-open-file)
+ ("^file:" . mml-dnd-attach-file))
+ "The functions to call when a drop in `mml-mode' is made.
+See `dnd-protocol-alist' for more information. When nil, behave
+as in other buffers."
+ :type '(choice (repeat (cons (regexp) (function)))
+ (const :tag "Behave as in other buffers" nil))
+ :version "22.1" ;; Gnus 5.10.9
+ :group 'message)
+
+(defcustom mml-dnd-attach-options nil
+ "Which options should be queried when attaching a file via drag and drop.
+
+If it is a list, valid members are `type', `description' and
+`disposition'. `disposition' implies `type'. If it is nil,
+don't ask for options. If it is t, ask the user whether or not
+to specify options."
+ :type '(choice
+ (const :tag "Non" nil)
+ (const :tag "Query" t)
+ (list :value (type description disposition)
+ (set :inline t
+ (const type)
+ (const description)
+ (const disposition))))
+ :version "22.1" ;; Gnus 5.10.9
+ :group 'message)
+
+(defun mml-attach-file (file &optional type description disposition)
"Attach a file to the outgoing MIME message.
The file is not inserted or encoded until you send the message with
`\\[message-send-and-exit]' or `\\[message-send]'.
-FILE is the name of the file to attach. TYPE is its content-type, a
-string of the form \"type/subtype\". DESCRIPTION is a one-line
-description of the attachment."
+FILE is the name of the file to attach. TYPE is its
+content-type, a string of the form \"type/subtype\". DESCRIPTION
+is a one-line description of the attachment. The DISPOSITION
+specifies how the attachment is intended to be displayed. It can
+be either \"inline\" (displayed automatically within the message
+body) or \"attachment\" (separate from the body)."
(interactive
(let* ((file (mml-minibuffer-read-file "Attach file: "))
(type (mml-minibuffer-read-type file))
- (description (mml-minibuffer-read-description)))
- (list file type description)))
- (mml-insert-empty-tag 'part 'type type 'filename file
- 'disposition "attachment" 'description description))
+ (description (mml-minibuffer-read-description))
+ (disposition (mml-minibuffer-read-disposition type)))
+ (list file type description disposition)))
+ (save-excursion
+ (unless (message-in-body-p) (goto-char (point-max)))
+ (mml-insert-empty-tag 'part
+ 'type type
+ 'filename file
+ 'disposition (or disposition "attachment")
+ 'description description)))
+
+(defun mml-dnd-attach-file (uri action)
+ "Attach a drag and drop file.
+
+Ask for type, description or disposition according to
+`mml-dnd-attach-options'."
+ (let ((file (dnd-get-local-file-name uri t)))
+ (when (and file (file-regular-p file))
+ (let ((mml-dnd-attach-options mml-dnd-attach-options)
+ type description disposition)
+ (setq mml-dnd-attach-options
+ (when (and (eq mml-dnd-attach-options t)
+ (not
+ (y-or-n-p
+ "Use default type, disposition and description? ")))
+ '(type description disposition)))
+ (when (or (memq 'type mml-dnd-attach-options)
+ (memq 'disposition mml-dnd-attach-options))
+ (setq type (mml-minibuffer-read-type file)))
+ (when (memq 'description mml-dnd-attach-options)
+ (setq description (mml-minibuffer-read-description)))
+ (when (memq 'disposition mml-dnd-attach-options)
+ (setq disposition (mml-minibuffer-read-disposition type)))
+ (mml-attach-file file type description disposition)))))
(defun mml-attach-buffer (buffer &optional type description)
"Attach a buffer to the outgoing MIME message.
(type (mml-minibuffer-read-type buffer "text/plain"))
(description (mml-minibuffer-read-description)))
(list buffer type description)))
- (mml-insert-empty-tag 'part 'type type 'buffer buffer
- 'disposition "attachment" 'description description))
+ (save-excursion
+ (unless (message-in-body-p) (goto-char (point-max)))
+ (mml-insert-empty-tag 'part 'type type 'buffer buffer
+ 'disposition "attachment"
+ 'description description)))
(defun mml-attach-external (file &optional type description)
"Attach an external file into the buffer.
(type (mml-minibuffer-read-type file))
(description (mml-minibuffer-read-description)))
(list file type description)))
- (mml-insert-empty-tag 'external 'type type 'name file
- 'disposition "attachment" 'description description))
+ (save-excursion
+ (unless (message-in-body-p) (goto-char (point-max)))
+ (mml-insert-empty-tag 'external 'type type 'name file
+ 'disposition "attachment" 'description description)))
(defun mml-insert-multipart (&optional type)
(interactive (list (completing-read "Multipart type (default mixed): "
(mml-insert-tag 'part 'type type 'disposition "inline")
(forward-line -1))
+(defun mml-preview-insert-mail-followup-to ()
+ "Insert a Mail-Followup-To header before previewing an article.
+Should be adopted if code in `message-send-mail' is changed."
+ (when (and (message-mail-p)
+ (message-subscribed-p)
+ (not (mail-fetch-field "mail-followup-to"))
+ (message-make-mail-followup-to))
+ (message-position-on-field "Mail-Followup-To" "X-Draft-From")
+ (insert (message-make-mail-followup-to))))
+
+(defvar mml-preview-buffer nil)
+
(defun mml-preview (&optional raw)
"Display current buffer with Gnus, in a new buffer.
-If RAW, don't highlight the article."
+If RAW, display a raw encoded MIME message.
+
+The window layout for the preview buffer is controled by the variables
+`special-display-buffer-names', `special-display-regexps', or
+`gnus-buffer-configuration' (the first match made will be used),
+or the `pop-to-buffer' function."
(interactive "P")
+ (setq mml-preview-buffer (generate-new-buffer
+ (concat (if raw "*Raw MIME preview of "
+ "*MIME preview of ") (buffer-name))))
(save-excursion
(let* ((buf (current-buffer))
(message-options message-options)
+ (message-this-is-mail (message-mail-p))
(message-this-is-news (message-news-p))
(message-posting-charset (or (gnus-setup-posting-charset
(save-restriction
(message-fetch-field "Newsgroups")))
message-posting-charset)))
(message-options-set-recipient)
- (switch-to-buffer (generate-new-buffer
- (concat (if raw "*Raw MIME preview of "
- "*MIME preview of ") (buffer-name))))
- (erase-buffer)
- (insert-buffer buf)
+ (when (boundp 'gnus-buffers)
+ (push mml-preview-buffer gnus-buffers))
+ (save-restriction
+ (widen)
+ (set-buffer mml-preview-buffer)
+ (erase-buffer)
+ (insert-buffer-substring buf))
+ (mml-preview-insert-mail-followup-to)
+ (let ((message-deletable-headers (if (message-news-p)
+ nil
+ message-deletable-headers)))
+ (message-generate-headers
+ (copy-sequence (if (message-news-p)
+ message-required-news-headers
+ message-required-mail-headers))))
(if (re-search-forward
(concat "^" (regexp-quote mail-header-separator) "\n") nil t)
(replace-match "\n"))
(let ((mail-header-separator ""));; mail-header-separator is removed.
+ (message-sort-headers)
(mml-to-mime))
(if raw
(when (fboundp 'set-buffer-multibyte)
(let ((gnus-newsgroup-charset (car message-posting-charset))
gnus-article-prepare-hook gnus-original-article-buffer)
(run-hooks 'gnus-article-decode-hook)
- (let ((gnus-newsgroup-name "dummy"))
+ (let ((gnus-newsgroup-name "dummy")
+ (gnus-newsrc-hashtb (or gnus-newsrc-hashtb
+ (gnus-make-hashtable 5))))
(gnus-article-prepare-display))))
;; Disable article-mode-map.
(use-local-map nil)
+ (gnus-make-local-hook 'kill-buffer-hook)
+ (add-hook 'kill-buffer-hook
+ (lambda ()
+ (mm-destroy-parts gnus-article-mime-handles)) nil t)
(setq buffer-read-only t)
(local-set-key "q" (lambda () (interactive) (kill-buffer nil)))
- (goto-char (point-min)))))
+ (local-set-key "=" (lambda () (interactive) (delete-other-windows)))
+ (local-set-key "\r"
+ (lambda ()
+ (interactive)
+ (widget-button-press (point))))
+ (local-set-key gnus-mouse-2
+ (lambda (event)
+ (interactive "@e")
+ (widget-button-press (widget-event-point event) event)))
+ ;; FIXME: Buffer is in article mode, but most tool bar commands won't
+ ;; work. Maybe only keep the following icons: search, print, quit
+ (goto-char (point-min))))
+ (if (and (not (mm-special-display-p (buffer-name mml-preview-buffer)))
+ (boundp 'gnus-buffer-configuration)
+ (assq 'mml-preview gnus-buffer-configuration))
+ (let ((gnus-message-buffer (current-buffer)))
+ (gnus-configure-windows 'mml-preview))
+ (pop-to-buffer mml-preview-buffer)))
(defun mml-validate ()
"Validate the current MML document."
(provide 'mml)
+;;; arch-tag: 583c96cf-1ffe-451b-a5e5-4733ae9ddd12
;;; mml.el ends here