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