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