mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-15 10:30:25 -08:00
Merge changes from emacs-23 branch
This commit is contained in:
commit
07976ae3b8
46 changed files with 1227 additions and 523 deletions
|
|
@ -27,17 +27,57 @@
|
|||
|
||||
;; Essentially based on the design of Alexander Pohoyda's MIME
|
||||
;; extensions (mime-display.el and mime.el).
|
||||
;; Call `M-x rmail-mime' when viewing an Rmail message.
|
||||
|
||||
;; This file provides two operation modes for viewing a MIME message.
|
||||
|
||||
;; (1) When rmail-enable-mime is non-nil (now it is the default), the
|
||||
;; function `rmail-show-mime' is automatically called. That function
|
||||
;; shows a MIME message directly in RMAIL's view buffer.
|
||||
|
||||
;; (2) When rmail-enable-mime is nil, the command 'v' (or M-x
|
||||
;; rmail-mime) shows a MIME message in a new buffer "*RMAIL*".
|
||||
|
||||
;; Both operations share the intermediate functions rmail-mime-process
|
||||
;; and rmail-mime-process-multipart as below.
|
||||
|
||||
;; rmail-show-mime
|
||||
;; +- rmail-mime-parse
|
||||
;; | +- rmail-mime-process <--+------------+
|
||||
;; | | +---------+ |
|
||||
;; | + rmail-mime-process-multipart --+
|
||||
;; |
|
||||
;; + rmail-mime-insert <----------------+
|
||||
;; +- rmail-mime-insert-text |
|
||||
;; +- rmail-mime-insert-bulk |
|
||||
;; +- rmail-mime-insert-multipart --+
|
||||
;;
|
||||
;; rmail-mime
|
||||
;; +- rmail-mime-show <----------------------------------+
|
||||
;; +- rmail-mime-process |
|
||||
;; +- rmail-mime-handle |
|
||||
;; +- rmail-mime-text-handler |
|
||||
;; +- rmail-mime-bulk-handler |
|
||||
;; | + rmail-mime-insert-bulk
|
||||
;; +- rmail-mime-multipart-handler |
|
||||
;; +- rmail-mime-process-multipart --+
|
||||
|
||||
;; In addition, for the case of rmail-enable-mime being non-nil, this
|
||||
;; file provides two functions rmail-insert-mime-forwarded-message and
|
||||
;; rmail-insert-mime-resent-message for composing forwarded and resent
|
||||
;; messages respectively.
|
||||
|
||||
;; Todo:
|
||||
|
||||
;; Handle multipart/alternative.
|
||||
;; Make rmail-mime-media-type-handlers-alist usable in the first
|
||||
;; operation mode.
|
||||
;; Handle multipart/alternative in the second operation mode.
|
||||
;; Offer the option to call external/internal viewers (doc-view, xpdf, etc).
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'rmail)
|
||||
(require 'mail-parse)
|
||||
(require 'message)
|
||||
|
||||
;;; User options.
|
||||
|
||||
|
|
@ -91,6 +131,52 @@ automatically display the image in the buffer."
|
|||
|
||||
;;; End of user options.
|
||||
|
||||
;;; MIME-entity object
|
||||
|
||||
(defun rmail-mime-entity (type disposition transfer-encoding
|
||||
header body children)
|
||||
"Retrun a newly created MIME-entity object.
|
||||
|
||||
A MIME-entity is a vector of 6 elements:
|
||||
|
||||
[ TYPE DISPOSITION TRANSFER-ENCODING HEADER BODY CHILDREN ]
|
||||
|
||||
TYPE and DISPOSITION correspond to MIME headers Content-Type: and
|
||||
Cotent-Disposition: respectively, and has this format:
|
||||
|
||||
\(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
|
||||
|
||||
VALUE is a string and ATTRIBUTE is a symbol.
|
||||
|
||||
Consider the following header, for example:
|
||||
|
||||
Content-Type: multipart/mixed;
|
||||
boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
|
||||
|
||||
The corresponding TYPE argument must be:
|
||||
|
||||
\(\"multipart/mixed\"
|
||||
\(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))
|
||||
|
||||
TRANSFER-ENCODING corresponds to MIME header
|
||||
Content-Transfer-Encoding, and is a lowercased string.
|
||||
|
||||
HEADER and BODY are a cons (BEG . END), where BEG and END specify
|
||||
the region of the corresponding part in RMAIL's data (mbox)
|
||||
buffer. BODY may be nil. In that case, the current buffer is
|
||||
narrowed to the body part.
|
||||
|
||||
CHILDREN is a list of MIME-entities for a \"multipart\" entity, and
|
||||
nil for the other types."
|
||||
(vector type disposition transfer-encoding header body children))
|
||||
|
||||
;; Accessors for a MIME-entity object.
|
||||
(defsubst rmail-mime-entity-type (entity) (aref entity 0))
|
||||
(defsubst rmail-mime-entity-disposition (entity) (aref entity 1))
|
||||
(defsubst rmail-mime-entity-transfer-encoding (entity) (aref entity 2))
|
||||
(defsubst rmail-mime-entity-header (entity) (aref entity 3))
|
||||
(defsubst rmail-mime-entity-body (entity) (aref entity 4))
|
||||
(defsubst rmail-mime-entity-children (entity) (aref entity 5))
|
||||
|
||||
;;; Buttons
|
||||
|
||||
|
|
@ -99,6 +185,7 @@ automatically display the image in the buffer."
|
|||
(let* ((filename (button-get button 'filename))
|
||||
(directory (button-get button 'directory))
|
||||
(data (button-get button 'data))
|
||||
(mbox-buf rmail-view-buffer)
|
||||
(ofilename filename))
|
||||
(setq filename (expand-file-name
|
||||
(read-file-name (format "Save as (default: %s): " filename)
|
||||
|
|
@ -117,7 +204,17 @@ automatically display the image in the buffer."
|
|||
;; file, the magic signature compares equal with the unibyte
|
||||
;; signature string recorded in jka-compr-compression-info-list.
|
||||
(set-buffer-multibyte nil)
|
||||
(insert data)
|
||||
(setq buffer-undo-list t)
|
||||
(if (stringp data)
|
||||
(insert data)
|
||||
;; DATA is a MIME-entity object.
|
||||
(let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
|
||||
(body (rmail-mime-entity-body data)))
|
||||
(insert-buffer-substring mbox-buf (car body) (cdr body))
|
||||
(cond ((string= transfer-encoding "base64")
|
||||
(ignore-errors (base64-decode-region (point-min) (point-max))))
|
||||
((string= transfer-encoding "quoted-printable")
|
||||
(quoted-printable-decode-region (point-min) (point-max))))))
|
||||
(write-region nil nil filename nil nil nil t))))
|
||||
|
||||
(define-button-type 'rmail-mime-save 'action 'rmail-mime-save)
|
||||
|
|
@ -134,6 +231,23 @@ automatically display the image in the buffer."
|
|||
(when (coding-system-p coding-system)
|
||||
(decode-coding-region (point-min) (point-max) coding-system))))
|
||||
|
||||
(defun rmail-mime-insert-text (entity)
|
||||
"Insert MIME-entity ENTITY as a plain text MIME part in the current buffer."
|
||||
(let* ((content-type (rmail-mime-entity-type entity))
|
||||
(charset (cdr (assq 'charset (cdr content-type))))
|
||||
(coding-system (if charset (intern (downcase charset))))
|
||||
(transfer-encoding (rmail-mime-entity-transfer-encoding entity))
|
||||
(body (rmail-mime-entity-body entity)))
|
||||
(save-restriction
|
||||
(narrow-to-region (point) (point))
|
||||
(insert-buffer-substring rmail-buffer (car body) (cdr body))
|
||||
(cond ((string= transfer-encoding "base64")
|
||||
(ignore-errors (base64-decode-region (point-min) (point-max))))
|
||||
((string= transfer-encoding "quoted-printable")
|
||||
(quoted-printable-decode-region (point-min) (point-max))))
|
||||
(if (coding-system-p coding-system)
|
||||
(decode-coding-region (point-min) (point-max) coding-system)))))
|
||||
|
||||
;; FIXME move to the test/ directory?
|
||||
(defun test-rmail-mime-handler ()
|
||||
"Test of a mail using no MIME parts at all."
|
||||
|
|
@ -152,10 +266,28 @@ MIME-Version: 1.0
|
|||
|
||||
|
||||
(defun rmail-mime-insert-image (type data)
|
||||
"Insert an image of type TYPE, where DATA is the image data."
|
||||
"Insert an image of type TYPE, where DATA is the image data.
|
||||
If DATA is not a string, it is a MIME-entity object."
|
||||
(end-of-line)
|
||||
(insert ?\n)
|
||||
(insert-image (create-image data type t)))
|
||||
(let ((modified (buffer-modified-p)))
|
||||
(insert ?\n)
|
||||
(unless (stringp data)
|
||||
;; DATA is a MIME-entity.
|
||||
(let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
|
||||
(body (rmail-mime-entity-body data))
|
||||
(mbox-buffer rmail-view-buffer))
|
||||
(with-temp-buffer
|
||||
(set-buffer-multibyte nil)
|
||||
(setq buffer-undo-list t)
|
||||
(insert-buffer-substring mbox-buffer (car body) (cdr body))
|
||||
(cond ((string= transfer-encoding "base64")
|
||||
(ignore-errors (base64-decode-region (point-min) (point-max))))
|
||||
((string= transfer-encoding "quoted-printable")
|
||||
(quoted-printable-decode-region (point-min) (point-max))))
|
||||
(setq data
|
||||
(buffer-substring-no-properties (point-min) (point-max))))))
|
||||
(insert-image (create-image data type t))
|
||||
(set-buffer-modified-p modified)))
|
||||
|
||||
(defun rmail-mime-image (button)
|
||||
"Display the image associated with BUTTON."
|
||||
|
|
@ -172,8 +304,19 @@ MIME-Version: 1.0
|
|||
"Handle the current buffer as an attachment to download.
|
||||
For images that Emacs is capable of displaying, the behavior
|
||||
depends upon the value of `rmail-mime-show-images'."
|
||||
(rmail-mime-insert-bulk
|
||||
(rmail-mime-entity content-type content-disposition content-transfer-encoding
|
||||
nil nil nil)))
|
||||
|
||||
(defun rmail-mime-insert-bulk (entity)
|
||||
"Inesrt a MIME-entity ENTITY as an attachment.
|
||||
The optional second arg DATA, if non-nil, is a string containing
|
||||
the attachment data that is already decoded."
|
||||
;; Find the default directory for this media type.
|
||||
(let* ((directory (catch 'directory
|
||||
(let* ((content-type (rmail-mime-entity-type entity))
|
||||
(content-disposition (rmail-mime-entity-disposition entity))
|
||||
(body (rmail-mime-entity-body entity))
|
||||
(directory (catch 'directory
|
||||
(dolist (entry rmail-mime-attachment-dirs-alist)
|
||||
(when (string-match (car entry) (car content-type))
|
||||
(dolist (dir (cdr entry))
|
||||
|
|
@ -183,17 +326,21 @@ depends upon the value of `rmail-mime-show-images'."
|
|||
(cdr (assq 'filename (cdr content-disposition)))
|
||||
"noname"))
|
||||
(label (format "\nAttached %s file: " (car content-type)))
|
||||
(data (buffer-string))
|
||||
(udata (string-as-unibyte data))
|
||||
(size (length udata))
|
||||
(osize size)
|
||||
(units '(B kB MB GB))
|
||||
type)
|
||||
(while (and (> size 1024.0) ; cribbed from gnus-agent-expire-done-message
|
||||
data udata size osize type)
|
||||
(if body
|
||||
(setq data entity
|
||||
udata entity
|
||||
size (- (cdr body) (car body)))
|
||||
(setq data (buffer-string)
|
||||
udata (string-as-unibyte data)
|
||||
size (length udata))
|
||||
(delete-region (point-min) (point-max)))
|
||||
(setq osize size)
|
||||
(while (and (> size 1024.0) ; cribbed from gnus-agent-expire-done-message
|
||||
(cdr units))
|
||||
(setq size (/ size 1024.0)
|
||||
units (cdr units)))
|
||||
(delete-region (point-min) (point-max))
|
||||
(insert label)
|
||||
(insert-button filename
|
||||
:type 'rmail-mime-save
|
||||
|
|
@ -249,6 +396,22 @@ The current buffer should be narrowed to the body. CONTENT-TYPE,
|
|||
CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING are the values
|
||||
of the respective parsed headers. See `rmail-mime-handle' for their
|
||||
format."
|
||||
(rmail-mime-process-multipart
|
||||
content-type content-disposition content-transfer-encoding nil))
|
||||
|
||||
(defun rmail-mime-process-multipart (content-type
|
||||
content-disposition
|
||||
content-transfer-encoding
|
||||
parse-only)
|
||||
"Process the current buffer as a multipart MIME body.
|
||||
|
||||
If PARSE-ONLY is nil, modify the current buffer directly for showing
|
||||
the MIME body and return nil.
|
||||
|
||||
Otherwise, just parse the current buffer and return a list of
|
||||
MIME-entity objects.
|
||||
|
||||
The other arguments are the same as `rmail-mime-multipart-handler'."
|
||||
;; Some MUAs start boundaries with "--", while it should start
|
||||
;; with "CRLF--", as defined by RFC 2046:
|
||||
;; The boundary delimiter MUST occur at the beginning of a line,
|
||||
|
|
@ -257,7 +420,7 @@ format."
|
|||
;; of the preceding part.
|
||||
;; We currently don't handle that.
|
||||
(let ((boundary (cdr (assq 'boundary content-type)))
|
||||
beg end next)
|
||||
beg end next entities)
|
||||
(unless boundary
|
||||
(rmail-mm-get-boundary-error-message
|
||||
"No boundary defined" content-type content-disposition
|
||||
|
|
@ -267,7 +430,9 @@ format."
|
|||
(goto-char (point-min))
|
||||
(when (and (search-forward boundary nil t)
|
||||
(looking-at "[ \t]*\n"))
|
||||
(delete-region (point-min) (match-end 0)))
|
||||
(if parse-only
|
||||
(narrow-to-region (match-end 0) (point-max))
|
||||
(delete-region (point-min) (match-end 0))))
|
||||
;; Loop over all body parts, where beg points at the beginning of
|
||||
;; the part and end points at the end of the part. next points at
|
||||
;; the beginning of the next part.
|
||||
|
|
@ -285,13 +450,17 @@ format."
|
|||
(rmail-mm-get-boundary-error-message
|
||||
"Malformed boundary" content-type content-disposition
|
||||
content-transfer-encoding)))
|
||||
(delete-region end next)
|
||||
;; Handle the part.
|
||||
(save-restriction
|
||||
(narrow-to-region beg end)
|
||||
(rmail-mime-show))
|
||||
(goto-char (setq beg next)))))
|
||||
|
||||
(if parse-only
|
||||
(save-restriction
|
||||
(narrow-to-region beg end)
|
||||
(setq entities (cons (rmail-mime-process nil t) entities)))
|
||||
(delete-region end next)
|
||||
(save-restriction
|
||||
(narrow-to-region beg end)
|
||||
(rmail-mime-show)))
|
||||
(goto-char (setq beg next)))
|
||||
(nreverse entities)))
|
||||
|
||||
(defun test-rmail-mime-multipart-handler ()
|
||||
"Test of a mail used as an example in RFC 2046."
|
||||
|
|
@ -394,6 +563,9 @@ called recursively if multiple parts are available.
|
|||
|
||||
The current buffer must contain a single message. It will be
|
||||
modified."
|
||||
(rmail-mime-process show-headers nil))
|
||||
|
||||
(defun rmail-mime-process (show-headers parse-only)
|
||||
(let ((end (point-min))
|
||||
content-type
|
||||
content-transfer-encoding
|
||||
|
|
@ -437,14 +609,105 @@ modified."
|
|||
;; attachment according to RFC 2183.
|
||||
(unless (member (car content-disposition) '("inline" "attachment"))
|
||||
(setq content-disposition '("attachment")))
|
||||
;; Hide headers and handle the part.
|
||||
(save-restriction
|
||||
(cond ((string= (car content-type) "message/rfc822")
|
||||
(narrow-to-region end (point-max)))
|
||||
((not show-headers)
|
||||
(delete-region (point-min) end)))
|
||||
(rmail-mime-handle content-type content-disposition
|
||||
content-transfer-encoding))))
|
||||
|
||||
(if parse-only
|
||||
(cond ((string-match "multipart/.*" (car content-type))
|
||||
(setq end (1- end))
|
||||
(save-restriction
|
||||
(let ((header (if show-headers (cons (point-min) end))))
|
||||
(narrow-to-region end (point-max))
|
||||
(rmail-mime-entity content-type
|
||||
content-disposition
|
||||
content-transfer-encoding
|
||||
header nil
|
||||
(rmail-mime-process-multipart
|
||||
content-type content-disposition
|
||||
content-transfer-encoding t)))))
|
||||
((string-match "message/rfc822" (car content-type))
|
||||
(or show-headers
|
||||
(narrow-to-region end (point-max)))
|
||||
(rmail-mime-process t t))
|
||||
(t
|
||||
(rmail-mime-entity content-type
|
||||
content-disposition
|
||||
content-transfer-encoding
|
||||
nil
|
||||
(cons end (point-max))
|
||||
nil)))
|
||||
;; Hide headers and handle the part.
|
||||
(save-restriction
|
||||
(cond ((string= (car content-type) "message/rfc822")
|
||||
(narrow-to-region end (point-max)))
|
||||
((not show-headers)
|
||||
(delete-region (point-min) end)))
|
||||
(rmail-mime-handle content-type content-disposition
|
||||
content-transfer-encoding)))))
|
||||
|
||||
(defun rmail-mime-insert-multipart (entity)
|
||||
"Insert MIME-entity ENTITY of multipart type in the current buffer."
|
||||
(let ((subtype (cadr (split-string (car (rmail-mime-entity-type entity))
|
||||
"/")))
|
||||
(disposition (rmail-mime-entity-disposition entity))
|
||||
(header (rmail-mime-entity-header entity))
|
||||
(children (rmail-mime-entity-children entity)))
|
||||
(if header
|
||||
(let ((pos (point)))
|
||||
(or (bolp)
|
||||
(insert "\n"))
|
||||
(insert-buffer-substring rmail-buffer (car header) (cdr header))
|
||||
(rfc2047-decode-region pos (point))
|
||||
(insert "\n")))
|
||||
(cond
|
||||
((string= subtype "mixed")
|
||||
(dolist (child children)
|
||||
(rmail-mime-insert child '("text/plain") disposition)))
|
||||
((string= subtype "digest")
|
||||
(dolist (child children)
|
||||
(rmail-mime-insert child '("message/rfc822") disposition)))
|
||||
((string= subtype "alternative")
|
||||
(let (best-plain-text best-text)
|
||||
(dolist (child children)
|
||||
(if (string= (or (car (rmail-mime-entity-disposition child))
|
||||
(car disposition))
|
||||
"inline")
|
||||
(if (string-match "text/plain"
|
||||
(car (rmail-mime-entity-type child)))
|
||||
(setq best-plain-text child)
|
||||
(if (string-match "text/.*"
|
||||
(car (rmail-mime-entity-type child)))
|
||||
(setq best-text child)))))
|
||||
(if (or best-plain-text best-text)
|
||||
(rmail-mime-insert (or best-plain-text best-text))
|
||||
;; No child could be handled. Insert all.
|
||||
(dolist (child children)
|
||||
(rmail-mime-insert child nil disposition)))))
|
||||
(t
|
||||
;; Unsupported subtype. Insert all of them.
|
||||
(dolist (child children)
|
||||
(rmail-mime-insert child))))))
|
||||
|
||||
(defun rmail-mime-parse ()
|
||||
"Parse the current Rmail message as a MIME message.
|
||||
The value is a MIME-entiy object (see `rmail-mime-enty-new')."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(rmail-mime-process nil t)))
|
||||
|
||||
(defun rmail-mime-insert (entity &optional content-type disposition)
|
||||
"Insert a MIME-entity ENTITY in the current buffer.
|
||||
|
||||
This function will be called recursively if multiple parts are
|
||||
available."
|
||||
(if (rmail-mime-entity-children entity)
|
||||
(rmail-mime-insert-multipart entity)
|
||||
(setq content-type
|
||||
(or (rmail-mime-entity-type entity) content-type))
|
||||
(setq disposition
|
||||
(or (rmail-mime-entity-disposition entity) disposition))
|
||||
(if (and (string= (car disposition) "inline")
|
||||
(string-match "text/.*" (car content-type)))
|
||||
(rmail-mime-insert-text entity)
|
||||
(rmail-mime-insert-bulk entity))))
|
||||
|
||||
(define-derived-mode rmail-mime-mode fundamental-mode "RMIME"
|
||||
"Major mode used in `rmail-mime' buffers."
|
||||
|
|
@ -480,6 +743,50 @@ attachments as specfied by `rmail-mime-attachment-dirs-alist'."
|
|||
(error "%s; type: %s; disposition: %s; encoding: %s"
|
||||
message type disposition encoding))
|
||||
|
||||
(defun rmail-show-mime ()
|
||||
(let ((mbox-buf rmail-buffer))
|
||||
(condition-case nil
|
||||
(let ((entity (rmail-mime-parse)))
|
||||
(with-current-buffer rmail-view-buffer
|
||||
(let ((inhibit-read-only t)
|
||||
(rmail-buffer mbox-buf))
|
||||
(erase-buffer)
|
||||
(rmail-mime-insert entity))))
|
||||
(error
|
||||
;; Decoding failed. Insert the original message body as is.
|
||||
(let ((region (with-current-buffer mbox-buf
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$" nil t)
|
||||
(forward-line 1)
|
||||
(cons (point) (point-max)))))
|
||||
(with-current-buffer rmail-view-buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring mbox-buf (car region) (cdr region))))
|
||||
(message "MIME decoding failed"))))))
|
||||
|
||||
(setq rmail-show-mime-function 'rmail-show-mime)
|
||||
|
||||
(defun rmail-insert-mime-forwarded-message (forward-buffer)
|
||||
(let ((mbox-buf (with-current-buffer forward-buffer rmail-view-buffer)))
|
||||
(save-restriction
|
||||
(narrow-to-region (point) (point))
|
||||
(message-forward-make-body-mime mbox-buf))))
|
||||
|
||||
(setq rmail-insert-mime-forwarded-message-function
|
||||
'rmail-insert-mime-forwarded-message)
|
||||
|
||||
(defun rmail-insert-mime-resent-message (forward-buffer)
|
||||
(insert-buffer-substring
|
||||
(with-current-buffer forward-buffer rmail-view-buffer))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "From ")
|
||||
(forward-line 1)
|
||||
(delete-region (point-min) (point))))
|
||||
|
||||
(setq rmail-insert-mime-resent-message-function
|
||||
'rmail-insert-mime-resent-message)
|
||||
|
||||
(provide 'rmailmm)
|
||||
|
||||
;; Local Variables:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue