*** empty log message ***
[gnus] / lisp / mm-bodies.el
1 ;;; mm-bodies.el --- Functions for decoding MIME things
2
3 ;; Copyright (C) 1998, 1999, 2000, 2001
4 ;;        Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (eval-and-compile
30   (or (fboundp  'base64-decode-region)
31       (require 'base64)))
32
33 (eval-when-compile
34   (defvar mm-uu-decode-function)
35   (defvar mm-uu-binhex-decode-function))
36
37 (require 'mm-util)
38 (require 'rfc2047)
39 (require 'mm-encode)
40
41 ;; 8bit treatment gets any char except: 0x32 - 0x7f, CR, LF, TAB, BEL,
42 ;; BS, vertical TAB, form feed, and ^_
43 (defvar mm-7bit-chars "\x20-\x7f\r\n\t\x7\x8\xb\xc\x1f")
44
45 (defcustom mm-body-charset-encoding-alist
46   '((iso-2022-jp . 7bit)
47     (iso-2022-jp-2 . 7bit))
48   "Alist of MIME charsets to encodings.
49 Valid encodings are `7bit', `8bit', `quoted-printable' and `base64'."
50   :type '(repeat (cons (symbol :tag "charset")
51                        (choice :tag "encoding"
52                                (const 7bit)
53                                (const 8bit)
54                                (const quoted-printable)
55                                (const base64))))
56   :group 'mime)
57
58 (defun mm-encode-body (&optional charset)
59   "Encode a body.
60 Should be called narrowed to the body that is to be encoded.
61 If there is more than one non-ASCII MULE charset, then list of found
62 MULE charsets are returned.
63 If CHARSET is non-nil, it is used.
64 If successful, the MIME charset is returned.
65 If no encoding was done, nil is returned."
66   (if (not (mm-multibyte-p))
67       ;; In the non-Mule case, we search for non-ASCII chars and
68       ;; return the value of `mail-parse-charset' if any are found.
69       (or charset
70           (save-excursion
71             (goto-char (point-min))
72             (if (re-search-forward "[^\x0-\x7f]" nil t)
73                 (or mail-parse-charset
74                     (message-options-get 'mm-encody-body-charset)
75                     (message-options-set
76                      'mm-encody-body-charset
77                      (mm-read-charset "Charset used in the article: ")))
78               ;; The logic in `mml-generate-mime-1' confirms that it's OK
79               ;; to return nil here.
80               nil)))
81     (save-excursion
82       (if charset
83           (progn
84             (mm-encode-coding-region (point-min) (point-max) charset)
85             charset)
86         (goto-char (point-min))
87         (let ((charsets (mm-find-mime-charset-region (point-min) (point-max)
88                                                      mm-hack-charsets))
89               start)
90           (cond
91            ;; No encoding.
92            ((null charsets)
93             nil)
94            ;; Too many charsets.
95            ((> (length charsets) 1)
96             charsets)
97            ;; We encode.
98            (t
99             (setq charset (car charsets))
100             (while (not (eobp))
101               (if (eq (mm-charset-after) 'ascii)
102                   (when start
103                     (save-restriction
104                       (narrow-to-region start (point))
105                       (mm-encode-coding-region
106                        start (point) (mm-charset-to-coding-system charset))
107                       (goto-char (point-max)))
108                     (setq start nil))
109                 (unless start
110                   (setq start (point))))
111               (forward-char 1))
112             (when start
113               (mm-encode-coding-region start (point)
114                                        (mm-charset-to-coding-system charset))
115               (setq start nil))
116             charset)))))))
117
118 (defun mm-long-lines-p (length)
119   "Say whether any of the lines in the buffer is longer than LINES."
120   (save-excursion
121     (goto-char (point-min))
122     (end-of-line)
123     (while (and (not (eobp))
124                 (not (> (current-column) length)))
125       (forward-line 1)
126       (end-of-line))
127     (and (> (current-column) length)
128          (current-column))))
129
130 (defvar message-posting-charset)
131
132 (defun mm-body-encoding (charset &optional encoding)
133   "Do Content-Transfer-Encoding and return the encoding of the current buffer."
134   (when (stringp encoding)
135     (setq encoding (intern (downcase encoding))))
136   (let ((bits (mm-body-7-or-8))
137         (longp (mm-long-lines-p 1000)))
138     (require 'message)
139     (cond
140      ((and (not mm-use-ultra-safe-encoding)
141            (not longp)
142            (eq bits '7bit))
143       bits)
144      ((and (not mm-use-ultra-safe-encoding)
145            (not longp)
146            (not (eq '7bit (cdr (assq charset mm-body-charset-encoding-alist))))
147            (or (eq t (cdr message-posting-charset))
148                (memq charset (cdr message-posting-charset))
149                (eq charset mail-parse-charset)))
150       bits)
151      (t
152       (let ((encoding (or encoding
153                           (cdr (assq charset mm-body-charset-encoding-alist))
154                           (mm-qp-or-base64))))
155         (when mm-use-ultra-safe-encoding
156           (setq encoding (mm-safer-encoding encoding)))
157         (mm-encode-content-transfer-encoding encoding "text/plain")
158         encoding)))))
159
160 (defun mm-body-7-or-8 ()
161   "Say whether the body is 7bit or 8bit."
162   (cond
163    ((not (featurep 'mule))
164     (if (save-excursion
165           (goto-char (point-min))
166           (skip-chars-forward mm-7bit-chars)
167           (eobp))
168         '7bit
169       '8bit))
170    (t
171     ;; Mule version
172     (if (and (null (delq 'ascii
173                          (mm-find-charset-region (point-min) (point-max))))
174              ;;!!!The following is necessary because the function
175              ;;!!!above seems to return the wrong result under
176              ;;!!!Emacs 20.3.  Sometimes.
177              (save-excursion
178                (goto-char (point-min))
179                (skip-chars-forward mm-7bit-chars)
180                (eobp)))
181         '7bit
182       '8bit))))
183
184 ;;;
185 ;;; Functions for decoding
186 ;;;
187
188 (defun mm-decode-content-transfer-encoding (encoding &optional type)
189   "Decodes buffer encoded with ENCODING, returning success status.
190 If TYPE is `text/plain' CRLF->LF translation may occur."
191   (prog1
192       (condition-case error
193           (cond
194            ((eq encoding 'quoted-printable)
195             (quoted-printable-decode-region (point-min) (point-max))
196             t)
197            ((eq encoding 'base64)
198             (base64-decode-region
199              (point-min)
200              ;; Some mailers insert whitespace
201              ;; junk at the end which
202              ;; base64-decode-region dislikes.
203              ;; Also remove possible junk which could
204              ;; have been added by mailing list software.
205              (save-excursion
206                (goto-char (point-min))
207                (while (re-search-forward "^[\t ]*\r?\n" nil t)
208                  (delete-region (match-beginning 0) (match-end 0)))
209                (goto-char (point-max))
210                (when (re-search-backward "^[A-Za-z0-9+/]+=*[\t ]*$" nil t)
211                  (forward-line))
212                (point))))
213            ((memq encoding '(7bit 8bit binary))
214             ;; Do nothing.
215             t)
216            ((null encoding)
217             ;; Do nothing.
218             t)
219            ((memq encoding '(x-uuencode x-uue))
220             (require 'mm-uu)
221             (funcall mm-uu-decode-function (point-min) (point-max))
222             t)
223            ((eq encoding 'x-binhex)
224             (require 'mm-uu)
225             (funcall mm-uu-binhex-decode-function (point-min) (point-max))
226             t)
227            ((eq encoding 'x-yenc)
228             (require 'mm-uu)
229             (funcall mm-uu-yenc-decode-function (point-min) (point-max))
230             )
231            ((functionp encoding)
232             (funcall encoding (point-min) (point-max))
233             t)
234            (t
235             (message "Unknown encoding %s; defaulting to 8bit" encoding)))
236         (error
237          (message "Error while decoding: %s" error)
238          nil))
239     (when (and
240            (memq encoding '(base64 x-uuencode x-uue x-binhex x-yenc))
241            (equal type "text/plain"))
242       (goto-char (point-min))
243       (while (search-forward "\r\n" nil t)
244         (replace-match "\n" t t)))))
245
246 (defun mm-decode-body (charset &optional encoding type force)
247   "Decode the current article that has been encoded with ENCODING.
248 The characters in CHARSET should then be decoded.  If FORCE is non-nil
249 use the supplied charset unconditionally."
250   (if (stringp charset)
251       (setq charset (intern (downcase charset))))
252   (if (or (not charset)
253           (eq 'gnus-all mail-parse-ignored-charsets)
254           (memq 'gnus-all mail-parse-ignored-charsets)
255           (memq charset mail-parse-ignored-charsets))
256       (setq charset mail-parse-charset))
257   (save-excursion
258     (when encoding
259       (mm-decode-content-transfer-encoding encoding type))
260     (when (featurep 'mule)
261       (let ((coding-system (mm-charset-to-coding-system charset)))
262         (if (and (not coding-system)
263                  (listp mail-parse-ignored-charsets)
264                  (memq 'gnus-unknown mail-parse-ignored-charsets))
265             (setq coding-system
266                   (mm-charset-to-coding-system mail-parse-charset)))
267         (when (and charset coding-system
268                    ;; buffer-file-coding-system
269                    ;;Article buffer is nil coding system
270                    ;;in XEmacs
271                    (mm-multibyte-p)
272                    (or (not (eq coding-system 'ascii))
273                        (setq coding-system mail-parse-charset))
274                    (not (eq coding-system 'gnus-decoded)))
275           (if force
276               (mm-decode-coding-region (point-min) (point-max)
277                                               coding-system)
278             (mm-decode-coding-region-safely (point-min) (point-max)
279                                             coding-system)))))))
280
281 (defun mm-decode-coding-region-safely (start end coding-system)
282   "Decode region between START and END with CODING-SYSTEM.
283 If CODING-SYSTEM is not a valid coding system for the text, let Emacs
284 decide which coding system to use."
285   (let* ((decoded (mm-decode-coding-string (buffer-substring start end)
286                                            coding-system))
287          (charsets (find-charset-string decoded)))
288     (if (or (memq 'eight-bit-control charsets)
289             (memq 'eight-bit-graphic charsets))
290         (mm-decode-coding-region start end 'undecided)
291       (delete-region start end)
292       (insert decoded))))
293
294 (defun mm-decode-string (string charset)
295   "Decode STRING with CHARSET."
296   (when (stringp charset)
297     (setq charset (intern (downcase charset))))
298   (when (or (not charset)
299             (eq 'gnus-all mail-parse-ignored-charsets)
300             (memq 'gnus-all mail-parse-ignored-charsets)
301             (memq charset mail-parse-ignored-charsets))
302     (setq charset mail-parse-charset))
303   (or
304    (when (featurep 'mule)
305      (let ((coding-system (mm-charset-to-coding-system charset)))
306        (if (and (not coding-system)
307                 (listp mail-parse-ignored-charsets)
308                 (memq 'gnus-unknown mail-parse-ignored-charsets))
309            (setq coding-system
310                  (mm-charset-to-coding-system mail-parse-charset)))
311        (when (and charset coding-system
312                   (mm-multibyte-p)
313                   (or (not (eq coding-system 'ascii))
314                       (setq coding-system mail-parse-charset)))
315          (mm-decode-coding-string string coding-system))))
316    string))
317
318 (provide 'mm-bodies)
319
320 ;;; mm-bodies.el ends here