(rfc2231-parse-string): Sort segmented parameters; don't decode quoted
[gnus] / lisp / rfc2231.el
1 ;;; rfc2231.el --- Functions for decoding rfc2231 headers
2
3 ;; Copyright (C) 1998, 1999, 2000, 2002, 2003, 2004, 2005,
4 ;;   2006 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 2, or (at your option)
12 ;; 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; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29 (require 'ietf-drums)
30 (require 'rfc2047)
31 (autoload 'mm-encode-body "mm-bodies")
32 (autoload 'mail-header-remove-whitespace "mail-parse")
33 (autoload 'mail-header-remove-comments "mail-parse")
34
35 (defun rfc2231-get-value (ct attribute)
36   "Return the value of ATTRIBUTE from CT."
37   (cdr (assq attribute (cdr ct))))
38
39 (defun rfc2231-parse-qp-string (string)
40   "Parse QP-encoded string using `rfc2231-parse-string'.
41 N.B.  This is in violation with RFC2047, but it seem to be in common use."
42   (rfc2231-parse-string (rfc2047-decode-string string)))
43
44 (defun rfc2231-parse-string (string &optional signal-error)
45   "Parse STRING and return a list.
46 The list will be on the form
47  `(name (attribute . value) (attribute . value)...)'.
48
49 If the optional SIGNAL-ERROR is non-nil, signal an error when this
50 function fails in parsing of parameters."
51   (with-temp-buffer
52     (let ((ttoken (ietf-drums-token-to-list ietf-drums-text-token))
53           (stoken (ietf-drums-token-to-list ietf-drums-tspecials))
54           (ntoken (ietf-drums-token-to-list "0-9"))
55           c type attribute encoded number prev-attribute vals
56           prev-encoded parameters value)
57       (ietf-drums-init (mail-header-remove-whitespace
58                         (mail-header-remove-comments string)))
59       (let ((table (copy-syntax-table ietf-drums-syntax-table)))
60         (modify-syntax-entry ?\' "w" table)
61         (modify-syntax-entry ?* " " table)
62         (modify-syntax-entry ?\; " " table)
63         (modify-syntax-entry ?= " " table)
64         ;; The following isn't valid, but one should be liberal
65         ;; in what one receives.
66         (modify-syntax-entry ?\: "w" table)
67         (set-syntax-table table))
68       (setq c (char-after))
69       (when (and (memq c ttoken)
70                  (not (memq c stoken)))
71         (setq type (downcase (buffer-substring
72                               (point) (progn (forward-sexp 1) (point)))))
73         ;; Do the params
74         (condition-case err
75             (progn
76               (while (not (eobp))
77                 (setq c (char-after))
78                 (unless (eq c ?\;)
79                   (error "Invalid header: %s" string))
80                 (forward-char 1)
81                 ;; If c in nil, then this is an invalid header, but
82                 ;; since elm generates invalid headers on this form,
83                 ;; we allow it.
84                 (when (setq c (char-after))
85                   (if (and (memq c ttoken)
86                            (not (memq c stoken)))
87                       (setq attribute
88                             (intern
89                              (downcase
90                               (buffer-substring
91                                (point) (progn (forward-sexp 1) (point))))))
92                     (error "Invalid header: %s" string))
93                   (setq c (char-after))
94                   (if (eq c ?*)
95                       (progn
96                         (forward-char 1)
97                         (setq c (char-after))
98                         (if (not (memq c ntoken))
99                             (setq encoded t
100                                   number nil)
101                           (setq number
102                                 (string-to-number
103                                  (buffer-substring
104                                   (point) (progn (forward-sexp 1) (point)))))
105                           (setq c (char-after))
106                           (when (eq c ?*)
107                             (setq encoded t)
108                             (forward-char 1)
109                             (setq c (char-after)))))
110                     (setq number nil
111                           encoded nil))
112                   ;; See if we have any previous continuations.
113                   (when (and prev-attribute
114                              (not (eq prev-attribute attribute)))
115                     (setq vals
116                           (mapconcat 'cdr (sort vals 'car-less-than-car) ""))
117                     (push (cons prev-attribute
118                                 (if prev-encoded
119                                     (rfc2231-decode-encoded-string vals)
120                                   vals))
121                           parameters)
122                     (setq prev-attribute nil
123                           vals nil
124                           prev-encoded nil))
125                   (unless (eq c ?=)
126                     (error "Invalid header: %s" string))
127                   (forward-char 1)
128                   (setq c (char-after))
129                   (cond
130                    ((eq c ?\")
131                     (setq value (buffer-substring (1+ (point))
132                                                   (progn
133                                                     (forward-sexp 1)
134                                                     (1- (point)))))
135                     (when encoded
136                       (setq value (mapconcat (lambda (c) (format "%%%02x" c))
137                                              value ""))))
138                    ((and (or (memq c ttoken)
139                              ;; EXTENSION: Support non-ascii chars.
140                              (> c ?\177))
141                          (not (memq c stoken)))
142                     (setq value
143                           (buffer-substring
144                            (point)
145                            (progn
146                              (forward-sexp)
147                              ;; We might not have reached at the end of
148                              ;; the value because of non-ascii chars,
149                              ;; so we should jump over them if any.
150                              (while (and (not (eobp))
151                                          (> (char-after) ?\177))
152                                (forward-char 1)
153                                (forward-sexp))
154                              (point)))))
155                    (t
156                     (error "Invalid header: %s" string)))
157                   (if number
158                       (progn
159                         (push (cons number value) vals)
160                         (setq prev-attribute attribute
161                               prev-encoded encoded))
162                     (push (cons attribute
163                                 (if encoded
164                                     (rfc2231-decode-encoded-string value)
165                                   value))
166                           parameters))))
167
168               ;; Take care of any final continuations.
169               (when prev-attribute
170                 (setq vals (mapconcat 'cdr (sort vals 'car-less-than-car) ""))
171                 (push (cons prev-attribute
172                             (if prev-encoded
173                                 (rfc2231-decode-encoded-string vals)
174                               vals))
175                       parameters)))
176           (error
177            (setq parameters nil)
178            (if signal-error
179                (signal (car err) (cdr err))
180              ;;(message "%s" (error-message-string err))
181              )))
182
183         (when type
184           `(,type ,@(nreverse parameters)))))))
185
186 (defun rfc2231-decode-encoded-string (string)
187   "Decode an RFC2231-encoded string.
188 These look like:
189  \"us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A\",
190  \"us-ascii''This%20is%20%2A%2A%2Afun%2A%2A%2A\",
191  \"'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A\",
192  \"''This%20is%20%2A%2A%2Afun%2A%2A%2A\", or
193  \"This is ***fun***\"."
194   (string-match "\\`\\(?:\\([^']+\\)?'\\([^']+\\)?'\\)?\\(.+\\)" string)
195   (let ((coding-system (mm-charset-to-coding-system (match-string 1 string)))
196         ;;(language (match-string 2 string))
197         (value (match-string 3 string)))
198     (mm-with-multibyte-buffer
199       (insert value)
200       (goto-char (point-min))
201       (while (search-forward "%" nil t)
202         (insert
203          (prog1
204              (string-to-number (buffer-substring (point) (+ (point) 2)) 16)
205            (delete-region (1- (point)) (+ (point) 2)))))
206       ;; Decode using the charset, if any.
207       (unless (memq coding-system '(nil ascii))
208         (mm-decode-coding-region (point-min) (point-max) coding-system))
209       (buffer-string))))
210
211 (defun rfc2231-encode-string (param value)
212   "Return and PARAM=VALUE string encoded according to RFC2231.
213 Use `mml-insert-parameter' or `mml-insert-parameter-string' to insert
214 the result of this function."
215   (let ((control (ietf-drums-token-to-list ietf-drums-no-ws-ctl-token))
216         (tspecial (ietf-drums-token-to-list ietf-drums-tspecials))
217         (special (ietf-drums-token-to-list "*'%\n\t"))
218         (ascii (ietf-drums-token-to-list ietf-drums-text-token))
219         (num -1)
220         ;; Don't make lines exceeding 76 column.
221         (limit (- 74 (length param)))
222         spacep encodep charsetp charset broken)
223     (with-temp-buffer
224       (insert value)
225       (goto-char (point-min))
226       (while (not (eobp))
227         (cond
228          ((or (memq (following-char) control)
229               (memq (following-char) tspecial)
230               (memq (following-char) special))
231           (setq encodep t))
232          ((eq (following-char) ? )
233           (setq spacep t))
234          ((not (memq (following-char) ascii))
235           (setq charsetp t)))
236         (forward-char 1))
237       (when charsetp
238         (setq charset (mm-encode-body)))
239       (cond
240        ((or encodep charsetp
241             (progn
242               (end-of-line)
243               (> (current-column) (if spacep (- limit 2) limit))))
244         (setq limit (- limit 6))
245         (goto-char (point-min))
246         (insert (symbol-name (or charset 'us-ascii)) "''")
247         (while (not (eobp))
248           (if (or (not (memq (following-char) ascii))
249                   (memq (following-char) control)
250                   (memq (following-char) tspecial)
251                   (memq (following-char) special)
252                   (eq (following-char) ? ))
253               (progn
254                 (when (>= (current-column) (1- limit))
255                   (insert ";\n")
256                   (setq broken t))
257                 (insert "%" (format "%02x" (following-char)))
258                 (delete-char 1))
259             (when (> (current-column) limit)
260               (insert ";\n")
261               (setq broken t))
262             (forward-char 1)))
263         (goto-char (point-min))
264         (if (not broken)
265             (insert param "*=")
266           (while (not (eobp))
267             (insert (if (>= num 0) " " "")
268                     param "*" (format "%d" (incf num)) "*=")
269             (forward-line 1))))
270        (spacep
271         (goto-char (point-min))
272         (insert param "=\"")
273         (goto-char (point-max))
274         (insert "\""))
275        (t
276         (goto-char (point-min))
277         (insert param "=")))
278       (buffer-string))))
279
280 (provide 'rfc2231)
281
282 ;;; arch-tag: c3ab751d-d108-406a-b301-68882ad8cd63
283 ;;; rfc2231.el ends here