89a2e5aa36491390cc2466aaeca5a3d221cc41c4
[claws.git] / src / codeconv.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <stdlib.h>
29 #include <errno.h>
30
31 #if HAVE_LOCALE_H
32 #  include <locale.h>
33 #endif
34
35 #include <iconv.h>
36
37 #include "codeconv.h"
38 #include "unmime.h"
39 #include "base64.h"
40 #include "quoted-printable.h"
41 #include "utils.h"
42 #include "prefs_common.h"
43
44 typedef enum
45 {
46         JIS_ASCII,
47         JIS_KANJI,
48         JIS_HWKANA,
49         JIS_AUXKANJI
50 } JISState;
51
52 #define SUBST_CHAR      0x5f;
53 #define ESC             '\033'
54
55 #define iseuckanji(c) \
56         (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xfe)
57 #define iseuchwkana1(c) \
58         (((c) & 0xff) == 0x8e)
59 #define iseuchwkana2(c) \
60         (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf)
61 #define iseucaux(c) \
62         (((c) & 0xff) == 0x8f)
63 #define issjiskanji1(c) \
64         ((((c) & 0xff) >= 0x81 && ((c) & 0xff) <= 0x9f) || \
65          (((c) & 0xff) >= 0xe0 && ((c) & 0xff) <= 0xfc))
66 #define issjiskanji2(c) \
67         ((((c) & 0xff) >= 0x40 && ((c) & 0xff) <= 0x7e) || \
68          (((c) & 0xff) >= 0x80 && ((c) & 0xff) <= 0xfc))
69 #define issjishwkana(c) \
70         (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf)
71
72 #define K_IN()                          \
73         if (state != JIS_KANJI) {       \
74                 *out++ = ESC;           \
75                 *out++ = '$';           \
76                 *out++ = 'B';           \
77                 state = JIS_KANJI;      \
78         }
79
80 #define K_OUT()                         \
81         if (state != JIS_ASCII) {       \
82                 *out++ = ESC;           \
83                 *out++ = '(';           \
84                 *out++ = 'B';           \
85                 state = JIS_ASCII;      \
86         }
87
88 #define HW_IN()                         \
89         if (state != JIS_HWKANA) {      \
90                 *out++ = ESC;           \
91                 *out++ = '(';           \
92                 *out++ = 'I';           \
93                 state = JIS_HWKANA;     \
94         }
95
96 #define AUX_IN()                        \
97         if (state != JIS_AUXKANJI) {    \
98                 *out++ = ESC;           \
99                 *out++ = '$';           \
100                 *out++ = '(';           \
101                 *out++ = 'D';           \
102                 state = JIS_AUXKANJI;   \
103         }
104
105 static void conv_jistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf);
106 static void conv_euctojis(gchar *outbuf, gint outlen, const gchar *inbuf);
107 static void conv_sjistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf);
108
109 static void conv_jistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf);
110 static void conv_sjistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf);
111 static void conv_euctoutf8(gchar *outbuf, gint outlen, const gchar *inbuf);
112 static void conv_anytoutf8(gchar *outbuf, gint outlen, const gchar *inbuf);
113
114 static void conv_utf8toeuc(gchar *outbuf, gint outlen, const gchar *inbuf);
115 static void conv_utf8tojis(gchar *outbuf, gint outlen, const gchar *inbuf);
116
117 static void conv_unreadable_eucjp(gchar *str);
118 static void conv_unreadable_8bit(gchar *str);
119 static void conv_unreadable_latin(gchar *str);
120
121 static void conv_jistodisp(gchar *outbuf, gint outlen, const gchar *inbuf);
122 static void conv_sjistodisp(gchar *outbuf, gint outlen, const gchar *inbuf);
123 static void conv_euctodisp(gchar *outbuf, gint outlen, const gchar *inbuf);
124
125 static void conv_anytodisp(gchar *outbuf, gint outlen, const gchar *inbuf);
126 static void conv_ustodisp(gchar *outbuf, gint outlen, const gchar *inbuf);
127 static void conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf);
128
129 static void conv_jistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf)
130 {
131         const guchar *in = inbuf;
132         guchar *out = outbuf;
133         JISState state = JIS_ASCII;
134
135         while (*in != '\0') {
136                 if (*in == ESC) {
137                         in++;
138                         if (*in == '$') {
139                                 if (*(in + 1) == '@' || *(in + 1) == 'B') {
140                                         state = JIS_KANJI;
141                                         in += 2;
142                                 } else if (*(in + 1) == '(' &&
143                                            *(in + 2) == 'D') {
144                                         state = JIS_AUXKANJI;
145                                         in += 3;
146                                 } else {
147                                         /* unknown escape sequence */
148                                         state = JIS_ASCII;
149                                 }
150                         } else if (*in == '(') {
151                                 if (*(in + 1) == 'B' || *(in + 1) == 'J') {
152                                         state = JIS_ASCII;
153                                         in += 2;
154                                 } else if (*(in + 1) == 'I') {
155                                         state = JIS_HWKANA;
156                                         in += 2;
157                                 } else {
158                                         /* unknown escape sequence */
159                                         state = JIS_ASCII;
160                                 }
161                         } else {
162                                 /* unknown escape sequence */
163                                 state = JIS_ASCII;
164                         }
165                 } else if (*in == 0x0e) {
166                         state = JIS_HWKANA;
167                         in++;
168                 } else if (*in == 0x0f) {
169                         state = JIS_ASCII;
170                         in++;
171                 } else {
172                         switch (state) {
173                         case JIS_ASCII:
174                                 *out++ = *in++;
175                                 break;
176                         case JIS_KANJI:
177                                 *out++ = *in++ | 0x80;
178                                 if (*in == '\0') break;
179                                 *out++ = *in++ | 0x80;
180                                 break;
181                         case JIS_HWKANA:
182                                 *out++ = 0x8e;
183                                 *out++ = *in++ | 0x80;
184                                 break;
185                         case JIS_AUXKANJI:
186                                 *out++ = 0x8f;
187                                 *out++ = *in++ | 0x80;
188                                 if (*in == '\0') break;
189                                 *out++ = *in++ | 0x80;
190                                 break;
191                         }
192                 }
193         }
194
195         *out = '\0';
196 }
197
198 #define JIS_HWDAKUTEN           0x5e
199 #define JIS_HWHANDAKUTEN        0x5f
200
201 static gint conv_jis_hantozen(guchar *outbuf, guchar jis_code, guchar sound_sym)
202 {
203         static guint16 h2z_tbl[] = {
204                 /* 0x20 - 0x2f */
205                 0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521,
206                 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543,
207                 /* 0x30 - 0x3f */
208                 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d,
209                 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d,
210                 /* 0x40 - 0x4f */
211                 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c,
212                 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e,
213                 /* 0x50 - 0x5f */
214                 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569,
215                 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c
216         };
217
218         static guint16 dakuten_tbl[] = {
219                 /* 0x30 - 0x3f */
220                 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x252c, 0x252e,
221                 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e,
222                 /* 0x40 - 0x4f */
223                 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x0000, 0x0000, 0x0000,
224                 0x0000, 0x0000, 0x2550, 0x2553, 0x2556, 0x2559, 0x255c, 0x0000
225         };
226
227         static guint16 handakuten_tbl[] = {
228                 /* 0x4a - 0x4e */
229                 0x2551, 0x2554, 0x2557, 0x255a, 0x255d
230         };
231
232         guint16 out_code;
233
234         jis_code &= 0x7f;
235         sound_sym &= 0x7f;
236
237         if (jis_code < 0x21 || jis_code > 0x5f)
238                 return 0;
239
240         if (sound_sym == JIS_HWDAKUTEN &&
241             jis_code >= 0x36 && jis_code <= 0x4e) {
242                 out_code = dakuten_tbl[jis_code - 0x30];
243                 if (out_code != 0) {
244                         *outbuf = out_code >> 8;
245                         *(outbuf + 1) = out_code & 0xff;
246                         return 2;
247                 }
248         }
249
250         if (sound_sym == JIS_HWHANDAKUTEN &&
251             jis_code >= 0x4a && jis_code <= 0x4e) {
252                 out_code = handakuten_tbl[jis_code - 0x4a];
253                 *outbuf = out_code >> 8;
254                 *(outbuf + 1) = out_code & 0xff;
255                 return 2;
256         }
257
258         out_code = h2z_tbl[jis_code - 0x20];
259         *outbuf = out_code >> 8;
260         *(outbuf + 1) = out_code & 0xff;
261         return 1;
262 }
263
264 static void conv_euctojis(gchar *outbuf, gint outlen, const gchar *inbuf)
265 {
266         const guchar *in = inbuf;
267         guchar *out = outbuf;
268         JISState state = JIS_ASCII;
269
270         while (*in != '\0') {
271                 if (IS_ASCII(*in)) {
272                         K_OUT();
273                         *out++ = *in++;
274                 } else if (iseuckanji(*in)) {
275                         if (iseuckanji(*(in + 1))) {
276                                 K_IN();
277                                 *out++ = *in++ & 0x7f;
278                                 *out++ = *in++ & 0x7f;
279                         } else {
280                                 K_OUT();
281                                 *out++ = SUBST_CHAR;
282                                 in++;
283                                 if (*in != '\0' && !IS_ASCII(*in)) {
284                                         *out++ = SUBST_CHAR;
285                                         in++;
286                                 }
287                         }
288                 } else if (iseuchwkana1(*in)) {
289                         if (iseuchwkana2(*(in + 1))) {
290                                 if (prefs_common.allow_jisx0201_kana) {
291                                         HW_IN();
292                                         in++;
293                                         *out++ = *in++ & 0x7f;
294                                 } else {
295                                         guchar jis_ch[2];
296                                         gint len;
297
298                                         if (iseuchwkana1(*(in + 2)) &&
299                                             iseuchwkana2(*(in + 3)))
300                                                 len = conv_jis_hantozen
301                                                         (jis_ch,
302                                                          *(in + 1), *(in + 3));
303                                         else
304                                                 len = conv_jis_hantozen
305                                                         (jis_ch,
306                                                          *(in + 1), '\0');
307                                         if (len == 0)
308                                                 in += 2;
309                                         else {
310                                                 K_IN();
311                                                 in += len * 2;
312                                                 *out++ = jis_ch[0];
313                                                 *out++ = jis_ch[1];
314                                         }
315                                 }
316                         } else {
317                                 K_OUT();
318                                 in++;
319                                 if (*in != '\0' && !IS_ASCII(*in)) {
320                                         *out++ = SUBST_CHAR;
321                                         in++;
322                                 }
323                         }
324                 } else if (iseucaux(*in)) {
325                         in++;
326                         if (iseuckanji(*in) && iseuckanji(*(in + 1))) {
327                                 AUX_IN();
328                                 *out++ = *in++ & 0x7f;
329                                 *out++ = *in++ & 0x7f;
330                         } else {
331                                 K_OUT();
332                                 if (*in != '\0' && !IS_ASCII(*in)) {
333                                         *out++ = SUBST_CHAR;
334                                         in++;
335                                         if (*in != '\0' && !IS_ASCII(*in)) {
336                                                 *out++ = SUBST_CHAR;
337                                                 in++;
338                                         }
339                                 }
340                         }
341                 } else {
342                         K_OUT();
343                         *out++ = SUBST_CHAR;
344                         in++;
345                 }
346         }
347
348         K_OUT();
349         *out = '\0';
350 }
351
352 static void conv_sjistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf)
353 {
354         const guchar *in = inbuf;
355         guchar *out = outbuf;
356
357         while (*in != '\0') {
358                 if (IS_ASCII(*in)) {
359                         *out++ = *in++;
360                 } else if (issjiskanji1(*in)) {
361                         if (issjiskanji2(*(in + 1))) {
362                                 guchar out1 = *in;
363                                 guchar out2 = *(in + 1);
364                                 guchar row;
365
366                                 row = out1 < 0xa0 ? 0x70 : 0xb0;
367                                 if (out2 < 0x9f) {
368                                         out1 = (out1 - row) * 2 - 1;
369                                         out2 -= out2 > 0x7f ? 0x20 : 0x1f;
370                                 } else {
371                                         out1 = (out1 - row) * 2;
372                                         out2 -= 0x7e;
373                                 }
374
375                                 *out++ = out1 | 0x80;
376                                 *out++ = out2 | 0x80;
377                                 in += 2;
378                         } else {
379                                 *out++ = SUBST_CHAR;
380                                 in++;
381                                 if (*in != '\0' && !IS_ASCII(*in)) {
382                                         *out++ = SUBST_CHAR;
383                                         in++;
384                                 }
385                         }
386                 } else if (issjishwkana(*in)) {
387                         *out++ = 0x8e;
388                         *out++ = *in++;
389                 } else {
390                         *out++ = SUBST_CHAR;
391                         in++;
392                 }
393         }
394
395         *out = '\0';
396 }
397
398 static void conv_jistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf)
399 {
400         gchar *eucstr;
401
402         Xalloca(eucstr, outlen, return);
403
404         conv_jistoeuc(eucstr, outlen, inbuf);
405         conv_euctoutf8(outbuf, outlen, eucstr);
406 }
407
408 static void conv_sjistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf)
409 {
410         gchar *tmpstr;
411
412         tmpstr = conv_iconv_strdup(inbuf, CS_SHIFT_JIS, CS_UTF_8);
413         if (tmpstr) {
414                 strncpy2(outbuf, tmpstr, outlen);
415                 g_free(tmpstr);
416         } else
417                 strncpy2(outbuf, inbuf, outlen);
418 }
419
420 static void conv_euctoutf8(gchar *outbuf, gint outlen, const gchar *inbuf)
421 {
422         static iconv_t cd = (iconv_t)-1;
423         static gboolean iconv_ok = TRUE;
424         gchar *tmpstr;
425
426         if (cd == (iconv_t)-1) {
427                 if (!iconv_ok) {
428                         strncpy2(outbuf, inbuf, outlen);
429                         return;
430                 }
431                 cd = iconv_open(CS_UTF_8, CS_EUC_JP_MS);
432                 if (cd == (iconv_t)-1) {
433                         cd = iconv_open(CS_UTF_8, CS_EUC_JP);
434                         if (cd == (iconv_t)-1) {
435                                 g_warning("conv_euctoutf8(): %s\n",
436                                           g_strerror(errno));
437                                 iconv_ok = FALSE;
438                                 strncpy2(outbuf, inbuf, outlen);
439                                 return;
440                         }
441                 }
442         }
443
444         tmpstr = conv_iconv_strdup_with_cd(inbuf, cd);
445         if (tmpstr) {
446                 strncpy2(outbuf, tmpstr, outlen);
447                 g_free(tmpstr);
448         } else
449                 strncpy2(outbuf, inbuf, outlen);
450 }
451
452 static void conv_anytoutf8(gchar *outbuf, gint outlen, const gchar *inbuf)
453 {
454         switch (conv_guess_ja_encoding(inbuf)) {
455         case C_ISO_2022_JP:
456                 conv_jistoutf8(outbuf, outlen, inbuf);
457                 break;
458         case C_SHIFT_JIS:
459                 conv_sjistoutf8(outbuf, outlen, inbuf);
460                 break;
461         case C_EUC_JP:
462                 conv_euctoutf8(outbuf, outlen, inbuf);
463                 break;
464         default:
465                 strncpy2(outbuf, inbuf, outlen);
466                 break;
467         }
468 }
469
470 static void conv_utf8toeuc(gchar *outbuf, gint outlen, const gchar *inbuf)
471 {
472         static iconv_t cd = (iconv_t)-1;
473         static gboolean iconv_ok = TRUE;
474         gchar *tmpstr;
475
476         if (cd == (iconv_t)-1) {
477                 if (!iconv_ok) {
478                         strncpy2(outbuf, inbuf, outlen);
479                         return;
480                 }
481                 cd = iconv_open(CS_EUC_JP_MS, CS_UTF_8);
482                 if (cd == (iconv_t)-1) {
483                         cd = iconv_open(CS_EUC_JP, CS_UTF_8);
484                         if (cd == (iconv_t)-1) {
485                                 g_warning("conv_utf8toeuc(): %s\n",
486                                           g_strerror(errno));
487                                 iconv_ok = FALSE;
488                                 strncpy2(outbuf, inbuf, outlen);
489                                 return;
490                         }
491                 }
492         }
493
494         tmpstr = conv_iconv_strdup_with_cd(inbuf, cd);
495         if (tmpstr) {
496                 strncpy2(outbuf, tmpstr, outlen);
497                 g_free(tmpstr);
498         } else
499                 strncpy2(outbuf, inbuf, outlen);
500 }
501
502 static void conv_utf8tojis(gchar *outbuf, gint outlen, const gchar *inbuf)
503 {
504         gchar *eucstr;
505
506         Xalloca(eucstr, outlen, return);
507
508         conv_utf8toeuc(eucstr, outlen, inbuf);
509         conv_euctojis(outbuf, outlen, eucstr);
510 }
511
512 static gchar valid_eucjp_tbl[][96] = {
513         /* 0xa2a0 - 0xa2ff */
514         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 0,
515           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 1, 1, 1, 1, 1, 1,
516           1, 1, 0, 0, 0, 0, 0, 0,  0, 0, 1, 1, 1, 1, 1, 1,
517           1, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 1, 1, 1, 1,
518           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 0, 0, 0, 0, 0,
519           0, 0, 1, 1, 1, 1, 1, 1,  1, 1, 0, 0, 0, 0, 1, 0 },
520
521         /* 0xa3a0 - 0xa3ff */
522         { 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
523           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 0, 0, 0, 0, 0, 0,
524           0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
525           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 0, 0, 0, 0, 0,
526           0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
527           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 0, 0, 0, 0, 0 },
528
529         /* 0xa4a0 - 0xa4ff */
530         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
531           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
532           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
533           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
534           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
535           1, 1, 1, 1, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0 },
536
537         /* 0xa5a0 - 0xa5ff */
538         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
539           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
540           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
541           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
542           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
543           1, 1, 1, 1, 1, 1, 1, 0,  0, 0, 0, 0, 0, 0, 0, 0 },
544
545         /* 0xa6a0 - 0xa6ff */
546         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
547           1, 1, 1, 1, 1, 1, 1, 1,  1, 0, 0, 0, 0, 0, 0, 0,
548           0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
549           1, 1, 1, 1, 1, 1, 1, 1,  1, 0, 0, 0, 0, 0, 0, 0,
550           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
551           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0 },
552
553         /* 0xa7a0 - 0xa7ff */
554         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
555           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
556           1, 1, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
557           0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
558           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
559           1, 1, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0 },
560
561         /* 0xa8a0 - 0xa8ff */
562         { 0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
563           1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
564           1, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
565           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
566           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
567           0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0 }
568 };
569
570 static gboolean isprintableeuckanji(guchar c1, guchar c2)
571 {
572         if (c1 <= 0xa0 || c1 >= 0xf5)
573                 return FALSE;
574         if (c2 <= 0xa0 || c2 == 0xff)
575                 return FALSE;
576
577         if (c1 >= 0xa9 && c1 <= 0xaf)
578                 return FALSE;
579
580         if (c1 >= 0xa2 && c1 <= 0xa8)
581                 return (gboolean)valid_eucjp_tbl[c1 - 0xa2][c2 - 0xa0];
582
583         if (c1 == 0xcf) {
584                 if (c2 >= 0xd4 && c2 <= 0xfe)
585                         return FALSE;
586         } else if (c1 == 0xf4) {
587                 if (c2 >= 0xa7 && c2 <= 0xfe)
588                         return FALSE;
589         }
590
591         return TRUE;
592 }
593
594 static void conv_unreadable_eucjp(gchar *str)
595 {
596         register guchar *p = str;
597
598         while (*p != '\0') {
599                 if (IS_ASCII(*p)) {
600                         /* convert CR+LF -> LF */
601                         if (*p == '\r' && *(p + 1) == '\n')
602                                 memmove(p, p + 1, strlen(p));
603                         /* printable 7 bit code */
604                         p++;
605                 } else if (iseuckanji(*p)) {
606                         if (isprintableeuckanji(*p, *(p + 1))) {
607                                 /* printable euc-jp code */
608                                 p += 2;
609                         } else {
610                                 /* substitute unprintable code */
611                                 *p++ = SUBST_CHAR;
612                                 if (*p != '\0') {
613                                         if (IS_ASCII(*p))
614                                                 p++;
615                                         else
616                                                 *p++ = SUBST_CHAR;
617                                 }
618                         }
619                 } else if (iseuchwkana1(*p)) {
620                         if (iseuchwkana2(*(p + 1)))
621                                 /* euc-jp hankaku kana */
622                                 p += 2;
623                         else
624                                 *p++ = SUBST_CHAR;
625                 } else if (iseucaux(*p)) {
626                         if (iseuckanji(*(p + 1)) && iseuckanji(*(p + 2))) {
627                                 /* auxiliary kanji */
628                                 p += 3;
629                         } else
630                                 *p++ = SUBST_CHAR;
631                 } else
632                         /* substitute unprintable 1 byte code */
633                         *p++ = SUBST_CHAR;
634         }
635 }
636
637 static void conv_unreadable_8bit(gchar *str)
638 {
639         register guchar *p = str;
640
641         while (*p != '\0') {
642                 /* convert CR+LF -> LF */
643                 if (*p == '\r' && *(p + 1) == '\n')
644                         memmove(p, p + 1, strlen(p));
645                 else if (!IS_ASCII(*p)) *p = SUBST_CHAR;
646                 p++;
647         }
648 }
649
650 static void conv_unreadable_latin(gchar *str)
651 {
652         register guchar *p = str;
653
654         while (*p != '\0') {
655                 /* convert CR+LF -> LF */
656                 if (*p == '\r' && *(p + 1) == '\n')
657                         memmove(p, p + 1, strlen(p));
658                 else if ((*p & 0xff) >= 0x7f && (*p & 0xff) <= 0x9f)
659                         *p = SUBST_CHAR;
660                 p++;
661         }
662 }
663
664 #define NCV     '\0'
665
666 void conv_mb_alnum(gchar *str)
667 {
668         static guchar char_tbl[] = {
669                 /* 0xa0 - 0xaf */
670                 NCV, ' ', NCV, NCV, ',', '.', NCV, ':',
671                 ';', '?', '!', NCV, NCV, NCV, NCV, NCV,
672                 /* 0xb0 - 0xbf */
673                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
674                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
675                 /* 0xc0 - 0xcf */
676                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
677                 NCV, NCV, '(', ')', NCV, NCV, '[', ']',
678                 /* 0xd0 - 0xdf */
679                 '{', '}', NCV, NCV, NCV, NCV, NCV, NCV,
680                 NCV, NCV, NCV, NCV, '+', '-', NCV, NCV,
681                 /* 0xe0 - 0xef */
682                 NCV, '=', NCV, '<', '>', NCV, NCV, NCV,
683                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV
684         };
685
686         register guchar *p = str;
687         register gint len;
688
689         len = strlen(str);
690
691         while (len > 1) {
692                 if (*p == 0xa3) {
693                         register guchar ch = *(p + 1);
694
695                         if (ch >= 0xb0 && ch <= 0xfa) {
696                                 /* [a-zA-Z] */
697                                 *p = ch & 0x7f;
698                                 p++;
699                                 len--;
700                                 memmove(p, p + 1, len);
701                                 len--;
702                         } else  {
703                                 p += 2;
704                                 len -= 2;
705                         }
706                 } else if (*p == 0xa1) {
707                         register guchar ch = *(p + 1);
708
709                         if (ch >= 0xa0 && ch <= 0xef &&
710                             NCV != char_tbl[ch - 0xa0]) {
711                                 *p = char_tbl[ch - 0xa0];
712                                 p++;
713                                 len--;
714                                 memmove(p, p + 1, len);
715                                 len--;
716                         } else {
717                                 p += 2;
718                                 len -= 2;
719                         }
720                 } else if (iseuckanji(*p)) {
721                         p += 2;
722                         len -= 2;
723                 } else {
724                         p++;
725                         len--;
726                 }
727         }
728 }
729
730 CharSet conv_guess_ja_encoding(const gchar *str)
731 {
732         const guchar *p = str;
733         CharSet guessed = C_US_ASCII;
734
735         while (*p != '\0') {
736                 if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) {
737                         if (guessed == C_US_ASCII)
738                                 return C_ISO_2022_JP;
739                         p += 2;
740                 } else if (IS_ASCII(*p)) {
741                         p++;
742                 } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) {
743                         if (*p >= 0xfd && *p <= 0xfe)
744                                 return C_EUC_JP;
745                         else if (guessed == C_SHIFT_JIS) {
746                                 if ((issjiskanji1(*p) &&
747                                      issjiskanji2(*(p + 1))) ||
748                                     issjishwkana(*p))
749                                         guessed = C_SHIFT_JIS;
750                                 else
751                                         guessed = C_EUC_JP;
752                         } else
753                                 guessed = C_EUC_JP;
754                         p += 2;
755                 } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) {
756                         if (iseuchwkana1(*p) && iseuchwkana2(*(p + 1)))
757                                 guessed = C_SHIFT_JIS;
758                         else
759                                 return C_SHIFT_JIS;
760                         p += 2;
761                 } else if (issjishwkana(*p)) {
762                         guessed = C_SHIFT_JIS;
763                         p++;
764                 } else {
765                         p++;
766                 }
767         }
768
769         return guessed;
770 }
771
772 static void conv_jistodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
773 {
774         conv_jistoutf8(outbuf, outlen, inbuf);
775 }
776
777 static void conv_sjistodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
778 {
779         conv_sjistoutf8(outbuf, outlen, inbuf);
780 }
781
782 static void conv_euctodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
783 {
784         conv_euctoutf8(outbuf, outlen, inbuf);
785 }
786
787 void conv_utf8todisp(gchar *outbuf, gint outlen, const gchar *inbuf)
788 {
789         if (g_utf8_validate(inbuf, -1, NULL) == TRUE)
790                 strncpy2(outbuf, inbuf, outlen);
791         else
792                 conv_ustodisp(outbuf, outlen, inbuf);
793 }
794
795 static void conv_anytodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
796 {
797         conv_anytoutf8(outbuf, outlen, inbuf);
798         if (g_utf8_validate(outbuf, -1, NULL) != TRUE)
799                 conv_unreadable_8bit(outbuf);
800 }
801
802 static void conv_ustodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
803 {
804         strncpy2(outbuf, inbuf, outlen);
805         conv_unreadable_8bit(outbuf);
806 }
807
808 void conv_localetodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
809 {
810         gchar *tmpstr;
811
812         tmpstr = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(),
813                                    CS_INTERNAL);
814         if (tmpstr) {
815                 strncpy2(outbuf, tmpstr, outlen);
816                 g_free(tmpstr);
817         } else
818                 conv_utf8todisp(outbuf, outlen, inbuf);
819 }
820
821 static void conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf)
822 {
823         strncpy2(outbuf, inbuf, outlen);
824 }
825
826 CodeConverter *conv_code_converter_new(const gchar *src_charset)
827 {
828         CodeConverter *conv;
829
830         conv = g_new0(CodeConverter, 1);
831         conv->code_conv_func = conv_get_code_conv_func(src_charset, NULL);
832         conv->charset_str = g_strdup(src_charset);
833         conv->charset = conv_get_charset_from_str(src_charset);
834
835         return conv;
836 }
837
838 void conv_code_converter_destroy(CodeConverter *conv)
839 {
840         g_free(conv->charset_str);
841         g_free(conv);
842 }
843
844 gint conv_convert(CodeConverter *conv, gchar *outbuf, gint outlen,
845                   const gchar *inbuf)
846 {
847         if (conv->code_conv_func != conv_noconv)
848                 conv->code_conv_func(outbuf, outlen, inbuf);
849         else {
850                 gchar *str;
851
852                 str = conv_iconv_strdup(inbuf, conv->charset_str, NULL);
853                 if (!str)
854                         return -1;
855                 else {
856                         strncpy2(outbuf, str, outlen);
857                         g_free(str);
858                 }
859         }
860
861         return 0;
862 }
863
864 gchar *conv_codeset_strdup(const gchar *inbuf,
865                            const gchar *src_code, const gchar *dest_code)
866 {
867         gchar *buf;
868         size_t len;
869         CodeConvFunc conv_func;
870
871         conv_func = conv_get_code_conv_func(src_code, dest_code);
872         if (conv_func != conv_noconv) {
873                 len = (strlen(inbuf) + 1) * 3;
874                 buf = g_malloc(len);
875                 if (!buf) return NULL;
876
877                 conv_func(buf, len, inbuf);
878                 return g_realloc(buf, strlen(buf) + 1);
879         }
880
881         return conv_iconv_strdup(inbuf, src_code, dest_code);
882 }
883
884 CodeConvFunc conv_get_code_conv_func(const gchar *src_charset_str,
885                                      const gchar *dest_charset_str)
886 {
887         CodeConvFunc code_conv = conv_noconv;
888         CharSet src_charset;
889         CharSet dest_charset;
890
891         if (!src_charset_str)
892                 src_charset = conv_get_locale_charset();
893         else
894                 src_charset = conv_get_charset_from_str(src_charset_str);
895
896         /* auto detection mode */
897         if (!src_charset_str && !dest_charset_str) {
898                 if (src_charset == C_EUC_JP || src_charset == C_SHIFT_JIS)
899                         return conv_anytodisp;
900                 else
901                         return conv_noconv;
902         }
903
904         dest_charset = conv_get_charset_from_str(dest_charset_str);
905
906         if (dest_charset == C_US_ASCII)
907                 return conv_ustodisp;
908
909         switch (src_charset) {
910         case C_US_ASCII:
911         case C_ISO_8859_1:
912         case C_ISO_8859_2:
913         case C_ISO_8859_3:
914         case C_ISO_8859_4:
915         case C_ISO_8859_5:
916         case C_ISO_8859_6:
917         case C_ISO_8859_7:
918         case C_ISO_8859_8:
919         case C_ISO_8859_9:
920         case C_ISO_8859_10:
921         case C_ISO_8859_11:
922         case C_ISO_8859_13:
923         case C_ISO_8859_14:
924         case C_ISO_8859_15:
925                 break;
926         case C_ISO_2022_JP:
927         case C_ISO_2022_JP_2:
928         case C_ISO_2022_JP_3:
929                 if (dest_charset == C_AUTO)
930                         code_conv = conv_jistodisp;
931                 else if (dest_charset == C_EUC_JP)
932                         code_conv = conv_jistoeuc;
933                 else if (dest_charset == C_UTF_8)
934                         code_conv = conv_jistoutf8;
935                 break;
936         case C_SHIFT_JIS:
937                 if (dest_charset == C_AUTO)
938                         code_conv = conv_sjistodisp;
939                 else if (dest_charset == C_EUC_JP)
940                         code_conv = conv_sjistoeuc;
941                 else if (dest_charset == C_UTF_8)
942                         code_conv = conv_sjistoutf8;
943                 break;
944         case C_EUC_JP:
945                 if (dest_charset == C_AUTO)
946                         code_conv = conv_euctodisp;
947                 else if (dest_charset == C_ISO_2022_JP   ||
948                          dest_charset == C_ISO_2022_JP_2 ||
949                          dest_charset == C_ISO_2022_JP_3)
950                         code_conv = conv_euctojis;
951                 else if (dest_charset == C_UTF_8)
952                         code_conv = conv_euctoutf8;
953                 break;
954         case C_UTF_8:
955                 if (dest_charset == C_EUC_JP)
956                         code_conv = conv_utf8toeuc;
957                 else if (dest_charset == C_ISO_2022_JP   ||
958                          dest_charset == C_ISO_2022_JP_2 ||
959                          dest_charset == C_ISO_2022_JP_3)
960                         code_conv = conv_utf8tojis;
961                 break;
962         default:
963                 break;
964         }
965
966         return code_conv;
967 }
968
969 gchar *conv_iconv_strdup(const gchar *inbuf,
970                          const gchar *src_code, const gchar *dest_code)
971 {
972         iconv_t cd;
973         gchar *outbuf;
974
975         if (!src_code)
976                 src_code = conv_get_outgoing_charset_str();
977         if (!dest_code)
978                 dest_code = CS_INTERNAL;
979
980         /* don't convert if src and dest codeset are identical */
981         if (!strcasecmp(src_code, dest_code))
982                 return g_strdup(inbuf);
983
984         /* don't convert if current codeset is US-ASCII */
985         if (!strcasecmp(dest_code, CS_US_ASCII))
986                 return g_strdup(inbuf);
987
988         cd = iconv_open(dest_code, src_code);
989         if (cd == (iconv_t)-1)
990                 return NULL;
991
992         outbuf = conv_iconv_strdup_with_cd(inbuf, cd);
993
994         iconv_close(cd);
995
996         return outbuf;
997 }
998
999 gchar *conv_iconv_strdup_with_cd(const gchar *inbuf, iconv_t cd)
1000 {
1001         const gchar *inbuf_p;
1002         gchar *outbuf;
1003         gchar *outbuf_p;
1004         size_t in_size;
1005         size_t in_left;
1006         size_t out_size;
1007         size_t out_left;
1008         size_t n_conv;
1009         size_t len;
1010
1011         inbuf_p = inbuf;
1012         in_size = strlen(inbuf);
1013         in_left = in_size;
1014         out_size = (in_size + 1) * 2;
1015         outbuf = g_malloc(out_size);
1016         outbuf_p = outbuf;
1017         out_left = out_size;
1018
1019 #define EXPAND_BUF()                            \
1020 {                                               \
1021         len = outbuf_p - outbuf;                \
1022         out_size *= 2;                          \
1023         outbuf = g_realloc(outbuf, out_size);   \
1024         outbuf_p = outbuf + len;                \
1025         out_left = out_size - len;              \
1026 }
1027
1028         while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left,
1029                                &outbuf_p, &out_left)) == (size_t)-1) {
1030                 if (EILSEQ == errno) {
1031                         //g_print("iconv(): at %d: %s\n", in_size - in_left, g_strerror(errno));
1032                         inbuf_p++;
1033                         in_left--;
1034                         if (out_left == 0) {
1035                                 EXPAND_BUF();
1036                         }
1037                         *outbuf_p++ = SUBST_CHAR;
1038                         out_left--;
1039                 } else if (EINVAL == errno) {
1040                         break;
1041                 } else if (E2BIG == errno) {
1042                         EXPAND_BUF();
1043                 } else {
1044                         g_warning("conv_iconv_strdup(): %s\n",
1045                                   g_strerror(errno));
1046                         break;
1047                 }
1048         }
1049
1050         while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) ==
1051                (size_t)-1) {
1052                 if (E2BIG == errno) {
1053                         EXPAND_BUF();
1054                 } else {
1055                         g_warning("conv_iconv_strdup(): %s\n",
1056                                   g_strerror(errno));
1057                         break;
1058                 }
1059         }
1060
1061 #undef EXPAND_BUF
1062
1063         len = outbuf_p - outbuf;
1064         outbuf = g_realloc(outbuf, len + 1);
1065         outbuf[len] = '\0';
1066
1067         return outbuf;
1068 }
1069
1070 static const struct {
1071         CharSet charset;
1072         gchar *const name;
1073 } charsets[] = {
1074         {C_US_ASCII,            CS_US_ASCII},
1075         {C_US_ASCII,            CS_ANSI_X3_4_1968},
1076         {C_UTF_8,               CS_UTF_8},
1077         {C_UTF_7,               CS_UTF_7},
1078         {C_ISO_8859_1,          CS_ISO_8859_1},
1079         {C_ISO_8859_2,          CS_ISO_8859_2},
1080         {C_ISO_8859_3,          CS_ISO_8859_3},
1081         {C_ISO_8859_4,          CS_ISO_8859_4},
1082         {C_ISO_8859_5,          CS_ISO_8859_5},
1083         {C_ISO_8859_6,          CS_ISO_8859_6},
1084         {C_ISO_8859_7,          CS_ISO_8859_7},
1085         {C_ISO_8859_8,          CS_ISO_8859_8},
1086         {C_ISO_8859_9,          CS_ISO_8859_9},
1087         {C_ISO_8859_10,         CS_ISO_8859_10},
1088         {C_ISO_8859_11,         CS_ISO_8859_11},
1089         {C_ISO_8859_13,         CS_ISO_8859_13},
1090         {C_ISO_8859_14,         CS_ISO_8859_14},
1091         {C_ISO_8859_15,         CS_ISO_8859_15},
1092         {C_BALTIC,              CS_BALTIC},
1093         {C_CP1250,              CS_CP1250},
1094         {C_CP1251,              CS_CP1251},
1095         {C_CP1252,              CS_CP1252},
1096         {C_CP1253,              CS_CP1253},
1097         {C_CP1254,              CS_CP1254},
1098         {C_CP1255,              CS_CP1255},
1099         {C_CP1256,              CS_CP1256},
1100         {C_CP1257,              CS_CP1257},
1101         {C_CP1258,              CS_CP1258},
1102         {C_WINDOWS_1250,        CS_WINDOWS_1250},
1103         {C_WINDOWS_1251,        CS_WINDOWS_1251},
1104         {C_WINDOWS_1252,        CS_WINDOWS_1252},
1105         {C_WINDOWS_1253,        CS_WINDOWS_1253},
1106         {C_WINDOWS_1254,        CS_WINDOWS_1254},
1107         {C_WINDOWS_1255,        CS_WINDOWS_1255},
1108         {C_WINDOWS_1256,        CS_WINDOWS_1256},
1109         {C_WINDOWS_1257,        CS_WINDOWS_1257},
1110         {C_WINDOWS_1258,        CS_WINDOWS_1258},
1111         {C_KOI8_R,              CS_KOI8_R},
1112         {C_KOI8_T,              CS_KOI8_T},
1113         {C_KOI8_U,              CS_KOI8_U},
1114         {C_ISO_2022_JP,         CS_ISO_2022_JP},
1115         {C_ISO_2022_JP_2,       CS_ISO_2022_JP_2},
1116         {C_ISO_2022_JP_3,       CS_ISO_2022_JP_3},
1117         {C_EUC_JP,              CS_EUC_JP},
1118         {C_EUC_JP,              CS_EUCJP},
1119         {C_EUC_JP_MS,           CS_EUC_JP_MS},
1120         {C_SHIFT_JIS,           CS_SHIFT_JIS},
1121         {C_SHIFT_JIS,           CS_SHIFT__JIS},
1122         {C_SHIFT_JIS,           CS_SJIS},
1123         {C_ISO_2022_KR,         CS_ISO_2022_KR},
1124         {C_EUC_KR,              CS_EUC_KR},
1125         {C_ISO_2022_CN,         CS_ISO_2022_CN},
1126         {C_EUC_CN,              CS_EUC_CN},
1127         {C_GB2312,              CS_GB2312},
1128         {C_GBK,                 CS_GBK},
1129         {C_EUC_TW,              CS_EUC_TW},
1130         {C_BIG5,                CS_BIG5},
1131         {C_BIG5_HKSCS,          CS_BIG5_HKSCS},
1132         {C_TIS_620,             CS_TIS_620},
1133         {C_WINDOWS_874,         CS_WINDOWS_874},
1134         {C_GEORGIAN_PS,         CS_GEORGIAN_PS},
1135         {C_TCVN5712_1,          CS_TCVN5712_1},
1136 };
1137
1138 static const struct {
1139         gchar *const locale;
1140         CharSet charset;
1141         CharSet out_charset;
1142 } locale_table[] = {
1143         {"ja_JP.eucJP"  , C_EUC_JP      , C_ISO_2022_JP},
1144         {"ja_JP.EUC-JP" , C_EUC_JP      , C_ISO_2022_JP},
1145         {"ja_JP.EUC"    , C_EUC_JP      , C_ISO_2022_JP},
1146         {"ja_JP.ujis"   , C_EUC_JP      , C_ISO_2022_JP},
1147         {"ja_JP.SJIS"   , C_SHIFT_JIS   , C_ISO_2022_JP},
1148         {"ja_JP.JIS"    , C_ISO_2022_JP , C_ISO_2022_JP},
1149         {"ja_JP"        , C_EUC_JP      , C_ISO_2022_JP},
1150         {"ko_KR.EUC-KR" , C_EUC_KR      , C_EUC_KR},
1151         {"ko_KR"        , C_EUC_KR      , C_EUC_KR},
1152         {"zh_CN.GB2312" , C_GB2312      , C_GB2312},
1153         {"zh_CN.GBK"    , C_GBK         , C_GB2312},
1154         {"zh_CN"        , C_GB2312      , C_GB2312},
1155         {"zh_HK"        , C_BIG5_HKSCS  , C_BIG5_HKSCS},
1156         {"zh_TW.eucTW"  , C_EUC_TW      , C_BIG5},
1157         {"zh_TW.EUC-TW" , C_EUC_TW      , C_BIG5},
1158         {"zh_TW.Big5"   , C_BIG5        , C_BIG5},
1159         {"zh_TW"        , C_BIG5        , C_BIG5},
1160
1161         {"ru_RU.KOI8-R" , C_KOI8_R      , C_KOI8_R},
1162         {"ru_RU.KOI8R"  , C_KOI8_R      , C_KOI8_R},
1163         {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
1164         {"ru_RU"        , C_ISO_8859_5  , C_KOI8_R},
1165         {"tg_TJ"        , C_KOI8_T      , C_KOI8_T},
1166         {"ru_UA"        , C_KOI8_U      , C_KOI8_U},
1167         {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U},
1168         {"uk_UA"        , C_KOI8_U      , C_KOI8_U},
1169
1170         {"be_BY"        , C_WINDOWS_1251, C_WINDOWS_1251},
1171         {"bg_BG"        , C_WINDOWS_1251, C_WINDOWS_1251},
1172
1173         {"yi_US"        , C_WINDOWS_1255, C_WINDOWS_1255},
1174
1175         {"af_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1176         {"br_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1177         {"ca_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1178         {"da_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1179         {"de_AT"        , C_ISO_8859_1  , C_ISO_8859_1},
1180         {"de_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1181         {"de_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1182         {"de_DE"        , C_ISO_8859_1  , C_ISO_8859_1},
1183         {"de_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1184         {"en_AU"        , C_ISO_8859_1  , C_ISO_8859_1},
1185         {"en_BW"        , C_ISO_8859_1  , C_ISO_8859_1},
1186         {"en_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1187         {"en_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1188         {"en_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1189         {"en_HK"        , C_ISO_8859_1  , C_ISO_8859_1},
1190         {"en_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1191         {"en_NZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1192         {"en_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1193         {"en_SG"        , C_ISO_8859_1  , C_ISO_8859_1},
1194         {"en_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1195         {"en_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1196         {"en_ZW"        , C_ISO_8859_1  , C_ISO_8859_1},
1197         {"es_AR"        , C_ISO_8859_1  , C_ISO_8859_1},
1198         {"es_BO"        , C_ISO_8859_1  , C_ISO_8859_1},
1199         {"es_CL"        , C_ISO_8859_1  , C_ISO_8859_1},
1200         {"es_CO"        , C_ISO_8859_1  , C_ISO_8859_1},
1201         {"es_CR"        , C_ISO_8859_1  , C_ISO_8859_1},
1202         {"es_DO"        , C_ISO_8859_1  , C_ISO_8859_1},
1203         {"es_EC"        , C_ISO_8859_1  , C_ISO_8859_1},
1204         {"es_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1205         {"es_GT"        , C_ISO_8859_1  , C_ISO_8859_1},
1206         {"es_HN"        , C_ISO_8859_1  , C_ISO_8859_1},
1207         {"es_MX"        , C_ISO_8859_1  , C_ISO_8859_1},
1208         {"es_NI"        , C_ISO_8859_1  , C_ISO_8859_1},
1209         {"es_PA"        , C_ISO_8859_1  , C_ISO_8859_1},
1210         {"es_PE"        , C_ISO_8859_1  , C_ISO_8859_1},
1211         {"es_PR"        , C_ISO_8859_1  , C_ISO_8859_1},
1212         {"es_PY"        , C_ISO_8859_1  , C_ISO_8859_1},
1213         {"es_SV"        , C_ISO_8859_1  , C_ISO_8859_1},
1214         {"es_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1215         {"es_UY"        , C_ISO_8859_1  , C_ISO_8859_1},
1216         {"es_VE"        , C_ISO_8859_1  , C_ISO_8859_1},
1217         {"et_EE"        , C_ISO_8859_1  , C_ISO_8859_1},
1218         {"eu_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1219         {"fi_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1220         {"fo_FO"        , C_ISO_8859_1  , C_ISO_8859_1},
1221         {"fr_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1222         {"fr_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1223         {"fr_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1224         {"fr_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1225         {"fr_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1226         {"ga_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1227         {"gl_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1228         {"gv_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1229         {"id_ID"        , C_ISO_8859_1  , C_ISO_8859_1},
1230         {"is_IS"        , C_ISO_8859_1  , C_ISO_8859_1},
1231         {"it_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1232         {"it_IT"        , C_ISO_8859_1  , C_ISO_8859_1},
1233         {"kl_GL"        , C_ISO_8859_1  , C_ISO_8859_1},
1234         {"kw_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1235         {"ms_MY"        , C_ISO_8859_1  , C_ISO_8859_1},
1236         {"nl_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1237         {"nl_NL"        , C_ISO_8859_1  , C_ISO_8859_1},
1238         {"nn_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1239         {"no_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1240         {"oc_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1241         {"pt_BR"        , C_ISO_8859_1  , C_ISO_8859_1},
1242         {"pt_PT"        , C_ISO_8859_1  , C_ISO_8859_1},
1243         {"sq_AL"        , C_ISO_8859_1  , C_ISO_8859_1},
1244         {"sv_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1245         {"sv_SE"        , C_ISO_8859_1  , C_ISO_8859_1},
1246         {"tl_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1247         {"uz_UZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1248         {"wa_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1249
1250         {"bs_BA"        , C_ISO_8859_2  , C_ISO_8859_2},
1251         {"cs_CZ"        , C_ISO_8859_2  , C_ISO_8859_2},
1252         {"hr_HR"        , C_ISO_8859_2  , C_ISO_8859_2},
1253         {"hu_HU"        , C_ISO_8859_2  , C_ISO_8859_2},
1254         {"pl_PL"        , C_ISO_8859_2  , C_ISO_8859_2},
1255         {"ro_RO"        , C_ISO_8859_2  , C_ISO_8859_2},
1256         {"sk_SK"        , C_ISO_8859_2  , C_ISO_8859_2},
1257         {"sl_SI"        , C_ISO_8859_2  , C_ISO_8859_2},
1258
1259         {"sr_YU@cyrillic"       , C_ISO_8859_5  , C_ISO_8859_5},
1260         {"sr_YU"                , C_ISO_8859_2  , C_ISO_8859_2},
1261
1262         {"mt_MT"                , C_ISO_8859_3  , C_ISO_8859_3},
1263
1264         {"lt_LT.iso88594"       , C_ISO_8859_4  , C_ISO_8859_4},
1265         {"lt_LT.ISO8859-4"      , C_ISO_8859_4  , C_ISO_8859_4},
1266         {"lt_LT.ISO_8859-4"     , C_ISO_8859_4  , C_ISO_8859_4},
1267         {"lt_LT"                , C_ISO_8859_13 , C_ISO_8859_13},
1268
1269         {"mk_MK"        , C_ISO_8859_5  , C_ISO_8859_5},
1270
1271         {"ar_AE"        , C_ISO_8859_6  , C_ISO_8859_6},
1272         {"ar_BH"        , C_ISO_8859_6  , C_ISO_8859_6},
1273         {"ar_DZ"        , C_ISO_8859_6  , C_ISO_8859_6},
1274         {"ar_EG"        , C_ISO_8859_6  , C_ISO_8859_6},
1275         {"ar_IQ"        , C_ISO_8859_6  , C_ISO_8859_6},
1276         {"ar_JO"        , C_ISO_8859_6  , C_ISO_8859_6},
1277         {"ar_KW"        , C_ISO_8859_6  , C_ISO_8859_6},
1278         {"ar_LB"        , C_ISO_8859_6  , C_ISO_8859_6},
1279         {"ar_LY"        , C_ISO_8859_6  , C_ISO_8859_6},
1280         {"ar_MA"        , C_ISO_8859_6  , C_ISO_8859_6},
1281         {"ar_OM"        , C_ISO_8859_6  , C_ISO_8859_6},
1282         {"ar_QA"        , C_ISO_8859_6  , C_ISO_8859_6},
1283         {"ar_SA"        , C_ISO_8859_6  , C_ISO_8859_6},
1284         {"ar_SD"        , C_ISO_8859_6  , C_ISO_8859_6},
1285         {"ar_SY"        , C_ISO_8859_6  , C_ISO_8859_6},
1286         {"ar_TN"        , C_ISO_8859_6  , C_ISO_8859_6},
1287         {"ar_YE"        , C_ISO_8859_6  , C_ISO_8859_6},
1288
1289         {"el_GR"        , C_ISO_8859_7  , C_ISO_8859_7},
1290         {"he_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1291         {"iw_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1292         {"tr_TR"        , C_ISO_8859_9  , C_ISO_8859_9},
1293
1294         {"lv_LV"        , C_ISO_8859_13 , C_ISO_8859_13},
1295         {"mi_NZ"        , C_ISO_8859_13 , C_ISO_8859_13},
1296
1297         {"cy_GB"        , C_ISO_8859_14 , C_ISO_8859_14},
1298
1299         {"ar_IN"        , C_UTF_8       , C_UTF_8},
1300         {"en_IN"        , C_UTF_8       , C_UTF_8},
1301         {"se_NO"        , C_UTF_8       , C_UTF_8},
1302         {"ta_IN"        , C_UTF_8       , C_UTF_8},
1303         {"te_IN"        , C_UTF_8       , C_UTF_8},
1304         {"ur_PK"        , C_UTF_8       , C_UTF_8},
1305
1306         {"th_TH"        , C_TIS_620     , C_TIS_620},
1307         /* {"th_TH"     , C_WINDOWS_874}, */
1308         /* {"th_TH"     , C_ISO_8859_11}, */
1309
1310         {"ka_GE"        , C_GEORGIAN_PS , C_GEORGIAN_PS},
1311         {"vi_VN.TCVN"   , C_TCVN5712_1  , C_TCVN5712_1},
1312
1313         {"C"                    , C_US_ASCII    , C_US_ASCII},
1314         {"POSIX"                , C_US_ASCII    , C_US_ASCII},
1315         {"ANSI_X3.4-1968"       , C_US_ASCII    , C_US_ASCII},
1316 };
1317
1318 static GHashTable *conv_get_charset_to_str_table(void)
1319 {
1320         static GHashTable *table;
1321         gint i;
1322
1323         if (table)
1324                 return table;
1325
1326         table = g_hash_table_new(NULL, g_direct_equal);
1327
1328         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1329                 if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
1330                     == NULL) {
1331                         g_hash_table_insert
1332                                 (table, GUINT_TO_POINTER(charsets[i].charset),
1333                                  charsets[i].name);
1334                 }
1335         }
1336
1337         return table;
1338 }
1339
1340 static GHashTable *conv_get_charset_from_str_table(void)
1341 {
1342         static GHashTable *table;
1343         gint i;
1344
1345         if (table)
1346                 return table;
1347
1348         table = g_hash_table_new(str_case_hash, str_case_equal);
1349
1350         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1351                 g_hash_table_insert(table, charsets[i].name,
1352                                     GUINT_TO_POINTER(charsets[i].charset));
1353         }
1354
1355         return table;
1356 }
1357
1358 const gchar *conv_get_charset_str(CharSet charset)
1359 {
1360         GHashTable *table;
1361
1362         table = conv_get_charset_to_str_table();
1363         return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
1364 }
1365
1366 CharSet conv_get_charset_from_str(const gchar *charset)
1367 {
1368         GHashTable *table;
1369
1370         if (!charset) return C_AUTO;
1371
1372         table = conv_get_charset_from_str_table();
1373         return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset));
1374 }
1375
1376 CharSet conv_get_locale_charset(void)
1377 {
1378         static CharSet cur_charset = -1;
1379         const gchar *cur_locale;
1380         const gchar *p;
1381         gint i;
1382
1383         if (cur_charset != -1)
1384                 return cur_charset;
1385
1386         cur_locale = conv_get_current_locale();
1387         if (!cur_locale) {
1388                 cur_charset = C_US_ASCII;
1389                 return cur_charset;
1390         }
1391
1392         if (strcasestr(cur_locale, "UTF-8")) {
1393                 cur_charset = C_UTF_8;
1394                 return cur_charset;
1395         }
1396
1397         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1398                 cur_charset = C_ISO_8859_15;
1399                 return cur_charset;
1400         }
1401
1402         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1403                 const gchar *p;
1404
1405                 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1406                    "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1407                 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
1408                                  strlen(locale_table[i].locale))) {
1409                         cur_charset = locale_table[i].charset;
1410                         return cur_charset;
1411                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1412                          !strchr(p + 1, '.')) {
1413                         if (strlen(cur_locale) == 2 &&
1414                             !g_ascii_strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1415                                 cur_charset = locale_table[i].charset;
1416                                 return cur_charset;
1417                         }
1418                 }
1419         }
1420
1421         cur_charset = C_AUTO;
1422         return cur_charset;
1423 }
1424
1425 const gchar *conv_get_locale_charset_str(void)
1426 {
1427         static const gchar *codeset = NULL;
1428
1429         if (!codeset)
1430                 codeset = conv_get_charset_str(conv_get_locale_charset());
1431
1432         return codeset ? codeset : CS_INTERNAL;
1433 }
1434
1435 CharSet conv_get_internal_charset(void)
1436 {
1437         return C_INTERNAL;
1438 }
1439
1440 const gchar *conv_get_internal_charset_str(void)
1441 {
1442         return CS_INTERNAL;
1443 }
1444
1445 CharSet conv_get_outgoing_charset(void)
1446 {
1447         static CharSet out_charset = -1;
1448         const gchar *cur_locale;
1449         const gchar *p;
1450         gint i;
1451
1452         if (out_charset != -1)
1453                 return out_charset;
1454
1455         cur_locale = conv_get_current_locale();
1456         if (!cur_locale) {
1457                 out_charset = C_AUTO;
1458                 return out_charset;
1459         }
1460
1461         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1462                 out_charset = C_ISO_8859_15;
1463                 return out_charset;
1464         }
1465
1466         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1467                 const gchar *p;
1468
1469                 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
1470                                  strlen(locale_table[i].locale))) {
1471                         out_charset = locale_table[i].out_charset;
1472                         break;
1473                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1474                          !strchr(p + 1, '.')) {
1475                         if (strlen(cur_locale) == 2 &&
1476                             !g_ascii_strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1477                                 out_charset = locale_table[i].out_charset;
1478                                 break;
1479                         }
1480                 }
1481         }
1482
1483         return out_charset;
1484 }
1485
1486 const gchar *conv_get_outgoing_charset_str(void)
1487 {
1488         CharSet out_charset;
1489         const gchar *str;
1490
1491         if (prefs_common.outgoing_charset) {
1492                 if (!isalpha((guchar)prefs_common.outgoing_charset[0])) {
1493                         g_free(prefs_common.outgoing_charset);
1494                         prefs_common.outgoing_charset = g_strdup(CS_AUTO);
1495                 } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0)
1496                         return prefs_common.outgoing_charset;
1497         }
1498
1499         out_charset = conv_get_outgoing_charset();
1500         str = conv_get_charset_str(out_charset);
1501
1502         return str ? str : CS_UTF_8;
1503 }
1504
1505 gboolean conv_is_multibyte_encoding(CharSet encoding)
1506 {
1507         switch (encoding) {
1508         case C_EUC_JP:
1509         case C_EUC_JP_MS:
1510         case C_EUC_KR:
1511         case C_EUC_TW:
1512         case C_EUC_CN:
1513         case C_ISO_2022_JP:
1514         case C_ISO_2022_JP_2:
1515         case C_ISO_2022_JP_3:
1516         case C_ISO_2022_KR:
1517         case C_ISO_2022_CN:
1518         case C_SHIFT_JIS:
1519         case C_GB2312:
1520         case C_BIG5:
1521         case C_UTF_8:
1522         case C_UTF_7:
1523                 return TRUE;
1524         default:
1525                 return FALSE;
1526         }
1527 }
1528
1529 const gchar *conv_get_current_locale(void)
1530 {
1531         const gchar *cur_locale;
1532
1533         cur_locale = g_getenv("LC_ALL");
1534         if (!cur_locale) cur_locale = g_getenv("LC_CTYPE");
1535         if (!cur_locale) cur_locale = g_getenv("LANG");
1536         if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL);
1537
1538         debug_print("current locale: %s\n",
1539                     cur_locale ? cur_locale : "(none)");
1540
1541         return cur_locale;
1542 }
1543
1544 void conv_unmime_header_overwrite(gchar *str)
1545 {
1546         gchar *buf;
1547         gint buflen;
1548
1549         buflen = strlen(str) * 2 + 1;
1550         Xalloca(buf, buflen, return);
1551
1552         if (conv_get_locale_charset() == C_EUC_JP)
1553                 conv_anytodisp(buf, buflen, str);
1554         else
1555                 conv_localetodisp(buf, buflen, str);
1556
1557         unmime_header(str, buf);
1558 }
1559
1560 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1561                         const gchar *charset)
1562 {
1563         gchar *buf;
1564         gint buflen;
1565
1566         buflen = strlen(str) * 2 + 1;
1567         Xalloca(buf, buflen, return);
1568
1569         if (conv_get_locale_charset() == C_EUC_JP)
1570                 conv_anytodisp(buf, buflen, str);
1571         else
1572                 conv_localetodisp(buf, buflen, str);
1573
1574         unmime_header(outbuf, buf);
1575 }
1576
1577 #define MAX_LINELEN             76
1578 #define MAX_HARD_LINELEN        996
1579 #define MIMESEP_BEGIN           "=?"
1580 #define MIMESEP_END             "?="
1581
1582 #define LBREAK_IF_REQUIRED(cond, is_plain_text)                         \
1583 {                                                                       \
1584         if (len - (destp - (guchar *)dest) < MAX_LINELEN + 2) {         \
1585                 *destp = '\0';                                          \
1586                 return;                                                 \
1587         }                                                               \
1588                                                                         \
1589         if ((cond) && *srcp) {                                          \
1590                 if (destp > (guchar *)dest && left < MAX_LINELEN - 1) { \
1591                         if (isspace(*(destp - 1)))                      \
1592                                 destp--;                                \
1593                         else if (is_plain_text && isspace(*srcp))       \
1594                                 srcp++;                                 \
1595                         if (*srcp) {                                    \
1596                                 *destp++ = '\n';                        \
1597                                 *destp++ = ' ';                         \
1598                                 left = MAX_LINELEN - 1;                 \
1599                         }                                               \
1600                 }                                                       \
1601         }                                                               \
1602 }
1603
1604 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1605                         gint header_len, gboolean addr_field)
1606 {
1607         const gchar *cur_encoding;
1608         const gchar *out_encoding;
1609         gint mimestr_len;
1610         gchar *mimesep_enc;
1611         gint left;
1612         const guchar *srcp = src;
1613         guchar *destp = dest;
1614         gboolean use_base64;
1615
1616         g_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE);
1617
1618         if (MB_CUR_MAX > 1) {
1619                 use_base64 = TRUE;
1620                 mimesep_enc = "?B?";
1621         } else {
1622                 use_base64 = FALSE;
1623                 mimesep_enc = "?Q?";
1624         }
1625
1626         cur_encoding = CS_INTERNAL;
1627         out_encoding = conv_get_outgoing_charset_str();
1628         if (!strcmp(out_encoding, CS_US_ASCII))
1629                 out_encoding = CS_ISO_8859_1;
1630
1631         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1632                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1633
1634         left = MAX_LINELEN - header_len;
1635
1636         while (*srcp) {
1637                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1638
1639                 while (isspace(*srcp)) {
1640                         *destp++ = *srcp++;
1641                         left--;
1642                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1643                 }
1644
1645                 /* output as it is if the next word is ASCII string */
1646                 if (!is_next_nonascii(srcp)) {
1647                         gint word_len;
1648
1649                         word_len = get_next_word_len(srcp);
1650                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1651                         while (word_len > 0) {
1652                                 LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
1653                                 *destp++ = *srcp++;
1654                                 left--;
1655                                 word_len--;
1656                         }
1657
1658                         continue;
1659                 }
1660
1661                 /* don't include parentheses in encoded strings */
1662                 if (addr_field && (*srcp == '(' || *srcp == ')')) {
1663                         LBREAK_IF_REQUIRED(left < 2, FALSE);
1664                         *destp++ = *srcp++;
1665                         left--;
1666                 }
1667
1668                 while (1) {
1669                         gint mb_len = 0;
1670                         gint cur_len = 0;
1671                         gchar *part_str;
1672                         gchar *out_str;
1673                         gchar *enc_str;
1674                         const guchar *p = srcp;
1675                         gint out_str_len;
1676                         gint out_enc_str_len;
1677                         gint mime_block_len;
1678                         gboolean cont = FALSE;
1679
1680                         while (*p != '\0') {
1681                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1682                                         break;
1683                                 /* don't include parentheses in encoded
1684                                    strings */
1685                                 if (addr_field && (*p == '(' || *p == ')'))
1686                                         break;
1687
1688                                 mb_len = g_utf8_skip[*p];
1689
1690                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1691                                 out_str = conv_codeset_strdup
1692                                         (part_str, cur_encoding, out_encoding);
1693                                 if (!out_str) {
1694                                         g_warning("conv_encode_header(): code conversion failed\n");
1695                                         conv_unreadable_8bit(part_str);
1696                                         out_str = g_strdup(part_str);
1697                                 }
1698                                 out_str_len = strlen(out_str);
1699
1700                                 if (use_base64)
1701                                         out_enc_str_len = B64LEN(out_str_len);
1702                                 else
1703                                         out_enc_str_len =
1704                                                 qp_get_q_encoding_len(out_str);
1705
1706                                 g_free(out_str);
1707
1708                                 if (mimestr_len + out_enc_str_len <= left) {
1709                                         cur_len += mb_len;
1710                                         p += mb_len;
1711                                 } else if (cur_len == 0) {
1712                                         LBREAK_IF_REQUIRED(1, FALSE);
1713                                         continue;
1714                                 } else {
1715                                         cont = TRUE;
1716                                         break;
1717                                 }
1718                         }
1719
1720                         if (cur_len > 0) {
1721                                 Xstrndup_a(part_str, srcp, cur_len, );
1722                                 out_str = conv_codeset_strdup
1723                                         (part_str, cur_encoding, out_encoding);
1724                                 if (!out_str) {
1725                                         g_warning("conv_encode_header(): code conversion failed\n");
1726                                         conv_unreadable_8bit(part_str);
1727                                         out_str = g_strdup(part_str);
1728                                 }
1729                                 out_str_len = strlen(out_str);
1730
1731                                 if (use_base64)
1732                                         out_enc_str_len = B64LEN(out_str_len);
1733                                 else
1734                                         out_enc_str_len =
1735                                                 qp_get_q_encoding_len(out_str);
1736
1737                                 Xalloca(enc_str, out_enc_str_len + 1, );
1738                                 if (use_base64)
1739                                         base64_encode(enc_str, out_str, out_str_len);
1740                                 else
1741                                         qp_q_encode(enc_str, out_str);
1742
1743                                 g_free(out_str);
1744
1745                                 /* output MIME-encoded string block */
1746                                 mime_block_len = mimestr_len + strlen(enc_str);
1747                                 g_snprintf(destp, mime_block_len + 1,
1748                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1749                                            out_encoding, mimesep_enc, enc_str);
1750                                 destp += mime_block_len;
1751                                 srcp += cur_len;
1752
1753                                 left -= mime_block_len;
1754                         }
1755
1756                         LBREAK_IF_REQUIRED(cont, FALSE);
1757
1758                         if (cur_len == 0)
1759                                 break;
1760                 }
1761         }
1762
1763         *destp = '\0';
1764 }
1765
1766 #undef LBREAK_IF_REQUIRED
1767 gchar *conv_filename_from_utf8(const gchar *utf8_file)
1768 {
1769         gchar *fs_file;
1770         GError *error = NULL;
1771
1772         fs_file = g_filename_from_utf8(utf8_file, -1, NULL, NULL, &error);
1773         if (error) {
1774                 g_warning("failed to convert encoding of file name: %s\n",
1775                           error->message);
1776                 g_error_free(error);
1777         }
1778         if (!fs_file)
1779                 fs_file = g_strdup(utf8_file);
1780
1781         return fs_file;
1782 }
1783
1784 gchar *conv_filename_to_utf8(const gchar *fs_file)
1785 {
1786         gchar *utf8_file = NULL;
1787         GError *error = NULL;
1788
1789         utf8_file = g_filename_to_utf8(fs_file, -1, NULL, NULL, &error);
1790         if (error) {
1791                 g_warning("failed to convert encoding of file name: %s\n",
1792                           error->message);
1793                 g_error_free(error);
1794         }
1795
1796         if (!utf8_file || !g_utf8_validate(utf8_file, -1, NULL)) {
1797                 g_free(utf8_file);
1798                 utf8_file = g_strdup(fs_file);
1799                 conv_unreadable_8bit(utf8_file);
1800         }
1801
1802         return utf8_file;
1803 }