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