turn charset into a lua package as well.
[apps/madmutt.git] / charset.cpkg
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 "charset.h"
35
36 #ifndef EILSEQ
37 #  define EILSEQ EINVAL
38 #endif
39 @import "lib-lua/base.cpkg"
40
41 @package MCharset {
42     /*
43      ** .pp
44      ** This variable is a colon-separated list of character encoding
45      ** schemes for messages without character encoding indication.
46      ** Header field values and message body content without character encoding
47      ** indication would be assumed that they are written in one of this list.
48      ** By default, all the header fields and message body without any charset
49      ** indication are assumed to be in \fTus-ascii\fP.
50      ** .pp
51      ** For example, Japanese users might prefer this:
52      ** .pp
53      ** \fTset assumed_charset="iso-2022-jp:euc-jp:shift_jis:utf-8"\fP
54      ** .pp
55      ** However, only the first content is valid for the message body.
56      ** This variable is valid only if $$strict_mime is unset.
57      */
58     string_t assumed_charset = m_strdup("us-ascii");
59
60     /*
61      ** .pp
62      ** Character set your terminal uses to display and enter textual data.
63      */
64     string_t charset         = NULL;
65
66     /*
67      ** .pp
68      ** This variable is a colon-separated list of character encoding
69      ** schemes for text file attatchments.
70      ** If \fIunset\fP, $$charset value will be used instead.
71      ** For example, the following configuration would work for Japanese
72      ** text handling:
73      ** .pp
74      ** \fTset file_charset="iso-2022-jp:euc-jp:shift_jis:utf-8"\fP
75      ** .pp
76      ** Note: ``\fTiso-2022-*\fP'' must be put at the head of the value as shown above
77      ** if included.
78      */
79     string_t file_charset    = NULL;
80
81     /*
82      ** .pp
83      ** A list of character sets for outgoing messages. Madmutt will use the
84      ** first character set into which the text can be converted exactly.
85      ** If your ``$$charset'' is not \fTiso-8859-1\fP and recipients may not
86      ** understand \fTUTF-8\fP, it is advisable to include in the list an
87      ** appropriate widely used standard character set (such as
88      ** \fTiso-8859-2\fP, \fTkoi8-r\fP or \fTiso-2022-jp\fP) either
89      ** instead of or after \fTiso-8859-1\fP.
90      */
91     string_t send_charset    = m_strdup("us-ascii:iso-8859-1:utf-8");
92 };
93
94 int Charset_is_utf8 = 0;
95 wchar_t CharsetReplacement = '?';
96
97
98 /****************************************************************************/
99 /* charset functions                                                        */
100 /****************************************************************************/
101
102 void charset_initialize(void)
103 {
104 #ifdef HAVE_LANGINFO_CODESET
105     char buff[STRING];
106     char buff2[STRING];
107
108     m_strcpy(buff, sizeof(buff), nl_langinfo(CODESET));
109     charset_canonicalize(buff2, sizeof(buff2), buff);
110
111     /* finally, set $charset */
112     if (!m_strisempty(buff2)) {
113         m_strreplace(&MCharset.charset, buff2);
114     } else
115 #endif
116     {
117         m_strreplace(&MCharset.charset, "iso-8859-1");
118     }
119
120     Charset_is_utf8    = !m_strcmp(MCharset.charset, "utf-8");
121     CharsetReplacement = Charset_is_utf8 ? 0xfffd : '?';
122
123 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
124     bind_textdomain_codeset(PACKAGE, MCharset.charset);
125 #endif
126 }
127
128 #include "charset.gperf"
129 void charset_canonicalize(char *dest, ssize_t dlen, const char *name)
130 {
131     const struct cset_pair *cp;
132     char scratch[STRING];
133     const char *p;
134     int i = 0;
135
136     if (!name) {
137         m_strcpy(dest, dlen, "us-ascii");
138         return;
139     }
140
141     // canonize name: only keep a-z0-9 and dots, put into lowercase
142     for (p = name; *p && *p != ':' && i < ssizeof(scratch) - 1; p++) {
143         if (isalnum(*p) || *p== '.') {
144             scratch[i++] = tolower((unsigned char)*p);
145         }
146     }
147     scratch[i] = '\0';
148
149     cp = charset_canonicalize_aux(scratch, strlen(scratch));
150     if (cp) {
151         m_strcpy(dest, dlen, cp->pref);
152     } else {
153         m_strcpy(dest, dlen, name);
154         m_strtolower(dest);
155     }
156 }
157
158 /* XXX: MC: UGLY return of local static */
159 const char *charset_getfirst(const char *charset)
160 {
161     static char fcharset[STRING];
162     const char *p;
163
164     if (m_strisempty(charset))
165         return "us-ascii";
166
167     p = m_strchrnul(charset, ':');
168     m_strncpy(fcharset, sizeof(fcharset), charset, p - charset);
169     return fcharset;
170 }
171
172 int charset_is_utf8(const char *s)
173 {
174     char buf[STRING];
175     charset_canonicalize(buf, sizeof(buf), s);
176     return !m_strcmp(buf, "utf-8");
177 }
178
179 int charset_is_us_ascii(const char *s)
180 {
181     char buf[STRING];
182     charset_canonicalize(buf, sizeof(buf), s);
183     return !m_strcmp(buf, "us-ascii");
184 }
185
186
187 /****************************************************************************/
188 /* iconv-line functions                                                     */
189 /****************************************************************************/
190
191 /* Like iconv_open, but canonicalises the charsets */
192 iconv_t mutt_iconv_open(const char *tocode, const char *fromcode, int flags)
193 {
194     char tocode1[STRING];
195     char fromcode1[STRING];
196     const char *tmp;
197
198     iconv_t cd;
199
200     if ((flags & M_ICONV_HOOK_TO) && (tmp = mutt_charset_hook(tocode1))) {
201         charset_canonicalize(tocode1, sizeof(tocode1), tmp);
202     } else {
203         charset_canonicalize(tocode1, sizeof(tocode1), tocode);
204     }
205
206     if ((flags & M_ICONV_HOOK_FROM) && (tmp = mutt_charset_hook(fromcode1))) {
207         charset_canonicalize(fromcode1, sizeof(fromcode1), tmp);
208     } else {
209         charset_canonicalize(fromcode1, sizeof(fromcode1), fromcode);
210     }
211
212     cd = iconv_open(tocode1, fromcode1);
213     if (cd != MUTT_ICONV_ERROR)
214         return cd;
215
216     {
217         const char *to = mutt_iconv_hook(tocode1);
218         const char *from = mutt_iconv_hook(fromcode1);
219
220         return to && from ? iconv_open(to, from) : MUTT_ICONV_ERROR;
221     }
222 }
223
224
225 /* Like iconv, but keeps going even when the input is invalid
226    If you're supplying inrepls, the source charset should be stateless;
227    if you're supplying an outrepl, the target charset should be.  */
228 /* XXX: MC: I do not understand what it does yet */
229 ssize_t mutt_iconv(iconv_t cd,
230                    const char **inbuf, ssize_t *inbytesleft,
231                    char **outbuf, ssize_t *outbytesleft,
232                    const char **inrepls, const char *outrepl)
233 {
234     ssize_t ret = 0, ret1;
235     const char *ib = *inbuf;
236     ssize_t ibl = *inbytesleft;
237     char *ob = *outbuf;
238     ssize_t obl = *outbytesleft;
239
240     for (;;) {
241         ret1 = my_iconv(cd, &ib, &ibl, &ob, &obl);
242         if (ret1 != -1)
243             ret += ret1;
244
245         if (ibl && obl && errno == EILSEQ) {
246             if (inrepls) {
247                 /* Try replacing the input */
248                 const char **t;
249
250                 for (t = inrepls; *t; t++) {
251                     const char *ib1 = *t;
252                     ssize_t ibl1 = m_strlen(*t);
253                     char *ob1 = ob;
254                     ssize_t obl1 = obl;
255
256                     my_iconv(cd, &ib1, &ibl1, &ob1, &obl1);
257                     if (!ibl1) {
258                         ++ib, --ibl;
259                         ob = ob1, obl = obl1;
260                         ++ret;
261                         break;
262                     }
263                 }
264                 if (*t)
265                     continue;
266             }
267             /* Replace the output */
268             if (!outrepl)
269                 outrepl = "?";
270             my_iconv(cd, 0, 0, &ob, &obl);
271             if (obl) {
272                 ssize_t n = m_strlen(outrepl);
273
274                 if (n > obl) {
275                     outrepl = "?";
276                     n = 1;
277                 }
278                 memcpy(ob, outrepl, n);
279                 ++ib, --ibl;
280                 ob += n, obl -= n;
281                 ++ret;
282                 my_iconv(cd, 0, 0, 0, 0); /* for good measure */
283                 continue;
284             }
285         }
286         *inbuf = ib, *inbytesleft = ibl;
287         *outbuf = ob, *outbytesleft = obl;
288         return ret;
289     }
290 }
291
292 /* Convert a string */
293 int
294 mutt_convert_string(char **ps, const char *from, const char *to, int flags)
295 {
296     iconv_t cd;
297     const char *repls[] = { "\357\277\275", "?", 0 };
298
299     if (m_strisempty(*ps))
300         return 0;
301
302     cd = mutt_iconv_open(to, from, flags);
303     if (cd != MUTT_ICONV_ERROR) {
304         const char **inrepls = NULL;
305         const char *outrepl = NULL;
306         const char *ib;
307         char *buf, *ob;
308         ssize_t ibl, obl;
309
310         if (charset_is_utf8(to))
311             outrepl = "\357\277\275";
312         else
313         if (charset_is_utf8(from))
314             inrepls = repls;
315         else
316             outrepl = "?";
317
318         ibl = m_strlen(*ps) + 1;
319         ib  = *ps;
320
321         obl = MB_LEN_MAX * ibl;
322         ob  = buf = p_new(char, obl + 1);
323
324         mutt_iconv(cd, &ib, &ibl, &ob, &obl, inrepls, outrepl);
325         iconv_close(cd);
326
327         *ob = '\0';
328
329         p_delete(ps);
330         *ps = buf;
331         return 0;
332     }
333
334     return -1;
335 }
336
337 static ssize_t convert_string(const char *f, ssize_t flen,
338                               const char *from, const char *to,
339                               char **t, ssize_t * tlen)
340 {
341     iconv_t cd;
342     char *buf, *ob;
343     ssize_t obl;
344     ssize_t n;
345     int e;
346
347     if ((cd = mutt_iconv_open(to, from, 0)) == MUTT_ICONV_ERROR)
348         return -1;
349
350     obl = 4 * flen + 1;
351     ob  = buf = p_new(char, obl);
352     n   = my_iconv(cd, &f, &flen, &ob, &obl);
353
354     if (n < 0 || my_iconv(cd, 0, 0, &ob, &obl) < 0) {
355         e = errno;
356         p_delete(&buf);
357         iconv_close(cd);
358         errno = e;
359         return -1;
360     }
361
362     *ob   = '\0';
363     *tlen = ob - buf;
364     *t    = buf;
365     iconv_close(cd);
366     return n;
367 }
368
369 int mutt_convert_nonmime_string(char **ps)
370 {
371     const char *p = MCharset.assumed_charset;
372     ssize_t ulen = m_strlen(*ps);
373     char *u = *ps;
374
375     while (*p) {
376         const char *q;
377         char fromcode[LONG_STRING], *s = NULL;
378         ssize_t slen;
379
380         if (!ulen)
381             return 0;
382
383         while (*p == ':')
384             *p++;
385
386         q = m_strchrnul(p, ':');
387         m_strncpy(fromcode, sizeof(fromcode), p, q - p);
388         p = q;
389
390         if (convert_string(u, ulen, fromcode, MCharset.charset, &s, &slen) >= 0) {
391             p_delete(ps);
392             *ps = s;
393             return 0;
394         }
395     }
396
397     return -1;
398 }
399
400 /****************************************************************************/
401 /* fgetconv functions                                                       */
402 /****************************************************************************/
403
404 /* fgetconv_t stuff for converting a file while reading it
405    Used in sendlib.c for converting from mutt's charset */
406
407 struct fgetconv_t {
408     FILE *file;
409     iconv_t cd;
410     char bufi[BUFSIZ];
411     char bufo[BUFSIZ];
412     char *p;
413     char *ob;
414     char *ib;
415     ssize_t ibl;
416     const char **inrepls;
417 };
418
419 fgetconv_t *
420 fgetconv_open(FILE *file, const char *from, const char *to, int flags)
421 {
422     static const char *repls[] = { "\357\277\275", "?", 0 };
423
424     struct fgetconv_t *fc = p_new(struct fgetconv_t, 1);
425
426     fc->file = file;
427     fc->cd   = MUTT_ICONV_ERROR;
428     if (from && to)
429         fc->cd = mutt_iconv_open(to, from, flags);
430
431     if (fc->cd != MUTT_ICONV_ERROR) {
432         fc->p  = fc->ob = fc->bufo;
433         fc->ib = fc->bufi;
434         fc->ibl = 0;
435         fc->inrepls = repls + charset_is_utf8(to);
436     }
437     return fc;
438 }
439
440 void fgetconv_close(fgetconv_t **fcp)
441 {
442     struct fgetconv_t *fc = *fcp;
443
444     if (fc->cd != MUTT_ICONV_ERROR)
445         iconv_close (fc->cd);
446     p_delete(fcp);
447 }
448
449
450 int fgetconv(fgetconv_t *fc)
451 {
452     if (!fc)
453         return EOF;
454
455     if (fc->cd == MUTT_ICONV_ERROR)
456         return fgetc(fc->file);
457
458     if (!fc->p)
459         return EOF;
460     if (fc->p < fc->ob)
461         return (unsigned char)*(fc->p)++;
462
463     /* Try to convert some more */
464     fc->p = fc->ob = fc->bufo;
465     if (fc->ibl) {
466         ssize_t obl = ssizeof(fc->bufo);
467
468         my_iconv(fc->cd, (const char **)&fc->ib, &fc->ibl, &fc->ob, &obl);
469         if (fc->p < fc->ob)
470             return (unsigned char)*(fc->p)++;
471     }
472
473     /* If we trusted iconv a bit more, we would at this point
474      * ask why it had stopped converting ... */
475
476     /* Try to read some more */
477     if (fc->ibl == sizeof(fc->bufi)
478     || (fc->ibl && fc->ib + fc->ibl < fc->bufi + sizeof(fc->bufi))) {
479         fc->p = NULL;
480         return EOF;
481     }
482
483     if (fc->ibl) {
484         memcpy(fc->bufi, fc->ib, fc->ibl);
485     }
486     fc->ib = fc->bufi;
487     fc->ibl += fread(fc->ib + fc->ibl, 1, sizeof(fc->bufi) - fc->ibl,
488                      fc->file);
489
490     /* Try harder this time to convert some */
491     if (fc->ibl) {
492         ssize_t obl = ssizeof(fc->bufo);
493
494         mutt_iconv(fc->cd, (const char **)&fc->ib, &fc->ibl, &fc->ob, &obl,
495                    fc->inrepls, 0);
496         if (fc->p < fc->ob) {
497             return (unsigned char)*(fc->p)++;
498         }
499     }
500
501     /* Either the file has finished or one of the buffers is too small */
502     fc->p = NULL;
503     return EOF;
504 }
505
506 char *fgetconvs(char *buf, ssize_t len, fgetconv_t *fc)
507 {
508     ssize_t pos = 0;
509
510     while (pos < len - 1) {
511         int c = fgetconv(fc);
512         if (c == EOF)
513             break;
514
515         buf[pos++] = c;
516         if (c == '\n')
517             break;
518     }
519     buf[pos] = '\0';
520
521     return pos ? buf : NULL;
522 }
523
524 /* vim:set ft=c: */