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