1 ;;; rfc2047.el --- functions for encoding and decoding rfc2047 messages -*- lexical-binding:t -*-
3 ;; Copyright (C) 1998-2017 Free Software Foundation, Inc.
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; MORIOKA Tomohiko <morioka@jaist.ac.jp>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24 ;; RFC 2047 is "MIME (Multipurpose Internet Mail Extensions) Part
25 ;; Three: Message Header Extensions for Non-ASCII Text".
29 (eval-when-compile (require 'cl-lib
))
30 (defvar message-posting-charset
)
34 ;; Fixme: Avoid this (used for mail-parse-charset) mm dependence on gnus.
36 (require 'rfc2045
) ;; rfc2045-encode-string
37 (autoload 'mm-body-7-or-8
"mm-bodies")
44 (defcustom rfc2047-header-encoding-alist
45 '(("Newsgroups" . nil
)
48 ("\\(Resent-\\)?\\(From\\|Cc\\|To\\|Bcc\\|\\(In-\\)?Reply-To\\|Sender\
49 \\|Mail-Followup-To\\|Mail-Copies-To\\|Approved\\)" . address-mime
)
51 "Header/encoding method alist.
52 The list is traversed sequentially. The keys can either be
57 1) nil, in which case no encoding is done;
58 2) `mime', in which case the header will be encoded according to RFC2047;
59 3) `address-mime', like `mime', but takes account of the rules for address
60 fields (where quoted strings and comments must be treated separately);
61 4) a charset, in which case it will be encoded as that charset;
62 5) `default', in which case the field will be encoded as the rest
64 :type
'(alist :key-type
(choice regexp
(const t
))
65 :value-type
(choice (const nil
) (const mime
)
70 (defvar rfc2047-charset-encoding-alist
96 "Alist of MIME charsets to RFC2047 encodings.
97 Valid encodings are nil, `Q' and `B'. These indicate binary (no) encoding,
98 quoted-printable and base64 respectively.")
100 (defvar rfc2047-encode-function-alist
101 '((Q . rfc2047-q-encode-string
)
102 (B . rfc2047-b-encode-string
)
104 "Alist of RFC2047 encodings to encoding functions.")
106 (defvar rfc2047-encode-encoded-words t
107 "Whether encoded words should be encoded again.")
109 (defcustom rfc2047-allow-irregular-q-encoded-words t
110 "Whether to decode irregular Q-encoded words."
113 (eval-and-compile ;; Necessary to hard code them in `rfc2047-decode-region'.
114 (defconst rfc2047-encoded-word-regexp
115 "=\\?\\([^][\000-\040()<>@,;:*\\\"/?.=]+\\)\\(?:\\*[^?]+\\)?\\?\
116 \\(B\\?[+/0-9A-Za-z]*=*\
119 "Regexp that matches encoded word."
120 ;; The patterns for the B encoding and the Q encoding, i.e. the ones
121 ;; beginning with "B" and "Q" respectively, are restricted into only
122 ;; the characters that those encodings may generally use.
124 (defconst rfc2047-encoded-word-regexp-loose
125 "=\\?\\([^][\000-\040()<>@,;:*\\\"/?.=]+\\)\\(?:\\*[^?]+\\)?\\?\
126 \\(B\\?[+/0-9A-Za-z]*=*\
127 \\|Q\\?\\(?:\\?+[ -<>@-~]\\)?\\(?:[ ->@-~]+\\?+[ -<>@-~]\\)*[ ->@-~]*\\?*\
129 "Regexp that matches encoded word allowing loose Q encoding."
130 ;; The pattern for the Q encoding, i.e. the one beginning with "Q",
132 ;; "Q\\?\\(\\?+[^\n=?]\\)?\\([^\n?]+\\?+[^\n=?]\\)*[^\n?]*\\?*"
133 ;; <--------1-------><----------2,3----------><--4--><-5->
135 ;; 1. After "Q?", allow "?"s that follow a character other than "=".
136 ;; 2. Allow "=" after "Q?"; it isn't regarded as the terminator.
137 ;; 3. In the middle of an encoded word, allow "?"s that follow a
138 ;; character other than "=".
139 ;; 4. Allow any characters other than "?" in the middle of an
141 ;; 5. At the end, allow "?"s.
145 ;;; Functions for encoding RFC2047 messages
148 (defun rfc2047-qp-or-base64 ()
149 "Return the type with which to encode the buffer.
150 This is either `base64' or `quoted-printable'."
152 (let ((limit (min (point-max) (+ 2000 (point-min))))
154 (goto-char (point-min))
155 (skip-chars-forward "\x20-\x7f\r\n\t" limit
)
156 (while (< (point) limit
)
159 (skip-chars-forward "\x20-\x7f\r\n\t" limit
))
160 (if (or (< (* 6 n8bit
) (- limit
(point-min)))
161 ;; Don't base64, say, a short line with a single
162 ;; non-ASCII char when splitting parts by charset.
167 (defun rfc2047-narrow-to-field ()
168 "Narrow the buffer to the header on the current line."
174 (if (re-search-forward "^[^ \n\t]" nil t
)
177 (goto-char (point-min)))
179 (defun rfc2047-field-value ()
180 "Return the value of the field at point."
183 (rfc2047-narrow-to-field)
184 (re-search-forward ":[ \t\n]*" nil t
)
185 (buffer-substring-no-properties (point) (point-max)))))
187 (defun rfc2047-quote-special-characters-in-quoted-strings (&optional
189 "Quote special characters with `\\'s in quoted strings.
190 Quoting will not be done in a quoted string if it contains characters
191 matching ENCODABLE-REGEXP or it is within parentheses."
192 (goto-char (point-min))
193 (let ((tspecials (concat "[" ietf-drums-tspecials
"]"))
196 (with-syntax-table (standard-syntax-table)
200 (eq (char-before) ?\
)))
202 (goto-char (point-max)))
204 (narrow-to-region start
(point))
206 (while (search-forward "\"" nil t
)
207 (setq beg
(match-beginning 0))
208 (unless (eq (char-before beg
) ?
\\)
214 (setq end
(1- (point)))
216 (if (and encodable-regexp
217 (re-search-forward encodable-regexp end t
))
220 (narrow-to-region beg end
)
221 (while (re-search-forward tspecials nil
'move
)
222 (if (eq (char-before) ?
\\)
223 (if (looking-at tspecials
) ;; Already quoted.
226 (goto-char (match-beginning 0))
232 (goto-char (point-max)))
234 (setq start
(point))))))
236 (defvar rfc2047-encoding-type
'address-mime
237 "The type of encoding done by `rfc2047-encode-region'.
238 This should be dynamically bound around calls to
239 `rfc2047-encode-region' to either `mime' or `address-mime'. See
240 `rfc2047-header-encoding-alist', for definitions.")
242 (defun rfc2047-encode-message-header ()
243 "Encode the message header according to `rfc2047-header-encoding-alist'.
244 Should be called narrowed to the head of the message."
247 (goto-char (point-min))
248 (let (alist elem method charsets
)
251 (rfc2047-narrow-to-field)
253 alist rfc2047-header-encoding-alist
254 charsets
(mm-find-mime-charset-region (point-min) (point-max)))
255 ;; M$ Outlook boycotts decoding of a header if it consists
256 ;; of two or more encoded words and those charsets differ;
257 ;; it seems to decode all words in a header from a charset
258 ;; found first in the header. So, we unify the charsets into
259 ;; a single one used for encoding the whole text in a header.
260 (let ((mm-coding-system-priorities
261 (if (= (length charsets
) 1)
262 (cons (mm-charset-to-coding-system (car charsets
))
263 mm-coding-system-priorities
)
264 mm-coding-system-priorities
)))
265 (while (setq elem
(pop alist
))
266 (when (or (and (stringp (car elem
))
267 (looking-at (car elem
)))
271 (if (not (rfc2047-encodable-p))
273 (when (eq method
'address-mime
)
274 (rfc2047-quote-special-characters-in-quoted-strings))
275 (if (and (eq (mm-body-7-or-8) '8bit
)
278 (car message-posting-charset
)))
279 ;; 8 bit must be decoded.
280 (encode-coding-region
281 (point-min) (point-max)
282 (mm-charset-to-coding-system
283 (car message-posting-charset
)))))
284 ;; We found something that may perhaps be encoded.
285 (re-search-forward "^[^:]+: *" nil t
)
287 ((eq method
'address-mime
)
288 (rfc2047-encode-region (point) (point-max)))
290 (let ((rfc2047-encoding-type 'mime
))
291 (rfc2047-encode-region (point) (point-max))))
292 ((eq method
'default
)
293 (if (and (default-value 'enable-multibyte-characters
)
295 (encode-coding-region (point) (point-max)
296 mail-parse-charset
)))
297 ;; We get this when CC'ing messages to newsgroups with
298 ;; 8-bit names. The group name mail copy just got
299 ;; unconditionally encoded. Previously, it would ask
300 ;; whether to encode, which was quite confusing for the
301 ;; user. If the new behavior is wrong, tell me. I have
302 ;; left the old code commented out below.
303 ;; -- Per Abrahamsen <abraham@dina.kvl.dk> Date: 2001-10-07.
304 ;; Modified by Dave Love, with the commented-out code changed
305 ;; in accordance with changes elsewhere.
307 (rfc2047-encode-region (point) (point-max)))
309 ;;; (if (or (message-options-get
310 ;;; 'rfc2047-encode-message-header-encode-any)
311 ;;; (message-options-set
312 ;;; 'rfc2047-encode-message-header-encode-any
314 ;;; "Some texts are not encoded. Encode anyway?")))
315 ;;; (rfc2047-encode-region (point-min) (point-max))
316 ;;; (error "Cannot send unencoded text")))
317 ((mm-coding-system-p method
)
318 (when (default-value 'enable-multibyte-characters
)
319 (encode-coding-region (point) (point-max) method
)))
322 (goto-char (point-max))))))))
324 ;; Fixme: This, and the require below may not be the Right Thing, but
325 ;; should be safe just before release. -- fx 2001-02-08
327 (defun rfc2047-encodable-p ()
328 "Return non-nil if any characters in current buffer need encoding in headers.
329 The buffer may be narrowed."
330 (require 'message
) ; for message-posting-charset
332 (mm-find-mime-charset-region (point-min) (point-max))))
333 (goto-char (point-min))
334 (or (and rfc2047-encode-encoded-words
336 (re-search-forward rfc2047-encoded-word-regexp nil t
)
337 (goto-char (point-min))))
339 (not (equal charsets
(list (car message-posting-charset
))))))))
341 ;; Use this syntax table when parsing into regions that may need
342 ;; encoding. Double quotes are string delimiters, backslash is
343 ;; character quoting, and all other RFC 2822 special characters are
344 ;; treated as punctuation so we can use forward-sexp/forward-word to
345 ;; skip to the end of regions appropriately. Nb. ietf-drums does
346 ;; things differently.
347 (defconst rfc2047-syntax-table
348 ;; (make-char-table 'syntax-table '(2)) only works in Emacs.
349 (let ((table (make-syntax-table)))
350 ;; The following is done to work for setting all elements of the table;
351 ;; it appears to be the cleanest way.
352 ;; Play safe and don't assume the form of the word syntax entry --
354 (set-char-table-range table t
(aref (standard-syntax-table) ?a
))
355 (modify-syntax-entry ?
\\ "\\" table
)
356 (modify-syntax-entry ?
\" "\"" table
)
357 (modify-syntax-entry ?\
( "(" table
)
358 (modify-syntax-entry ?\
) ")" table
)
359 (modify-syntax-entry ?\
< "." table
)
360 (modify-syntax-entry ?\
> "." table
)
361 (modify-syntax-entry ?\
[ "." table
)
362 (modify-syntax-entry ?\
] "." table
)
363 (modify-syntax-entry ?
: "." table
)
364 (modify-syntax-entry ?\
; "." table)
365 (modify-syntax-entry ?
, "." table
)
366 (modify-syntax-entry ?
@ "." table
)
369 (defun rfc2047-encode-region (b e
&optional dont-fold
)
370 "Encode words in region B to E that need encoding.
371 By default, the region is treated as containing RFC2822 addresses.
372 Dynamically bind `rfc2047-encoding-type' to change that."
374 (narrow-to-region b e
)
375 (let ((encodable-regexp (if rfc2047-encode-encoded-words
376 "[^\000-\177]+\\|=\\?"
378 start
; start of current token
380 ;; Whether there's an encoded word before the current token,
381 ;; either immediately or separated by space.
383 (orig-text (buffer-substring-no-properties b e
)))
384 (if (eq 'mime rfc2047-encoding-type
)
385 ;; Simple case. Continuous words in which all those contain
386 ;; non-ASCII characters are encoded collectively. Encoding
387 ;; ASCII words, including `Re:' used in Subject headers, is
388 ;; avoided for interoperability with non-MIME clients and
389 ;; for making it easy to find keywords.
391 (goto-char (point-min))
392 (while (progn (skip-chars-forward " \t\n")
395 (while (and (looking-at "[ \t\n]*\\([^ \t\n]+\\)")
397 (setq end
(match-end 0))
398 (re-search-forward encodable-regexp end t
)))
400 (if (> (point) start
)
401 (rfc2047-encode start
(point))
403 ;; `address-mime' case -- take care of quoted words, comments.
404 (rfc2047-quote-special-characters-in-quoted-strings encodable-regexp
)
405 (with-syntax-table rfc2047-syntax-table
406 (goto-char (point-min))
407 (condition-case err
; in case of unbalanced quotes
408 ;; Look for rfc2822-style: sequences of atoms, quoted
409 ;; strings, specials, whitespace. (Specials mustn't be
413 (skip-chars-forward " \t\n")
416 ((not (char-after))) ; eob
418 ((eq ?
\" (setq csyntax
(char-syntax (char-after))))
422 ;; Does it need encoding?
424 (if (re-search-forward encodable-regexp end
'move
)
425 ;; It needs encoding. Strip the quotes first,
426 ;; since encoded words can't occur in quotes.
433 ;; There was a preceding quoted word. We need
434 ;; to include any separating whitespace in this
435 ;; word to avoid it getting lost.
436 (skip-chars-backward " \t")
437 ;; A space is needed between the encoded words.
441 ;; Adjust the end position for the deleted quotes.
442 (rfc2047-encode start
(- end
2))
443 (setq last-encoded t
)) ; record that it was encoded
444 (setq last-encoded nil
)))
446 ;; Skip other delimiters, but record that they've
447 ;; potentially separated quoted words.
449 (setq last-encoded nil
))
451 (error "Unbalanced parentheses"))
453 ;; Look for the end of parentheses.
455 ;; Encode text as an unstructured field.
456 (let ((rfc2047-encoding-type 'mime
))
457 (rfc2047-encode-region (1+ start
) (1- (point))))
458 (skip-chars-forward ")"))
459 (t ; normal token/whitespace sequence
461 ;; Skip one ASCII word, or encode continuous words
462 ;; in which all those contain non-ASCII characters.
464 (while (not (or end
(eobp)))
465 (when (looking-at "[\000-\177]+")
469 (while (and (or (re-search-forward
470 "[ \t\n]\\|\\Sw" end
'move
)
472 (eq ?
\\ (char-syntax (char-before))))
473 ;; Skip backslash-quoted characters.
476 (setq end
(match-beginning 0))
477 (if rfc2047-encode-encoded-words
480 (when (search-forward "=?" end
'move
)
481 (goto-char (match-beginning 0))
484 ;; Where the value nil of `end' means there may be
485 ;; text to have to be encoded following the point.
486 ;; Otherwise, the point reached to the end of ASCII
487 ;; words separated by whitespace or a special char.
489 (when (looking-at encodable-regexp
)
490 (goto-char (setq begin
(match-end 0)))
491 (while (and (looking-at "[ \t\n]+\\([^ \t\n]+\\)")
492 (setq end
(match-end 0))
494 (while (re-search-forward
495 encodable-regexp end t
))
498 (or (not (re-search-forward "\\Sw" end t
))
500 (goto-char (match-beginning 0))
503 (when (looking-at "[^ \t\n]+")
504 (setq end
(match-end 0))
505 (if (re-search-forward "\\Sw+" end t
)
506 ;; There are special characters better
507 ;; to be encoded so that MTAs may parse
509 (cond ((= end
(point)))
510 ((looking-at (concat "\\sw*\\("
515 (goto-char (1- (match-end 0)))
516 (unless (= (point) (match-beginning 0))
517 ;; Separate encodable text and
521 (skip-chars-forward " \t\n")
522 (if (and (looking-at "[^ \t\n]+")
523 (string-match encodable-regexp
526 (goto-char end
)))))))
527 (skip-chars-backward " \t\n")
530 (if (re-search-forward encodable-regexp end
'move
)
532 (unless (memq (char-before start
) '(nil ?
\t ?
))
535 (skip-chars-backward "^ \t\n")
536 (and (looking-at "\\Sw+")
537 (= (match-end 0) start
)))
538 ;; Also encode bogus delimiters.
540 ;; Separate encodable text and delimiter.
543 (setq start
(1+ start
)
545 (rfc2047-encode start end
)
546 (setq last-encoded t
))
547 (setq last-encoded nil
)))))
549 (if (or debug-on-quit debug-on-error
)
550 (signal (car err
) (cdr err
))
551 (error "Invalid data for rfc2047 encoding: %s"
552 (replace-regexp-in-string "[ \t\n]+" " " orig-text
))))))))
554 (rfc2047-fold-region b
(point)))
555 (goto-char (point-max))))
557 (defun rfc2047-encode-string (string &optional dont-fold
)
558 "Encode words in STRING.
559 By default, the string is treated as containing addresses (see
560 `rfc2047-encoding-type')."
561 (mm-with-multibyte-buffer
563 (rfc2047-encode-region (point-min) (point-max) dont-fold
)
567 ;; 2. Syntax of encoded-words
569 ;; While there is no limit to the length of a multiple-line header
570 ;; field, each line of a header field that contains one or more
571 ;; 'encoded-word's is limited to 76 characters.
573 ;; In `rfc2047-encode-parameter' it is bound to nil, so don't defconst it.
574 (defvar rfc2047-encode-max-chars
76
575 "Maximum characters of each header line that contain encoded-words.
576 According to RFC 2047, it is 76. If it is nil, encoded-words
577 will not be folded. Too small value may cause an error. You
578 should not change this value.")
580 (defun rfc2047-encode-1 (column string cs encoder start crest tail
582 "Subroutine used by `rfc2047-encode'."
583 (cond ((string-equal string
"")
585 ((not rfc2047-encode-max-chars
)
587 (funcall encoder
(if cs
588 (encode-coding-string string cs
)
591 ((>= column rfc2047-encode-max-chars
)
593 (cond ((string-match "\n[ \t]+\\'" eword
)
594 ;; Remove a superfluous empty line.
595 (setq eword
(substring eword
0 (match-beginning 0))))
596 ((string-match "(+\\'" eword
)
597 ;; Break the line before the open parenthesis.
598 (setq crest
(concat crest
(match-string 0 eword
))
599 eword
(substring eword
0 (match-beginning 0))))))
600 (rfc2047-encode-1 (length crest
) string cs encoder start
" " tail
601 (concat eword
"\n" crest
)))
604 (limit (1- (length string
)))
609 (setq next
(concat start
612 (encode-coding-string
613 (substring string
0 (1+ index
))
615 (substring string
0 (1+ index
))))
617 len
(+ column
(length next
)))
618 (if (> len rfc2047-encode-max-chars
)
621 (if (or (< index limit
)
622 (<= (+ len
(or (string-match "\n" tail
)
624 rfc2047-encode-max-chars
))
627 (if (string-match "\\`)+" tail
)
628 ;; Break the line after the close parenthesis.
629 (setq tail
(concat (substring tail
0 (match-end 0))
631 (substring tail
(match-end 0)))
637 (concat eword next tail
)
640 (string-match "(+\\'" eword
))
641 (setq crest
(concat crest
(match-string 0 eword
))
642 eword
(substring eword
0 (match-beginning 0)))
643 (setq eword
(concat eword next
)))
645 eword
(concat eword next
)))
646 (when (string-match "\n[ \t]+\\'" eword
)
647 ;; Remove a superfluous empty line.
648 (setq eword
(substring eword
0 (match-beginning 0))))
649 (rfc2047-encode-1 (length crest
) (substring string index
)
650 cs encoder start
" " tail
651 (concat eword
"\n" crest
)))))))
653 (defun rfc2047-encode (b e
)
654 "Encode the word(s) in the region B to E.
655 Point moves to the end of the region."
656 (let ((mime-charset (or (mm-find-mime-charset-region b e
) (list 'us-ascii
)))
657 cs encoding tail crest eword
)
658 ;; Use utf-8 as a last resort if determining charset of text fails.
659 (if (memq nil mime-charset
)
660 (setq mime-charset
(list 'utf-8
)))
661 (cond ((> (length mime-charset
) 1)
662 (error "Can't rfc2047-encode `%s'"
663 (buffer-substring-no-properties b e
)))
664 ((= (length mime-charset
) 1)
665 (setq mime-charset
(car mime-charset
)
666 cs
(mm-charset-to-coding-system mime-charset
))
667 (unless (and (mm-multibyte-p)
668 (mm-coding-system-p cs
))
671 (narrow-to-region b e
)
673 (or (cdr (assq mime-charset
674 rfc2047-charset-encoding-alist
))
675 ;; For the charsets that don't have a preferred
676 ;; encoding, choose the one that's shorter.
677 (if (eq (rfc2047-qp-or-base64) 'base64
)
682 (skip-chars-forward "^ \t\n")
683 ;; `tail' may contain a close parenthesis.
684 (setq tail
(buffer-substring-no-properties e
(point)))
686 (setq b
(point-marker)
687 e
(set-marker (make-marker) e
))
688 (rfc2047-fold-region (point-at-bol) b
)
690 (skip-chars-backward "^ \t\n")
691 (unless (= 0 (skip-chars-backward " \t"))
692 ;; `crest' may contain whitespace and an open parenthesis.
693 (setq crest
(buffer-substring-no-properties (point) b
)))
694 (setq eword
(rfc2047-encode-1
696 (replace-regexp-in-string
697 "\n\\([ \t]?\\)" "\\1"
698 (buffer-substring-no-properties b e
))
700 (or (cdr (assq encoding
701 rfc2047-encode-function-alist
))
703 (concat "=?" (downcase (symbol-name mime-charset
))
704 "?" (upcase (symbol-name encoding
)) "?")
707 (delete-region (if (eq (aref eword
0) ?
\n)
709 ;; The line was folded before encoding.
714 ;; `eword' contains `crest' and `tail'.
718 (unless (or (/= 0 (length tail
))
720 (looking-at "[ \t\n)]"))
725 (defun rfc2047-fold-field ()
726 "Fold the current header field."
729 (rfc2047-narrow-to-field)
730 (rfc2047-fold-region (point-min) (point-max)))))
732 (defun rfc2047-fold-region (b e
)
733 "Fold long lines in region B to E."
735 (narrow-to-region b e
)
736 (goto-char (point-min))
740 (bol (save-restriction
744 (when (and (or break qword-break
)
745 (> (- (point) bol
) 76))
746 (goto-char (or break qword-break
))
749 (skip-chars-backward " \t")
750 (if (looking-at "[ \t]")
753 (setq bol
(1- (point)))
754 ;; Don't break before the first non-LWSP characters.
755 (skip-chars-forward " \t")
759 ((eq (char-after) ?
\n)
764 (skip-chars-forward " \t")
765 (unless (or (eobp) (eq (char-after) ?
\n))
767 ((eq (char-after) ?
\r)
769 ((memq (char-after) '(? ?
\t))
770 (skip-chars-forward " \t")
771 (unless first
;; Don't break just after the header name.
772 (setq break
(point))))
774 (if (not (looking-at "=\\?[^=]"))
775 (if (eq (char-after) ?
=)
777 (skip-chars-forward "^ \t\n\r="))
778 ;; Don't break at the start of the field.
779 (unless (= (point) b
)
780 (setq qword-break
(point)))
781 (skip-chars-forward "^ \t\n\r")))
783 (skip-chars-forward "^ \t\n\r")))
785 (when (and (or break qword-break
)
786 (> (- (point) bol
) 76))
787 (goto-char (or break qword-break
))
790 (if (or (> 0 (skip-chars-backward " \t"))
791 (looking-at "[ \t]"))
794 (setq bol
(1- (point)))
795 ;; Don't break before the first non-LWSP characters.
796 (skip-chars-forward " \t")
798 (forward-char 1))))))
800 (defun rfc2047-unfold-field ()
801 "Fold the current line."
804 (rfc2047-narrow-to-field)
805 (rfc2047-unfold-region (point-min) (point-max)))))
807 (defun rfc2047-unfold-region (b e
)
808 "Unfold lines in region B to E."
810 (narrow-to-region b e
)
811 (goto-char (point-min))
812 (let ((bol (save-restriction
815 (eol (point-at-eol)))
818 (if (and (looking-at "[ \t]")
819 (< (- (point-at-eol) bol
) 76))
820 (delete-region eol
(progn
822 (skip-chars-forward "\r\n")
824 (setq bol
(point-at-bol)))
825 (setq eol
(point-at-eol))
828 (defun rfc2047-b-encode-string (string)
829 "Base64-encode the header contained in STRING."
830 (base64-encode-string string t
))
832 (autoload 'quoted-printable-encode-region
"qp")
834 (defun rfc2047-q-encode-string (string)
835 "Quoted-printable-encode the header in STRING."
836 (mm-with-unibyte-buffer
838 (quoted-printable-encode-region
839 (point-min) (point-max) nil
840 ;; = (\075), _ (\137), ? (\077) are used in the encoded word.
841 ;; Avoid using 8bit characters.
842 ;; This list excludes `especials' (see the RFC2047 syntax),
843 ;; meaning that some characters in non-structured fields will
844 ;; get encoded when they con't need to be. The following is
845 ;; what it used to be.
846 ;;; ;; Equivalent to "^\000-\007\011\013\015-\037\200-\377=_?"
847 ;;; "\010\012\014\040-\074\076\100-\136\140-\177")
848 "-\b\n\f !#-'*+0-9A-Z\\^`-~\d")
849 (subst-char-in-region (point-min) (point-max) ? ?_
)
852 (defun rfc2047-encode-parameter (param value
)
853 "Return a PARAM=VALUE string encoded in the RFC2047-like style.
854 This is a substitution for the `rfc2231-encode-string' function, that
855 is the standard but many mailers don't support it."
856 (let ((rfc2047-encoding-type 'mime
)
857 (rfc2047-encode-max-chars nil
))
858 (rfc2045-encode-string param
(rfc2047-encode-string value t
))))
861 ;;; Functions for decoding RFC2047 messages
864 (defvar rfc2047-quote-decoded-words-containing-tspecials nil
865 "If non-nil, quote decoded words containing special characters.")
867 (defcustom rfc2047-allow-incomplete-encoded-text t
868 "Non-nil means allow incomplete encoded-text in successive encoded-words.
869 Dividing of encoded-text in the place other than character boundaries
870 violates RFC2047 section 5, while we have a capability to decode it.
871 If it is non-nil, the decoder will decode B- or Q-encoding in each
872 encoded-word, concatenate them, and decode it by charset. Otherwise,
873 the decoder will fully decode each encoded-word before concatenating
877 (defun rfc2047-strip-backslashes-in-quoted-strings ()
878 "Strip backslashes in quoted strings. `\\\"' remains."
879 (goto-char (point-min))
881 (with-syntax-table (standard-syntax-table)
882 (while (search-forward "\"" nil t
)
883 (unless (eq (char-before) ?
\\)
884 (setq beg
(match-end 0))
885 (goto-char (match-beginning 0))
890 (narrow-to-region beg
(1- (point)))
892 (while (search-forward "\\" nil
'move
)
893 (unless (memq (char-after) '(?
\"))
898 (goto-char beg
))))))))
900 (defun rfc2047-charset-to-coding-system (charset &optional allow-override
)
901 "Return coding-system corresponding to MIME CHARSET.
902 If your Emacs implementation can't decode CHARSET, return nil.
904 If allow-override is given, use `mm-charset-override-alist' to
905 map undesired charset names to their replacement. This should
906 only be used for decoding, not for encoding."
907 (when (stringp charset
)
908 (setq charset
(intern (downcase charset
))))
909 (when (or (not charset
)
910 (eq 'gnus-all mail-parse-ignored-charsets
)
911 (memq 'gnus-all mail-parse-ignored-charsets
)
912 (memq charset mail-parse-ignored-charsets
))
913 (setq charset mail-parse-charset
))
914 (let ((cs (mm-charset-to-coding-system charset nil allow-override
)))
915 (cond ((eq cs
'ascii
)
916 (setq cs
(or (mm-charset-to-coding-system mail-parse-charset
)
918 ((mm-coding-system-p cs
))
920 (listp mail-parse-ignored-charsets
)
921 (memq 'gnus-unknown mail-parse-ignored-charsets
))
922 (setq cs
(mm-charset-to-coding-system mail-parse-charset
))))
927 (autoload 'quoted-printable-decode-string
"qp")
929 (defun rfc2047-decode-encoded-words (words)
930 "Decode successive encoded-words in WORDS and return a decoded string.
931 Each element of WORDS looks like (CHARSET ENCODING ENCODED-TEXT
935 (if (and (setq cs
(rfc2047-charset-to-coding-system
938 (cond ((char-equal ?B
(nth 1 word
))
939 (setq text
(base64-decode-string
940 (rfc2047-pad-base64 (nth 2 word
)))))
941 ((char-equal ?Q
(nth 1 word
))
942 (setq text
(quoted-printable-decode-string
943 (subst-char-in-string
944 ?_ ?
(nth 2 word
) t
)))))
946 (message "%s" (error-message-string code
))
948 (if (and rfc2047-allow-incomplete-encoded-text
950 ;; Concatenate text of which the charset is the same.
951 (setcdr (car rest
) (concat (cdar rest
) text
))
952 (push (cons cs text
) rest
))
953 ;; Don't decode encoded-word.
954 (push (cons nil
(nth 3 word
)) rest
)))
957 ;; FIXME: This looks O(N²). Can we make it more efficient
958 ;; with something like mapconcat?
960 (or (and (setq cs
(caar rest
))
962 (decode-coding-string (cdar rest
) cs
)
964 (message "%s" (error-message-string code
))
966 (concat (when (cdr rest
) " ")
969 (not (eq (string-to-char words
) ?
)))
975 ;; Fixme: This should decode in place, not cons intermediate strings.
976 ;; Also check whether it needs to worry about delimiting fields like
979 ;; In fact it's reported that (invalid) encoding of mailboxes in
980 ;; addr-specs is in use, so delimiting fields might help. Probably
981 ;; not decoding a word which isn't properly delimited is good enough
982 ;; and worthwhile (is it more correct or not?), e.g. something like
983 ;; `=?iso-8859-1?q?foo?=@'.
985 (defun rfc2047-decode-region (start end
&optional address-mime
)
986 "Decode MIME-encoded words in region between START and END.
987 If ADDRESS-MIME is non-nil, strip backslashes which precede characters
988 other than `\"' and `\\' in quoted strings."
990 (let ((case-fold-search t
)
992 (if rfc2047-allow-irregular-q-encoded-words
994 (concat "[\n\t ]*\\(" rfc2047-encoded-word-regexp-loose
"\\)"))
996 (concat "[\n\t ]*\\(" rfc2047-encoded-word-regexp
"\\)"))))
1000 (narrow-to-region start end
)
1002 (rfc2047-strip-backslashes-in-quoted-strings))
1003 (goto-char (setq b start
))
1004 ;; Look for the encoded-words.
1005 (while (setq match
(re-search-forward eword-regexp nil t
))
1006 (setq e
(match-beginning 1)
1010 (push (list (match-string 2) ;; charset
1011 (char-after (match-beginning 3)) ;; encoding
1012 (substring (match-string 3) 2) ;; encoded-text
1013 (match-string 1)) ;; encoded-word
1015 ;; Look for the subsequent encoded-words.
1016 (when (setq match
(looking-at eword-regexp
))
1017 (goto-char (setq end
(match-end 0)))))
1018 ;; Replace the encoded-words with the decoded one.
1019 (delete-region e end
)
1020 (insert (rfc2047-decode-encoded-words (nreverse words
)))
1022 (narrow-to-region e
(point))
1024 ;; Remove newlines between decoded words, though such
1025 ;; things essentially must not be there.
1026 (while (re-search-forward "[\n\r]+" nil t
)
1027 (replace-match " "))
1028 (setq end
(point-max))
1029 ;; Quote decoded words if there are special characters
1030 ;; which might violate RFC2822.
1031 (when (and rfc2047-quote-decoded-words-containing-tspecials
1032 (let ((regexp (car (rassq
1034 rfc2047-header-encoding-alist
))))
1039 ;; Don't quote words if already quoted.
1040 (not (and (eq (char-before e
) ?
\")
1041 (eq (char-after end
) ?
\")))
1044 (while (and (memq (char-after) '(? ?
\t))
1045 (zerop (forward-line -
1))))
1046 (looking-at regexp
)))))))
1049 (skip-chars-forward " \t")
1050 (setq start
(point))
1051 (setq quoted
(eq (char-after) ?
\"))
1052 (goto-char (point-max))
1053 (skip-chars-backward " \t" start
)
1054 (if (setq quoted
(and quoted
1055 (> (point) (1+ start
))
1056 (eq (char-before) ?
\")))
1059 (setq start
(1+ start
)
1060 end
(point-marker)))
1061 (setq end
(point-marker)))
1063 (while (search-forward "\"" end t
)
1066 (zerop (%
(skip-chars-backward "\\\\") 2))
1067 (goto-char (match-beginning 0)))
1070 (when (and (not quoted
)
1074 (concat "[" ietf-drums-tspecials
"]")
1080 (set-marker end nil
)))
1081 (goto-char (point-max)))
1082 (when (and (mm-multibyte-p)
1084 (not (eq mail-parse-charset
'us-ascii
))
1085 (not (eq mail-parse-charset
'gnus-decoded
)))
1086 (decode-coding-region b e mail-parse-charset
))
1088 (when (and (mm-multibyte-p)
1090 (not (eq mail-parse-charset
'us-ascii
))
1091 (not (eq mail-parse-charset
'gnus-decoded
)))
1092 (decode-coding-region b
(point-max) mail-parse-charset
))))))
1094 (defun rfc2047-decode-address-region (start end
)
1095 "Decode MIME-encoded words in region between START and END.
1096 Backslashes which precede characters other than `\"' and `\\' in quoted
1097 strings are stripped."
1098 (rfc2047-decode-region start end t
))
1100 (defun rfc2047-decode-string (string &optional address-mime
)
1101 "Decode MIME-encoded STRING and return the result.
1102 If ADDRESS-MIME is non-nil, strip backslashes which precede characters
1103 other than `\"' and `\\' in quoted strings."
1104 (if (string-match "=\\?" string
)
1106 ;; We used to only call mm-enable-multibyte if `m' is non-nil,
1107 ;; but this can't be the right criterion. Don't just revert this
1108 ;; change if it encounters a bug. Please help me fix it
1109 ;; right instead. --Stef
1110 ;; The string returned should always be multibyte in a multibyte
1111 ;; session, i.e. the buffer should be multibyte before
1112 ;; `buffer-string' is called.
1113 (mm-enable-multibyte)
1116 (rfc2047-decode-region (point-min) (point-max) address-mime
))
1121 (when (multibyte-string-p string
)
1122 (mm-enable-multibyte))
1124 (rfc2047-strip-backslashes-in-quoted-strings)
1126 ;; Fixme: As above, `m' here is inappropriate.
1129 (not (eq mail-parse-charset
'us-ascii
))
1130 (not (eq mail-parse-charset
'gnus-decoded
)))
1131 ;; `decode-coding-string' in Emacs offers a third optional
1132 ;; arg NOCOPY to avoid consing a new string if the decoding
1133 ;; is "trivial". Unfortunately it currently doesn't
1134 ;; consider anything else than a nil coding system
1136 ;; `rfc2047-decode-string' is called multiple times for each
1137 ;; article during summary buffer generation, and we really
1138 ;; want to avoid unnecessary consing. So we bypass
1139 ;; `decode-coding-string' if the string is purely ASCII.
1140 (if (eq (detect-coding-string string t
) 'undecided
)
1141 ;; string is purely ASCII
1143 (decode-coding-string string mail-parse-charset
))
1144 (if (multibyte-string-p string
)
1146 (decode-coding-string string
'us-ascii
)))))
1148 (defun rfc2047-decode-address-string (string)
1149 "Decode MIME-encoded STRING and return the result.
1150 Backslashes which precede characters other than `\"' and `\\' in quoted
1151 strings are stripped."
1152 (rfc2047-decode-string string t
))
1154 (defun rfc2047-pad-base64 (string)
1155 "Pad STRING to quartets."
1156 ;; Be more liberal to accept buggy base64 strings. If
1157 ;; base64-decode-string accepts buggy strings, this function could
1158 ;; be aliased to identity.
1159 (if (= 0 (mod (length string
) 4))
1161 (when (string-match "=+$" string
)
1162 (setq string
(substring string
0 (match-beginning 0))))
1163 (pcase (mod (length string
) 4)
1165 (1 string
) ;; Error, don't pad it.
1166 (2 (concat string
"=="))
1167 (3 (concat string
"=")))))
1171 ;;; rfc2047.el ends here