more work in the lib-mime. begin to "rewr^H^Had" the code in rfc2231.c
[apps/madmutt.git] / lib-mime / rfc2231.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2006 Pierre Habouzit
18  */
19
20 /*
21  * Copyright notice from original mutt:
22  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
23  *
24  * This file is part of mutt-ng, see http://www.muttng.org/.
25  * It's licensed under the GNU General Public License,
26  * please see the file GPL in the top level source directory.
27  */
28
29 /*
30  * Yet another MIME encoding for header data.  This time, it's
31  * parameters, specified in RFC 2231, and modeled after the
32  * encoding used in URLs.
33  * 
34  * Additionally, continuations and encoding are mixed in an, errrm,
35  * interesting manner.
36  *
37  */
38
39 #include <ctype.h>
40 #include <string.h>
41 #include <stdlib.h>
42
43 #include <lib-lib/mem.h>
44 #include <lib-lib/str.h>
45 #include <lib-lib/ascii.h>
46
47 #include <lib-mime/mime.h>
48
49 #include "charset.h"
50 #include "rfc2047.h"
51
52 typedef struct rfc2231_parameter {
53     char *attribute;
54     char *value;
55     int idx;
56     int encoded;
57     struct rfc2231_parameter *next;
58 } rfc2231_parameter;
59
60 DO_INIT(rfc2231_parameter, rfc2231_parameter);
61 static inline void rfc2231_parameter_wipe(rfc2231_parameter *param)
62 {
63     p_delete(&param->attribute);
64     p_delete(&param->value);
65 }
66 DO_NEW(rfc2231_parameter, rfc2231_parameter);
67 DO_DELETE(rfc2231_parameter, rfc2231_parameter);
68
69
70 /* insert parameter into an ordered list.
71  * 
72  * Primary sorting key: attribute
73  * Secondary sorting key: idx
74  */
75 static void
76 rfc2231_list_insert(rfc2231_parameter **list, rfc2231_parameter *par)
77 {
78     rfc2231_parameter **last = list;
79     rfc2231_parameter *p = *list, *q;
80     int c;
81
82     while (p) {
83         last = &p->next;
84         q = p;
85         p = p->next;
86
87         c = m_strcmp(par->value, q->value);
88         if ((c > 0) || (c == 0 && par->idx >= q->idx))
89             break;
90     }
91
92     par->next = p;
93     *last = par;
94 }
95
96
97 static void rfc2231_decode_one(char *dst, const char *src)
98 {
99     while (*src) {
100         int h1, h2;
101
102         if (*src == '%'
103         && (h1 = hexval(src[1])) >= 0 && (h2 = hexval(src[2])) >= 0)
104         {
105             *dst++ = (h1 << 4) | h2;
106             src += 3;
107         } else {
108             *dst++ = *src++;
109         }
110     }
111
112     *dst = '\0';
113 }
114
115 /* ---------------- TODO FIXME READ MARK ---------------- */
116
117 static char *rfc2231_get_charset(char *value, char *charset, size_t chslen)
118 {
119     char *t, *u;
120
121     t = strchr(value, '\'');
122     if (!t) {
123         charset[0] = '\0';
124         return value;
125     }
126
127     *t = '\0';
128     m_strcpy(charset, chslen, value);
129
130     if ((u = strchr(t + 1, '\''))) {
131         return u + 1;
132     } else {
133         return t + 1;
134     }
135 }
136
137
138 static void rfc2231_join_continuations(PARAMETER **, rfc2231_parameter *);
139
140 static void purge_empty_parameters (PARAMETER **headp)
141 {
142     PARAMETER *p, *q, **last;
143
144     for (last = headp, p = *headp; p; p = q) {
145         q = p->next;
146         if (!p->attribute || !p->value) {
147             *last = q;
148             p->next = NULL;
149             mutt_free_parameter (&p);
150         }
151         else
152             last = &p->next;
153     }
154 }
155
156
157 void rfc2231_decode_parameters (PARAMETER ** headp)
158 {
159     PARAMETER *head = NULL;
160     PARAMETER **last;
161     PARAMETER *p, *q;
162
163     rfc2231_parameter *conthead = NULL;
164     rfc2231_parameter *conttmp;
165
166     char *s, *t;
167     char charset[STRING];
168
169     int encoded;
170     int idx;
171     short dirty = 0;              /* set to 1 when we may have created
172                                    * empty parameters.
173                                    */
174
175     if (!headp)
176         return;
177
178     purge_empty_parameters (headp);
179
180     for (last = &head, p = *headp; p; p = q) {
181         q = p->next;
182
183         if (!(s = strchr (p->attribute, '*'))) {
184
185             /* 
186              * Using RFC 2047 encoding in MIME parameters is explicitly
187              * forbidden by that document.  Nevertheless, it's being
188              * generated by some software, including certain Lotus Notes to 
189              * Internet Gateways.  So we actually decode it.
190              */
191
192             if (option (OPTRFC2047PARAMS) && p->value && strstr (p->value, "=?"))
193                 rfc2047_decode (&p->value);
194             else if (!option (OPTSTRICTMIME)) {
195                 if (ascii_strcasecmp (AssumedCharset, "us-ascii"))
196                     mutt_convert_nonmime_string (&p->value);
197             }
198
199             *last = p;
200             last = &p->next;
201             p->next = NULL;
202         }
203         else if (*(s + 1) == '\0') {
204             *s = '\0';
205
206             s = rfc2231_get_charset (p->value, charset, sizeof (charset));
207             rfc2231_decode_one (p->value, s);
208             mutt_convert_string (&p->value, charset, Charset, M_ICONV_HOOK_FROM);
209
210             *last = p;
211             last = &p->next;
212             p->next = NULL;
213
214             dirty = 1;
215         }
216         else {
217             *s = '\0';
218             s++;                      /* let s point to the first character of idx. */
219             for (t = s; *t && isdigit ((unsigned char) *t); t++);
220             encoded = (*t == '*');
221             *t = '\0';
222
223             idx = atoi (s);
224
225             conttmp = rfc2231_parameter_new ();
226             conttmp->attribute = p->attribute;
227             conttmp->value = p->value;
228             conttmp->encoded = encoded;
229             conttmp->idx = idx;
230
231             p->attribute = NULL;
232             p->value = NULL;
233             p_delete(&p);
234
235             rfc2231_list_insert (&conthead, conttmp);
236         }
237     }
238
239     if (conthead) {
240         rfc2231_join_continuations (last, conthead);
241         dirty = 1;
242     }
243
244     *headp = head;
245
246     if (dirty)
247         purge_empty_parameters (headp);
248 }
249
250 /* process continuation parameters */
251
252 static void
253 rfc2231_join_continuations(PARAMETER **head, rfc2231_parameter *par)
254 {
255     rfc2231_parameter *q;
256
257     char attribute[STRING];
258     char charset[STRING];
259     char *value = NULL;
260     char *valp;
261     int encoded;
262
263     size_t l, vl;
264
265     while (par) {
266         value = NULL;
267         l = 0;
268
269         m_strcpy(attribute, sizeof(attribute), par->attribute);
270
271         if ((encoded = par->encoded))
272             valp = rfc2231_get_charset (par->value, charset, sizeof (charset));
273         else
274             valp = par->value;
275
276         do {
277             if (encoded && par->encoded)
278                 rfc2231_decode_one (par->value, valp);
279
280             vl = m_strlen(par->value);
281
282             p_realloc(&value, l + vl + 1);
283             strcpy (value + l, par->value);   /* __STRCPY_CHECKED__ */
284             l += vl;
285
286             q = par->next;
287             rfc2231_parameter_delete (&par);
288             if ((par = q))
289                 valp = par->value;
290         } while (par && !m_strcmp(par->attribute, attribute));
291
292         if (value) {
293             if (encoded)
294                 mutt_convert_string (&value, charset, Charset, M_ICONV_HOOK_FROM);
295             *head = mutt_new_parameter ();
296             (*head)->attribute = m_strdup(attribute);
297             (*head)->value = value;
298             head = &(*head)->next;
299         }
300     }
301 }
302
303 int rfc2231_encode_string (char **pd)
304 {
305     int ext = 0, encode = 0;
306     char *charset, *s, *t, *e, *d = 0;
307     size_t slen, dlen = 0;
308
309     /* 
310      * A shortcut to detect pure 7bit data.
311      * 
312      * This should prevent the worst when character set handling
313      * is flawed.
314      */
315
316     for (s = *pd; *s; s++)
317         if (*s & 0x80)
318             break;
319
320     if (!*s)
321         return 0;
322
323     if (!Charset || !SendCharset ||
324         !(charset = mutt_choose_charset (Charset, SendCharset,
325                                          *pd, m_strlen(*pd), &d, &dlen))) {
326         charset = m_strdup(Charset ? Charset : "unknown-8bit");
327         d = *pd;
328         dlen = m_strlen(d);
329     }
330
331     if (!mutt_is_us_ascii (charset))
332         encode = 1;
333
334     for (s = d, slen = dlen; slen; s++, slen--)
335         if (*s < 0x20 || *s >= 0x7f)
336             encode = 1, ++ext;
337         else if (strchr (MimeSpecials, *s) || strchr ("*'%", *s))
338             ++ext;
339
340     if (encode) {
341         e = p_new(char, dlen + 2 * ext + m_strlen(charset) + 3);
342         sprintf (e, "%s''", charset);       /* __SPRINTF_CHECKED__ */
343         t = e + m_strlen(e);
344         for (s = d, slen = dlen; slen; s++, slen--)
345             if (*s < 0x20 || *s >= 0x7f ||
346                 strchr (MimeSpecials, *s) || strchr ("*'%", *s)) {
347                 sprintf (t, "%%%02X", (unsigned char) *s);
348                 t += 3;
349             }
350             else
351                 *t++ = *s;
352         *t = '\0';
353
354         if (d != *pd)
355             p_delete(&d);
356         p_delete(pd);
357         *pd = e;
358     }
359     else if (d != *pd) {
360         p_delete(pd);
361         *pd = d;
362     }
363
364     p_delete(&charset);
365
366     return encode;
367 }