small touchups
[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],   to2[STRING];
213     char from1[STRING], from2[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     if (rx_list_match2(iconv_hooks, to1, to2, sizeof(to2))
235     &&  rx_list_match2(iconv_hooks, from1, from2, sizeof(from2)))
236         return iconv_open(to2, from2);
237
238     return MUTT_ICONV_ERROR;
239 }
240
241
242 /* Like iconv, but keeps going even when the input is invalid
243    If you're supplying inrepls, the source charset should be stateless;
244    if you're supplying an outrepl, the target charset should be.  */
245 /* XXX: MC: I do not understand what it does yet */
246 ssize_t mutt_iconv(iconv_t cd,
247                    const char **inbuf, ssize_t *inbytesleft,
248                    char **outbuf, ssize_t *outbytesleft,
249                    const char **inrepls, const char *outrepl)
250 {
251     ssize_t ret = 0, ret1;
252     const char *ib = *inbuf;
253     ssize_t ibl = *inbytesleft;
254     char *ob = *outbuf;
255     ssize_t obl = *outbytesleft;
256
257     for (;;) {
258         ret1 = my_iconv(cd, &ib, &ibl, &ob, &obl);
259         if (ret1 != -1)
260             ret += ret1;
261
262         if (ibl && obl && errno == EILSEQ) {
263             if (inrepls) {
264                 /* Try replacing the input */
265                 const char **t;
266
267                 for (t = inrepls; *t; t++) {
268                     const char *ib1 = *t;
269                     ssize_t ibl1 = m_strlen(*t);
270                     char *ob1 = ob;
271                     ssize_t obl1 = obl;
272
273                     my_iconv(cd, &ib1, &ibl1, &ob1, &obl1);
274                     if (!ibl1) {
275                         ++ib, --ibl;
276                         ob = ob1, obl = obl1;
277                         ++ret;
278                         break;
279                     }
280                 }
281                 if (*t)
282                     continue;
283             }
284             /* Replace the output */
285             if (!outrepl)
286                 outrepl = "?";
287             my_iconv(cd, 0, 0, &ob, &obl);
288             if (obl) {
289                 ssize_t n = m_strlen(outrepl);
290
291                 if (n > obl) {
292                     outrepl = "?";
293                     n = 1;
294                 }
295                 memcpy(ob, outrepl, n);
296                 ++ib, --ibl;
297                 ob += n, obl -= n;
298                 ++ret;
299                 my_iconv(cd, 0, 0, 0, 0); /* for good measure */
300                 continue;
301             }
302         }
303         *inbuf = ib, *inbytesleft = ibl;
304         *outbuf = ob, *outbytesleft = obl;
305         return ret;
306     }
307 }
308
309 /* Convert a string */
310 int
311 mutt_convert_string(char **ps, const char *from, const char *to, int flags)
312 {
313     iconv_t cd;
314     const char *repls[] = { "\357\277\275", "?", 0 };
315
316     if (m_strisempty(*ps))
317         return 0;
318
319     cd = mutt_iconv_open(to, from, flags);
320     if (cd != MUTT_ICONV_ERROR) {
321         const char **inrepls = NULL;
322         const char *outrepl = NULL;
323         const char *ib;
324         char *buf, *ob;
325         ssize_t ibl, obl;
326
327         if (charset_is_utf8(to))
328             outrepl = "\357\277\275";
329         else
330         if (charset_is_utf8(from))
331             inrepls = repls;
332         else
333             outrepl = "?";
334
335         ibl = m_strlen(*ps) + 1;
336         ib  = *ps;
337
338         obl = MB_LEN_MAX * ibl;
339         ob  = buf = p_new(char, obl + 1);
340
341         mutt_iconv(cd, &ib, &ibl, &ob, &obl, inrepls, outrepl);
342         iconv_close(cd);
343
344         *ob = '\0';
345
346         p_delete(ps);
347         *ps = buf;
348         return 0;
349     }
350
351     return -1;
352 }
353
354 static ssize_t convert_string(const char *f, ssize_t flen,
355                               const char *from, const char *to,
356                               char **t, ssize_t * tlen)
357 {
358     iconv_t cd;
359     char *buf, *ob;
360     ssize_t obl;
361     ssize_t n;
362     int e;
363
364     if ((cd = mutt_iconv_open(to, from, 0)) == MUTT_ICONV_ERROR)
365         return -1;
366
367     obl = 4 * flen + 1;
368     ob  = buf = p_new(char, obl);
369     n   = my_iconv(cd, &f, &flen, &ob, &obl);
370
371     if (n < 0 || my_iconv(cd, 0, 0, &ob, &obl) < 0) {
372         e = errno;
373         p_delete(&buf);
374         iconv_close(cd);
375         errno = e;
376         return -1;
377     }
378
379     *ob   = '\0';
380     *tlen = ob - buf;
381     *t    = buf;
382     iconv_close(cd);
383     return n;
384 }
385
386 int mutt_convert_nonmime_string(char **ps)
387 {
388     const char *p = MCharset.assumed_charset;
389     ssize_t ulen = m_strlen(*ps);
390     char *u = *ps;
391
392     while (*p) {
393         const char *q;
394         char fromcode[LONG_STRING], *s = NULL;
395         ssize_t slen;
396
397         if (!ulen)
398             return 0;
399
400         while (*p == ':')
401             p++;
402
403         q = m_strchrnul(p, ':');
404         m_strncpy(fromcode, sizeof(fromcode), p, q - p);
405         p = q;
406
407         if (convert_string(u, ulen, fromcode, MCharset.charset, &s, &slen) >= 0) {
408             p_delete(ps);
409             *ps = s;
410             return 0;
411         }
412     }
413
414     return -1;
415 }
416
417 /****************************************************************************/
418 /* fgetconv functions                                                       */
419 /****************************************************************************/
420
421 /* fgetconv_t stuff for converting a file while reading it
422    Used in sendlib.c for converting from mutt's charset */
423
424 struct fgetconv_t {
425     FILE *file;
426     iconv_t cd;
427     char bufi[BUFSIZ];
428     char bufo[BUFSIZ];
429     char *p;
430     char *ob;
431     char *ib;
432     ssize_t ibl;
433     const char **inrepls;
434 };
435
436 fgetconv_t *
437 fgetconv_open(FILE *file, const char *from, const char *to, int flags)
438 {
439     static const char *repls[] = { "\357\277\275", "?", 0 };
440
441     struct fgetconv_t *fc = p_new(struct fgetconv_t, 1);
442
443     fc->file = file;
444     fc->cd   = MUTT_ICONV_ERROR;
445     if (from && to)
446         fc->cd = mutt_iconv_open(to, from, flags);
447
448     if (fc->cd != MUTT_ICONV_ERROR) {
449         fc->p  = fc->ob = fc->bufo;
450         fc->ib = fc->bufi;
451         fc->ibl = 0;
452         fc->inrepls = repls + charset_is_utf8(to);
453     }
454     return fc;
455 }
456
457 void fgetconv_close(fgetconv_t **fcp)
458 {
459     struct fgetconv_t *fc = *fcp;
460
461     if (fc->cd != MUTT_ICONV_ERROR)
462         iconv_close (fc->cd);
463     p_delete(fcp);
464 }
465
466
467 int fgetconv(fgetconv_t *fc)
468 {
469     if (!fc)
470         return EOF;
471
472     if (fc->cd == MUTT_ICONV_ERROR)
473         return fgetc(fc->file);
474
475     if (!fc->p)
476         return EOF;
477     if (fc->p < fc->ob)
478         return (unsigned char)*(fc->p)++;
479
480     /* Try to convert some more */
481     fc->p = fc->ob = fc->bufo;
482     if (fc->ibl) {
483         ssize_t obl = ssizeof(fc->bufo);
484
485         my_iconv(fc->cd, (const char **)&fc->ib, &fc->ibl, &fc->ob, &obl);
486         if (fc->p < fc->ob)
487             return (unsigned char)*(fc->p)++;
488     }
489
490     /* If we trusted iconv a bit more, we would at this point
491      * ask why it had stopped converting ... */
492
493     /* Try to read some more */
494     if (fc->ibl == sizeof(fc->bufi)
495     || (fc->ibl && fc->ib + fc->ibl < fc->bufi + sizeof(fc->bufi))) {
496         fc->p = NULL;
497         return EOF;
498     }
499
500     if (fc->ibl) {
501         memcpy(fc->bufi, fc->ib, fc->ibl);
502     }
503     fc->ib = fc->bufi;
504     fc->ibl += fread(fc->ib + fc->ibl, 1, sizeof(fc->bufi) - fc->ibl,
505                      fc->file);
506
507     /* Try harder this time to convert some */
508     if (fc->ibl) {
509         ssize_t obl = ssizeof(fc->bufo);
510
511         mutt_iconv(fc->cd, (const char **)&fc->ib, &fc->ibl, &fc->ob, &obl,
512                    fc->inrepls, 0);
513         if (fc->p < fc->ob) {
514             return (unsigned char)*(fc->p)++;
515         }
516     }
517
518     /* Either the file has finished or one of the buffers is too small */
519     fc->p = NULL;
520     return EOF;
521 }
522
523 char *fgetconvs(char *buf, ssize_t len, fgetconv_t *fc)
524 {
525     ssize_t pos = 0;
526
527     while (pos < len - 1) {
528         int c = fgetconv(fc);
529         if (c == EOF)
530             break;
531
532         buf[pos++] = c;
533         if (c == '\n')
534             break;
535     }
536     buf[pos] = '\0';
537
538     return pos ? buf : NULL;
539 }
540
541 /* vim:set ft=c: */