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