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