fix typo
[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) cur_locale = g_getenv("LC_CTYPE");
1395         if (!cur_locale) cur_locale = g_getenv("LANG");
1396         if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL);
1397
1398 /*      debug_print("current locale: %s\n",
1399                     cur_locale ? cur_locale : "(none)"); */
1400
1401         return cur_locale;
1402 }
1403
1404 void conv_unmime_header_overwrite(gchar *str)
1405 {
1406         gchar *buf;
1407         gint buflen;
1408         CharSet cur_charset;
1409         const gchar *locale;
1410
1411         g_return_if_fail(str != NULL);
1412         
1413         cur_charset = conv_get_current_charset();
1414
1415 #warning FIXME_GTK2
1416 /* Should we always ensure to convert? */
1417         locale = conv_get_current_locale();
1418
1419         if (locale && !strncasecmp(locale, "ja", 2)) {
1420                 buflen = strlen(str) * 2 + 1;
1421                 Xalloca(buf, buflen, return);
1422                 conv_anytodisp(buf, buflen, str);
1423                 unmime_header(str, buf);
1424         } else {
1425                 buflen = strlen(str) + 1;
1426                 Xalloca(buf, buflen, return);
1427                 unmime_header(buf, str);
1428                 strncpy2(str, buf, buflen);
1429         }
1430 }
1431
1432 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1433                         const gchar *charset)
1434 {
1435         CharSet cur_charset;
1436         const gchar *locale;
1437
1438         cur_charset = conv_get_current_charset();
1439
1440 #warning FIXME_GTK2
1441 /* Should we always ensure to convert? */
1442         locale = conv_get_current_locale();
1443
1444         if (locale && !strncasecmp(locale, "ja", 2)) {
1445                 gchar *buf;
1446                 gint buflen;
1447
1448                 buflen = strlen(str) * 2 + 1;
1449                 Xalloca(buf, buflen, return);
1450                 conv_anytodisp(buf, buflen, str);
1451                 unmime_header(outbuf, buf);
1452         } else if (g_utf8_validate(str, -1, NULL)) {
1453                 unmime_header(outbuf, str);
1454         } else {
1455                 gchar *buf;
1456                 gint buflen;
1457                 const gchar *src_codeset, *dest_codeset;
1458                 src_codeset = conv_get_current_charset_str();
1459                 dest_codeset = CS_UTF_8;
1460                 buf = conv_codeset_strdup(str, src_codeset, dest_codeset);
1461                 unmime_header(outbuf, buf);
1462         }
1463 }
1464
1465 #define MAX_LINELEN             76
1466 #define MAX_HARD_LINELEN        996
1467 #define MIMESEP_BEGIN           "=?"
1468 #define MIMESEP_END             "?="
1469
1470 #define B64LEN(len)     ((len) / 3 * 4 + ((len) % 3 ? 4 : 0))
1471
1472 #define LBREAK_IF_REQUIRED(cond, is_plain_text)                         \
1473 {                                                                       \
1474         if (len - (destp - (guchar *)dest) < MAX_LINELEN + 2) {         \
1475                 *destp = '\0';                                          \
1476                 return;                                                 \
1477         }                                                               \
1478                                                                         \
1479         if ((cond) && *srcp) {                                          \
1480                 if (destp > (guchar *)dest && left < MAX_LINELEN - 1) { \
1481                         if (isspace(*(destp - 1)))                      \
1482                                 destp--;                                \
1483                         else if (is_plain_text && isspace(*srcp))       \
1484                                 srcp++;                                 \
1485                         if (*srcp) {                                    \
1486                                 *destp++ = '\n';                        \
1487                                 *destp++ = ' ';                         \
1488                                 left = MAX_LINELEN - 1;                 \
1489                         }                                               \
1490                 }                                                       \
1491         }                                                               \
1492 }
1493
1494 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1495                         gint header_len, gboolean addr_field)
1496 {
1497         const gchar *cur_encoding;
1498         const gchar *out_encoding;
1499         gint mimestr_len;
1500         gchar *mimesep_enc;
1501         gint left;
1502         const guchar *srcp = src;
1503         guchar *destp = dest;
1504         gboolean use_base64;
1505
1506         if (MB_CUR_MAX > 1) {
1507                 use_base64 = TRUE;
1508                 mimesep_enc = "?B?";
1509         } else {
1510                 use_base64 = FALSE;
1511                 mimesep_enc = "?Q?";
1512         }
1513
1514         cur_encoding = conv_get_current_charset_str();
1515         if (!strcmp(cur_encoding, CS_US_ASCII))
1516                 cur_encoding = CS_ISO_8859_1;
1517         out_encoding = conv_get_outgoing_charset_str();
1518         if (!strcmp(out_encoding, CS_US_ASCII))
1519                 out_encoding = CS_ISO_8859_1;
1520
1521         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1522                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1523
1524         left = MAX_LINELEN - header_len;
1525
1526         while (*srcp) {
1527                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1528
1529                 while (isspace(*srcp)) {
1530                         *destp++ = *srcp++;
1531                         left--;
1532                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1533                 }
1534
1535                 /* output as it is if the next word is ASCII string */
1536                 if (!is_next_nonascii(srcp)) {
1537                         gint word_len;
1538
1539                         word_len = get_next_word_len(srcp);
1540                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1541                         while (word_len > 0) {
1542                                 LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
1543                                 *destp++ = *srcp++;
1544                                 left--;
1545                                 word_len--;
1546                         }
1547
1548                         continue;
1549                 }
1550
1551                 /* don't include parentheses in encoded strings */
1552                 if (addr_field && (*srcp == '(' || *srcp == ')')) {
1553                         LBREAK_IF_REQUIRED(left < 2, FALSE);
1554                         *destp++ = *srcp++;
1555                         left--;
1556                 }
1557
1558                 while (1) {
1559                         gint mb_len = 0;
1560                         gint cur_len = 0;
1561                         gchar *part_str;
1562                         gchar *out_str;
1563                         gchar *enc_str;
1564                         const guchar *p = srcp;
1565                         gint out_str_len;
1566                         gint out_enc_str_len;
1567                         gint mime_block_len;
1568                         gboolean cont = FALSE;
1569
1570                         while (*p != '\0') {
1571                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1572                                         break;
1573                                 /* don't include parentheses in encoded
1574                                    strings */
1575                                 if (addr_field && (*p == '(' || *p == ')'))
1576                                         break;
1577
1578                                 if (MB_CUR_MAX > 1) {
1579                                         mb_len = mblen(p, MB_CUR_MAX);
1580                                         if (mb_len < 0) {
1581                                                 g_warning("conv_encode_header(): invalid multibyte character encountered\n");
1582                                                 mb_len = 1;
1583                                         }
1584                                 } else
1585                                         mb_len = 1;
1586
1587                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1588                                 out_str = conv_codeset_strdup
1589                                         (part_str, cur_encoding, out_encoding);
1590                                 if (!out_str) {
1591                                         g_warning("conv_encode_header(): code conversion failed\n");
1592                                         conv_unreadable_8bit(part_str);
1593                                         out_str = g_strdup(part_str);
1594                                 }
1595                                 out_str_len = strlen(out_str);
1596
1597                                 if (use_base64)
1598                                         out_enc_str_len = B64LEN(out_str_len);
1599                                 else
1600                                         out_enc_str_len =
1601                                                 qp_get_q_encoding_len(out_str);
1602
1603                                 g_free(out_str);
1604
1605                                 if (mimestr_len + out_enc_str_len <= left) {
1606                                         cur_len += mb_len;
1607                                         p += mb_len;
1608                                 } else if (cur_len == 0) {
1609                                         LBREAK_IF_REQUIRED(1, FALSE);
1610                                         continue;
1611                                 } else {
1612                                         cont = TRUE;
1613                                         break;
1614                                 }
1615                         }
1616
1617                         if (cur_len > 0) {
1618                                 Xstrndup_a(part_str, srcp, cur_len, );
1619                                 out_str = conv_codeset_strdup
1620                                         (part_str, cur_encoding, out_encoding);
1621                                 if (!out_str) {
1622                                         g_warning("conv_encode_header(): code conversion failed\n");
1623                                         conv_unreadable_8bit(part_str);
1624                                         out_str = g_strdup(part_str);
1625                                 }
1626                                 out_str_len = strlen(out_str);
1627
1628                                 if (use_base64)
1629                                         out_enc_str_len = B64LEN(out_str_len);
1630                                 else
1631                                         out_enc_str_len =
1632                                                 qp_get_q_encoding_len(out_str);
1633
1634                                 Xalloca(enc_str, out_enc_str_len + 1, );
1635                                 if (use_base64)
1636                                         base64_encode(enc_str, out_str, out_str_len);
1637                                 else
1638                                         qp_q_encode(enc_str, out_str);
1639
1640                                 g_free(out_str);
1641
1642                                 /* output MIME-encoded string block */
1643                                 mime_block_len = mimestr_len + strlen(enc_str);
1644                                 g_snprintf(destp, mime_block_len + 1,
1645                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1646                                            out_encoding, mimesep_enc, enc_str);
1647                                 destp += mime_block_len;
1648                                 srcp += cur_len;
1649
1650                                 left -= mime_block_len;
1651                         }
1652
1653                         LBREAK_IF_REQUIRED(cont, FALSE);
1654
1655                         if (cur_len == 0)
1656                                 break;
1657                 }
1658         }
1659
1660         *destp = '\0';
1661 }
1662
1663 #undef LBREAK_IF_REQUIRED