2004-08-20 [colin] 0.9.12cvs66.1
[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 = conv_get_outgoing_charset_str();
913         gchar *dest_code = conv_get_current_charset_str();
914         
915         if (isrc_code)
916                 src_code = isrc_code;
917         if (idest_code)
918                 dest_code = 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 && strcasecmp(src_code, CS_ISO_8859_15)) 
933                 /* also try iso-8859-15 */
934                 outbuf = conv_iconv_strdup(inbuf, CS_ISO_8859_15, dest_code);
935         if (outbuf == NULL)
936                 g_warning(_("Valid locale type set? (Currently: %s to %s)\n"),
937                           src_code, dest_code);
938         
939         return outbuf;                     
940 }
941
942 static const struct {
943         CharSet charset;
944         gchar *const name;
945 } charsets[] = {
946         {C_US_ASCII,            CS_US_ASCII},
947         {C_US_ASCII,            CS_ANSI_X3_4_1968},
948         {C_UTF_8,               CS_UTF_8},
949         {C_UTF_7,               CS_UTF_7},
950         {C_ISO_8859_1,          CS_ISO_8859_1},
951         {C_ISO_8859_2,          CS_ISO_8859_2},
952         {C_ISO_8859_3,          CS_ISO_8859_3},
953         {C_ISO_8859_4,          CS_ISO_8859_4},
954         {C_ISO_8859_5,          CS_ISO_8859_5},
955         {C_ISO_8859_6,          CS_ISO_8859_6},
956         {C_ISO_8859_7,          CS_ISO_8859_7},
957         {C_ISO_8859_8,          CS_ISO_8859_8},
958         {C_ISO_8859_9,          CS_ISO_8859_9},
959         {C_ISO_8859_10,         CS_ISO_8859_10},
960         {C_ISO_8859_11,         CS_ISO_8859_11},
961         {C_ISO_8859_13,         CS_ISO_8859_13},
962         {C_ISO_8859_14,         CS_ISO_8859_14},
963         {C_ISO_8859_15,         CS_ISO_8859_15},
964         {C_BALTIC,              CS_BALTIC},
965         {C_CP1250,              CS_CP1250},
966         {C_CP1251,              CS_CP1251},
967         {C_CP1252,              CS_CP1252},
968         {C_CP1253,              CS_CP1253},
969         {C_CP1254,              CS_CP1254},
970         {C_CP1255,              CS_CP1255},
971         {C_CP1256,              CS_CP1256},
972         {C_CP1257,              CS_CP1257},
973         {C_CP1258,              CS_CP1258},
974         {C_WINDOWS_1250,        CS_WINDOWS_1250},
975         {C_WINDOWS_1251,        CS_WINDOWS_1251},
976         {C_WINDOWS_1252,        CS_WINDOWS_1252},
977         {C_WINDOWS_1253,        CS_WINDOWS_1253},
978         {C_WINDOWS_1254,        CS_WINDOWS_1254},
979         {C_WINDOWS_1255,        CS_WINDOWS_1255},
980         {C_WINDOWS_1256,        CS_WINDOWS_1256},
981         {C_WINDOWS_1257,        CS_WINDOWS_1257},
982         {C_WINDOWS_1258,        CS_WINDOWS_1258},
983         {C_KOI8_R,              CS_KOI8_R},
984         {C_KOI8_T,              CS_KOI8_T},
985         {C_KOI8_U,              CS_KOI8_U},
986         {C_ISO_2022_JP,         CS_ISO_2022_JP},
987         {C_ISO_2022_JP_2,       CS_ISO_2022_JP_2},
988         {C_ISO_2022_JP_3,       CS_ISO_2022_JP_3},
989         {C_EUC_JP,              CS_EUC_JP},
990         {C_EUC_JP,              CS_EUCJP},
991         {C_SHIFT_JIS,           CS_SHIFT_JIS},
992         {C_SHIFT_JIS,           CS_SHIFT__JIS},
993         {C_SHIFT_JIS,           CS_SJIS},
994         {C_ISO_2022_KR,         CS_ISO_2022_KR},
995         {C_EUC_KR,              CS_EUC_KR},
996         {C_ISO_2022_CN,         CS_ISO_2022_CN},
997         {C_EUC_CN,              CS_EUC_CN},
998         {C_GB2312,              CS_GB2312},
999         {C_GBK,                 CS_GBK},
1000         {C_EUC_TW,              CS_EUC_TW},
1001         {C_BIG5,                CS_BIG5},
1002         {C_BIG5_HKSCS,          CS_BIG5_HKSCS},
1003         {C_TIS_620,             CS_TIS_620},
1004         {C_WINDOWS_874,         CS_WINDOWS_874},
1005         {C_GEORGIAN_PS,         CS_GEORGIAN_PS},
1006         {C_TCVN5712_1,          CS_TCVN5712_1},
1007 };
1008
1009 static const struct {
1010         gchar *const locale;
1011         CharSet charset;
1012         CharSet out_charset;
1013 } locale_table[] = {
1014         {"ja_JP.eucJP"  , C_EUC_JP      , C_ISO_2022_JP},
1015         {"ja_JP.EUC-JP" , C_EUC_JP      , C_ISO_2022_JP},
1016         {"ja_JP.EUC"    , C_EUC_JP      , C_ISO_2022_JP},
1017         {"ja_JP.ujis"   , C_EUC_JP      , C_ISO_2022_JP},
1018         {"ja_JP.SJIS"   , C_SHIFT_JIS   , C_ISO_2022_JP},
1019         {"ja_JP.JIS"    , C_ISO_2022_JP , C_ISO_2022_JP},
1020         {"ja_JP"        , C_EUC_JP      , C_ISO_2022_JP},
1021         {"ko_KR.EUC-KR" , C_EUC_KR      , C_EUC_KR},
1022         {"ko_KR"        , C_EUC_KR      , C_EUC_KR},
1023         {"zh_CN.GB2312" , C_GB2312      , C_GB2312},
1024         {"zh_CN.GBK"    , C_GBK         , C_GB2312},
1025         {"zh_CN"        , C_GB2312      , C_GB2312},
1026         {"zh_HK"        , C_BIG5_HKSCS  , C_BIG5_HKSCS},
1027         {"zh_TW.eucTW"  , C_EUC_TW      , C_BIG5},
1028         {"zh_TW.EUC-TW" , C_EUC_TW      , C_BIG5},
1029         {"zh_TW.Big5"   , C_BIG5        , C_BIG5},
1030         {"zh_TW"        , C_BIG5        , C_BIG5},
1031
1032         {"ru_RU.KOI8-R" , C_KOI8_R      , C_KOI8_R},
1033         {"ru_RU.KOI8R"  , C_KOI8_R      , C_KOI8_R},
1034         {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
1035         {"ru_RU"        , C_ISO_8859_5  , C_KOI8_R},
1036         {"tg_TJ"        , C_KOI8_T      , C_KOI8_T},
1037         {"ru_UA"        , C_KOI8_U      , C_KOI8_U},
1038         {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U},
1039         {"uk_UA"        , C_KOI8_U      , C_KOI8_U},
1040
1041         {"be_BY"        , C_WINDOWS_1251, C_WINDOWS_1251},
1042         {"bg_BG"        , C_WINDOWS_1251, C_WINDOWS_1251},
1043
1044         {"yi_US"        , C_WINDOWS_1255, C_WINDOWS_1255},
1045
1046         {"af_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1047         {"br_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1048         {"ca_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1049         {"da_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1050         {"de_AT"        , C_ISO_8859_1  , C_ISO_8859_1},
1051         {"de_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1052         {"de_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1053         {"de_DE"        , C_ISO_8859_1  , C_ISO_8859_1},
1054         {"de_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1055         {"en_AU"        , C_ISO_8859_1  , C_ISO_8859_1},
1056         {"en_BW"        , C_ISO_8859_1  , C_ISO_8859_1},
1057         {"en_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1058         {"en_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
1059         {"en_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1060         {"en_HK"        , C_ISO_8859_1  , C_ISO_8859_1},
1061         {"en_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1062         {"en_NZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1063         {"en_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1064         {"en_SG"        , C_ISO_8859_1  , C_ISO_8859_1},
1065         {"en_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1066         {"en_ZA"        , C_ISO_8859_1  , C_ISO_8859_1},
1067         {"en_ZW"        , C_ISO_8859_1  , C_ISO_8859_1},
1068         {"es_AR"        , C_ISO_8859_1  , C_ISO_8859_1},
1069         {"es_BO"        , C_ISO_8859_1  , C_ISO_8859_1},
1070         {"es_CL"        , C_ISO_8859_1  , C_ISO_8859_1},
1071         {"es_CO"        , C_ISO_8859_1  , C_ISO_8859_1},
1072         {"es_CR"        , C_ISO_8859_1  , C_ISO_8859_1},
1073         {"es_DO"        , C_ISO_8859_1  , C_ISO_8859_1},
1074         {"es_EC"        , C_ISO_8859_1  , C_ISO_8859_1},
1075         {"es_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1076         {"es_GT"        , C_ISO_8859_1  , C_ISO_8859_1},
1077         {"es_HN"        , C_ISO_8859_1  , C_ISO_8859_1},
1078         {"es_MX"        , C_ISO_8859_1  , C_ISO_8859_1},
1079         {"es_NI"        , C_ISO_8859_1  , C_ISO_8859_1},
1080         {"es_PA"        , C_ISO_8859_1  , C_ISO_8859_1},
1081         {"es_PE"        , C_ISO_8859_1  , C_ISO_8859_1},
1082         {"es_PR"        , C_ISO_8859_1  , C_ISO_8859_1},
1083         {"es_PY"        , C_ISO_8859_1  , C_ISO_8859_1},
1084         {"es_SV"        , C_ISO_8859_1  , C_ISO_8859_1},
1085         {"es_US"        , C_ISO_8859_1  , C_ISO_8859_1},
1086         {"es_UY"        , C_ISO_8859_1  , C_ISO_8859_1},
1087         {"es_VE"        , C_ISO_8859_1  , C_ISO_8859_1},
1088         {"et_EE"        , C_ISO_8859_1  , C_ISO_8859_1},
1089         {"eu_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1090         {"fi_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1091         {"fo_FO"        , C_ISO_8859_1  , C_ISO_8859_1},
1092         {"fr_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1093         {"fr_CA"        , C_ISO_8859_1  , C_ISO_8859_1},
1094         {"fr_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1095         {"fr_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1096         {"fr_LU"        , C_ISO_8859_1  , C_ISO_8859_1},
1097         {"ga_IE"        , C_ISO_8859_1  , C_ISO_8859_1},
1098         {"gl_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
1099         {"gv_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1100         {"id_ID"        , C_ISO_8859_1  , C_ISO_8859_1},
1101         {"is_IS"        , C_ISO_8859_1  , C_ISO_8859_1},
1102         {"it_CH"        , C_ISO_8859_1  , C_ISO_8859_1},
1103         {"it_IT"        , C_ISO_8859_1  , C_ISO_8859_1},
1104         {"kl_GL"        , C_ISO_8859_1  , C_ISO_8859_1},
1105         {"kw_GB"        , C_ISO_8859_1  , C_ISO_8859_1},
1106         {"ms_MY"        , C_ISO_8859_1  , C_ISO_8859_1},
1107         {"nl_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1108         {"nl_NL"        , C_ISO_8859_1  , C_ISO_8859_1},
1109         {"nn_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1110         {"no_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
1111         {"oc_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
1112         {"pt_BR"        , C_ISO_8859_1  , C_ISO_8859_1},
1113         {"pt_PT"        , C_ISO_8859_1  , C_ISO_8859_1},
1114         {"sq_AL"        , C_ISO_8859_1  , C_ISO_8859_1},
1115         {"sv_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
1116         {"sv_SE"        , C_ISO_8859_1  , C_ISO_8859_1},
1117         {"tl_PH"        , C_ISO_8859_1  , C_ISO_8859_1},
1118         {"uz_UZ"        , C_ISO_8859_1  , C_ISO_8859_1},
1119         {"wa_BE"        , C_ISO_8859_1  , C_ISO_8859_1},
1120
1121         {"bs_BA"        , C_ISO_8859_2  , C_ISO_8859_2},
1122         {"cs_CZ"        , C_ISO_8859_2  , C_ISO_8859_2},
1123         {"hr_HR"        , C_ISO_8859_2  , C_ISO_8859_2},
1124         {"hu_HU"        , C_ISO_8859_2  , C_ISO_8859_2},
1125         {"pl_PL"        , C_ISO_8859_2  , C_ISO_8859_2},
1126         {"ro_RO"        , C_ISO_8859_2  , C_ISO_8859_2},
1127         {"sk_SK"        , C_ISO_8859_2  , C_ISO_8859_2},
1128         {"sl_SI"        , C_ISO_8859_2  , C_ISO_8859_2},
1129
1130         {"sr_YU@cyrillic"       , C_ISO_8859_5  , C_ISO_8859_5},
1131         {"sr_YU"                , C_ISO_8859_2  , C_ISO_8859_2},
1132
1133         {"mt_MT"                , C_ISO_8859_3  , C_ISO_8859_3},
1134
1135         {"lt_LT.iso88594"       , C_ISO_8859_4  , C_ISO_8859_4},
1136         {"lt_LT.ISO8859-4"      , C_ISO_8859_4  , C_ISO_8859_4},
1137         {"lt_LT.ISO_8859-4"     , C_ISO_8859_4  , C_ISO_8859_4},
1138         {"lt_LT"                , C_ISO_8859_13 , C_ISO_8859_13},
1139
1140         {"mk_MK"        , C_ISO_8859_5  , C_ISO_8859_5},
1141
1142         {"ar_AE"        , C_ISO_8859_6  , C_ISO_8859_6},
1143         {"ar_BH"        , C_ISO_8859_6  , C_ISO_8859_6},
1144         {"ar_DZ"        , C_ISO_8859_6  , C_ISO_8859_6},
1145         {"ar_EG"        , C_ISO_8859_6  , C_ISO_8859_6},
1146         {"ar_IQ"        , C_ISO_8859_6  , C_ISO_8859_6},
1147         {"ar_JO"        , C_ISO_8859_6  , C_ISO_8859_6},
1148         {"ar_KW"        , C_ISO_8859_6  , C_ISO_8859_6},
1149         {"ar_LB"        , C_ISO_8859_6  , C_ISO_8859_6},
1150         {"ar_LY"        , C_ISO_8859_6  , C_ISO_8859_6},
1151         {"ar_MA"        , C_ISO_8859_6  , C_ISO_8859_6},
1152         {"ar_OM"        , C_ISO_8859_6  , C_ISO_8859_6},
1153         {"ar_QA"        , C_ISO_8859_6  , C_ISO_8859_6},
1154         {"ar_SA"        , C_ISO_8859_6  , C_ISO_8859_6},
1155         {"ar_SD"        , C_ISO_8859_6  , C_ISO_8859_6},
1156         {"ar_SY"        , C_ISO_8859_6  , C_ISO_8859_6},
1157         {"ar_TN"        , C_ISO_8859_6  , C_ISO_8859_6},
1158         {"ar_YE"        , C_ISO_8859_6  , C_ISO_8859_6},
1159
1160         {"el_GR"        , C_ISO_8859_7  , C_ISO_8859_7},
1161         {"he_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1162         {"iw_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
1163         {"tr_TR"        , C_ISO_8859_9  , C_ISO_8859_9},
1164
1165         {"lv_LV"        , C_ISO_8859_13 , C_ISO_8859_13},
1166         {"mi_NZ"        , C_ISO_8859_13 , C_ISO_8859_13},
1167
1168         {"cy_GB"        , C_ISO_8859_14 , C_ISO_8859_14},
1169
1170         {"ar_IN"        , C_UTF_8       , C_UTF_8},
1171         {"en_IN"        , C_UTF_8       , C_UTF_8},
1172         {"se_NO"        , C_UTF_8       , C_UTF_8},
1173         {"ta_IN"        , C_UTF_8       , C_UTF_8},
1174         {"te_IN"        , C_UTF_8       , C_UTF_8},
1175         {"ur_PK"        , C_UTF_8       , C_UTF_8},
1176
1177         {"th_TH"        , C_TIS_620     , C_TIS_620},
1178         /* {"th_TH"     , C_WINDOWS_874}, */
1179         /* {"th_TH"     , C_ISO_8859_11}, */
1180
1181         {"ka_GE"        , C_GEORGIAN_PS , C_GEORGIAN_PS},
1182         {"vi_VN.TCVN"   , C_TCVN5712_1  , C_TCVN5712_1},
1183
1184         {"C"                    , C_US_ASCII    , C_US_ASCII},
1185         {"POSIX"                , C_US_ASCII    , C_US_ASCII},
1186         {"ANSI_X3.4-1968"       , C_US_ASCII    , C_US_ASCII},
1187 };
1188
1189 static GHashTable *conv_get_charset_to_str_table(void)
1190 {
1191         static GHashTable *table;
1192         gint i;
1193
1194         if (table)
1195                 return table;
1196
1197         table = g_hash_table_new(NULL, g_direct_equal);
1198
1199         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1200                 if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
1201                     == NULL) {
1202                         g_hash_table_insert
1203                                 (table, GUINT_TO_POINTER(charsets[i].charset),
1204                                  charsets[i].name);
1205                 }
1206         }
1207
1208         return table;
1209 }
1210
1211 static GHashTable *conv_get_charset_from_str_table(void)
1212 {
1213         static GHashTable *table;
1214         gint i;
1215
1216         if (table)
1217                 return table;
1218
1219         table = g_hash_table_new(str_case_hash, str_case_equal);
1220
1221         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1222                 g_hash_table_insert(table, charsets[i].name,
1223                                     GUINT_TO_POINTER(charsets[i].charset));
1224         }
1225
1226         return table;
1227 }
1228
1229 const gchar *conv_get_charset_str(CharSet charset)
1230 {
1231         GHashTable *table;
1232
1233         table = conv_get_charset_to_str_table();
1234         return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
1235 }
1236
1237 CharSet conv_get_charset_from_str(const gchar *charset)
1238 {
1239         GHashTable *table;
1240
1241         if (!charset) return C_AUTO;
1242
1243         table = conv_get_charset_from_str_table();
1244         return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset));
1245 }
1246
1247 CharSet conv_get_current_charset(void)
1248 {
1249         static CharSet cur_charset = -1;
1250         const gchar *cur_locale;
1251         const gchar *p;
1252         gint i;
1253
1254         if (cur_charset != -1)
1255                 return cur_charset;
1256
1257         cur_locale = conv_get_current_locale();
1258         if (!cur_locale) {
1259                 cur_charset = C_US_ASCII;
1260                 return cur_charset;
1261         }
1262
1263         if (strcasestr(cur_locale, "UTF-8")) {
1264                 cur_charset = C_UTF_8;
1265                 return cur_charset;
1266         }
1267
1268         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1269                 cur_charset = C_ISO_8859_15;
1270                 return cur_charset;
1271         }
1272
1273         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1274                 const gchar *p;
1275
1276                 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1277                    "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1278                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1279                                  strlen(locale_table[i].locale))) {
1280                         cur_charset = locale_table[i].charset;
1281                         return cur_charset;
1282                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1283                          !strchr(p + 1, '.')) {
1284                         if (strlen(cur_locale) == 2 &&
1285                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1286                                 cur_charset = locale_table[i].charset;
1287                                 return cur_charset;
1288                         }
1289                 }
1290         }
1291
1292         cur_charset = C_AUTO;
1293         return cur_charset;
1294 }
1295
1296 const gchar *conv_get_current_charset_str(void)
1297 {
1298         static const gchar *codeset = NULL;
1299
1300         if (!codeset)
1301                 codeset = conv_get_charset_str(conv_get_current_charset());
1302
1303         return codeset ? codeset : CS_US_ASCII;
1304 }
1305
1306 CharSet conv_get_outgoing_charset(void)
1307 {
1308         static CharSet out_charset = -1;
1309         const gchar *cur_locale;
1310         const gchar *p;
1311         gint i;
1312
1313         if (out_charset != -1)
1314                 return out_charset;
1315
1316         cur_locale = conv_get_current_locale();
1317         if (!cur_locale) {
1318                 out_charset = C_AUTO;
1319                 return out_charset;
1320         }
1321
1322         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1323                 out_charset = C_ISO_8859_15;
1324                 return out_charset;
1325         }
1326
1327         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1328                 const gchar *p;
1329
1330                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1331                                  strlen(locale_table[i].locale))) {
1332                         out_charset = locale_table[i].out_charset;
1333                         break;
1334                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1335                          !strchr(p + 1, '.')) {
1336                         if (strlen(cur_locale) == 2 &&
1337                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1338                                 out_charset = locale_table[i].out_charset;
1339                                 break;
1340                         }
1341                 }
1342         }
1343
1344         return out_charset;
1345 }
1346
1347 const gchar *conv_get_outgoing_charset_str(void)
1348 {
1349         CharSet out_charset;
1350         const gchar *str;
1351
1352         if (prefs_common.outgoing_charset) {
1353                 if (!isalpha((guchar)prefs_common.outgoing_charset[0])) {
1354                         g_free(prefs_common.outgoing_charset);
1355                         prefs_common.outgoing_charset = g_strdup(CS_AUTO);
1356                 } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0)
1357                         return prefs_common.outgoing_charset;
1358         }
1359
1360         out_charset = conv_get_outgoing_charset();
1361         str = conv_get_charset_str(out_charset);
1362
1363         return str ? str : CS_US_ASCII;
1364 }
1365
1366 gboolean conv_is_multibyte_encoding(CharSet encoding)
1367 {
1368         switch (encoding) {
1369         case C_EUC_JP:
1370         case C_EUC_KR:
1371         case C_EUC_TW:
1372         case C_EUC_CN:
1373         case C_ISO_2022_JP:
1374         case C_ISO_2022_JP_2:
1375         case C_ISO_2022_JP_3:
1376         case C_ISO_2022_KR:
1377         case C_ISO_2022_CN:
1378         case C_SHIFT_JIS:
1379         case C_GB2312:
1380         case C_BIG5:
1381         case C_UTF_8:
1382         case C_UTF_7:
1383                 return TRUE;
1384         default:
1385                 return FALSE;
1386         }
1387 }
1388
1389 const gchar *conv_get_current_locale(void)
1390 {
1391         const gchar *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 /*      debug_print("current locale: %s\n",
1402                     cur_locale ? cur_locale : "(none)"); */
1403
1404         return cur_locale;
1405 }
1406
1407 void conv_unmime_header_overwrite(gchar *str)
1408 {
1409         gchar *buf;
1410         gint buflen;
1411         CharSet cur_charset;
1412         const gchar *locale;
1413
1414         g_return_if_fail(str != NULL);
1415         
1416         cur_charset = conv_get_current_charset();
1417
1418 #warning FIXME_GTK2
1419 /* Should we always ensure to convert? */
1420         locale = conv_get_current_locale();
1421
1422         if (locale && !strncasecmp(locale, "ja", 2)) {
1423                 buflen = strlen(str) * 2 + 1;
1424                 Xalloca(buf, buflen, return);
1425                 conv_anytodisp(buf, buflen, str);
1426                 unmime_header(str, buf);
1427         } else {
1428                 buflen = strlen(str) + 1;
1429                 Xalloca(buf, buflen, return);
1430                 unmime_header(buf, str);
1431                 strncpy2(str, buf, buflen);
1432         }
1433 }
1434
1435 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1436                         const gchar *charset)
1437 {
1438         CharSet cur_charset;
1439         const gchar *locale;
1440
1441         cur_charset = conv_get_current_charset();
1442
1443 #warning FIXME_GTK2
1444 /* Should we always ensure to convert? */
1445         locale = conv_get_current_locale();
1446
1447         if (locale && !strncasecmp(locale, "ja", 2)) {
1448                 gchar *buf;
1449                 gint buflen;
1450
1451                 buflen = strlen(str) * 2 + 1;
1452                 Xalloca(buf, buflen, return);
1453                 conv_anytodisp(buf, buflen, str);
1454                 unmime_header(outbuf, buf);
1455         } else if (g_utf8_validate(str, -1, NULL)) {
1456                 unmime_header(outbuf, str);
1457         } else {
1458                 gchar *buf;
1459                 gint buflen;
1460                 const gchar *src_codeset, *dest_codeset;
1461                 src_codeset = conv_get_current_charset_str();
1462                 dest_codeset = CS_UTF_8;
1463                 buf = conv_codeset_strdup(str, src_codeset, dest_codeset);
1464                 unmime_header(outbuf, buf);
1465         }
1466 }
1467
1468 #define MAX_LINELEN             76
1469 #define MAX_HARD_LINELEN        996
1470 #define MIMESEP_BEGIN           "=?"
1471 #define MIMESEP_END             "?="
1472
1473 #define B64LEN(len)     ((len) / 3 * 4 + ((len) % 3 ? 4 : 0))
1474
1475 #define LBREAK_IF_REQUIRED(cond, is_plain_text)                         \
1476 {                                                                       \
1477         if (len - (destp - (guchar *)dest) < MAX_LINELEN + 2) {         \
1478                 *destp = '\0';                                          \
1479                 return;                                                 \
1480         }                                                               \
1481                                                                         \
1482         if ((cond) && *srcp) {                                          \
1483                 if (destp > (guchar *)dest && left < MAX_LINELEN - 1) { \
1484                         if (isspace(*(destp - 1)))                      \
1485                                 destp--;                                \
1486                         else if (is_plain_text && isspace(*srcp))       \
1487                                 srcp++;                                 \
1488                         if (*srcp) {                                    \
1489                                 *destp++ = '\n';                        \
1490                                 *destp++ = ' ';                         \
1491                                 left = MAX_LINELEN - 1;                 \
1492                         }                                               \
1493                 }                                                       \
1494         }                                                               \
1495 }
1496
1497 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1498                         gint header_len, gboolean addr_field)
1499 {
1500         const gchar *cur_encoding;
1501         const gchar *out_encoding;
1502         gint mimestr_len;
1503         gchar *mimesep_enc;
1504         gint left;
1505         const guchar *srcp = src;
1506         guchar *destp = dest;
1507         gboolean use_base64;
1508
1509         if (MB_CUR_MAX > 1) {
1510                 use_base64 = TRUE;
1511                 mimesep_enc = "?B?";
1512         } else {
1513                 use_base64 = FALSE;
1514                 mimesep_enc = "?Q?";
1515         }
1516
1517         cur_encoding = CS_UTF_8; /* gtk2 */
1518
1519         out_encoding = conv_get_outgoing_charset_str();
1520         if (!strcmp(out_encoding, CS_US_ASCII))
1521                 out_encoding = CS_ISO_8859_1;
1522
1523         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1524                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1525
1526         left = MAX_LINELEN - header_len;
1527
1528         while (*srcp) {
1529                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1530
1531                 while (isspace(*srcp)) {
1532                         *destp++ = *srcp++;
1533                         left--;
1534                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1535                 }
1536
1537                 /* output as it is if the next word is ASCII string */
1538                 if (!is_next_nonascii(srcp)) {
1539                         gint word_len;
1540
1541                         word_len = get_next_word_len(srcp);
1542                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1543                         while (word_len > 0) {
1544                                 LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
1545                                 *destp++ = *srcp++;
1546                                 left--;
1547                                 word_len--;
1548                         }
1549
1550                         continue;
1551                 }
1552
1553                 /* don't include parentheses in encoded strings */
1554                 if (addr_field && (*srcp == '(' || *srcp == ')')) {
1555                         LBREAK_IF_REQUIRED(left < 2, FALSE);
1556                         *destp++ = *srcp++;
1557                         left--;
1558                 }
1559
1560                 while (1) {
1561                         gint mb_len = 0;
1562                         gint cur_len = 0;
1563                         gchar *part_str;
1564                         gchar *out_str;
1565                         gchar *enc_str;
1566                         const guchar *p = srcp;
1567                         gint out_str_len;
1568                         gint out_enc_str_len;
1569                         gint mime_block_len;
1570                         gboolean cont = FALSE;
1571
1572                         while (*p != '\0') {
1573                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1574                                         break;
1575                                 /* don't include parentheses in encoded
1576                                    strings */
1577                                 if (addr_field && (*p == '(' || *p == ')'))
1578                                         break;
1579
1580                                 if (MB_CUR_MAX > 1) {
1581                                         mb_len = mblen(p, MB_CUR_MAX);
1582                                         if (mb_len < 0) {
1583                                                 g_warning("conv_encode_header(): invalid multibyte character encountered\n");
1584                                                 mb_len = 1;
1585                                         }
1586                                 } else
1587                                         mb_len = 1;
1588
1589                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1590                                 out_str = conv_codeset_strdup
1591                                         (part_str, cur_encoding, out_encoding);
1592                                 if (!out_str) {
1593                                         g_warning("conv_encode_header(): code conversion failed\n");
1594                                         conv_unreadable_8bit(part_str);
1595                                         out_str = g_strdup(part_str);
1596                                 }
1597                                 out_str_len = strlen(out_str);
1598
1599                                 if (use_base64)
1600                                         out_enc_str_len = B64LEN(out_str_len);
1601                                 else
1602                                         out_enc_str_len =
1603                                                 qp_get_q_encoding_len(out_str);
1604
1605                                 g_free(out_str);
1606
1607                                 if (mimestr_len + out_enc_str_len <= left) {
1608                                         cur_len += mb_len;
1609                                         p += mb_len;
1610                                 } else if (cur_len == 0) {
1611                                         LBREAK_IF_REQUIRED(1, FALSE);
1612                                         continue;
1613                                 } else {
1614                                         cont = TRUE;
1615                                         break;
1616                                 }
1617                         }
1618
1619                         if (cur_len > 0) {
1620                                 Xstrndup_a(part_str, srcp, cur_len, );
1621                                 out_str = conv_codeset_strdup
1622                                         (part_str, cur_encoding, out_encoding);
1623                                 if (!out_str) {
1624                                         g_warning("conv_encode_header(): code conversion failed\n");
1625                                         conv_unreadable_8bit(part_str);
1626                                         out_str = g_strdup(part_str);
1627                                 }
1628                                 out_str_len = strlen(out_str);
1629
1630                                 if (use_base64)
1631                                         out_enc_str_len = B64LEN(out_str_len);
1632                                 else
1633                                         out_enc_str_len =
1634                                                 qp_get_q_encoding_len(out_str);
1635
1636                                 Xalloca(enc_str, out_enc_str_len + 1, );
1637                                 if (use_base64)
1638                                         base64_encode(enc_str, out_str, out_str_len);
1639                                 else
1640                                         qp_q_encode(enc_str, out_str);
1641
1642                                 g_free(out_str);
1643
1644                                 /* output MIME-encoded string block */
1645                                 mime_block_len = mimestr_len + strlen(enc_str);
1646                                 g_snprintf(destp, mime_block_len + 1,
1647                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1648                                            out_encoding, mimesep_enc, enc_str);
1649                                 destp += mime_block_len;
1650                                 srcp += cur_len;
1651
1652                                 left -= mime_block_len;
1653                         }
1654
1655                         LBREAK_IF_REQUIRED(cont, FALSE);
1656
1657                         if (cur_len == 0)
1658                                 break;
1659                 }
1660         }
1661
1662         *destp = '\0';
1663 }
1664
1665 #undef LBREAK_IF_REQUIRED