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