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