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