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