4633d369e84e5d97196be8a81095b09e335bb392
[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)
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 }
799
800 static void conv_ustodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
801 {
802         strncpy2(outbuf, inbuf, outlen);
803         conv_unreadable_8bit(outbuf);
804 }
805
806 void conv_localetodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
807 {
808         gchar *tmpstr;
809
810         tmpstr = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(),
811                                    CS_INTERNAL);
812         if (tmpstr) {
813                 strncpy2(outbuf, tmpstr, outlen);
814                 g_free(tmpstr);
815         } else
816                 strncpy2(outbuf, inbuf, outlen);
817 }
818
819 static void conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf)
820 {
821         strncpy2(outbuf, inbuf, outlen);
822 }
823
824 CodeConverter *conv_code_converter_new(const gchar *src_charset)
825 {
826         CodeConverter *conv;
827
828         conv = g_new0(CodeConverter, 1);
829         conv->code_conv_func = conv_get_code_conv_func(src_charset, NULL);
830         conv->charset_str = g_strdup(src_charset);
831         conv->charset = conv_get_charset_from_str(src_charset);
832
833         return conv;
834 }
835
836 void conv_code_converter_destroy(CodeConverter *conv)
837 {
838         g_free(conv->charset_str);
839         g_free(conv);
840 }
841
842 gint conv_convert(CodeConverter *conv, gchar *outbuf, gint outlen,
843                   const gchar *inbuf)
844 {
845         if (conv->code_conv_func != conv_noconv)
846                 conv->code_conv_func(outbuf, outlen, inbuf);
847         else {
848                 gchar *str;
849
850                 str = conv_iconv_strdup(inbuf, conv->charset_str, NULL);
851                 if (!str)
852                         return -1;
853                 else {
854                         strncpy2(outbuf, str, outlen);
855                         g_free(str);
856                 }
857         }
858
859         return 0;
860 }
861
862 gchar *conv_codeset_strdup(const gchar *inbuf,
863                            const gchar *src_code, const gchar *dest_code)
864 {
865         gchar *buf;
866         size_t len;
867         CodeConvFunc conv_func;
868
869         conv_func = conv_get_code_conv_func(src_code, dest_code);
870         if (conv_func != conv_noconv) {
871                 len = (strlen(inbuf) + 1) * 3;
872                 buf = g_malloc(len);
873                 if (!buf) return NULL;
874
875                 conv_func(buf, len, inbuf);
876                 return g_realloc(buf, strlen(buf) + 1);
877         }
878
879         return conv_iconv_strdup(inbuf, src_code, dest_code);
880 }
881
882 CodeConvFunc conv_get_code_conv_func(const gchar *src_charset_str,
883                                      const gchar *dest_charset_str)
884 {
885         CodeConvFunc code_conv = conv_noconv;
886         CharSet src_charset;
887         CharSet dest_charset;
888
889         if (!src_charset_str)
890                 src_charset = conv_get_locale_charset();
891         else
892                 src_charset = conv_get_charset_from_str(src_charset_str);
893
894         /* auto detection mode */
895         if (!src_charset_str && !dest_charset_str) {
896                 if (src_charset == C_EUC_JP || src_charset == C_SHIFT_JIS)
897                         return conv_anytodisp;
898                 else
899                         return conv_noconv;
900         }
901
902         dest_charset = conv_get_charset_from_str(dest_charset_str);
903
904         if (dest_charset == C_US_ASCII)
905                 return conv_ustodisp;
906
907         switch (src_charset) {
908         case C_US_ASCII:
909         case C_ISO_8859_1:
910         case C_ISO_8859_2:
911         case C_ISO_8859_3:
912         case C_ISO_8859_4:
913         case C_ISO_8859_5:
914         case C_ISO_8859_6:
915         case C_ISO_8859_7:
916         case C_ISO_8859_8:
917         case C_ISO_8859_9:
918         case C_ISO_8859_10:
919         case C_ISO_8859_11:
920         case C_ISO_8859_13:
921         case C_ISO_8859_14:
922         case C_ISO_8859_15:
923                 break;
924         case C_ISO_2022_JP:
925         case C_ISO_2022_JP_2:
926         case C_ISO_2022_JP_3:
927                 if (dest_charset == C_AUTO)
928                         code_conv = conv_jistodisp;
929                 else if (dest_charset == C_EUC_JP)
930                         code_conv = conv_jistoeuc;
931                 else if (dest_charset == C_UTF_8)
932                         code_conv = conv_jistoutf8;
933                 break;
934         case C_SHIFT_JIS:
935                 if (dest_charset == C_AUTO)
936                         code_conv = conv_sjistodisp;
937                 else if (dest_charset == C_EUC_JP)
938                         code_conv = conv_sjistoeuc;
939                 else if (dest_charset == C_UTF_8)
940                         code_conv = conv_sjistoutf8;
941                 break;
942         case C_EUC_JP:
943                 if (dest_charset == C_AUTO)
944                         code_conv = conv_euctodisp;
945                 else if (dest_charset == C_ISO_2022_JP   ||
946                          dest_charset == C_ISO_2022_JP_2 ||
947                          dest_charset == C_ISO_2022_JP_3)
948                         code_conv = conv_euctojis;
949                 else if (dest_charset == C_UTF_8)
950                         code_conv = conv_euctoutf8;
951                 break;
952         case C_UTF_8:
953                 if (dest_charset == C_EUC_JP)
954                         code_conv = conv_utf8toeuc;
955                 else if (dest_charset == C_ISO_2022_JP   ||
956                          dest_charset == C_ISO_2022_JP_2 ||
957                          dest_charset == C_ISO_2022_JP_3)
958                         code_conv = conv_utf8tojis;
959                 break;
960         default:
961                 break;
962         }
963
964         return code_conv;
965 }
966
967 gchar *conv_iconv_strdup(const gchar *inbuf,
968                          const gchar *src_code, const gchar *dest_code)
969 {
970         iconv_t cd;
971         gchar *outbuf;
972
973         if (!src_code)
974                 src_code = conv_get_outgoing_charset_str();
975         if (!dest_code)
976                 dest_code = CS_INTERNAL;
977
978         /* don't convert if src and dest codeset are identical */
979         if (!strcasecmp(src_code, dest_code))
980                 return g_strdup(inbuf);
981
982         /* don't convert if current codeset is US-ASCII */
983         if (!strcasecmp(dest_code, CS_US_ASCII))
984                 return g_strdup(inbuf);
985
986         cd = iconv_open(dest_code, src_code);
987         if (cd == (iconv_t)-1)
988                 return NULL;
989
990         outbuf = conv_iconv_strdup_with_cd(inbuf, cd);
991
992         iconv_close(cd);
993
994         return outbuf;
995 }
996
997 gchar *conv_iconv_strdup_with_cd(const gchar *inbuf, iconv_t cd)
998 {
999         const gchar *inbuf_p;
1000         gchar *outbuf;
1001         gchar *outbuf_p;
1002         size_t in_size;
1003         size_t in_left;
1004         size_t out_size;
1005         size_t out_left;
1006         size_t n_conv;
1007         size_t len;
1008
1009         inbuf_p = inbuf;
1010         in_size = strlen(inbuf);
1011         in_left = in_size;
1012         out_size = (in_size + 1) * 2;
1013         outbuf = g_malloc(out_size);
1014         outbuf_p = outbuf;
1015         out_left = out_size;
1016
1017 #define EXPAND_BUF()                            \
1018 {                                               \
1019         len = outbuf_p - outbuf;                \
1020         out_size *= 2;                          \
1021         outbuf = g_realloc(outbuf, out_size);   \
1022         outbuf_p = outbuf + len;                \
1023         out_left = out_size - len;              \
1024 }
1025
1026         while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left,
1027                                &outbuf_p, &out_left)) == (size_t)-1) {
1028                 if (EILSEQ == errno) {
1029                         //g_print("iconv(): at %d: %s\n", in_size - in_left, g_strerror(errno));
1030                         inbuf_p++;
1031                         in_left--;
1032                         if (out_left == 0) {
1033                                 EXPAND_BUF();
1034                         }
1035                         *outbuf_p++ = SUBST_CHAR;
1036                         out_left--;
1037                 } else if (EINVAL == errno) {
1038                         break;
1039                 } else if (E2BIG == errno) {
1040                         EXPAND_BUF();
1041                 } else {
1042                         g_warning("conv_iconv_strdup(): %s\n",
1043                                   g_strerror(errno));
1044                         break;
1045                 }
1046         }
1047
1048         while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) ==
1049                (size_t)-1) {
1050                 if (E2BIG == errno) {
1051                         EXPAND_BUF();
1052                 } else {
1053                         g_warning("conv_iconv_strdup(): %s\n",
1054                                   g_strerror(errno));
1055                         break;
1056                 }
1057         }
1058
1059 #undef EXPAND_BUF
1060
1061         len = outbuf_p - outbuf;
1062         outbuf = g_realloc(outbuf, len + 1);
1063         outbuf[len] = '\0';
1064
1065         return outbuf;
1066 }
1067
1068 static const struct {
1069         CharSet charset;
1070         gchar *const name;
1071 } charsets[] = {
1072         {C_US_ASCII,            CS_US_ASCII},
1073         {C_US_ASCII,            CS_ANSI_X3_4_1968},
1074         {C_UTF_8,               CS_UTF_8},
1075         {C_UTF_7,               CS_UTF_7},
1076         {C_ISO_8859_1,          CS_ISO_8859_1},
1077         {C_ISO_8859_2,          CS_ISO_8859_2},
1078         {C_ISO_8859_3,          CS_ISO_8859_3},
1079         {C_ISO_8859_4,          CS_ISO_8859_4},
1080         {C_ISO_8859_5,          CS_ISO_8859_5},
1081         {C_ISO_8859_6,          CS_ISO_8859_6},
1082         {C_ISO_8859_7,          CS_ISO_8859_7},
1083         {C_ISO_8859_8,          CS_ISO_8859_8},
1084         {C_ISO_8859_9,          CS_ISO_8859_9},
1085         {C_ISO_8859_10,         CS_ISO_8859_10},
1086         {C_ISO_8859_11,         CS_ISO_8859_11},
1087         {C_ISO_8859_13,         CS_ISO_8859_13},
1088         {C_ISO_8859_14,         CS_ISO_8859_14},
1089         {C_ISO_8859_15,         CS_ISO_8859_15},
1090         {C_BALTIC,              CS_BALTIC},
1091         {C_CP1250,              CS_CP1250},
1092         {C_CP1251,              CS_CP1251},
1093         {C_CP1252,              CS_CP1252},
1094         {C_CP1253,              CS_CP1253},
1095         {C_CP1254,              CS_CP1254},
1096         {C_CP1255,              CS_CP1255},
1097         {C_CP1256,              CS_CP1256},
1098         {C_CP1257,              CS_CP1257},
1099         {C_CP1258,              CS_CP1258},
1100         {C_WINDOWS_1250,        CS_WINDOWS_1250},
1101         {C_WINDOWS_1251,        CS_WINDOWS_1251},
1102         {C_WINDOWS_1252,        CS_WINDOWS_1252},
1103         {C_WINDOWS_1253,        CS_WINDOWS_1253},
1104         {C_WINDOWS_1254,        CS_WINDOWS_1254},
1105         {C_WINDOWS_1255,        CS_WINDOWS_1255},
1106         {C_WINDOWS_1256,        CS_WINDOWS_1256},
1107         {C_WINDOWS_1257,        CS_WINDOWS_1257},
1108         {C_WINDOWS_1258,        CS_WINDOWS_1258},
1109         {C_KOI8_R,              CS_KOI8_R},
1110         {C_KOI8_T,              CS_KOI8_T},
1111         {C_KOI8_U,              CS_KOI8_U},
1112         {C_ISO_2022_JP,         CS_ISO_2022_JP},
1113         {C_ISO_2022_JP_2,       CS_ISO_2022_JP_2},
1114         {C_ISO_2022_JP_3,       CS_ISO_2022_JP_3},
1115         {C_EUC_JP,              CS_EUC_JP},
1116         {C_EUC_JP,              CS_EUCJP},
1117         {C_EUC_JP_MS,           CS_EUC_JP_MS},
1118         {C_SHIFT_JIS,           CS_SHIFT_JIS},
1119         {C_SHIFT_JIS,           CS_SHIFT__JIS},
1120         {C_SHIFT_JIS,           CS_SJIS},
1121         {C_ISO_2022_KR,         CS_ISO_2022_KR},
1122         {C_EUC_KR,              CS_EUC_KR},
1123         {C_ISO_2022_CN,         CS_ISO_2022_CN},
1124         {C_EUC_CN,              CS_EUC_CN},
1125         {C_GB2312,              CS_GB2312},
1126         {C_GBK,                 CS_GBK},
1127         {C_EUC_TW,              CS_EUC_TW},
1128         {C_BIG5,                CS_BIG5},
1129         {C_BIG5_HKSCS,          CS_BIG5_HKSCS},
1130         {C_TIS_620,             CS_TIS_620},
1131         {C_WINDOWS_874,         CS_WINDOWS_874},
1132         {C_GEORGIAN_PS,         CS_GEORGIAN_PS},
1133         {C_TCVN5712_1,          CS_TCVN5712_1},
1134 };
1135
1136 static const struct {
1137         gchar *const locale;
1138         CharSet charset;
1139         CharSet out_charset;
1140 } locale_table[] = {
1141         {"ja_JP.eucJP"  , C_EUC_JP      , C_ISO_2022_JP},
1142         {"ja_JP.EUC-JP" , C_EUC_JP      , C_ISO_2022_JP},
1143         {"ja_JP.EUC"    , C_EUC_JP      , C_ISO_2022_JP},
1144         {"ja_JP.ujis"   , C_EUC_JP      , C_ISO_2022_JP},
1145         {"ja_JP.SJIS"   , C_SHIFT_JIS   , C_ISO_2022_JP},
1146         {"ja_JP.JIS"    , C_ISO_2022_JP , C_ISO_2022_JP},
1147         {"ja_JP"        , C_EUC_JP      , C_ISO_2022_JP},
1148         {"ko_KR.EUC-KR" , C_EUC_KR      , C_EUC_KR},
1149         {"ko_KR"        , C_EUC_KR      , C_EUC_KR},
1150         {"zh_CN.GB2312" , C_GB2312      , C_GB2312},
1151         {"zh_CN.GBK"    , C_GBK         , C_GB2312},
1152         {"zh_CN"        , C_GB2312      , C_GB2312},
1153         {"zh_HK"        , C_BIG5_HKSCS  , C_BIG5_HKSCS},
1154         {"zh_TW.eucTW"  , C_EUC_TW      , C_BIG5},
1155         {"zh_TW.EUC-TW" , C_EUC_TW      , C_BIG5},
1156         {"zh_TW.Big5"   , C_BIG5        , C_BIG5},
1157         {"zh_TW"        , C_BIG5        , C_BIG5},
1158
1159         {"ru_RU.KOI8-R" , C_KOI8_R      , C_KOI8_R},
1160         {"ru_RU.KOI8R"  , C_KOI8_R      , C_KOI8_R},
1161         {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
1162         {"ru_RU"        , C_ISO_8859_5  , C_KOI8_R},
1163         {"tg_TJ"        , C_KOI8_T      , C_KOI8_T},
1164         {"ru_UA"        , C_KOI8_U      , C_KOI8_U},
1165         {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U},
1166         {"uk_UA"        , C_KOI8_U      , C_KOI8_U},
1167
1168         {"be_BY"        , C_WINDOWS_1251, C_WINDOWS_1251},
1169         {"bg_BG"        , C_WINDOWS_1251, C_WINDOWS_1251},
1170
1171         {"yi_US"        , C_WINDOWS_1255, C_WINDOWS_1255},
1172
1173         {"af_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1174         {"br_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1175         {"ca_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1176         {"da_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1177         {"de_AT"        , C_ISO_8859_1  , C_ISO_8859_1},
1178         {"de_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1179         {"de_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1180         {"de_DE"        , C_ISO_8859_1  , C_ISO_8859_1},
1181         {"de_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1182         {"en_AU"        , C_ISO_8859_1  , C_ISO_8859_1},
1183         {"en_BW"        , C_ISO_8859_1  , C_ISO_8859_1},
1184         {"en_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1185         {"en_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1186         {"en_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1187         {"en_HK"        , C_ISO_8859_1  , C_ISO_8859_1},
1188         {"en_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1189         {"en_NZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1190         {"en_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1191         {"en_SG"        , C_ISO_8859_1  , C_ISO_8859_1},
1192         {"en_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1193         {"en_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1194         {"en_ZW"        , C_ISO_8859_1  , C_ISO_8859_1},
1195         {"es_AR"        , C_ISO_8859_1  , C_ISO_8859_1},
1196         {"es_BO"        , C_ISO_8859_1  , C_ISO_8859_1},
1197         {"es_CL"        , C_ISO_8859_1  , C_ISO_8859_1},
1198         {"es_CO"        , C_ISO_8859_1  , C_ISO_8859_1},
1199         {"es_CR"        , C_ISO_8859_1  , C_ISO_8859_1},
1200         {"es_DO"        , C_ISO_8859_1  , C_ISO_8859_1},
1201         {"es_EC"        , C_ISO_8859_1  , C_ISO_8859_1},
1202         {"es_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1203         {"es_GT"        , C_ISO_8859_1  , C_ISO_8859_1},
1204         {"es_HN"        , C_ISO_8859_1  , C_ISO_8859_1},
1205         {"es_MX"        , C_ISO_8859_1  , C_ISO_8859_1},
1206         {"es_NI"        , C_ISO_8859_1  , C_ISO_8859_1},
1207         {"es_PA"        , C_ISO_8859_1  , C_ISO_8859_1},
1208         {"es_PE"        , C_ISO_8859_1  , C_ISO_8859_1},
1209         {"es_PR"        , C_ISO_8859_1  , C_ISO_8859_1},
1210         {"es_PY"        , C_ISO_8859_1  , C_ISO_8859_1},
1211         {"es_SV"        , C_ISO_8859_1  , C_ISO_8859_1},
1212         {"es_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1213         {"es_UY"        , C_ISO_8859_1  , C_ISO_8859_1},
1214         {"es_VE"        , C_ISO_8859_1  , C_ISO_8859_1},
1215         {"et_EE"        , C_ISO_8859_1  , C_ISO_8859_1},
1216         {"eu_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1217         {"fi_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1218         {"fo_FO"        , C_ISO_8859_1  , C_ISO_8859_1},
1219         {"fr_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1220         {"fr_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1221         {"fr_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1222         {"fr_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1223         {"fr_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1224         {"ga_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1225         {"gl_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1226         {"gv_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1227         {"id_ID"        , C_ISO_8859_1  , C_ISO_8859_1},
1228         {"is_IS"        , C_ISO_8859_1  , C_ISO_8859_1},
1229         {"it_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1230         {"it_IT"        , C_ISO_8859_1  , C_ISO_8859_1},
1231         {"kl_GL"        , C_ISO_8859_1  , C_ISO_8859_1},
1232         {"kw_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1233         {"ms_MY"        , C_ISO_8859_1  , C_ISO_8859_1},
1234         {"nl_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1235         {"nl_NL"        , C_ISO_8859_1  , C_ISO_8859_1},
1236         {"nn_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1237         {"no_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1238         {"oc_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1239         {"pt_BR"        , C_ISO_8859_1  , C_ISO_8859_1},
1240         {"pt_PT"        , C_ISO_8859_1  , C_ISO_8859_1},
1241         {"sq_AL"        , C_ISO_8859_1  , C_ISO_8859_1},
1242         {"sv_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1243         {"sv_SE"        , C_ISO_8859_1  , C_ISO_8859_1},
1244         {"tl_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1245         {"uz_UZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1246         {"wa_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1247
1248         {"bs_BA"        , C_ISO_8859_2  , C_ISO_8859_2},
1249         {"cs_CZ"        , C_ISO_8859_2  , C_ISO_8859_2},
1250         {"hr_HR"        , C_ISO_8859_2  , C_ISO_8859_2},
1251         {"hu_HU"        , C_ISO_8859_2  , C_ISO_8859_2},
1252         {"pl_PL"        , C_ISO_8859_2  , C_ISO_8859_2},
1253         {"ro_RO"        , C_ISO_8859_2  , C_ISO_8859_2},
1254         {"sk_SK"        , C_ISO_8859_2  , C_ISO_8859_2},
1255         {"sl_SI"        , C_ISO_8859_2  , C_ISO_8859_2},
1256
1257         {"sr_YU@cyrillic"       , C_ISO_8859_5  , C_ISO_8859_5},
1258         {"sr_YU"                , C_ISO_8859_2  , C_ISO_8859_2},
1259
1260         {"mt_MT"                , C_ISO_8859_3  , C_ISO_8859_3},
1261
1262         {"lt_LT.iso88594"       , C_ISO_8859_4  , C_ISO_8859_4},
1263         {"lt_LT.ISO8859-4"      , C_ISO_8859_4  , C_ISO_8859_4},
1264         {"lt_LT.ISO_8859-4"     , C_ISO_8859_4  , C_ISO_8859_4},
1265         {"lt_LT"                , C_ISO_8859_13 , C_ISO_8859_13},
1266
1267         {"mk_MK"        , C_ISO_8859_5  , C_ISO_8859_5},
1268
1269         {"ar_AE"        , C_ISO_8859_6  , C_ISO_8859_6},
1270         {"ar_BH"        , C_ISO_8859_6  , C_ISO_8859_6},
1271         {"ar_DZ"        , C_ISO_8859_6  , C_ISO_8859_6},
1272         {"ar_EG"        , C_ISO_8859_6  , C_ISO_8859_6},
1273         {"ar_IQ"        , C_ISO_8859_6  , C_ISO_8859_6},
1274         {"ar_JO"        , C_ISO_8859_6  , C_ISO_8859_6},
1275         {"ar_KW"        , C_ISO_8859_6  , C_ISO_8859_6},
1276         {"ar_LB"        , C_ISO_8859_6  , C_ISO_8859_6},
1277         {"ar_LY"        , C_ISO_8859_6  , C_ISO_8859_6},
1278         {"ar_MA"        , C_ISO_8859_6  , C_ISO_8859_6},
1279         {"ar_OM"        , C_ISO_8859_6  , C_ISO_8859_6},
1280         {"ar_QA"        , C_ISO_8859_6  , C_ISO_8859_6},
1281         {"ar_SA"        , C_ISO_8859_6  , C_ISO_8859_6},
1282         {"ar_SD"        , C_ISO_8859_6  , C_ISO_8859_6},
1283         {"ar_SY"        , C_ISO_8859_6  , C_ISO_8859_6},
1284         {"ar_TN"        , C_ISO_8859_6  , C_ISO_8859_6},
1285         {"ar_YE"        , C_ISO_8859_6  , C_ISO_8859_6},
1286
1287         {"el_GR"        , C_ISO_8859_7  , C_ISO_8859_7},
1288         {"he_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1289         {"iw_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1290         {"tr_TR"        , C_ISO_8859_9  , C_ISO_8859_9},
1291
1292         {"lv_LV"        , C_ISO_8859_13 , C_ISO_8859_13},
1293         {"mi_NZ"        , C_ISO_8859_13 , C_ISO_8859_13},
1294
1295         {"cy_GB"        , C_ISO_8859_14 , C_ISO_8859_14},
1296
1297         {"ar_IN"        , C_UTF_8       , C_UTF_8},
1298         {"en_IN"        , C_UTF_8       , C_UTF_8},
1299         {"se_NO"        , C_UTF_8       , C_UTF_8},
1300         {"ta_IN"        , C_UTF_8       , C_UTF_8},
1301         {"te_IN"        , C_UTF_8       , C_UTF_8},
1302         {"ur_PK"        , C_UTF_8       , C_UTF_8},
1303
1304         {"th_TH"        , C_TIS_620     , C_TIS_620},
1305         /* {"th_TH"     , C_WINDOWS_874}, */
1306         /* {"th_TH"     , C_ISO_8859_11}, */
1307
1308         {"ka_GE"        , C_GEORGIAN_PS , C_GEORGIAN_PS},
1309         {"vi_VN.TCVN"   , C_TCVN5712_1  , C_TCVN5712_1},
1310
1311         {"C"                    , C_US_ASCII    , C_US_ASCII},
1312         {"POSIX"                , C_US_ASCII    , C_US_ASCII},
1313         {"ANSI_X3.4-1968"       , C_US_ASCII    , C_US_ASCII},
1314 };
1315
1316 static GHashTable *conv_get_charset_to_str_table(void)
1317 {
1318         static GHashTable *table;
1319         gint i;
1320
1321         if (table)
1322                 return table;
1323
1324         table = g_hash_table_new(NULL, g_direct_equal);
1325
1326         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1327                 if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
1328                     == NULL) {
1329                         g_hash_table_insert
1330                                 (table, GUINT_TO_POINTER(charsets[i].charset),
1331                                  charsets[i].name);
1332                 }
1333         }
1334
1335         return table;
1336 }
1337
1338 static GHashTable *conv_get_charset_from_str_table(void)
1339 {
1340         static GHashTable *table;
1341         gint i;
1342
1343         if (table)
1344                 return table;
1345
1346         table = g_hash_table_new(str_case_hash, str_case_equal);
1347
1348         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1349                 g_hash_table_insert(table, charsets[i].name,
1350                                     GUINT_TO_POINTER(charsets[i].charset));
1351         }
1352
1353         return table;
1354 }
1355
1356 const gchar *conv_get_charset_str(CharSet charset)
1357 {
1358         GHashTable *table;
1359
1360         table = conv_get_charset_to_str_table();
1361         return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
1362 }
1363
1364 CharSet conv_get_charset_from_str(const gchar *charset)
1365 {
1366         GHashTable *table;
1367
1368         if (!charset) return C_AUTO;
1369
1370         table = conv_get_charset_from_str_table();
1371         return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset));
1372 }
1373
1374 CharSet conv_get_locale_charset(void)
1375 {
1376         static CharSet cur_charset = -1;
1377         const gchar *cur_locale;
1378         const gchar *p;
1379         gint i;
1380
1381         if (cur_charset != -1)
1382                 return cur_charset;
1383
1384         cur_locale = conv_get_current_locale();
1385         if (!cur_locale) {
1386                 cur_charset = C_US_ASCII;
1387                 return cur_charset;
1388         }
1389
1390         if (strcasestr(cur_locale, "UTF-8")) {
1391                 cur_charset = C_UTF_8;
1392                 return cur_charset;
1393         }
1394
1395         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1396                 cur_charset = C_ISO_8859_15;
1397                 return cur_charset;
1398         }
1399
1400         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1401                 const gchar *p;
1402
1403                 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1404                    "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1405                 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
1406                                  strlen(locale_table[i].locale))) {
1407                         cur_charset = locale_table[i].charset;
1408                         return cur_charset;
1409                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1410                          !strchr(p + 1, '.')) {
1411                         if (strlen(cur_locale) == 2 &&
1412                             !g_ascii_strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1413                                 cur_charset = locale_table[i].charset;
1414                                 return cur_charset;
1415                         }
1416                 }
1417         }
1418
1419         cur_charset = C_AUTO;
1420         return cur_charset;
1421 }
1422
1423 const gchar *conv_get_locale_charset_str(void)
1424 {
1425         static const gchar *codeset = NULL;
1426
1427         if (!codeset)
1428                 codeset = conv_get_charset_str(conv_get_locale_charset());
1429
1430         return codeset ? codeset : CS_INTERNAL;
1431 }
1432
1433 CharSet conv_get_internal_charset(void)
1434 {
1435         return C_INTERNAL;
1436 }
1437
1438 const gchar *conv_get_internal_charset_str(void)
1439 {
1440         return CS_INTERNAL;
1441 }
1442
1443 CharSet conv_get_outgoing_charset(void)
1444 {
1445         static CharSet out_charset = -1;
1446         const gchar *cur_locale;
1447         const gchar *p;
1448         gint i;
1449
1450         if (out_charset != -1)
1451                 return out_charset;
1452
1453         cur_locale = conv_get_current_locale();
1454         if (!cur_locale) {
1455                 out_charset = C_AUTO;
1456                 return out_charset;
1457         }
1458
1459         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1460                 out_charset = C_ISO_8859_15;
1461                 return out_charset;
1462         }
1463
1464         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1465                 const gchar *p;
1466
1467                 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
1468                                  strlen(locale_table[i].locale))) {
1469                         out_charset = locale_table[i].out_charset;
1470                         break;
1471                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1472                          !strchr(p + 1, '.')) {
1473                         if (strlen(cur_locale) == 2 &&
1474                             !g_ascii_strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1475                                 out_charset = locale_table[i].out_charset;
1476                                 break;
1477                         }
1478                 }
1479         }
1480
1481         return out_charset;
1482 }
1483
1484 const gchar *conv_get_outgoing_charset_str(void)
1485 {
1486         CharSet out_charset;
1487         const gchar *str;
1488
1489         if (prefs_common.outgoing_charset) {
1490                 if (!isalpha((guchar)prefs_common.outgoing_charset[0])) {
1491                         g_free(prefs_common.outgoing_charset);
1492                         prefs_common.outgoing_charset = g_strdup(CS_AUTO);
1493                 } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0)
1494                         return prefs_common.outgoing_charset;
1495         }
1496
1497         out_charset = conv_get_outgoing_charset();
1498         str = conv_get_charset_str(out_charset);
1499
1500         return str ? str : CS_UTF_8;
1501 }
1502
1503 gboolean conv_is_multibyte_encoding(CharSet encoding)
1504 {
1505         switch (encoding) {
1506         case C_EUC_JP:
1507         case C_EUC_JP_MS:
1508         case C_EUC_KR:
1509         case C_EUC_TW:
1510         case C_EUC_CN:
1511         case C_ISO_2022_JP:
1512         case C_ISO_2022_JP_2:
1513         case C_ISO_2022_JP_3:
1514         case C_ISO_2022_KR:
1515         case C_ISO_2022_CN:
1516         case C_SHIFT_JIS:
1517         case C_GB2312:
1518         case C_BIG5:
1519         case C_UTF_8:
1520         case C_UTF_7:
1521                 return TRUE;
1522         default:
1523                 return FALSE;
1524         }
1525 }
1526
1527 const gchar *conv_get_current_locale(void)
1528 {
1529         const gchar *cur_locale;
1530
1531         cur_locale = g_getenv("LC_ALL");
1532         if (!cur_locale) cur_locale = g_getenv("LC_CTYPE");
1533         if (!cur_locale) cur_locale = g_getenv("LANG");
1534         if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL);
1535
1536         debug_print("current locale: %s\n",
1537                     cur_locale ? cur_locale : "(none)");
1538
1539         return cur_locale;
1540 }
1541
1542 void conv_unmime_header_overwrite(gchar *str)
1543 {
1544         gchar *buf;
1545         gint buflen;
1546         CharSet cur_charset;
1547
1548         cur_charset = conv_get_locale_charset();
1549
1550         if (cur_charset == C_EUC_JP) {
1551                 buflen = strlen(str) * 2 + 1;
1552                 Xalloca(buf, buflen, return);
1553                 conv_anytodisp(buf, buflen, str);
1554                 unmime_header(str, buf);
1555         } else {
1556                 buflen = strlen(str) + 1;
1557                 Xalloca(buf, buflen, return);
1558                 unmime_header(buf, str);
1559                 strncpy2(str, buf, buflen);
1560         }
1561 }
1562
1563 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1564                         const gchar *charset)
1565 {
1566         CharSet cur_charset;
1567
1568         cur_charset = conv_get_locale_charset();
1569
1570         if (cur_charset == C_EUC_JP) {
1571                 gchar *buf;
1572                 gint buflen;
1573
1574                 buflen = strlen(str) * 2 + 1;
1575                 Xalloca(buf, buflen, return);
1576                 conv_anytodisp(buf, buflen, str);
1577                 unmime_header(outbuf, buf);
1578         } else {
1579                 gchar *tmp = NULL;
1580                 unmime_header(outbuf, str);
1581
1582                 if (outbuf && !g_utf8_validate(outbuf, -1, NULL)) {
1583                         if (conv_get_locale_charset() != C_INTERNAL)
1584                                 tmp = conv_codeset_strdup(outbuf,
1585                                         conv_get_locale_charset_str(),
1586                                         CS_INTERNAL);
1587
1588                         if (tmp) {
1589                                 strncpy(outbuf, tmp, outlen-1);
1590                                 g_free(tmp);
1591                         } else {
1592                                 conv_unreadable_8bit(outbuf);
1593                         }
1594                 }
1595         }
1596
1597 }
1598
1599 #define MAX_LINELEN             76
1600 #define MAX_HARD_LINELEN        996
1601 #define MIMESEP_BEGIN           "=?"
1602 #define MIMESEP_END             "?="
1603
1604 #define LBREAK_IF_REQUIRED(cond, is_plain_text)                         \
1605 {                                                                       \
1606         if (len - (destp - (guchar *)dest) < MAX_LINELEN + 2) {         \
1607                 *destp = '\0';                                          \
1608                 return;                                                 \
1609         }                                                               \
1610                                                                         \
1611         if ((cond) && *srcp) {                                          \
1612                 if (destp > (guchar *)dest && left < MAX_LINELEN - 1) { \
1613                         if (isspace(*(destp - 1)))                      \
1614                                 destp--;                                \
1615                         else if (is_plain_text && isspace(*srcp))       \
1616                                 srcp++;                                 \
1617                         if (*srcp) {                                    \
1618                                 *destp++ = '\n';                        \
1619                                 *destp++ = ' ';                         \
1620                                 left = MAX_LINELEN - 1;                 \
1621                         }                                               \
1622                 }                                                       \
1623         }                                                               \
1624 }
1625
1626 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1627                         gint header_len, gboolean addr_field)
1628 {
1629         const gchar *cur_encoding;
1630         const gchar *out_encoding;
1631         gint mimestr_len;
1632         gchar *mimesep_enc;
1633         gint left;
1634         const guchar *srcp = src;
1635         guchar *destp = dest;
1636         gboolean use_base64;
1637
1638         g_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE);
1639
1640         if (MB_CUR_MAX > 1) {
1641                 use_base64 = TRUE;
1642                 mimesep_enc = "?B?";
1643         } else {
1644                 use_base64 = FALSE;
1645                 mimesep_enc = "?Q?";
1646         }
1647
1648         cur_encoding = CS_INTERNAL;
1649         out_encoding = conv_get_outgoing_charset_str();
1650         if (!strcmp(out_encoding, CS_US_ASCII))
1651                 out_encoding = CS_ISO_8859_1;
1652
1653         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1654                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1655
1656         left = MAX_LINELEN - header_len;
1657
1658         while (*srcp) {
1659                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1660
1661                 while (isspace(*srcp)) {
1662                         *destp++ = *srcp++;
1663                         left--;
1664                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1665                 }
1666
1667                 /* output as it is if the next word is ASCII string */
1668                 if (!is_next_nonascii(srcp)) {
1669                         gint word_len;
1670
1671                         word_len = get_next_word_len(srcp);
1672                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1673                         while (word_len > 0) {
1674                                 LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
1675                                 *destp++ = *srcp++;
1676                                 left--;
1677                                 word_len--;
1678                         }
1679
1680                         continue;
1681                 }
1682
1683                 /* don't include parentheses in encoded strings */
1684                 if (addr_field && (*srcp == '(' || *srcp == ')')) {
1685                         LBREAK_IF_REQUIRED(left < 2, FALSE);
1686                         *destp++ = *srcp++;
1687                         left--;
1688                 }
1689
1690                 while (1) {
1691                         gint mb_len = 0;
1692                         gint cur_len = 0;
1693                         gchar *part_str;
1694                         gchar *out_str;
1695                         gchar *enc_str;
1696                         const guchar *p = srcp;
1697                         gint out_str_len;
1698                         gint out_enc_str_len;
1699                         gint mime_block_len;
1700                         gboolean cont = FALSE;
1701
1702                         while (*p != '\0') {
1703                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1704                                         break;
1705                                 /* don't include parentheses in encoded
1706                                    strings */
1707                                 if (addr_field && (*p == '(' || *p == ')'))
1708                                         break;
1709
1710                                 mb_len = g_utf8_skip[*p];
1711
1712                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1713                                 out_str = conv_codeset_strdup
1714                                         (part_str, cur_encoding, out_encoding);
1715                                 if (!out_str) {
1716                                         g_warning("conv_encode_header(): code conversion failed\n");
1717                                         conv_unreadable_8bit(part_str);
1718                                         out_str = g_strdup(part_str);
1719                                 }
1720                                 out_str_len = strlen(out_str);
1721
1722                                 if (use_base64)
1723                                         out_enc_str_len = B64LEN(out_str_len);
1724                                 else
1725                                         out_enc_str_len =
1726                                                 qp_get_q_encoding_len(out_str);
1727
1728                                 g_free(out_str);
1729
1730                                 if (mimestr_len + out_enc_str_len <= left) {
1731                                         cur_len += mb_len;
1732                                         p += mb_len;
1733                                 } else if (cur_len == 0) {
1734                                         LBREAK_IF_REQUIRED(1, FALSE);
1735                                         continue;
1736                                 } else {
1737                                         cont = TRUE;
1738                                         break;
1739                                 }
1740                         }
1741
1742                         if (cur_len > 0) {
1743                                 Xstrndup_a(part_str, srcp, cur_len, );
1744                                 out_str = conv_codeset_strdup
1745                                         (part_str, cur_encoding, out_encoding);
1746                                 if (!out_str) {
1747                                         g_warning("conv_encode_header(): code conversion failed\n");
1748                                         conv_unreadable_8bit(part_str);
1749                                         out_str = g_strdup(part_str);
1750                                 }
1751                                 out_str_len = strlen(out_str);
1752
1753                                 if (use_base64)
1754                                         out_enc_str_len = B64LEN(out_str_len);
1755                                 else
1756                                         out_enc_str_len =
1757                                                 qp_get_q_encoding_len(out_str);
1758
1759                                 Xalloca(enc_str, out_enc_str_len + 1, );
1760                                 if (use_base64)
1761                                         base64_encode(enc_str, out_str, out_str_len);
1762                                 else
1763                                         qp_q_encode(enc_str, out_str);
1764
1765                                 g_free(out_str);
1766
1767                                 /* output MIME-encoded string block */
1768                                 mime_block_len = mimestr_len + strlen(enc_str);
1769                                 g_snprintf(destp, mime_block_len + 1,
1770                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1771                                            out_encoding, mimesep_enc, enc_str);
1772                                 destp += mime_block_len;
1773                                 srcp += cur_len;
1774
1775                                 left -= mime_block_len;
1776                         }
1777
1778                         LBREAK_IF_REQUIRED(cont, FALSE);
1779
1780                         if (cur_len == 0)
1781                                 break;
1782                 }
1783         }
1784
1785         *destp = '\0';
1786 }
1787
1788 #undef LBREAK_IF_REQUIRED
1789 gchar *conv_filename_from_utf8(const gchar *utf8_file)
1790 {
1791         gchar *fs_file;
1792         GError *error = NULL;
1793
1794         fs_file = g_filename_from_utf8(utf8_file, -1, NULL, NULL, &error);
1795         if (error) {
1796                 g_warning("failed to convert encoding of file name: %s\n",
1797                           error->message);
1798                 g_error_free(error);
1799         }
1800         if (!fs_file)
1801                 fs_file = g_strdup(utf8_file);
1802
1803         return fs_file;
1804 }
1805
1806 gchar *conv_filename_to_utf8(const gchar *fs_file)
1807 {
1808         gchar *utf8_file = NULL;
1809         GError *error = NULL;
1810
1811         utf8_file = g_filename_to_utf8(fs_file, -1, NULL, NULL, &error);
1812         if (error) {
1813                 g_warning("failed to convert encoding of file name: %s\n",
1814                           error->message);
1815                 g_error_free(error);
1816         }
1817
1818         if (!utf8_file || !g_utf8_validate(utf8_file, -1, NULL)) {
1819                 g_free(utf8_file);
1820                 utf8_file = g_strdup(fs_file);
1821                 conv_unreadable_8bit(utf8_file);
1822         }
1823
1824         return utf8_file;
1825 }