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