X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=lisp%2Frfc2047.el;h=6fa537f2caf0c12b18831529a0afe63fa43afa57;hb=4cacb5f23eb830e6950dba987063f413977708d7;hp=81a3699f84e634ed69e45a9a56acbf5fbe1279b4;hpb=82e4a80b3c0ff0f1a74adf05d58d1e1824d6b623;p=elisp%2Fgnus.git- diff --git a/lisp/rfc2047.el b/lisp/rfc2047.el index 81a3699..6fa537f 100644 --- a/lisp/rfc2047.el +++ b/lisp/rfc2047.el @@ -1,7 +1,7 @@ ;;; rfc2047.el --- functions for encoding and decoding rfc2047 messages -;; Copyright (C) 1998, 1999, 2000, 2002, 2003, 2004 -;; Free Software Foundation, Inc. +;; Copyright (C) 1998, 1999, 2000, 2002, 2003, 2004, +;; 2005 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; MORIOKA Tomohiko @@ -19,8 +19,8 @@ ;; 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: @@ -35,6 +35,7 @@ (require 'qp) (require 'mm-util) +(require 'ietf-drums) ;; Fixme: Avoid this (used for mail-parse-charset) mm dependence on gnus. (require 'mail-prsvr) (require 'base64) @@ -44,7 +45,7 @@ '(("Newsgroups" . nil) ("Followup-To" . nil) ("Message-ID" . nil) - ("\\(Resent-\\)?\\(From\\|Cc\\|To\\|Bcc\\|Reply-To\\|Sender\ + ("\\(Resent-\\)?\\(From\\|Cc\\|To\\|Bcc\\|\\(In-\\)?Reply-To\\|Sender\ \\|Mail-Followup-To\\|Mail-Copies-To\\|Approved\\)" . address-mime) (t . mime)) "*Header/encoding method alist. @@ -319,7 +320,7 @@ Dynamically bind `rfc2047-encoding-type' to change that." ;; `address-mime' case -- take care of quoted words, comments. (with-syntax-table rfc2047-syntax-table (goto-char (point-min)) - (condition-case nil ; in case of unbalanced quotes + (condition-case err ; in case of unbalanced quotes ;; Look for rfc2822-style: sequences of atoms, quoted ;; strings, specials, whitespace. (Specials mustn't be ;; encoded.) @@ -369,14 +370,14 @@ Dynamically bind `rfc2047-encoding-type' to change that." (forward-list) ;; Encode text as an unstructured field. (let ((rfc2047-encoding-type 'mime)) - (rfc2047-encode-region (1+ start) (1- (point))) - (forward-char))) + (rfc2047-encode-region (1+ start) (1- (point)))) + (skip-chars-forward ")")) (t ; normal token/whitespace sequence ;; Find the end. ;; Skip one ASCII word, or encode continuous words ;; in which all those contain non-ASCII characters. (setq end nil) - (while (not end) + (while (not (or end (eobp))) (when (looking-at "[\000-\177]+") (setq begin (point) end (match-end 0)) @@ -396,14 +397,24 @@ Dynamically bind `rfc2047-encoding-type' to change that." (goto-char (match-beginning 0)) (setq end nil))) (goto-char end)))) + ;; Where the value nil of `end' means there may be + ;; text to have to be encoded following the point. + ;; Otherwise, the point reached to the end of ASCII + ;; words separated by whitespace or a special char. (unless end - (setq end t) (when (looking-at encodable-regexp) - (goto-char (match-end 0)) + (goto-char (setq begin (match-end 0))) (while (and (looking-at "[ \t\n]+\\([^ \t\n]+\\)") (setq end (match-end 0)) - (string-match encodable-regexp - (match-string 1))) + (progn + (while (re-search-forward + encodable-regexp end t)) + (< begin (point))) + (goto-char begin) + (or (not (re-search-forward "\\Sw" end t)) + (progn + (goto-char (match-beginning 0)) + nil))) (goto-char end)) (when (looking-at "[^ \t\n]+") (setq end (match-end 0)) @@ -412,11 +423,15 @@ Dynamically bind `rfc2047-encoding-type' to change that." ;; to be encoded so that MTAs may parse ;; them safely. (cond ((= end (point))) - ((looking-at encodable-regexp) + ((looking-at (concat "\\sw*\\(" + encodable-regexp + "\\)")) (setq end nil)) (t (goto-char (1- (match-end 0))) (unless (= (point) (match-beginning 0)) + ;; Separate encodable text and + ;; delimiter. (insert " ")))) (goto-char end) (skip-chars-forward " \t\n") @@ -430,12 +445,27 @@ Dynamically bind `rfc2047-encoding-type' to change that." (goto-char start) (if (re-search-forward encodable-regexp end 'move) (progn + (unless (memq (char-before start) '(nil ?\t ? )) + (if (progn + (goto-char start) + (skip-chars-backward "^ \t\n") + (and (looking-at "\\Sw+") + (= (match-end 0) start))) + ;; Also encode bogus delimiters. + (setq start (point)) + ;; Separate encodable text and delimiter. + (goto-char start) + (insert " ") + (setq start (1+ start) + end (1+ end)))) (rfc2047-encode start end) (setq last-encoded t)) (setq last-encoded nil))))) (error - (error "Invalid data for rfc2047 encoding: %s" - (mm-replace-in-string orig-text "[ \t\n]+" " "))))))) + (if (or debug-on-quit debug-on-error) + (signal (car err) (cdr err)) + (error "Invalid data for rfc2047 encoding: %s" + (mm-replace-in-string orig-text "[ \t\n]+" " ")))))))) (rfc2047-fold-region b (point)) (goto-char (point-max)))) @@ -448,22 +478,38 @@ By default, the string is treated as containing addresses (see (rfc2047-encode-region (point-min) (point-max)) (buffer-string))) -(defun rfc2047-encode-1 (column string cs encoder start space &optional eword) +(defvar rfc2047-encode-max-chars 76 + "Maximum characters of each header line that contain encoded-words. +If it is nil, encoded-words will not be folded. Too small value may +cause an error. Don't change this for no particular reason.") + +(defun rfc2047-encode-1 (column string cs encoder start crest tail + &optional eword) "Subroutine used by `rfc2047-encode'." (cond ((string-equal string "") (or eword "")) - ((>= column 76) - (when (and eword - (string-match "\n[ \t]+\\'" eword)) - ;; Reomove a superfluous empty line. - (setq eword (substring eword 0 (match-beginning 0)))) - (rfc2047-encode-1 (length space) string cs encoder start " " - (concat eword "\n" space))) + ((not rfc2047-encode-max-chars) + (concat start + (funcall encoder (if cs + (mm-encode-coding-string string cs) + string)) + "?=")) + ((>= column rfc2047-encode-max-chars) + (when eword + (cond ((string-match "\n[ \t]+\\'" eword) + ;; Reomove a superfluous empty line. + (setq eword (substring eword 0 (match-beginning 0)))) + ((string-match "(+\\'" eword) + ;; Break the line before the open parenthesis. + (setq crest (concat crest (match-string 0 eword)) + eword (substring eword 0 (match-beginning 0)))))) + (rfc2047-encode-1 (length crest) string cs encoder start " " tail + (concat eword "\n" crest))) (t (let ((index 0) (limit (1- (length string))) (prev "") - next) + next len) (while (and prev (<= index limit)) (setq next (concat start @@ -473,27 +519,48 @@ By default, the string is treated as containing addresses (see (substring string 0 (1+ index)) cs) (substring string 0 (1+ index)))) - "?=")) - (if (<= (+ column (length next)) 76) - (setq prev next - index (1+ index)) - (setq next prev - prev nil))) - (setq eword (concat eword next)) + "?=") + len (+ column (length next))) + (if (> len rfc2047-encode-max-chars) + (setq next prev + prev nil) + (if (or (< index limit) + (<= (+ len (or (string-match "\n" tail) + (length tail))) + rfc2047-encode-max-chars)) + (setq prev next + index (1+ index)) + (if (string-match "\\`)+" tail) + ;; Break the line after the close parenthesis. + (setq tail (concat (substring tail 0 (match-end 0)) + "\n " + (substring tail (match-end 0))) + prev next + index (1+ index)) + (setq next prev + prev nil))))) (if (> index limit) - eword + (concat eword next tail) + (if (= 0 index) + (if (and eword + (string-match "(+\\'" eword)) + (setq crest (concat crest (match-string 0 eword)) + eword (substring eword 0 (match-beginning 0))) + (setq eword (concat eword next))) + (setq crest " " + eword (concat eword next))) (when (string-match "\n[ \t]+\\'" eword) ;; Reomove a superfluous empty line. (setq eword (substring eword 0 (match-beginning 0)))) - (rfc2047-encode-1 (length space) (substring string index) - cs encoder start " " - (concat eword "\n" space))))))) + (rfc2047-encode-1 (length crest) (substring string index) + cs encoder start " " tail + (concat eword "\n" crest))))))) (defun rfc2047-encode (b e) "Encode the word(s) in the region B to E. Point moves to the end of the region." (let ((mime-charset (or (mm-find-mime-charset-region b e) (list 'us-ascii))) - cs encoding space eword) + cs encoding tail crest eword) (cond ((> (length mime-charset) 1) (error "Can't rfc2047-encode `%s'" (buffer-substring-no-properties b e))) @@ -514,12 +581,19 @@ Point moves to the end of the region." 'B 'Q))) (widen) + (goto-char e) + (skip-chars-forward "^ \t\n") + ;; `tail' may contain a close parenthesis. + (setq tail (buffer-substring-no-properties e (point))) (goto-char b) (setq b (point-marker) e (set-marker (make-marker) e)) (rfc2047-fold-region (point-at-bol) b) + (goto-char b) + (skip-chars-backward "^ \t\n") (unless (= 0 (skip-chars-backward " \t")) - (setq space (buffer-substring-no-properties (point) b))) + ;; `crest' may contain whitespace and an open parenthesis. + (setq crest (buffer-substring-no-properties (point) b))) (setq eword (rfc2047-encode-1 (- b (point-at-bol)) (mm-replace-in-string @@ -531,15 +605,21 @@ Point moves to the end of the region." 'identity) (concat "=?" (downcase (symbol-name mime-charset)) "?" (upcase (symbol-name encoding)) "?") - (or space " "))) + (or crest " ") + tail)) (delete-region (if (eq (aref eword 0) ?\n) - (point) + (if (bolp) + ;; The line was folded before encoding. + (1- (point)) + (point)) (goto-char b)) - e) + (+ e (length tail))) + ;; `eword' contains `crest' and `tail'. (insert eword) (set-marker b nil) (set-marker e nil) - (unless (or (eolp) + (unless (or (/= 0 (length tail)) + (eobp) (looking-at "[ \t\n)]")) (insert " ")))) (t @@ -610,9 +690,10 @@ Point moves to the end of the region." (goto-char (or break qword-break)) (setq break nil qword-break nil) - (if (looking-at "[ \t]") - (insert ?\n) - (insert "\n ")) + (if (or (> 0 (skip-chars-backward " \t")) + (looking-at "[ \t]")) + (insert ?\n) + (insert "\n ")) (setq bol (1- (point))) ;; Don't break before the first non-LWSP characters. (skip-chars-forward " \t") @@ -669,6 +750,27 @@ Point moves to the end of the region." (subst-char-in-region (point-min) (point-max) ? ?_) (buffer-string))) +(defun rfc2047-encode-parameter (param value) + "Return and PARAM=VALUE string encoded in the RFC2047-like style. +This is a replacement for the `rfc2231-encode-string' function. + +When attaching files as MIME parts, we should use the RFC2231 encoding +to specify the file names containing non-ASCII characters. However, +many mail softwares don't support it in practice and recipients won't +be able to extract files with correct names. Instead, the RFC2047-like +encoding is acceptable generally. This function provides the very +RFC2047-like encoding, resigning to such a regrettable trend. To use +it, put the following line in your ~/.gnus.el file: + +\(defalias 'mail-header-encode-parameter 'rfc2047-encode-parameter) +" + (let* ((rfc2047-encoding-type 'mime) + (rfc2047-encode-max-chars nil) + (string (rfc2047-encode-string value))) + (if (string-match (concat "[" ietf-drums-tspecials "]") string) + (format "%s=%S" param string) + (concat param "=" string)))) + ;;; ;;; Functions for decoding RFC2047 messages ;;; @@ -678,6 +780,9 @@ Point moves to the end of the region." "=\\?\\([^][\000-\040()<>@,\;:*\\\"/?.=]+\\)\\(?:\\*[^?]+\\)?\ \\?\\(B\\|Q\\)\\?\\([!->@-~ ]*\\)\\?=")) +(defvar rfc2047-quote-decoded-words-containing-tspecials nil + "If non-nil, quote decoded words containing special characters.") + ;; Fixme: This should decode in place, not cons intermediate strings. ;; Also check whether it needs to worry about delimiting fields like ;; encoding. @@ -712,14 +817,66 @@ Point moves to the end of the region." (insert (rfc2047-parse-and-decode (prog1 (match-string 0) - (delete-region (match-beginning 0) (match-end 0))))) - ;; Remove newlines between decoded words, though such things - ;; essentially must not be there. + (delete-region e (match-end 0))))) + (while (looking-at rfc2047-encoded-word-regexp) + (insert (rfc2047-parse-and-decode + (prog1 + (match-string 0) + (delete-region (point) (match-end 0)))))) (save-restriction (narrow-to-region e (point)) (goto-char e) + ;; Remove newlines between decoded words, though such + ;; things essentially must not be there. (while (re-search-forward "[\n\r]+" nil t) (replace-match " ")) + ;; Quote decoded words if there are special characters + ;; which might violate RFC2822. + (when (and rfc2047-quote-decoded-words-containing-tspecials + (let ((regexp (car (rassq + 'address-mime + rfc2047-header-encoding-alist)))) + (when regexp + (save-restriction + (widen) + (beginning-of-line) + (while (and (memq (char-after) '(? ?\t)) + (zerop (forward-line -1)))) + (looking-at regexp))))) + (let (quoted) + (goto-char e) + (skip-chars-forward " \t") + (setq start (point)) + (setq quoted (eq (char-after) ?\")) + (goto-char (point-max)) + (skip-chars-backward " \t") + (if (setq quoted (and quoted + (> (point) (1+ start)) + (eq (char-before) ?\"))) + (progn + (backward-char) + (setq start (1+ start) + end (point-marker))) + (setq end (point-marker))) + (goto-char start) + (while (search-forward "\"" end t) + (when (prog2 + (backward-char) + (zerop (% (skip-chars-backward "\\\\") 2)) + (goto-char (match-beginning 0))) + (insert "\\")) + (forward-char)) + (when (and (not quoted) + (progn + (goto-char start) + (re-search-forward + (concat "[" ietf-drums-tspecials "]") + end t))) + (goto-char start) + (insert "\"") + (goto-char end) + (insert "\"")) + (set-marker end nil))) (goto-char (point-max))) (when (and (mm-multibyte-p) mail-parse-charset