1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-12-18 10:16:51 +00:00
emacs/lisp/gnus/mml.el
Miles Bader e8beac8a04 Revision: miles@gnu.org--gnu-2005/emacs--cvs-trunk--0--patch-646
Merge from gnus--rel--5.10

Patches applied:

 * gnus--rel--5.10  (patch 152-156)

   - Update from CVS: lisp/mml.el (mml-preview): Doc fix.
   - Update from CVS

2005-11-09  Katsumi Yamaoka  <yamaoka@jpl.org>

   * lisp/gnus/message.el (message-generate-headers): Downcase the argument
   given to message-check-element.

2005-11-01  Katsumi Yamaoka  <yamaoka@jpl.org>

   * lisp/gnus/gnus.el (gnus-parameters-case-fold-search): New variable.
   (gnus-parameters-get-parameter): Use it.

   * lisp/gnus/gnus-score.el (gnus-home-score-file): Doc fix.

2005-11-01  Xavier Maillard  <zedek@gnu-rox.org>  (tiny change)

   * lisp/gnus/gnus-score.el (gnus-update-score-entry-dates): Doc fix.

2005-10-31  Katsumi Yamaoka  <yamaoka@jpl.org>

   * lisp/gnus/mml.el (mml-preview): Doc fix.

2005-11-10  Katsumi Yamaoka  <yamaoka@jpl.org>

   * man/gnus.texi (XVarious): Fix description of gnus-use-toolbar; add
   new variable gnus-toolbar-thickness.

2005-11-08  Katsumi Yamaoka  <yamaoka@jpl.org>

   * man/gnus.texi (XVarious): Revert description of gnus-use-toolbar.

2005-11-07  Katsumi Yamaoka  <yamaoka@jpl.org>

   * man/gnus.texi (X-Face): Fix description.
   (XVarious): Remove gnus-xmas-logo-color-alist and
   gnus-xmas-logo-color-style; fix description of gnus-use-toolbar.

2005-11-01  Katsumi Yamaoka  <yamaoka@jpl.org>

   * man/gnus.texi (Group Parameters): Mention new varable
   gnus-parameters-case-fold-search.
   (Home Score File): Addition.
2005-11-11 05:14:27 +00:00

1217 lines
42 KiB
EmacsLisp

;;; mml.el --- A package for parsing and validating MML documents
;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
;; 2005 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; 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., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;; Code:
(require 'mm-util)
(require 'mm-bodies)
(require 'mm-encode)
(require 'mm-decode)
(require 'mml-sec)
(eval-when-compile (require 'cl))
(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 'fill-flowed-encode "flow-fill")
(autoload 'message-posting-charset "message"))
(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)
(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)
(defcustom mml-content-disposition-parameters
'(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
is a Lisp function which is called with the MML handle to tweak the
part. This variable is used only when no TWEAK parameter exists in
the MML handle.")
(defvar mml-tweak-function-alist nil
"A list of (NAME . FUNCTION) for tweaking MML parts.
NAME is a string containing the name of the TWEAK parameter in the MML
handle. FUNCTION is a Lisp function which is called with the MML
handle to tweak the part.")
(defvar mml-tweak-sexp-alist
'((mml-externalize-attachments . mml-tweak-externalize-attachments))
"A list of (SEXP . FUNCTION) for tweaking MML parts.
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.")
(defvar mml-externalize-attachments nil
"*If non-nil, local-file attachments are generated as external parts.")
(defvar mml-generate-multipart-alist nil
"*Alist of multipart generation functions.
Each entry has the form (NAME . FUNCTION), where
NAME is a string containing the name of the part (without the
leading \"/multipart/\"),
FUNCTION is a Lisp function which is called to generate the part.
The Lisp function has to supply the appropriate MIME headers and the
contents of this part.")
(defvar mml-syntax-table
(let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
(modify-syntax-entry ?\\ "/" table)
(modify-syntax-entry ?< "(" table)
(modify-syntax-entry ?> ")" table)
(modify-syntax-entry ?@ "w" table)
(modify-syntax-entry ?/ "w" table)
(modify-syntax-entry ?= " " table)
(modify-syntax-entry ?* " " table)
(modify-syntax-entry ?\; " " table)
(modify-syntax-entry ?\' " " table)
table))
(defvar mml-boundary-function 'mml-make-boundary
"A function called to suggest a boundary.
The function may be called several times, and should try to make a new
suggestion each time. The function is called with one parameter,
which is a number that says how many times the function has been
called for this message.")
(defvar mml-confirmation-set nil
"A list of symbols, each of which disables some warning.
`unknown-encoding': always send messages contain characters with
unknown encoding; `use-ascii': always use ASCII for those characters
with unknown encoding; `multipart': always send messages with more than
one charsets.")
(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-generate-new-buffer (name)
(let ((buf (generate-new-buffer name)))
(push buf mml-buffer-list)
buf))
(defun mml-destroy-buffers ()
(let (kill-buffer-hook)
(mapcar '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)))))
(defun mml-parse-1 ()
"Parse the current buffer as an MML document."
(let (struct tag point contents charsets warn use-ascii no-markup-p raw)
(while (and (not (eobp))
(not (looking-at "<#/multipart")))
(cond
((looking-at "<#secure")
;; The secure part is essentially a meta-meta tag, which
;; expands to either a part tag if there are no other parts in
;; the document or a multipart tag if there are other parts
;; included in the message
(let* (secure-mode
(taginfo (mml-read-tag))
(recipients (cdr (assq 'recipients 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)
(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))
(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 recipients "recipients")
,recipients
,(if sender "sender")
,sender))
;; restart the parse
(goto-char location)))
((looking-at "<#multipart")
(push (nconc (mml-read-tag) (mml-parse-1)) struct))
((looking-at "<#external")
(push (nconc (mml-read-tag) (list (cons 'contents (mml-read-part))))
struct))
(t
(if (or (looking-at "<#part") (looking-at "<#mml"))
(setq tag (mml-read-tag)
no-markup-p nil
warn nil)
(setq tag (list 'part '(type . "text/plain"))
no-markup-p t
warn t))
(setq raw (cdr (assq 'raw tag))
point (point)
contents (mml-read-part (eq 'mml (car tag)))
charsets (cond
(raw nil)
((assq 'charset tag)
(list
(intern (downcase (cdr (assq 'charset tag))))))
(t
(mm-find-mime-charset-region point (point)
mm-hack-charsets))))
(when (and (not raw) (memq nil charsets))
(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-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? ")
(message-options-set 'use-ascii t))))
(setq charsets (delq nil charsets))
(setq warn nil))
(error "Edit your message to remove those characters")))
(if (or raw
(eq 'mml (car tag))
(< (length charsets) 2))
(if (or (not no-markup-p)
(string-match "[^ \t\r\n]" contents))
;; Don't create blank parts.
(push (nconc tag (list (cons 'contents contents)))
struct))
(let ((nstruct (mml-parse-singlepart-with-multiple-charsets
tag point (point) use-ascii)))
(when (and warn
(not (memq 'multipart mml-confirmation-set))
(not (message-options-get 'multipart))
(not (and (y-or-n-p (format "\
A message part needs to be split into %d charset parts. Really send? "
(length nstruct)))
(message-options-set 'multipart t))))
(error "Edit your message to use only one charset"))
(setq struct (nconc nstruct struct)))))))
(unless (eobp)
(forward-line 1))
(nreverse struct)))
(defun mml-parse-singlepart-with-multiple-charsets
(orig-tag beg end &optional use-ascii)
(save-excursion
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(let ((current (or (mm-mime-charset (mm-charset-after))
(and use-ascii 'us-ascii)))
charset struct space newline paragraph)
(while (not (eobp))
(setq charset (mm-mime-charset (mm-charset-after)))
(cond
;; The charset remains the same.
((eq charset 'us-ascii))
((or (and use-ascii (not charset))
(eq charset current))
(setq space nil
newline nil
paragraph nil))
;; The initial charset was ascii.
((eq current 'us-ascii)
(setq current charset
space nil
newline nil
paragraph nil))
;; We have a change in charsets.
(t
(push (append
orig-tag
(list (cons 'contents
(buffer-substring-no-properties
beg (or paragraph newline space (point))))))
struct)
(setq beg (or paragraph newline space (point))
current charset
space nil
newline nil
paragraph nil)))
;; Compute places where it might be nice to break the part.
(cond
((memq (following-char) '(? ?\t))
(setq space (1+ (point))))
((and (eq (following-char) ?\n)
(not (bobp))
(eq (char-after (1- (point))) ?\n))
(setq paragraph (point)))
((eq (following-char) ?\n)
(setq newline (1+ (point)))))
(forward-char 1))
;; Do the final part.
(unless (= beg (point))
(push (append orig-tag
(list (cons 'contents
(buffer-substring-no-properties
beg (point)))))
struct))
struct))))
(defun mml-read-tag ()
"Read a tag and return the contents."
(let ((orig-point (point))
contents name elem val)
(forward-char 2)
(setq name (buffer-substring-no-properties
(point) (progn (forward-sexp 1) (point))))
(skip-chars-forward " \t\n")
(while (not (looking-at ">[ \t]*\n?"))
(setq elem (buffer-substring-no-properties
(point) (progn (forward-sexp 1) (point))))
(skip-chars-forward "= \t\n")
(setq val (buffer-substring-no-properties
(point) (progn (forward-sexp 1) (point))))
(when (string-match "^\"\\(.*\\)\"$" val)
(setq val (match-string 1 val)))
(push (cons (intern elem) val) contents)
(skip-chars-forward " \t\n"))
(goto-char (match-end 0))
;; Don't skip the leading space.
;;(skip-chars-forward " \t\n")
;; Put the tag location into the returned contents
(setq contents (append (list (cons 'tag-location orig-point)) contents))
(cons (intern name) (nreverse contents))))
(defun mml-buffer-substring-no-properties-except-hard-newlines (start end)
(let ((str (buffer-substring-no-properties start end))
(bufstart start) tmp)
(while (setq tmp (text-property-any start end 'hard 't))
(set-text-properties (- tmp bufstart) (- tmp bufstart -1)
'(hard t) str)
(setq start (1+ tmp)))
str))
(defun mml-read-part (&optional mml)
"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.
(when (looking-at "[ \t]*\n")
(forward-line 1))
(if mml
(progn
(while (and (> count 0) (not (eobp)))
(if (re-search-forward "<#\\(/\\)?mml." nil t)
(setq count (+ count (if (match-beginning 1) -1 1)))
(goto-char (point-max))))
(mml-buffer-substring-no-properties-except-hard-newlines
beg (if (> count 0)
(point)
(match-beginning 0))))
(if (re-search-forward
"<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t)
(prog1
(mml-buffer-substring-no-properties-except-hard-newlines
beg (match-beginning 0))
(if (or (not (match-beginning 1))
(equal (match-string 2) "multipart"))
(goto-char (match-beginning 0))
(when (looking-at "[ \t]*\n")
(forward-line 1))))
(mml-buffer-substring-no-properties-except-hard-newlines
beg (goto-char (point-max)))))))
(defvar mml-boundary nil)
(defvar mml-base-boundary "-=-=")
(defvar mml-multipart-number 0)
(defun mml-generate-mime ()
"Generate a MIME message based on the current MML document."
(let ((cont (mml-parse))
(mml-multipart-number mml-multipart-number))
(if (not cont)
nil
(with-temp-buffer
(if (and (consp (car cont))
(= (length cont) 1))
(mml-generate-mime-1 (car cont))
(mml-generate-mime-1 (nconc (list 'multipart '(type . "mixed"))
cont)))
(buffer-string)))))
(defun mml-generate-mime-1 (cont)
(let ((mm-use-ultra-safe-encoding
(or mm-use-ultra-safe-encoding (assq 'sign cont))))
(save-restriction
(narrow-to-region (point) (point))
(mml-tweak-part cont)
(cond
((or (eq (car cont) 'part) (eq (car cont) 'mml))
(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")))
coded encoding charset flowed)
(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 filename
(not (equal (cdr (assq 'nofile cont)) "yes")))
(let ((coding-system-for-read charset))
(mm-insert-file-contents filename)))
((eq 'mml (car cont))
(insert (cdr (assq 'contents cont))))
(t
(save-restriction
(narrow-to-region (point) (point))
(insert (cdr (assq 'contents cont)))
;; Remove quotes from quoted tags.
(goto-char (point-min))
(while (re-search-forward
"<#!+/?\\(part\\|multipart\\|external\\|mml\\)"
nil t)
(delete-region (+ (match-beginning 0) 2)
(+ (match-beginning 0) 3))))))
(cond
((eq (car cont) 'mml)
(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))))
((string= (car (split-string type "/")) "message")
(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))))))
(setq coded (buffer-string)))
(mml-insert-mime-headers cont type charset encoding flowed)
(insert "\n")
(insert coded))
(mm-with-unibyte-buffer
(cond
((cdr (assq 'buffer 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)))
(t
(insert (cdr (assq 'contents cont)))))
(setq encoding (mm-encode-buffer type)
coded (mm-string-as-multibyte (buffer-string))))
(mml-insert-mime-headers cont type charset encoding nil)
(insert "\n")
(mm-with-unibyte-current-buffer
(insert coded)))))
((eq (car cont) 'external)
(insert "Content-Type: message/external-body")
(let ((parameters (mml-parameter-string
cont '(expiration size permission)))
(name (cdr (assq 'name cont)))
(url (cdr (assq 'url cont))))
(when name
(setq name (mml-parse-file-name name))
(if (stringp name)
(mml-insert-parameter
(mail-header-encode-parameter "name" name)
"access-type=local-file")
(mml-insert-parameter
(mail-header-encode-parameter
"name" (file-name-nondirectory (nth 2 name)))
(mail-header-encode-parameter "site" (nth 1 name))
(mail-header-encode-parameter
"directory" (file-name-directory (nth 2 name))))
(mml-insert-parameter
(concat "access-type="
(if (member (nth 0 name) '("ftp@" "anonymous@"))
"anon-ftp"
"ftp")))))
(when url
(mml-insert-parameter
(mail-header-encode-parameter "url" url)
"access-type=url"))
(when parameters
(mml-insert-parameter-string
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")
"message/rfc822"
"text/plain"))
(handler (assoc type mml-generate-multipart-alist)))
(if handler
(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\""
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))))
(insert "\n--" mml-boundary "--\n")))))
(t
(error "Invalid element: %S" cont)))
;; 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 (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))
(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."
(let ((mml-boundary (funcall mml-boundary-function
(incf mml-multipart-number))))
;; This function tries again and again until it has found
;; a unique boundary.
(while (not (catch 'not-unique
(mml-compute-boundary-1 cont))))
mml-boundary))
(defun mml-compute-boundary-1 (cont)
(let (filename)
(cond
((eq (car cont) 'part)
(with-temp-buffer
(cond
((cdr (assq 'buffer 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 nil nil nil nil t))
(t
(insert (cdr (assq 'contents cont)))))
(goto-char (point-min))
(when (re-search-forward (concat "^--" (regexp-quote mml-boundary))
nil t)
(setq mml-boundary (funcall mml-boundary-function
(incf mml-multipart-number)))
(throw 'not-unique nil))))
((eq (car cont) 'multipart)
(mapcar 'mml-compute-boundary-1 (cddr cont))))
t))
(defun mml-make-boundary (number)
(concat (make-string (% number 60) ?=)
(if (> number 17)
(format "%x" number)
"")
mml-base-boundary))
(defun mml-insert-mime-headers (cont type charset encoding flowed)
(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))
mml-insert-mime-headers-always)
(when (consp charset)
(error
"Can't encode a part with several charsets"))
(insert "Content-Type: " type)
(when charset
(insert "; " (mail-header-encode-parameter
"charset" (symbol-name charset))))
(when flowed
(insert "; 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))
(when (or (setq disposition (cdr (assq 'disposition cont)))
parameters)
(insert "Content-Disposition: " (or disposition "inline"))
(when parameters
(mml-insert-parameter-string
cont mml-content-disposition-parameters))
(insert "\n"))
(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"))))
(defun mml-parameter-string (cont types)
(let ((string "")
value type)
(while (setq type (pop types))
(when (setq value (cdr (assq type cont)))
;; Strip directory component from the filename parameter.
(when (eq type 'filename)
(setq value (file-name-nondirectory value)))
(setq string (concat string "; "
(mail-header-encode-parameter
(symbol-name type) value)))))
(when (not (zerop (length string)))
string)))
(defun mml-insert-parameter-string (cont types)
(let (value type)
(while (setq type (pop types))
(when (setq value (cdr (assq type cont)))
;; Strip directory component from the filename parameter.
(when (eq type 'filename)
(setq value (file-name-nondirectory value)))
(mml-insert-parameter
(mail-header-encode-parameter
(symbol-name type) value))))))
(eval-when-compile
(defvar ange-ftp-name-format)
(defvar efs-path-regexp))
(defun mml-parse-file-name (path)
(if (if (boundp 'efs-path-regexp)
(string-match efs-path-regexp path)
(if (boundp 'ange-ftp-name-format)
(string-match (car ange-ftp-name-format) path)))
(list (match-string 1 path) (match-string 2 path)
(substring path (1+ (match-end 2))))
path))
(defun mml-insert-buffer (buffer)
"Insert BUFFER at point and quote any MML markup."
(save-restriction
(narrow-to-region (point) (point))
(insert-buffer-substring buffer)
(mml-quote-region (point-min) (point-max))
(goto-char (point-max))))
;;;
;;; Transforming MIME to MML
;;;
(defun mime-to-mml (&optional handles)
"Translate the current buffer (which should be a message) into MML.
If HANDLES is non-nil, use it instead reparsing the buffer."
;; First decode the head.
(save-restriction
(message-narrow-to-head)
(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))
(search-forward "\n\n" nil t)
(delete-region (point) (point-max))
(if (stringp (car handles))
(mml-insert-mime handles)
(mml-insert-mime handles t))
(mm-destroy-parts handles)
(save-restriction
(message-narrow-to-head)
;; Remove them, they are confusing.
(message-remove-header "Content-Type")
(message-remove-header "MIME-Version")
(message-remove-header "Content-Disposition")
(message-remove-header "Content-Transfer-Encoding")))
(defun mml-to-mime ()
"Translate the current buffer from MML to MIME."
(message-encode-message-body)
(save-restriction
(message-narrow-to-headers-or-head)
;; Skip past any From_ headers.
(while (looking-at "From ")
(forward-line 1))
(let ((mail-parse-charset message-default-charset))
(mail-encode-encoded-word-buffer))))
(defun mml-insert-mime (handle &optional no-markup)
(let (textp buffer mmlp)
;; Determine type and stuff.
(unless (stringp (car handle))
(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)
(if (setq mmlp (equal (mm-handle-media-type handle)
"message/rfc822"))
(mime-to-mml)))))
(if mmlp
(mml-insert-mml-markup handle nil t t)
(unless (and no-markup
(equal (mm-handle-media-type handle) "text/plain"))
(mml-insert-mml-markup handle buffer textp)))
(cond
(mmlp
(insert-buffer-substring buffer)
(goto-char (point-max))
(insert "<#/mml>\n"))
((stringp (car handle))
(mapcar 'mml-insert-mime (cdr handle))
(insert "<#/multipart>\n"))
(textp
(let ((charset (mail-content-type-get
(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)))
(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))
(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)))
(dolist (elem (append (cdr (mm-handle-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
(insert " buffer=\"" (buffer-name buffer) "\""))
(when nofile
(insert " nofile=yes"))
(when (mm-handle-description handle)
(insert " description=\"" (mm-handle-description handle) "\""))
(insert ">\n")))
(defun mml-insert-parameter (&rest parameters)
"Insert PARAMETERS in a nice way."
(dolist (param parameters)
(insert ";")
(let ((point (point)))
(insert " " param)
(when (> (current-column) 71)
(goto-char point)
(insert "\n ")
(end-of-line)))))
;;;
;;; 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 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 "e" 'mml-attach-external)
(define-key map "q" 'mml-quote-region)
(define-key map "m" 'mml-insert-multipart)
(define-key map "p" 'mml-insert-part)
(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)
(define-key main "\C-c\C-m" map)
main))
(easy-menu-define
mml-menu mml-mode-map ""
`("Attachments"
["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]
("Secure MIME part"
["PGP/MIME Sign Part" mml-secure-sign-pgpmime t]
["PGP/MIME Encrypt Part" mml-secure-encrypt-pgpmime t]
["PGP Sign Part" mml-secure-sign-pgp t]
["PGP Encrypt Part" mml-secure-encrypt-pgp t]
["S/MIME Sign Part" mml-secure-sign-smime t]
["S/MIME Encrypt Part" mml-secure-encrypt-smime t])
["Encrypt/Sign off" mml-unsecure-message t]
;;["Narrow" mml-narrow-to-part t]
["Quote MML" mml-quote-region t]
["Validate MML" mml-validate t]
["Preview" mml-preview t]))
(defvar mml-mode nil
"Minor mode for editing MML.")
(defun mml-mode (&optional arg)
"Minor mode for editing MML.
MML is the MIME Meta Language, a minor mode for composing MIME articles.
See Info node `(emacs-mime)Composing'.
\\{mml-mode-map}"
(interactive "P")
(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)
(easy-menu-add mml-menu mml-mode-map)
(run-hooks 'mml-mode-hook)))
;;;
;;; Helper functions for reading MIME stuff from the minibuffer and
;;; inserting stuff to the buffer.
;;;
(defun mml-minibuffer-read-file (prompt)
(let* ((completion-ignored-extensions nil)
(file (read-file-name prompt nil 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))
(unless (file-exists-p file)
(error "No such file: %s" file))
(unless (file-readable-p file)
(error "Permission denied: %s" file))
file))
(defun mml-minibuffer-read-type (name &optional default)
(mailcap-parse-mimetypes)
(let* ((default (or default
(mm-default-file-encoding name)
;; Perhaps here we should check what the file
;; looks like, and offer text/plain if it looks
;; like text/plain.
"application/octet-stream"))
(string (completing-read
(format "Content type (default %s): " default)
(mapcar 'list (mailcap-mime-types)))))
(if (not (equal string ""))
string
default)))
(defun mml-minibuffer-read-description ()
(let ((description (read-string "One line description: ")))
(when (string-match "\\`[ \t]*\\'" description)
(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")
(save-excursion
(save-restriction
;; Temporarily narrow the region to defend from changes
;; invalidating END.
(narrow-to-region beg end)
(goto-char (point-min))
;; Quote parts.
(while (re-search-forward
"<#!*/?\\(multipart\\|part\\|external\\|mml\\)" nil t)
;; Insert ! after the #.
(goto-char (+ (match-beginning 0) 2))
(insert "!")))))
(defun mml-insert-tag (name &rest plist)
"Insert an MML tag described by NAME and PLIST."
(when (symbolp name)
(setq name (symbol-name name)))
(insert "<#" name)
(while plist
(let ((key (pop plist))
(value (pop plist)))
(when value
;; Quote VALUE if it contains suspicious characters.
(when (string-match "[\"'\\~/*;() \t\n]" value)
(setq value (with-output-to-string
(let (print-escape-nonascii)
(prin1 value)))))
(insert (format " %s=%s" key value)))))
(insert ">\n"))
(defun mml-insert-empty-tag (name &rest plist)
"Insert an empty MML tag described by NAME and PLIST."
(when (symbolp name)
(setq name (symbol-name name)))
(apply #'mml-insert-tag name plist)
(insert "<#/" name ">\n"))
;;; Attachment functions.
(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."
(interactive
(let* ((file (mml-minibuffer-read-file "Attach file: "))
(type (mml-minibuffer-read-type file))
(description (mml-minibuffer-read-description))
(disposition (mml-minibuffer-read-disposition type)))
(list file type description disposition)))
(mml-insert-empty-tag 'part
'type type
'filename file
'disposition (or disposition "attachment")
'description description))
(defun mml-attach-buffer (buffer &optional type description)
"Attach a buffer to the outgoing MIME message.
See `mml-attach-file' for details of operation."
(interactive
(let* ((buffer (read-buffer "Attach buffer: "))
(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))
(defun mml-attach-external (file &optional type description)
"Attach an external file into the buffer.
FILE is an ange-ftp/efs specification of the part location.
TYPE is the MIME type to use."
(interactive
(let* ((file (mml-minibuffer-read-file "Attach external file: "))
(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))
(defun mml-insert-multipart (&optional type)
(interactive (list (completing-read "Multipart type (default mixed): "
'(("mixed") ("alternative") ("digest") ("parallel")
("signed") ("encrypted"))
nil nil "mixed")))
(or type
(setq type "mixed"))
(mml-insert-empty-tag "multipart" 'type type)
(forward-line -1))
(defun mml-insert-part (&optional type)
(interactive
(list (mml-minibuffer-read-type "")))
(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))))
(defun mml-preview (&optional raw)
"Display current buffer with Gnus, in a new buffer.
If RAW, display a raw encoded MIME message."
(interactive "P")
(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-narrow-to-headers-or-head)
(message-fetch-field "Newsgroups")))
message-posting-charset)))
(message-options-set-recipient)
(pop-to-buffer (generate-new-buffer
(concat (if raw "*Raw MIME preview of "
"*MIME preview of ") (buffer-name))))
(when (boundp 'gnus-buffers)
(push (current-buffer) gnus-buffers))
(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.
(mml-to-mime))
(if raw
(when (fboundp 'set-buffer-multibyte)
(let ((s (buffer-string)))
;; Insert the content into unibyte buffer.
(erase-buffer)
(mm-disable-multibyte)
(insert s)))
(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")
(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)))
(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)))
(goto-char (point-min)))))
(defun mml-validate ()
"Validate the current MML document."
(interactive)
(mml-parse))
(defun mml-tweak-part (cont)
"Tweak a MML part."
(let ((tweak (cdr (assq 'tweak cont)))
func)
(cond
(tweak
(setq func
(or (cdr (assoc tweak mml-tweak-function-alist))
(intern tweak))))
(mml-tweak-type-alist
(let ((alist mml-tweak-type-alist)
(type (or (cdr (assq 'type cont)) "text/plain")))
(while alist
(if (string-match (caar alist) type)
(setq func (cdar alist)
alist nil)
(setq alist (cdr alist)))))))
(if func
(funcall func cont)
cont)
(let ((alist mml-tweak-sexp-alist))
(while alist
(if (eval (caar alist))
(funcall (cdar alist) cont))
(setq alist (cdr alist)))))
cont)
(defun mml-tweak-externalize-attachments (cont)
"Tweak attached files as external parts."
(let (filename-cons)
(when (and (eq (car cont) 'part)
(not (cdr (assq 'buffer cont)))
(and (setq filename-cons (assq 'filename cont))
(not (equal (cdr (assq 'nofile cont)) "yes"))))
(setcar cont 'external)
(setcar filename-cons 'name))))
(provide 'mml)
;;; arch-tag: 583c96cf-1ffe-451b-a5e5-4733ae9ddd12
;;; mml.el ends here