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