Rocco Rutte:
[apps/madmutt.git] / charset.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17
18 #include <ctype.h>
19
20 #include <sys/types.h>
21 #include <dirent.h>
22 #include <unistd.h>
23 #include <errno.h>
24
25 #include "mutt.h"
26 #include "charset.h"
27
28 #include "lib/mem.h"
29 #include "lib/intl.h"
30 #include "lib/str.h"
31
32 #ifndef EILSEQ
33 # define EILSEQ EINVAL
34 #endif
35
36 /* 
37  * The following list has been created manually from the data under:
38  * http://www.isi.edu/in-notes/iana/assignments/character-sets
39  * Last update: 2000-09-07
40  *
41  * Note that it includes only the subset of character sets for which
42  * a preferred MIME name is given.
43  */
44
45 static struct {
46   char *key;
47   char *pref;
48 } PreferredMIMENames[] = {
49   {
50   "ansi_x3.4-1968", "us-ascii"}, {
51   "iso-ir-6", "us-ascii"}, {
52   "iso_646.irv:1991", "us-ascii"}, {
53   "ascii", "us-ascii"}, {
54   "iso646-us", "us-ascii"}, {
55   "us", "us-ascii"}, {
56   "ibm367", "us-ascii"}, {
57   "cp367", "us-ascii"}, {
58   "csASCII", "us-ascii"}, {
59   "csISO2022KR", "iso-2022-kr"}, {
60   "csEUCKR", "euc-kr"}, {
61   "csISO2022JP", "iso-2022-jp"}, {
62   "csISO2022JP2", "iso-2022-jp-2"}, {
63   "ISO_8859-1:1987", "iso-8859-1"}, {
64   "iso-ir-100", "iso-8859-1"}, {
65   "iso_8859-1", "iso-8859-1"}, {
66   "latin1", "iso-8859-1"}, {
67   "l1", "iso-8859-1"}, {
68   "IBM819", "iso-8859-1"}, {
69   "CP819", "iso-8859-1"}, {
70   "csISOLatin1", "iso-8859-1"}, {
71   "ISO_8859-2:1987", "iso-8859-2"}, {
72   "iso-ir-101", "iso-8859-2"}, {
73   "iso_8859-2", "iso-8859-2"}, {
74   "latin2", "iso-8859-2"}, {
75   "l2", "iso-8859-2"}, {
76   "csISOLatin2", "iso-8859-2"}, {
77   "ISO_8859-3:1988", "iso-8859-3"}, {
78   "iso-ir-109", "iso-8859-3"}, {
79   "ISO_8859-3", "iso-8859-3"}, {
80   "latin3", "iso-8859-3"}, {
81   "l3", "iso-8859-3"}, {
82   "csISOLatin3", "iso-8859-3"}, {
83   "ISO_8859-4:1988", "iso-8859-4"}, {
84   "iso-ir-110", "iso-8859-4"}, {
85   "ISO_8859-4", "iso-8859-4"}, {
86   "latin4", "iso-8859-4"}, {
87   "l4", "iso-8859-4"}, {
88   "csISOLatin4", "iso-8859-4"}, {
89   "ISO_8859-6:1987", "iso-8859-6"}, {
90   "iso-ir-127", "iso-8859-6"}, {
91   "iso_8859-6", "iso-8859-6"}, {
92   "ECMA-114", "iso-8859-6"}, {
93   "ASMO-708", "iso-8859-6"}, {
94   "arabic", "iso-8859-6"}, {
95   "csISOLatinArabic", "iso-8859-6"}, {
96   "ISO_8859-7:1987", "iso-8859-7"}, {
97   "iso-ir-126", "iso-8859-7"}, {
98   "ISO_8859-7", "iso-8859-7"}, {
99   "ELOT_928", "iso-8859-7"}, {
100   "ECMA-118", "iso-8859-7"}, {
101   "greek", "iso-8859-7"}, {
102   "greek8", "iso-8859-7"}, {
103   "csISOLatinGreek", "iso-8859-7"}, {
104   "ISO_8859-8:1988", "iso-8859-8"}, {
105   "iso-ir-138", "iso-8859-8"}, {
106   "ISO_8859-8", "iso-8859-8"}, {
107   "hebrew", "iso-8859-8"}, {
108   "csISOLatinHebrew", "iso-8859-8"}, {
109   "ISO_8859-5:1988", "iso-8859-5"}, {
110   "iso-ir-144", "iso-8859-5"}, {
111   "ISO_8859-5", "iso-8859-5"}, {
112   "cyrillic", "iso-8859-5"}, {
113   "csISOLatinCyrillic", "iso8859-5"}, {
114   "ISO_8859-9:1989", "iso-8859-9"}, {
115   "iso-ir-148", "iso-8859-9"}, {
116   "ISO_8859-9", "iso-8859-9"}, {
117   "latin5", "iso-8859-9"},      /* this is not a bug */
118   {
119   "l5", "iso-8859-9"}, {
120   "csISOLatin5", "iso-8859-9"}, {
121   "ISO_8859-10:1992", "iso-8859-10"}, {
122   "iso-ir-157", "iso-8859-10"}, {
123   "latin6", "iso-8859-10"},     /* this is not a bug */
124   {
125   "l6", "iso-8859-10"}, {
126   "csISOLatin6" "iso-8859-10"}, {
127   "csKOI8r", "koi8-r"}, {
128   "MS_Kanji", "Shift_JIS"},     /* Note the underscore! */
129   {
130   "csShiftJis", "Shift_JIS"}, {
131   "Extended_UNIX_Code_Packed_Format_for_Japanese", "EUC-JP"}, {
132   "csEUCPkdFmtJapanese", "EUC-JP"}, {
133   "csGB2312", "gb2312"}, {
134   "csbig5", "big5"},
135     /* 
136      * End of official brain damage.  What follows has been taken
137      * from glibc's localedata files. 
138      */
139   {
140   "iso_8859-13", "iso-8859-13"}, {
141   "iso-ir-179", "iso-8859-13"}, {
142   "latin7", "iso-8859-13"},     /* this is not a bug */
143   {
144   "l7", "iso-8859-13"}, {
145   "iso_8859-14", "iso-8859-14"}, {
146   "latin8", "iso-8859-14"},     /* this is not a bug */
147   {
148   "l8", "iso-8859-14"}, {
149   "iso_8859-15", "iso-8859-15"}, {
150   "latin9", "iso-8859-15"},     /* this is not a bug */
151     /* Suggested by Ionel Mugurel Ciobica <tgakic@sg10.chem.tue.nl> */
152   {
153   "latin0", "iso-8859-15"},     /* this is not a bug */
154   {
155   "iso_8859-16", "iso-8859-16"}, {
156   "latin10", "iso-8859-16"},    /* this is not a bug */
157     /* 
158      * David Champion <dgc@uchicago.edu> has observed this with
159      * nl_langinfo under SunOS 5.8. 
160      */
161   {
162   "646", "us-ascii"},
163     /* 
164      * http://www.sun.com/software/white-papers/wp-unicode/
165      */
166   {
167   "eucJP", "euc-jp"}, {
168   "PCK", "Shift_JIS"}, {
169   "ko_KR-euc", "euc-kr"}, {
170   "zh_TW-big5", "big5"},
171     /* seems to be common on some systems */
172   {
173   "sjis", "Shift_JIS"}, {
174   "euc-jp-ms", "eucJP-ms"},
175     /*
176      * If you happen to encounter system-specific brain-damage with
177      * respect to character set naming, please add it above this
178      * comment, and submit a patch to <mutt-dev@mutt.org>. 
179      */
180     /* End of aliases.  Please keep this line last. */
181   {
182   NULL, NULL}
183 };
184
185 #ifdef HAVE_LANGINFO_CODESET
186 # include <langinfo.h>
187
188
189 void mutt_set_langinfo_charset (void)
190 {
191   char buff[LONG_STRING];
192   char buff2[LONG_STRING];
193
194   strfcpy (buff, nl_langinfo (CODESET), sizeof (buff));
195   mutt_canonical_charset (buff2, sizeof (buff2), buff);
196
197   /* finally, set $charset */
198   if (!(Charset = safe_strdup (buff2)))
199     Charset = safe_strdup ("iso-8859-1");
200 }
201
202 #else
203
204 void mutt_set_langinfo_charset (void)
205 {
206   Charset = safe_strdup ("iso-8859-1");
207 }
208
209 #endif
210
211 void mutt_canonical_charset (char *dest, size_t dlen, const char *name)
212 {
213   size_t i;
214   char *p;
215   char scratch[LONG_STRING];
216
217   /* catch some common iso-8859-something misspellings */
218   if (!ascii_strncasecmp (name, "8859", 4) && name[4] != '-')
219     snprintf (scratch, sizeof (scratch), "iso-8859-%s", name + 4);
220   else if (!ascii_strncasecmp (name, "8859-", 5))
221     snprintf (scratch, sizeof (scratch), "iso-8859-%s", name + 5);
222   else if (!ascii_strncasecmp (name, "iso8859", 7) && name[7] != '-')
223     snprintf (scratch, sizeof (scratch), "iso_8859-%s", name + 7);
224   else if (!ascii_strncasecmp (name, "iso8859-", 8))
225     snprintf (scratch, sizeof (scratch), "iso_8859-%s", name + 8);
226   else
227     strfcpy (scratch, NONULL (name), sizeof (scratch));
228
229   for (i = 0; PreferredMIMENames[i].key; i++)
230     if (!ascii_strcasecmp (scratch, PreferredMIMENames[i].key) ||
231         !safe_strcasecmp (scratch, PreferredMIMENames[i].key)) {
232       strfcpy (dest, PreferredMIMENames[i].pref, dlen);
233       return;
234     }
235
236   strfcpy (dest, scratch, dlen);
237
238   /* for cosmetics' sake, transform to lowercase. */
239   for (p = dest; *p; p++)
240     *p = ascii_tolower (*p);
241 }
242
243 int mutt_chscmp (const char *s, const char *chs)
244 {
245   char buffer[STRING];
246
247   if (!s)
248     return 0;
249
250   mutt_canonical_charset (buffer, sizeof (buffer), s);
251   return !ascii_strcasecmp (buffer, chs);
252 }
253
254
255 #ifndef HAVE_ICONV
256
257 iconv_t iconv_open (const char *tocode, const char *fromcode)
258 {
259   return (iconv_t) (-1);
260 }
261
262 size_t iconv (iconv_t cd, ICONV_CONST char **inbuf, size_t * inbytesleft,
263               char **outbuf, size_t * outbytesleft)
264 {
265   return 0;
266 }
267
268 int iconv_close (iconv_t cd)
269 {
270   return 0;
271 }
272
273 #endif /* !HAVE_ICONV */
274
275
276 /*
277  * Like iconv_open, but canonicalises the charsets
278  */
279
280 iconv_t mutt_iconv_open (const char *tocode, const char *fromcode, int flags)
281 {
282   char tocode1[SHORT_STRING];
283   char fromcode1[SHORT_STRING];
284   char *tocode2, *fromcode2;
285   char *tmp;
286
287   iconv_t cd;
288
289   mutt_canonical_charset (tocode1, sizeof (tocode1), tocode);
290
291 #ifdef M_ICONV_HOOK_TO
292   /* Not used. */
293   if ((flags & M_ICONV_HOOK_TO) && (tmp = mutt_charset_hook (tocode1)))
294     mutt_canonical_charset (tocode1, sizeof (tocode1), tmp);
295 #endif
296
297   mutt_canonical_charset (fromcode1, sizeof (fromcode1), fromcode);
298   if ((flags & M_ICONV_HOOK_FROM) && (tmp = mutt_charset_hook (fromcode1)))
299     mutt_canonical_charset (fromcode1, sizeof (fromcode1), tmp);
300
301   if ((cd = iconv_open (tocode1, fromcode1)) != (iconv_t) - 1)
302     return cd;
303   if ((tocode2 = mutt_iconv_hook (tocode1))
304       && (fromcode2 = mutt_iconv_hook (fromcode1)))
305     return iconv_open (tocode2, fromcode2);
306
307   return (iconv_t) - 1;
308 }
309
310
311 /*
312  * Like iconv, but keeps going even when the input is invalid
313  * If you're supplying inrepls, the source charset should be stateless;
314  * if you're supplying an outrepl, the target charset should be.
315  */
316
317 size_t mutt_iconv (iconv_t cd, ICONV_CONST char **inbuf, size_t * inbytesleft,
318                    char **outbuf, size_t * outbytesleft,
319                    ICONV_CONST char **inrepls, const char *outrepl)
320 {
321   size_t ret = 0, ret1;
322   ICONV_CONST char *ib = *inbuf;
323   size_t ibl = *inbytesleft;
324   char *ob = *outbuf;
325   size_t obl = *outbytesleft;
326
327   for (;;) {
328     ret1 = iconv (cd, &ib, &ibl, &ob, &obl);
329     if (ret1 != (size_t) - 1)
330       ret += ret1;
331     if (ibl && obl && errno == EILSEQ) {
332       if (inrepls) {
333         /* Try replacing the input */
334         ICONV_CONST char **t;
335
336         for (t = inrepls; *t; t++) {
337           ICONV_CONST char *ib1 = *t;
338           size_t ibl1 = mutt_strlen (*t);
339           char *ob1 = ob;
340           size_t obl1 = obl;
341
342           iconv (cd, &ib1, &ibl1, &ob1, &obl1);
343           if (!ibl1) {
344             ++ib, --ibl;
345             ob = ob1, obl = obl1;
346             ++ret;
347             break;
348           }
349         }
350         if (*t)
351           continue;
352       }
353       /* Replace the output */
354       if (!outrepl)
355         outrepl = "?";
356       iconv (cd, 0, 0, &ob, &obl);
357       if (obl) {
358         int n = mutt_strlen (outrepl);
359
360         if (n > obl) {
361           outrepl = "?";
362           n = 1;
363         }
364         memcpy (ob, outrepl, n);
365         ++ib, --ibl;
366         ob += n, obl -= n;
367         ++ret;
368         iconv (cd, 0, 0, 0, 0); /* for good measure */
369         continue;
370       }
371     }
372     *inbuf = ib, *inbytesleft = ibl;
373     *outbuf = ob, *outbytesleft = obl;
374     return ret;
375   }
376 }
377
378
379 /*
380  * Convert a string
381  * Used in rfc2047.c and rfc2231.c
382  */
383
384 int mutt_convert_string (char **ps, const char *from, const char *to,
385                          int flags)
386 {
387   iconv_t cd;
388   ICONV_CONST char *repls[] = { "\357\277\275", "?", 0 };
389   char *s = *ps;
390
391   if (!s || !*s)
392     return 0;
393
394   if (to && from && (cd = mutt_iconv_open (to, from, flags)) != (iconv_t) - 1) {
395     int len;
396     ICONV_CONST char *ib;
397     char *buf, *ob;
398     size_t ibl, obl;
399     ICONV_CONST char **inrepls = 0;
400     char *outrepl = 0;
401
402     if (mutt_is_utf8 (to))
403       outrepl = "\357\277\275";
404     else if (mutt_is_utf8 (from))
405       inrepls = repls;
406     else
407       outrepl = "?";
408
409     len = mutt_strlen (s);
410     ib = s, ibl = len + 1;
411     obl = MB_LEN_MAX * ibl;
412     ob = buf = safe_malloc (obl + 1);
413
414     mutt_iconv (cd, &ib, &ibl, &ob, &obl, inrepls, outrepl);
415     iconv_close (cd);
416
417     *ob = '\0';
418
419     FREE (ps);
420     *ps = buf;
421
422     str_adjust (ps);
423     return 0;
424   }
425   else
426     return -1;
427 }
428
429
430 /*
431  * FGETCONV stuff for converting a file while reading it
432  * Used in sendlib.c for converting from mutt's Charset
433  */
434
435 struct fgetconv_s {
436   FILE *file;
437   iconv_t cd;
438   char bufi[512];
439   char bufo[512];
440   char *p;
441   char *ob;
442   char *ib;
443   size_t ibl;
444   ICONV_CONST char **inrepls;
445 };
446
447 struct fgetconv_not {
448   FILE *file;
449   iconv_t cd;
450 };
451
452 FGETCONV *fgetconv_open (FILE * file, const char *from, const char *to,
453                          int flags)
454 {
455   struct fgetconv_s *fc;
456   iconv_t cd = (iconv_t) - 1;
457   static ICONV_CONST char *repls[] = { "\357\277\275", "?", 0 };
458
459   if (from && to)
460     cd = mutt_iconv_open (to, from, flags);
461
462   if (cd != (iconv_t) - 1) {
463     fc = safe_malloc (sizeof (struct fgetconv_s));
464     fc->p = fc->ob = fc->bufo;
465     fc->ib = fc->bufi;
466     fc->ibl = 0;
467     fc->inrepls = mutt_is_utf8 (to) ? repls : repls + 1;
468   }
469   else
470     fc = safe_malloc (sizeof (struct fgetconv_not));
471   fc->file = file;
472   fc->cd = cd;
473   return (FGETCONV *) fc;
474 }
475
476 char *fgetconvs (char *buf, size_t l, FGETCONV * _fc)
477 {
478   int c;
479   size_t r;
480
481   for (r = 0; r + 1 < l;) {
482     if ((c = fgetconv (_fc)) == EOF)
483       break;
484     buf[r++] = (char) c;
485     if (c == '\n')
486       break;
487   }
488   buf[r] = '\0';
489
490   if (r)
491     return buf;
492   else
493     return NULL;
494 }
495
496 int fgetconv (FGETCONV * _fc)
497 {
498   struct fgetconv_s *fc = (struct fgetconv_s *) _fc;
499
500   if (!fc)
501     return EOF;
502   if (fc->cd == (iconv_t) - 1)
503     return fgetc (fc->file);
504   if (!fc->p)
505     return EOF;
506   if (fc->p < fc->ob)
507     return (unsigned char) *(fc->p)++;
508
509   /* Try to convert some more */
510   fc->p = fc->ob = fc->bufo;
511   if (fc->ibl) {
512     size_t obl = sizeof (fc->bufo);
513
514     iconv (fc->cd, (ICONV_CONST char **) &fc->ib, &fc->ibl, &fc->ob, &obl);
515     if (fc->p < fc->ob)
516       return (unsigned char) *(fc->p)++;
517   }
518
519   /* If we trusted iconv a bit more, we would at this point
520    * ask why it had stopped converting ... */
521
522   /* Try to read some more */
523   if (fc->ibl == sizeof (fc->bufi) ||
524       (fc->ibl && fc->ib + fc->ibl < fc->bufi + sizeof (fc->bufi))) {
525     fc->p = 0;
526     return EOF;
527   }
528   if (fc->ibl)
529     memcpy (fc->bufi, fc->ib, fc->ibl);
530   fc->ib = fc->bufi;
531   fc->ibl +=
532     fread (fc->ib + fc->ibl, 1, sizeof (fc->bufi) - fc->ibl, fc->file);
533
534   /* Try harder this time to convert some */
535   if (fc->ibl) {
536     size_t obl = sizeof (fc->bufo);
537
538     mutt_iconv (fc->cd, (ICONV_CONST char **) &fc->ib, &fc->ibl, &fc->ob,
539                 &obl, fc->inrepls, 0);
540     if (fc->p < fc->ob)
541       return (unsigned char) *(fc->p)++;
542   }
543
544   /* Either the file has finished or one of the buffers is too small */
545   fc->p = 0;
546   return EOF;
547 }
548
549 void fgetconv_close (FGETCONV ** _fc)
550 {
551   struct fgetconv_s *fc = (struct fgetconv_s *) *_fc;
552
553   if (fc->cd != (iconv_t) - 1)
554     iconv_close (fc->cd);
555   FREE (_fc);
556 }
557
558 char *mutt_get_first_charset (const char *charset)
559 {
560   static char fcharset[SHORT_STRING];
561   const char *c, *c1;
562
563   c = charset;
564   if (!mutt_strlen (c))
565     return "us-ascii";
566   if (!(c1 = strchr (c, ':')))
567     return ((char*) charset);
568   strfcpy (fcharset, c, c1 - c + 1);
569   return fcharset;
570 }
571
572 static size_t convert_string (ICONV_CONST char *f, size_t flen,
573                               const char *from, const char *to,
574                               char **t, size_t * tlen)
575 {
576   iconv_t cd;
577   char *buf, *ob;
578   size_t obl, n;
579   int e;
580
581   cd = mutt_iconv_open (to, from, 0);
582   if (cd == (iconv_t) (-1))
583     return (size_t) (-1);
584   obl = 4 * flen + 1;
585   ob = buf = safe_malloc (obl);
586   n = iconv (cd, &f, &flen, &ob, &obl);
587   if (n == (size_t) (-1) || iconv (cd, 0, 0, &ob, &obl) == (size_t) (-1)) {
588     e = errno;
589     FREE (&buf);
590     iconv_close (cd);
591     errno = e;
592     return (size_t) (-1);
593   }
594   *ob = '\0';
595
596   *tlen = ob - buf;
597
598   safe_realloc (&buf, ob - buf + 1);
599   *t = buf;
600   iconv_close (cd);
601
602   return n;
603 }
604
605 int mutt_convert_nonmime_string (char **ps)
606 {
607   const char *c, *c1;
608
609   for (c = AssumedCharset; c; c = c1 ? c1 + 1 : 0) {
610     char *u = *ps;
611     char *s;
612     char *fromcode;
613     size_t m, n;
614     size_t ulen = mutt_strlen (*ps);
615     size_t slen;
616
617     if (!u || !*u)
618       return 0;
619
620     c1 = strchr (c, ':');
621     n = c1 ? c1 - c : mutt_strlen (c);
622     if (!n)
623       continue;
624     fromcode = safe_malloc (n + 1);
625     strfcpy (fromcode, c, n + 1);
626     m = convert_string (u, ulen, fromcode, Charset, &s, &slen);
627     FREE (&fromcode);
628     if (m != (size_t) (-1)) {
629       FREE (ps);
630       *ps = s;
631       return 0;
632     }
633   }
634   return -1;
635 }