0b028a08b8330e7ad81d176bc7705187b25ce3f3
[gnus] / lisp / rfc2231.el
1 ;;; rfc2231.el --- Functions for decoding rfc2231 headers
2
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
4 ;;   2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; This file is part of GNU Emacs.
8
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.
13
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.
18
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/>.
21
22 ;;; Commentary:
23
24 ;;; Code:
25
26 (eval-when-compile (require 'cl))
27 (require 'ietf-drums)
28 (require 'rfc2047)
29 (autoload 'mm-encode-body "mm-bodies")
30 (autoload 'mail-header-remove-whitespace "mail-parse")
31 (autoload 'mail-header-remove-comments "mail-parse")
32
33 (defun rfc2231-get-value (ct attribute)
34   "Return the value of ATTRIBUTE from CT."
35   (cdr (assq attribute (cdr ct))))
36
37 (defun rfc2231-parse-qp-string (string)
38   "Parse QP-encoded string using `rfc2231-parse-string'.
39 N.B.  This is in violation with RFC2047, but it seem to be in common use."
40   (rfc2231-parse-string (rfc2047-decode-string string)))
41
42 (defun rfc2231-parse-string (string &optional signal-error)
43   "Parse STRING and return a list.
44 The list will be on the form
45  `(name (attribute . value) (attribute . value)...)'.
46
47 If the optional SIGNAL-ERROR is non-nil, signal an error when this
48 function fails in parsing of parameters.  Otherwise, this function
49 must never cause a Lisp error."
50   (with-temp-buffer
51     (let ((ttoken (ietf-drums-token-to-list ietf-drums-text-token))
52           (stoken (ietf-drums-token-to-list ietf-drums-tspecials))
53           (ntoken (ietf-drums-token-to-list "0-9"))
54           c type attribute encoded number parameters value)
55       (ietf-drums-init
56        (condition-case nil
57            (mail-header-remove-whitespace
58             (mail-header-remove-comments string))
59          ;; The most likely cause of an error is unbalanced parentheses
60          ;; or double-quotes.  If all parentheses and double-quotes are
61          ;; quoted meaninglessly with backslashes, removing them might
62          ;; make it parseable.  Let's try...
63          (error
64           (let (mod)
65             (when (and (string-match "\\\\\"" string)
66                        (not (string-match "\\`\"\\|[^\\]\"" string)))
67               (setq string (mm-replace-in-string string "\\\\\"" "\"")
68                     mod t))
69             (when (and (string-match "\\\\(" string)
70                        (string-match "\\\\)" string)
71                        (not (string-match "\\`(\\|[^\\][()]" string)))
72               (setq string (mm-replace-in-string string "\\\\\\([()]\\)" "\\1")
73                     mod t))
74             (or (and mod
75                      (ignore-errors
76                        (mail-header-remove-whitespace
77                         (mail-header-remove-comments string))))
78                 ;; Finally, attempt to extract only type.
79                 (if (string-match
80                      (concat "\\`[\t\n ]*\\([^" ietf-drums-tspecials "\t\n ]+"
81                              "\\(?:/[^" ietf-drums-tspecials
82                              "\t\n ]+\\)?\\)\\(?:[\t\n ;]\\|\\'\\)")
83                      string)
84                     (match-string 1 string)
85                   ""))))))
86       (let ((table (copy-syntax-table ietf-drums-syntax-table)))
87         (modify-syntax-entry ?\' "w" table)
88         (modify-syntax-entry ?* " " table)
89         (modify-syntax-entry ?\; " " table)
90         (modify-syntax-entry ?= " " table)
91         ;; The following isn't valid, but one should be liberal
92         ;; in what one receives.
93         (modify-syntax-entry ?\: "w" table)
94         (set-syntax-table table))
95       (setq c (char-after))
96       (when (and (memq c ttoken)
97                  (not (memq c stoken))
98                  (setq type (ignore-errors
99                               (downcase
100                                (buffer-substring (point) (progn
101                                                            (forward-sexp 1)
102                                                            (point)))))))
103         ;; Do the params
104         (condition-case err
105             (progn
106               (while (not (eobp))
107                 (setq c (char-after))
108                 (unless (eq c ?\;)
109                   (error "Invalid header: %s" string))
110                 (forward-char 1)
111                 ;; If c in nil, then this is an invalid header, but
112                 ;; since elm generates invalid headers on this form,
113                 ;; we allow it.
114                 (when (setq c (char-after))
115                   (if (and (memq c ttoken)
116                            (not (memq c stoken)))
117                       (setq attribute
118                             (intern
119                              (downcase
120                               (buffer-substring
121                                (point) (progn (forward-sexp 1) (point))))))
122                     (error "Invalid header: %s" string))
123                   (setq c (char-after))
124                   (if (eq c ?*)
125                       (progn
126                         (forward-char 1)
127                         (setq c (char-after))
128                         (if (not (memq c ntoken))
129                             (setq encoded t
130                                   number nil)
131                           (setq number
132                                 (string-to-number
133                                  (buffer-substring
134                                   (point) (progn (forward-sexp 1) (point)))))
135                           (setq c (char-after))
136                           (when (eq c ?*)
137                             (setq encoded t)
138                             (forward-char 1)
139                             (setq c (char-after)))))
140                     (setq number nil
141                           encoded nil))
142                   (unless (eq c ?=)
143                     (error "Invalid header: %s" string))
144                   (forward-char 1)
145                   (setq c (char-after))
146                   (cond
147                    ((eq c ?\")
148                     (setq value (buffer-substring (1+ (point))
149                                                   (progn
150                                                     (forward-sexp 1)
151                                                     (1- (point)))))
152                     (when encoded
153                       (setq value (mapconcat (lambda (c) (format "%%%02x" c))
154                                              value ""))))
155                    ((and (or (memq c ttoken)
156                              ;; EXTENSION: Support non-ascii chars.
157                              (> c ?\177))
158                          (not (memq c stoken)))
159                     (setq value
160                           (buffer-substring
161                            (point)
162                            (progn
163                              ;; Jump over asterisk, non-ASCII
164                              ;; and non-boundary characters.
165                              (while (and c
166                                          (or (eq c ?*)
167                                              (> c ?\177)
168                                              (not (eq (char-syntax c) ? ))))
169                                (forward-char 1)
170                                (setq c (char-after)))
171                              (point)))))
172                    (t
173                     (error "Invalid header: %s" string)))
174                   (push (list attribute value number encoded)
175                         parameters))))
176           (error
177            (setq parameters nil)
178            (when signal-error
179              (signal (car err) (cdr err)))))
180
181         ;; Now collect and concatenate continuation parameters.
182         (let ((cparams nil)
183               elem)
184           (loop for (attribute value part encoded)
185                 in (sort parameters (lambda (e1 e2)
186                                       (< (or (caddr e1) 0)
187                                          (or (caddr e2) 0))))
188                 do (cond
189                     ;; First part.
190                     ((or (not (setq elem (assq attribute cparams)))
191                          (and (numberp part)
192                               (zerop part)))
193                      (push (list attribute value encoded) cparams))
194                     ;; Repetition of a part; do nothing.
195                     ((and elem
196                           (null number))
197                      )
198                     ;; Concatenate continuation parts.
199                     (t
200                      (setcar (cdr elem) (concat (cadr elem) value)))))
201           ;; Finally decode encoded values.
202           (cons type (mapcar
203                       (lambda (elem)
204                         (cons (car elem)
205                               (if (nth 2 elem)
206                                   (rfc2231-decode-encoded-string (nth 1 elem))
207                                 (nth 1 elem))))
208                       (nreverse cparams))))))))
209
210 (defun rfc2231-decode-encoded-string (string)
211   "Decode an RFC2231-encoded string.
212 These look like:
213  \"us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A\",
214  \"us-ascii''This%20is%20%2A%2A%2Afun%2A%2A%2A\",
215  \"'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A\",
216  \"''This%20is%20%2A%2A%2Afun%2A%2A%2A\", or
217  \"This is ***fun***\"."
218   (string-match "\\`\\(?:\\([^']+\\)?'\\([^']+\\)?'\\)?\\(.+\\)" string)
219   (let ((coding-system (mm-charset-to-coding-system (match-string 1 string)))
220         ;;(language (match-string 2 string))
221         (value (match-string 3 string)))
222     (mm-with-unibyte-buffer
223       (insert value)
224       (goto-char (point-min))
225       (while (re-search-forward "%\\([0-9A-Fa-f][0-9A-Fa-f]\\)" nil t)
226         (insert
227          (prog1
228              (string-to-number (match-string 1) 16)
229            (delete-region (match-beginning 0) (match-end 0)))))
230       ;; Decode using the charset, if any.
231       (if (memq coding-system '(nil ascii))
232           (buffer-string)
233         (mm-decode-coding-string (buffer-string) coding-system)))))
234
235 (defun rfc2231-encode-string (param value)
236   "Return and PARAM=VALUE string encoded according to RFC2231.
237 Use `mml-insert-parameter' or `mml-insert-parameter-string' to insert
238 the result of this function."
239   (let ((control (ietf-drums-token-to-list ietf-drums-no-ws-ctl-token))
240         (tspecial (ietf-drums-token-to-list ietf-drums-tspecials))
241         (special (ietf-drums-token-to-list "*'%\n\t"))
242         (ascii (ietf-drums-token-to-list ietf-drums-text-token))
243         (num -1)
244         ;; Don't make lines exceeding 76 column.
245         (limit (- 74 (length param)))
246         spacep encodep charsetp charset broken)
247     (mm-with-multibyte-buffer
248       (insert value)
249       (goto-char (point-min))
250       (while (not (eobp))
251         (cond
252          ((or (memq (following-char) control)
253               (memq (following-char) tspecial)
254               (memq (following-char) special))
255           (setq encodep t))
256          ((eq (following-char) ? )
257           (setq spacep t))
258          ((not (memq (following-char) ascii))
259           (setq charsetp t)))
260         (forward-char 1))
261       (when charsetp
262         (setq charset (mm-encode-body)))
263       (mm-disable-multibyte)
264       (cond
265        ((or encodep charsetp
266             (progn
267               (end-of-line)
268               (> (current-column) (if spacep (- limit 2) limit))))
269         (setq limit (- limit 6))
270         (goto-char (point-min))
271         (insert (symbol-name (or charset 'us-ascii)) "''")
272         (while (not (eobp))
273           (if (or (not (memq (following-char) ascii))
274                   (memq (following-char) control)
275                   (memq (following-char) tspecial)
276                   (memq (following-char) special)
277                   (eq (following-char) ? ))
278               (progn
279                 (when (>= (current-column) (1- limit))
280                   (insert ";\n")
281                   (setq broken t))
282                 (insert "%" (format "%02x" (following-char)))
283                 (delete-char 1))
284             (when (> (current-column) limit)
285               (insert ";\n")
286               (setq broken t))
287             (forward-char 1)))
288         (goto-char (point-min))
289         (if (not broken)
290             (insert param "*=")
291           (while (not (eobp))
292             (insert (if (>= num 0) " " "")
293                     param "*" (format "%d" (incf num)) "*=")
294             (forward-line 1))))
295        (spacep
296         (goto-char (point-min))
297         (insert param "=\"")
298         (goto-char (point-max))
299         (insert "\""))
300        (t
301         (goto-char (point-min))
302         (insert param "=")))
303       (buffer-string))))
304
305 (provide 'rfc2231)
306
307 ;;; rfc2231.el ends here