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