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