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