9a80d319366dacc7f3a3795063a9449366096760
[apps/madmutt.git] / charset.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  * Copyright notice from original mutt:
21  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
22  *
23  * This file is part of mutt-ng, see http://www.muttng.org/.
24  * It's licensed under the GNU General Public License,
25  * please see the file GPL in the top level source directory.
26  */
27
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <string.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35
36 #include <ctype.h>
37
38 #include <sys/types.h>
39 #include <dirent.h>
40 #include <unistd.h>
41 #include <errno.h>
42 #ifdef HAVE_LANGINFO_CODESET
43 #  include <langinfo.h>
44 #endif
45
46 #include <lib-lib/mem.h>
47 #include <lib-lib/ascii.h>
48 #include <lib-lib/str.h>
49 #include <lib-lib/macros.h>
50
51 #include "mutt.h"
52 #include "charset.h"
53
54 #ifndef EILSEQ
55 #  define EILSEQ EINVAL
56 #endif
57
58 char *Charset;
59 int Charset_is_utf8 = 0;
60 wchar_t CharsetReplacement = '?';
61
62 void charset_initialize(void)
63 {
64 #ifdef HAVE_LANGINFO_CODESET
65     char buff[LONG_STRING];
66     char buff2[LONG_STRING];
67
68     m_strcpy(buff, sizeof(buff), nl_langinfo(CODESET));
69     charset_canonicalize(buff2, sizeof(buff2), buff);
70
71     /* finally, set $charset */
72     if (!m_strisempty(buff2)) {
73         m_strreplace(&Charset, buff2);
74     } else
75 #endif
76     {
77         m_strreplace(&Charset, "iso-8859-1");
78     }
79
80     Charset_is_utf8    = !strcmp(Charset, "utf-8");
81     CharsetReplacement = Charset_is_utf8 ? 0xfffd : '?';
82
83 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
84     bind_textdomain_codeset(PACKAGE, Charset);
85 #endif
86 }
87
88 #include "charset.gperf"
89 void charset_canonicalize(char *dest, ssize_t dlen, const char *name)
90 {
91     const struct cset_pair *cp;
92     char scratch[LONG_STRING];
93     const char *p;
94     int i = 0;
95
96     // canonize name: only keep a-z0-9 and dots, put into lowercase
97     for (p = name; *p && *p != ':' && i < ssizeof(scratch) - 1; p++) {
98         if (isalnum(*p) || *p== '.') {
99             scratch[i++] = tolower((unsigned char)*p);
100         }
101     }
102     scratch[i] = '\0';
103
104     cp = charset_canonicalize_aux(scratch, strlen(scratch));
105     if (cp) {
106         m_strcpy(dest, dlen, cp->pref);
107     } else {
108         m_strcpy(dest, dlen, name);
109         m_strtolower(dest);
110     }
111 }
112
113 static int mutt_chscmp(const char *s, const char *chs)
114 {
115     char buffer[STRING];
116
117     if (!s)
118         return 0;
119
120     charset_canonicalize(buffer, sizeof(buffer), s);
121     return !strcmp(buffer, chs);
122 }
123
124 int charset_is_utf8(const char *s)
125 {
126     return mutt_chscmp(s, "utf-8");
127 }
128
129 int charset_is_us_ascii(const char *s)
130 {
131     return mutt_chscmp(s, "us-ascii");
132 }
133
134
135 /*
136  * Like iconv_open, but canonicalises the charsets
137  */
138
139 iconv_t mutt_iconv_open (const char *tocode, const char *fromcode, int flags)
140 {
141   char tocode1[SHORT_STRING];
142   char fromcode1[SHORT_STRING];
143   char *tocode2, *fromcode2;
144   char *tmp;
145
146   iconv_t cd;
147
148   charset_canonicalize (tocode1, sizeof (tocode1), tocode);
149
150 #ifdef M_ICONV_HOOK_TO
151   /* Not used. */
152   if ((flags & M_ICONV_HOOK_TO) && (tmp = mutt_charset_hook (tocode1)))
153     charset_canonicalize (tocode1, sizeof (tocode1), tmp);
154 #endif
155
156   charset_canonicalize (fromcode1, sizeof (fromcode1), fromcode);
157   if ((flags & M_ICONV_HOOK_FROM) && (tmp = mutt_charset_hook (fromcode1)))
158     charset_canonicalize (fromcode1, sizeof (fromcode1), tmp);
159
160   if ((cd = iconv_open (tocode1, fromcode1)) != (iconv_t) - 1)
161     return cd;
162   if ((tocode2 = mutt_iconv_hook (tocode1))
163       && (fromcode2 = mutt_iconv_hook (fromcode1)))
164     return iconv_open (tocode2, fromcode2);
165
166   return (iconv_t) - 1;
167 }
168
169
170 /*
171  * Like iconv, but keeps going even when the input is invalid
172  * If you're supplying inrepls, the source charset should be stateless;
173  * if you're supplying an outrepl, the target charset should be.
174  */
175
176 ssize_t mutt_iconv(iconv_t cd, const char **inbuf, ssize_t *inbytesleft,
177                    char **outbuf, ssize_t *outbytesleft,
178                    const char **inrepls, const char *outrepl)
179 {
180   ssize_t ret = 0, ret1;
181   const char *ib = *inbuf;
182   ssize_t ibl = *inbytesleft;
183   char *ob = *outbuf;
184   ssize_t obl = *outbytesleft;
185
186   for (;;) {
187     ret1 = my_iconv(cd, &ib, &ibl, &ob, &obl);
188     if (ret1 != -1)
189       ret += ret1;
190     if (ibl && obl && errno == EILSEQ) {
191       if (inrepls) {
192         /* Try replacing the input */
193         const char **t;
194
195         for (t = inrepls; *t; t++) {
196           const char *ib1 = *t;
197           ssize_t ibl1 = m_strlen(*t);
198           char *ob1 = ob;
199           ssize_t obl1 = obl;
200
201           my_iconv(cd, &ib1, &ibl1, &ob1, &obl1);
202           if (!ibl1) {
203             ++ib, --ibl;
204             ob = ob1, obl = obl1;
205             ++ret;
206             break;
207           }
208         }
209         if (*t)
210           continue;
211       }
212       /* Replace the output */
213       if (!outrepl)
214         outrepl = "?";
215       my_iconv(cd, 0, 0, &ob, &obl);
216       if (obl) {
217         ssize_t n = m_strlen(outrepl);
218
219         if (n > obl) {
220           outrepl = "?";
221           n = 1;
222         }
223         memcpy (ob, outrepl, n);
224         ++ib, --ibl;
225         ob += n, obl -= n;
226         ++ret;
227         my_iconv(cd, 0, 0, 0, 0); /* for good measure */
228         continue;
229       }
230     }
231     *inbuf = ib, *inbytesleft = ibl;
232     *outbuf = ob, *outbytesleft = obl;
233     return ret;
234   }
235 }
236
237
238 /*
239  * Convert a string
240  * Used in rfc2047.c and rfc2231.c
241  */
242
243 int mutt_convert_string (char **ps, const char *from, const char *to,
244                          int flags)
245 {
246   iconv_t cd;
247   const char *repls[] = { "\357\277\275", "?", 0 };
248   char *s = *ps;
249
250   if (!s || !*s)
251     return 0;
252
253   if (to && from && (cd = mutt_iconv_open (to, from, flags)) != (iconv_t) - 1) {
254     int len;
255     const char *ib;
256     char *buf, *ob;
257     ssize_t ibl, obl;
258     const char **inrepls = NULL;
259     const char *outrepl = NULL;
260
261     if (charset_is_utf8 (to))
262       outrepl = "\357\277\275";
263     else if (charset_is_utf8 (from))
264       inrepls = repls;
265     else
266       outrepl = "?";
267
268     len = m_strlen(s);
269     ib = s, ibl = len + 1;
270     obl = MB_LEN_MAX * ibl;
271     ob = buf = xmalloc(obl + 1);
272
273     mutt_iconv (cd, &ib, &ibl, &ob, &obl, inrepls, outrepl);
274     iconv_close (cd);
275
276     *ob = '\0';
277
278     p_delete(ps);
279     *ps = buf;
280     return 0;
281   }
282   else
283     return -1;
284 }
285
286
287 /*
288  * FGETCONV stuff for converting a file while reading it
289  * Used in sendlib.c for converting from mutt's Charset
290  */
291
292 struct fgetconv_s {
293   FILE *file;
294   iconv_t cd;
295   char bufi[512];
296   char bufo[512];
297   char *p;
298   char *ob;
299   char *ib;
300   ssize_t ibl;
301   const char **inrepls;
302 };
303
304 struct fgetconv_not {
305   FILE *file;
306   iconv_t cd;
307 };
308
309 FGETCONV *fgetconv_open (FILE * file, const char *from, const char *to,
310                          int flags)
311 {
312   struct fgetconv_s *fc;
313   iconv_t cd = (iconv_t) - 1;
314   static const char *repls[] = { "\357\277\275", "?", 0 };
315
316   if (from && to)
317     cd = mutt_iconv_open (to, from, flags);
318
319   if (cd != (iconv_t) - 1) {
320     fc = p_new(struct fgetconv_s, 1);
321     fc->p = fc->ob = fc->bufo;
322     fc->ib = fc->bufi;
323     fc->ibl = 0;
324     fc->inrepls = charset_is_utf8 (to) ? repls : repls + 1;
325   }
326   else
327     fc = p_new(struct fgetconv_s, 1);
328   fc->file = file;
329   fc->cd = cd;
330   return (FGETCONV *) fc;
331 }
332
333 char *fgetconvs (char *buf, ssize_t l, FGETCONV * _fc)
334 {
335   int c;
336   ssize_t r;
337
338   for (r = 0; r + 1 < l;) {
339     if ((c = fgetconv (_fc)) == EOF)
340       break;
341     buf[r++] = (char) c;
342     if (c == '\n')
343       break;
344   }
345   buf[r] = '\0';
346
347   if (r)
348     return buf;
349   else
350     return NULL;
351 }
352
353 int fgetconv (FGETCONV * _fc)
354 {
355   struct fgetconv_s *fc = (struct fgetconv_s *) _fc;
356
357   if (!fc)
358     return EOF;
359   if (fc->cd == (iconv_t) - 1)
360     return fgetc (fc->file);
361   if (!fc->p)
362     return EOF;
363   if (fc->p < fc->ob)
364     return (unsigned char) *(fc->p)++;
365
366   /* Try to convert some more */
367   fc->p = fc->ob = fc->bufo;
368   if (fc->ibl) {
369     ssize_t obl = ssizeof(fc->bufo);
370
371     my_iconv(fc->cd, (const char **) &fc->ib, &fc->ibl, &fc->ob, &obl);
372     if (fc->p < fc->ob)
373       return (unsigned char) *(fc->p)++;
374   }
375
376   /* If we trusted iconv a bit more, we would at this point
377    * ask why it had stopped converting ... */
378
379   /* Try to read some more */
380   if (fc->ibl == sizeof (fc->bufi) ||
381       (fc->ibl && fc->ib + fc->ibl < fc->bufi + sizeof (fc->bufi))) {
382     fc->p = 0;
383     return EOF;
384   }
385   if (fc->ibl)
386     memcpy (fc->bufi, fc->ib, fc->ibl);
387   fc->ib = fc->bufi;
388   fc->ibl +=
389     fread (fc->ib + fc->ibl, 1, sizeof (fc->bufi) - fc->ibl, fc->file);
390
391   /* Try harder this time to convert some */
392   if (fc->ibl) {
393     ssize_t obl = ssizeof(fc->bufo);
394
395     mutt_iconv (fc->cd, (const char **) &fc->ib, &fc->ibl, &fc->ob,
396                 &obl, fc->inrepls, 0);
397     if (fc->p < fc->ob)
398       return (unsigned char) *(fc->p)++;
399   }
400
401   /* Either the file has finished or one of the buffers is too small */
402   fc->p = 0;
403   return EOF;
404 }
405
406 void fgetconv_close (FGETCONV ** _fc)
407 {
408   struct fgetconv_s *fc = (struct fgetconv_s *) *_fc;
409
410   if (fc->cd != (iconv_t) - 1)
411     iconv_close (fc->cd);
412   p_delete(_fc);
413 }
414
415 const char *mutt_get_first_charset (const char *charset)
416 {
417   static char fcharset[SHORT_STRING];
418   const char *c, *c1;
419
420   c = charset;
421   if (!m_strlen(c))
422     return "us-ascii";
423   if (!(c1 = strchr (c, ':')))
424     return ((char*) charset);
425   m_strcpy(fcharset, c1 - c + 1, c);
426   return fcharset;
427 }
428
429 static ssize_t convert_string (const char *f, ssize_t flen,
430                               const char *from, const char *to,
431                               char **t, ssize_t * tlen)
432 {
433   iconv_t cd;
434   char *buf, *ob;
435   ssize_t obl;
436   ssize_t n;
437   int e;
438
439   cd = mutt_iconv_open (to, from, 0);
440   if (cd == (iconv_t) (-1))
441     return -1;
442   obl = 4 * flen + 1;
443   ob = buf = xmalloc(obl);
444   n = my_iconv(cd, &f, &flen, &ob, &obl);
445   if (n < 0 || my_iconv(cd, 0, 0, &ob, &obl) < 0) {
446     e = errno;
447     p_delete(&buf);
448     iconv_close (cd);
449     errno = e;
450     return -1;
451   }
452   *ob = '\0';
453
454   *tlen = ob - buf;
455
456   p_realloc(&buf, ob - buf + 1);
457   *t = buf;
458   iconv_close (cd);
459
460   return n;
461 }
462
463 int mutt_convert_nonmime_string (char **ps)
464 {
465   const char *c, *c1;
466
467   for (c = AssumedCharset; c; c = c1 ? c1 + 1 : 0) {
468     char *u = *ps;
469     char *s = NULL;
470     char *fromcode;
471     ssize_t m, n;
472     ssize_t ulen = m_strlen(*ps);
473     ssize_t slen;
474
475     if (!u || !*u)
476       return 0;
477
478     c1 = strchr (c, ':');
479     n = c1 ? c1 - c : m_strlen(c);
480     if (!n)
481       continue;
482     fromcode = p_dupstr(c, n);
483     m = convert_string (u, ulen, fromcode, Charset, &s, &slen);
484     p_delete(&fromcode);
485     if (m != -1) {
486       p_delete(ps);
487       *ps = s;
488       return 0;
489     }
490   }
491   return -1;
492 }