sync with 0.8.10cvs18
[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 <= 0xff)
386                         return FALSE;
387         } else if (c1 == 0xf4) {
388                 if (c2 >= 0xa7 && c2 <= 0xff)
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 #define NCV     '\0'
466
467 void conv_mb_alnum(gchar *str)
468 {
469         static guchar char_tbl[] = {
470                 /* 0xa0 - 0xaf */
471                 NCV, ' ', NCV, NCV, ',', '.', NCV, ':',
472                 ';', '?', '!', NCV, NCV, NCV, NCV, NCV,
473                 /* 0xb0 - 0xbf */
474                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
475                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
476                 /* 0xc0 - 0xcf */
477                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
478                 NCV, NCV, '(', ')', NCV, NCV, '[', ']',
479                 /* 0xd0 - 0xdf */
480                 '{', '}', NCV, NCV, NCV, NCV, NCV, NCV,
481                 NCV, NCV, NCV, NCV, '+', '-', NCV, NCV,
482                 /* 0xe0 - 0xef */
483                 NCV, '=', NCV, '<', '>', NCV, NCV, NCV,
484                 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV
485         };
486
487         register guchar *p = str;
488         register gint len;
489
490         len = strlen(str);
491
492         while (len > 1) {
493                 if (*p == 0xa3) {
494                         register guchar ch = *(p + 1);
495
496                         if (ch >= 0xb0 && ch <= 0xfa) {
497                                 /* [a-zA-Z] */
498                                 *p = ch & 0x7f;
499                                 p++;
500                                 len--;
501                                 memmove(p, p + 1, len);
502                                 len--;
503                         } else  {
504                                 p += 2;
505                                 len -= 2;
506                         }
507                 } else if (*p == 0xa1) {
508                         register guchar ch = *(p + 1);
509
510                         if (ch >= 0xa0 && ch <= 0xef &&
511                             NCV != char_tbl[ch - 0xa0]) {
512                                 *p = char_tbl[ch - 0xa0];
513                                 p++;
514                                 len--;
515                                 memmove(p, p + 1, len);
516                                 len--;
517                         } else {
518                                 p += 2;
519                                 len -= 2;
520                         }
521                 } else if (iseuckanji(*p)) {
522                         p += 2;
523                         len -= 2;
524                 } else {
525                         p++;
526                         len--;
527                 }
528         }
529 }
530
531 CharSet conv_guess_ja_encoding(const gchar *str)
532 {
533         const guchar *p = str;
534         CharSet guessed = C_US_ASCII;
535
536         while (*p != '\0') {
537                 if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) {
538                         if (guessed == C_US_ASCII)
539                                 return C_ISO_2022_JP;
540                         p += 2;
541                 } else if (isascii(*p)) {
542                         p++;
543                 } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) {
544                         if (*p >= 0xfd && *p <= 0xfe)
545                                 return C_EUC_JP;
546                         else if (guessed == C_SHIFT_JIS) {
547                                 if ((issjiskanji1(*p) &&
548                                      issjiskanji2(*(p + 1))) ||
549                                     issjishwkana(*p))
550                                         guessed = C_SHIFT_JIS;
551                                 else
552                                         guessed = C_EUC_JP;
553                         } else
554                                 guessed = C_EUC_JP;
555                         p += 2;
556                 } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) {
557                         if (iseuchwkana1(*p) && iseuchwkana2(*(p + 1)))
558                                 guessed = C_SHIFT_JIS;
559                         else
560                                 return C_SHIFT_JIS;
561                         p += 2;
562                 } else if (issjishwkana(*p)) {
563                         guessed = C_SHIFT_JIS;
564                         p++;
565                 } else {
566                         p++;
567                 }
568         }
569
570         return guessed;
571 }
572
573 void conv_jistodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
574 {
575         conv_jistoeuc(outbuf, outlen, inbuf);
576         conv_unreadable_eucjp(outbuf);
577 }
578
579 void conv_sjistodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
580 {
581         conv_sjistoeuc(outbuf, outlen, inbuf);
582         conv_unreadable_eucjp(outbuf);
583 }
584
585 void conv_euctodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
586 {
587         strncpy2(outbuf, inbuf, outlen);
588         conv_unreadable_eucjp(outbuf);
589 }
590
591 void conv_anytodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
592 {
593         conv_anytoeuc(outbuf, outlen, inbuf);
594         conv_unreadable_eucjp(outbuf);
595 }
596
597 void conv_ustodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
598 {
599         strncpy2(outbuf, inbuf, outlen);
600         conv_unreadable_8bit(outbuf);
601 }
602
603 void conv_latintodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
604 {
605         strncpy2(outbuf, inbuf, outlen);
606         conv_unreadable_latin(outbuf);
607 }
608
609 void conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf)
610 {
611         strncpy2(outbuf, inbuf, outlen);
612 }
613
614 void conv_localetodisp(gchar *outbuf, gint outlen, const gchar *inbuf)
615 {
616         strncpy2(outbuf, inbuf, outlen);
617
618         switch (conv_get_current_charset()) {
619         case C_US_ASCII:
620         case C_ISO_8859_1:
621         case C_ISO_8859_2:
622         case C_ISO_8859_4:
623         case C_ISO_8859_5:
624         case C_ISO_8859_7:
625         case C_ISO_8859_8:
626         case C_ISO_8859_9:
627         case C_ISO_8859_11:
628         case C_ISO_8859_13:
629         case C_ISO_8859_15:
630                 conv_unreadable_latin(outbuf);
631                 break;
632         case C_EUC_JP:
633                 conv_unreadable_eucjp(outbuf);
634                 break;
635         default:
636                 break;
637         }
638 }
639
640 CodeConverter *conv_code_converter_new(const gchar *charset)
641 {
642         CodeConverter *conv;
643
644         conv = g_new0(CodeConverter, 1);
645         conv->code_conv_func = conv_get_code_conv_func(charset, NULL);
646         conv->charset_str = g_strdup(charset);
647         conv->charset = conv_get_charset_from_str(charset);
648
649         return conv;
650 }
651
652 void conv_code_converter_destroy(CodeConverter *conv)
653 {
654         g_free(conv->charset_str);
655         g_free(conv);
656 }
657
658 gint conv_convert(CodeConverter *conv, gchar *outbuf, gint outlen,
659                   const gchar *inbuf)
660 {
661 #if HAVE_ICONV
662         if (conv->code_conv_func != conv_noconv)
663                 conv->code_conv_func(outbuf, outlen, inbuf);
664         else {
665                 gchar *str;
666
667                 str = conv_codeset_strdup(inbuf, conv->charset_str, NULL);
668                 if (!str)
669                         return -1;
670                 else {
671                         strncpy2(outbuf, str, outlen);
672                         g_free(str);
673                 }
674         }
675 #else /* !HAVE_ICONV */
676         conv->code_conv_func(outbuf, outlen, inbuf);
677 #endif
678
679         return 0;
680 }
681
682 gchar *conv_codeset_strdup(const gchar *inbuf,
683                            const gchar *src_code, const gchar *dest_code)
684 {
685         gchar *buf;
686         size_t len;
687         CodeConvFunc conv_func;
688
689         conv_func = conv_get_code_conv_func(src_code, dest_code);
690         if (conv_func != conv_noconv) {
691                 len = (strlen(inbuf) + 1) * 3;
692                 buf = g_malloc(len);
693                 if (!buf) return NULL;
694
695                 conv_func(buf, len, inbuf);
696                 return g_realloc(buf, strlen(buf) + 1);
697         }
698
699 #if HAVE_ICONV
700         if (!src_code)
701                 src_code = conv_get_outgoing_charset_str();
702         if (!dest_code)
703                 dest_code = conv_get_current_charset_str();
704
705         /* don't convert if current codeset is US-ASCII */
706         if (!strcasecmp(dest_code, CS_US_ASCII))
707                 return g_strdup(inbuf);
708
709         /* don't convert if src and dest codeset are identical */
710         if (!strcasecmp(src_code, dest_code))
711                 return g_strdup(inbuf);
712
713         return conv_iconv_strdup(inbuf, src_code, dest_code);
714 #else
715         return g_strdup(inbuf);
716 #endif /* HAVE_ICONV */
717 }
718
719 CodeConvFunc conv_get_code_conv_func(const gchar *src_charset_str,
720                                      const gchar *dest_charset_str)
721 {
722         CodeConvFunc code_conv = conv_noconv;
723         CharSet src_charset;
724         CharSet dest_charset;
725
726         if (!src_charset_str)
727                 src_charset = conv_get_current_charset();
728         else
729                 src_charset = conv_get_charset_from_str(src_charset_str);
730
731         /* auto detection mode */
732         if (!src_charset_str && !dest_charset_str) {
733                 if (src_charset == C_EUC_JP || src_charset == C_SHIFT_JIS)
734                         return conv_anytodisp;
735                 else
736                         return conv_noconv;
737         }
738
739         dest_charset = conv_get_charset_from_str(dest_charset_str);
740
741         if (dest_charset == C_US_ASCII)
742                 return conv_ustodisp;
743
744         switch (src_charset) {
745         case C_ISO_2022_JP:
746         case C_ISO_2022_JP_2:
747                 if (dest_charset == C_AUTO)
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 #if !HAVE_ICONV
758         case C_ISO_8859_2:
759         case C_ISO_8859_4:
760         case C_ISO_8859_5:
761         case C_ISO_8859_7:
762         case C_ISO_8859_8:
763         case C_ISO_8859_9:
764         case C_ISO_8859_11:
765         case C_ISO_8859_13:
766         case C_ISO_8859_15:
767 #endif
768                 if (dest_charset == C_AUTO)
769                         code_conv = conv_latintodisp;
770                 break;
771         case C_SHIFT_JIS:
772                 if (dest_charset == C_AUTO)
773                         code_conv = conv_sjistodisp;
774                 else if (dest_charset == C_EUC_JP)
775                         code_conv = conv_sjistoeuc;
776                 break;
777         case C_EUC_JP:
778                 if (dest_charset == C_AUTO)
779                         code_conv = conv_euctodisp;
780                 else if (dest_charset == C_ISO_2022_JP ||
781                          dest_charset == C_ISO_2022_JP_2)
782                         code_conv = conv_euctojis;
783                 break;
784         default:
785                 break;
786         }
787
788         return code_conv;
789 }
790
791 #if HAVE_ICONV
792 gchar *conv_iconv_strdup(const gchar *inbuf,
793                          const gchar *src_code, const gchar *dest_code)
794 {
795         iconv_t cd;
796         const gchar *inbuf_p;
797         gchar *outbuf;
798         gchar *outbuf_p;
799         gint in_size;
800         gint in_left;
801         gint out_size;
802         gint out_left;
803         gint n_conv;
804
805         cd = iconv_open(dest_code, src_code);
806         if (cd == (iconv_t)-1)
807                 return NULL;
808
809         inbuf_p = inbuf;
810         in_size = strlen(inbuf) + 1;
811         in_left = in_size;
812         out_size = in_size * 2;
813         outbuf = g_malloc(out_size);
814         outbuf_p = outbuf;
815         out_left = out_size;
816
817         while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left,
818                                &outbuf_p, &out_left)) < 0) {
819                 if (EILSEQ == errno) {
820                         inbuf_p++;
821                         in_left--;
822                         *outbuf_p++ = SUBST_CHAR;
823                         out_left--;
824                 } else if (EINVAL == errno) {
825                         *outbuf_p = '\0';
826                         break;
827                 } else if (E2BIG == errno) {
828                         out_size *= 2;
829                         outbuf = g_realloc(outbuf, out_size);
830                         inbuf_p = inbuf;
831                         in_left = in_size;
832                         outbuf_p = outbuf;
833                         out_left = out_size;
834                 } else {
835                         g_warning("conv_iconv_strdup(): %s\n",
836                                   g_strerror(errno));
837                         *outbuf_p = '\0';
838                         break;
839                 }
840         }
841
842         iconv(cd, NULL, NULL, &outbuf_p, &out_left);
843         outbuf = g_realloc(outbuf, strlen(outbuf) + 1);
844
845         iconv_close(cd);
846
847         return outbuf;
848 }
849 #endif /* HAVE_ICONV */
850
851 static const struct {
852         CharSet charset;
853         gchar *const name;
854 } charsets[] = {
855         {C_US_ASCII,            CS_US_ASCII},
856         {C_US_ASCII,            CS_ANSI_X3_4_1968},
857         {C_UTF_8,               CS_UTF_8},
858         {C_ISO_8859_1,          CS_ISO_8859_1},
859         {C_ISO_8859_2,          CS_ISO_8859_2},
860         {C_ISO_8859_4,          CS_ISO_8859_4},
861         {C_ISO_8859_5,          CS_ISO_8859_5},
862         {C_ISO_8859_7,          CS_ISO_8859_7},
863         {C_ISO_8859_8,          CS_ISO_8859_8},
864         {C_ISO_8859_9,          CS_ISO_8859_9},
865         {C_ISO_8859_11,         CS_ISO_8859_11},
866         {C_ISO_8859_13,         CS_ISO_8859_13},
867         {C_ISO_8859_15,         CS_ISO_8859_15},
868         {C_BALTIC,              CS_BALTIC},
869         {C_CP1251,              CS_CP1251},
870         {C_WINDOWS_1251,        CS_WINDOWS_1251},
871         {C_KOI8_R,              CS_KOI8_R},
872         {C_KOI8_U,              CS_KOI8_U},
873         {C_ISO_2022_JP,         CS_ISO_2022_JP},
874         {C_ISO_2022_JP_2,       CS_ISO_2022_JP_2},
875         {C_EUC_JP,              CS_EUC_JP},
876         {C_EUC_JP,              CS_EUCJP},
877         {C_SHIFT_JIS,           CS_SHIFT_JIS},
878         {C_SHIFT_JIS,           CS_SHIFT__JIS},
879         {C_SHIFT_JIS,           CS_SJIS},
880         {C_ISO_2022_KR,         CS_ISO_2022_KR},
881         {C_EUC_KR,              CS_EUC_KR},
882         {C_ISO_2022_CN,         CS_ISO_2022_CN},
883         {C_EUC_CN,              CS_EUC_CN},
884         {C_GB2312,              CS_GB2312},
885         {C_EUC_TW,              CS_EUC_TW},
886         {C_BIG5,                CS_BIG5},
887         {C_TIS_620,             CS_TIS_620},
888         {C_WINDOWS_874,         CS_WINDOWS_874},
889 };
890
891 static const struct {
892         gchar *const locale;
893         CharSet charset;
894         CharSet out_charset;
895 } locale_table[] = {
896         {"ja_JP.eucJP"  , C_EUC_JP      , C_ISO_2022_JP},
897         {"ja_JP.ujis"   , C_EUC_JP      , C_ISO_2022_JP},
898         {"ja_JP.EUC"    , C_EUC_JP      , C_ISO_2022_JP},
899         {"ja_JP.SJIS"   , C_SHIFT_JIS   , C_ISO_2022_JP},
900         {"ja_JP.JIS"    , C_ISO_2022_JP , C_ISO_2022_JP},
901         {"ja_JP"        , C_EUC_JP      , C_ISO_2022_JP},
902         {"ko_KR"        , C_EUC_KR      , C_EUC_KR},
903         {"zh_CN.GB2312" , C_GB2312      , C_GB2312},
904         {"zh_CN"        , C_GB2312      , C_GB2312},
905         {"zh_TW.eucTW"  , C_EUC_TW      , C_BIG5},
906         {"zh_TW.Big5"   , C_BIG5        , C_BIG5},
907         {"zh_TW"        , C_BIG5        , C_BIG5},
908
909         {"ru_RU.KOI8-R" , C_KOI8_R      , C_KOI8_R},
910         {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
911         {"ru_RU"        , C_ISO_8859_5  , C_KOI8_R},
912         {"ru_UA"        , C_KOI8_U      , C_KOI8_U},
913         {"uk_UA"        , C_KOI8_U      , C_KOI8_U},
914         {"be_BY"        , C_WINDOWS_1251, C_WINDOWS_1251},
915         {"bg_BG"        , C_WINDOWS_1251, C_WINDOWS_1251},
916
917         {"en_US"        , C_ISO_8859_1  , C_ISO_8859_1},
918         {"ca_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
919         {"da_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
920         {"de_DE"        , C_ISO_8859_1  , C_ISO_8859_1},
921         {"nl_NL"        , C_ISO_8859_1  , C_ISO_8859_1},
922         {"et_EE"        , C_ISO_8859_1  , C_ISO_8859_1},
923         {"fi_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
924         {"fr_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
925         {"is_IS"        , C_ISO_8859_1  , C_ISO_8859_1},
926         {"it_IT"        , C_ISO_8859_1  , C_ISO_8859_1},
927         {"no_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
928         {"pt_PT"        , C_ISO_8859_1  , C_ISO_8859_1},
929         {"pt_BR"        , C_ISO_8859_1  , C_ISO_8859_1},
930         {"es_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
931         {"sv_SE"        , C_ISO_8859_1  , C_ISO_8859_1},
932
933         {"hr_HR"        , C_ISO_8859_2  , C_ISO_8859_2},
934         {"hu_HU"        , C_ISO_8859_2  , C_ISO_8859_2},
935         {"pl_PL"        , C_ISO_8859_2  , C_ISO_8859_2},
936         {"ro_RO"        , C_ISO_8859_2  , C_ISO_8859_2},
937         {"sk_SK"        , C_ISO_8859_2  , C_ISO_8859_2},
938         {"sl_SI"        , C_ISO_8859_2  , C_ISO_8859_2},
939         {"el_GR"        , C_ISO_8859_7  , C_ISO_8859_7},
940         {"iw_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
941         {"tr_TR"        , C_ISO_8859_9  , C_ISO_8859_9},
942
943         {"th_TH"        , C_TIS_620     , C_TIS_620},
944         /* {"th_TH"     , C_WINDOWS_874}, */
945         /* {"th_TH"     , C_ISO_8859_11}, */
946
947         {"lt_LT.iso88594"       , C_ISO_8859_4  , C_ISO_8859_4},
948         {"lt_LT.ISO8859-4"      , C_ISO_8859_4  , C_ISO_8859_4},
949         {"lt_LT.ISO_8859-4"     , C_ISO_8859_4  , C_ISO_8859_4},
950         {"lt_LT"                , C_ISO_8859_13 , C_ISO_8859_13},
951         {"lv_LV"                , C_ISO_8859_13 , C_ISO_8859_13},
952
953         {"C"                    , C_US_ASCII    , C_US_ASCII},
954         {"POSIX"                , C_US_ASCII    , C_US_ASCII},
955         {"ANSI_X3.4-1968"       , C_US_ASCII    , C_US_ASCII},
956 };
957
958 static GHashTable *conv_get_charset_to_str_table(void)
959 {
960         static GHashTable *table;
961         gint i;
962
963         if (table)
964                 return table;
965
966         table = g_hash_table_new(NULL, g_direct_equal);
967
968         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
969                 if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
970                     == NULL) {
971                         g_hash_table_insert
972                                 (table, GUINT_TO_POINTER(charsets[i].charset),
973                                  charsets[i].name);
974                 }
975         }
976
977         return table;
978 }
979
980 static gint str_case_equal(gconstpointer v, gconstpointer v2)
981 {
982         return strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
983 }
984
985 static guint str_case_hash(gconstpointer key)
986 {
987         const gchar *p = key;
988         guint h = *p;
989
990         if (h) {
991                 h = tolower(h);
992                 for (p += 1; *p != '\0'; p++)
993                         h = (h << 5) - h + tolower(*p);
994         }
995
996         return h;
997 }
998
999 static GHashTable *conv_get_charset_from_str_table(void)
1000 {
1001         static GHashTable *table;
1002         gint i;
1003
1004         if (table)
1005                 return table;
1006
1007         table = g_hash_table_new(str_case_hash, str_case_equal);
1008
1009         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1010                 g_hash_table_insert(table, charsets[i].name,
1011                                     GUINT_TO_POINTER(charsets[i].charset));
1012         }
1013
1014         return table;
1015 }
1016
1017 const gchar *conv_get_charset_str(CharSet charset)
1018 {
1019         GHashTable *table;
1020
1021         table = conv_get_charset_to_str_table();
1022         return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
1023 }
1024
1025 CharSet conv_get_charset_from_str(const gchar *charset)
1026 {
1027         GHashTable *table;
1028
1029         if (!charset) return C_AUTO;
1030
1031         table = conv_get_charset_from_str_table();
1032         return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset));
1033 }
1034
1035 CharSet conv_get_current_charset(void)
1036 {
1037         static CharSet cur_charset = -1;
1038         const gchar *cur_locale;
1039         const gchar *p;
1040         gint i;
1041
1042         if (cur_charset != -1)
1043                 return cur_charset;
1044
1045         cur_locale = conv_get_current_locale();
1046         if (!cur_locale) {
1047                 cur_charset = C_US_ASCII;
1048                 return cur_charset;
1049         }
1050
1051         if (strcasestr(cur_locale, "UTF-8")) {
1052                 cur_charset = C_UTF_8;
1053                 return cur_charset;
1054         }
1055
1056         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1057                 cur_charset = C_ISO_8859_15;
1058                 return cur_charset;
1059         }
1060
1061         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1062                 const gchar *p;
1063
1064                 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1065                    "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1066                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1067                                  strlen(locale_table[i].locale))) {
1068                         cur_charset = locale_table[i].charset;
1069                         return cur_charset;
1070                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1071                          !strchr(p + 1, '.')) {
1072                         if (strlen(cur_locale) == 2 &&
1073                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1074                                 cur_charset = locale_table[i].charset;
1075                                 return cur_charset;
1076                         }
1077                 }
1078         }
1079
1080         cur_charset = C_AUTO;
1081         return cur_charset;
1082 }
1083
1084 const gchar *conv_get_current_charset_str(void)
1085 {
1086         static const gchar *codeset = NULL;
1087
1088         if (!codeset)
1089                 codeset = conv_get_charset_str(conv_get_current_charset());
1090
1091         return codeset ? codeset : CS_US_ASCII;
1092 }
1093
1094 CharSet conv_get_outgoing_charset(void)
1095 {
1096         static CharSet out_charset = -1;
1097         const gchar *cur_locale;
1098         const gchar *p;
1099         gint i;
1100
1101         if (out_charset != -1)
1102                 return out_charset;
1103
1104         cur_locale = conv_get_current_locale();
1105         if (!cur_locale) {
1106                 out_charset = C_AUTO;
1107                 return out_charset;
1108         }
1109
1110         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1111                 out_charset = C_ISO_8859_15;
1112                 return out_charset;
1113         }
1114
1115         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1116                 const gchar *p;
1117
1118                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1119                                  strlen(locale_table[i].locale))) {
1120                         out_charset = locale_table[i].out_charset;
1121                         break;
1122                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1123                          !strchr(p + 1, '.')) {
1124                         if (strlen(cur_locale) == 2 &&
1125                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1126                                 out_charset = locale_table[i].out_charset;
1127                                 break;
1128                         }
1129                 }
1130         }
1131
1132 #if !HAVE_ICONV
1133         /* encoding conversion without iconv() is only supported
1134            on Japanese locale for now */
1135         if (out_charset == C_ISO_2022_JP)
1136                 return out_charset;
1137         else
1138                 return conv_get_current_charset();
1139 #endif
1140
1141         return out_charset;
1142 }
1143
1144 const gchar *conv_get_outgoing_charset_str(void)
1145 {
1146         CharSet out_charset;
1147         const gchar *str;
1148
1149         if (prefs_common.outgoing_charset) {
1150                 if (!isalpha(prefs_common.outgoing_charset[0])) {
1151                         g_free(prefs_common.outgoing_charset);
1152                         prefs_common.outgoing_charset = g_strdup(CS_AUTO);
1153                 } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0)
1154                         return prefs_common.outgoing_charset;
1155         }
1156
1157         out_charset = conv_get_outgoing_charset();
1158         str = conv_get_charset_str(out_charset);
1159
1160         return str ? str : CS_US_ASCII;
1161 }
1162
1163 const gchar *conv_get_current_locale(void)
1164 {
1165         gchar *cur_locale;
1166
1167         cur_locale = g_getenv("LC_ALL");
1168         if (!cur_locale) cur_locale = g_getenv("LC_CTYPE");
1169         if (!cur_locale) cur_locale = g_getenv("LANG");
1170         if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL);
1171
1172         debug_print("current locale: %s\n",
1173                     cur_locale ? cur_locale : "(none)");
1174
1175         return cur_locale;
1176 }
1177
1178 void conv_unmime_header_overwrite(gchar *str)
1179 {
1180         gchar *buf;
1181         gint buflen;
1182         CharSet cur_charset;
1183
1184         cur_charset = conv_get_current_charset();
1185
1186         if (cur_charset == C_EUC_JP) {
1187                 buflen = strlen(str) * 2 + 1;
1188                 Xalloca(buf, buflen, return);
1189                 conv_anytodisp(buf, buflen, str);
1190                 unmime_header(str, buf);
1191         } else {
1192                 buflen = strlen(str) + 1;
1193                 Xalloca(buf, buflen, return);
1194                 unmime_header(buf, str);
1195                 strncpy2(str, buf, buflen);
1196         }
1197 }
1198
1199 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1200                         const gchar *charset)
1201 {
1202         CharSet cur_charset;
1203
1204         cur_charset = conv_get_current_charset();
1205
1206         if (cur_charset == C_EUC_JP) {
1207                 gchar *buf;
1208                 gint buflen;
1209
1210                 buflen = strlen(str) * 2 + 1;
1211                 Xalloca(buf, buflen, return);
1212                 conv_anytodisp(buf, buflen, str);
1213                 unmime_header(outbuf, buf);
1214         } else
1215                 unmime_header(outbuf, str);
1216 }
1217
1218 #define MAX_LINELEN     76
1219 #define MIMESEP_BEGIN   "=?"
1220 #define MIMESEP_END     "?="
1221
1222 #define B64LEN(len)     ((len) / 3 * 4 + ((len) % 3 ? 4 : 0))
1223
1224 #define LBREAK_IF_REQUIRED(cond, plaintext)                     \
1225 {                                                               \
1226         if (len - (destp - dest) < MAX_LINELEN + 2) {           \
1227                 *destp = '\0';                                  \
1228                 return;                                         \
1229         }                                                       \
1230                                                                 \
1231         if ((cond) && *srcp) {                                  \
1232                 if (destp > dest && left < MAX_LINELEN - 1) {   \
1233                         if (isspace(*(destp - 1)))              \
1234                                 destp--;                        \
1235                         else if (plaintext && isspace(*srcp))   \
1236                                 srcp++;                         \
1237                         if (*srcp) {                            \
1238                                 *destp++ = '\n';                \
1239                                 *destp++ = ' ';                 \
1240                                 left = MAX_LINELEN - 1;         \
1241                         }                                       \
1242                 }                                               \
1243         }                                                       \
1244 }
1245
1246 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1247                         gint header_len)
1248 {
1249         const gchar *cur_encoding;
1250         const gchar *out_encoding;
1251         gint mimestr_len;
1252         gchar *mimesep_enc;
1253         gint left;
1254         const gchar *srcp = src;
1255         gchar *destp = dest;
1256         gboolean use_base64;
1257
1258         if (MB_CUR_MAX > 1) {
1259                 use_base64 = TRUE;
1260                 mimesep_enc = "?B?";
1261         } else {
1262                 use_base64 = FALSE;
1263                 mimesep_enc = "?Q?";
1264         }
1265
1266         cur_encoding = conv_get_current_charset_str();
1267         if (!strcmp(cur_encoding, CS_US_ASCII))
1268                 cur_encoding = CS_ISO_8859_1;
1269         out_encoding = conv_get_outgoing_charset_str();
1270         if (!strcmp(out_encoding, CS_US_ASCII))
1271                 out_encoding = CS_ISO_8859_1;
1272
1273         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1274                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1275
1276         left = MAX_LINELEN - header_len;
1277
1278         while (*srcp) {
1279                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1280
1281                 while (isspace(*srcp)) {
1282                         *destp++ = *srcp++;
1283                         left--;
1284                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1285                 }
1286
1287                 /* output as it is if the next word is ASCII string */
1288                 if (!is_next_nonascii(srcp)) {
1289                         gint word_len;
1290
1291                         word_len = get_next_word_len(srcp);
1292                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1293                         while (word_len > 0) {
1294                                 LBREAK_IF_REQUIRED(left + 22 <= 0, TRUE);
1295                                 *destp++ = *srcp++;
1296                                 left--;
1297                                 word_len--;
1298                         }
1299
1300                         continue;
1301                 }
1302
1303                 while (1) {
1304                         gint mb_len = 0;
1305                         gint cur_len = 0;
1306                         gchar *part_str;
1307                         gchar *out_str;
1308                         gchar *enc_str;
1309                         const gchar *p = srcp;
1310                         gint out_str_len;
1311                         gint out_enc_str_len;
1312                         gint mime_block_len;
1313                         gboolean cont = FALSE;
1314
1315                         while (*p != '\0') {
1316                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1317                                         break;
1318
1319                                 if (MB_CUR_MAX > 1) {
1320                                         mb_len = mblen(p, MB_CUR_MAX);
1321                                         if (mb_len < 0) {
1322                                                 g_warning("conv_encode_header(): invalid multibyte character encountered\n");
1323                                                 mb_len = 1;
1324                                         }
1325                                 } else
1326                                         mb_len = 1;
1327
1328                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1329                                 out_str = conv_codeset_strdup
1330                                         (part_str, cur_encoding, out_encoding);
1331                                 if (!out_str) {
1332                                         g_warning("conv_encode_header(): code conversion failed\n");
1333                                         conv_unreadable_8bit(part_str);
1334                                         out_str = g_strdup(part_str);
1335                                 }
1336                                 out_str_len = strlen(out_str);
1337
1338                                 if (use_base64)
1339                                         out_enc_str_len = B64LEN(out_str_len);
1340                                 else
1341                                         out_enc_str_len =
1342                                                 qp_get_q_encoding_len(out_str);
1343
1344                                 g_free(out_str);
1345
1346                                 if (mimestr_len + out_enc_str_len <= left) {
1347                                         cur_len += mb_len;
1348                                         p += mb_len;
1349                                 } else if (cur_len == 0) {
1350                                         LBREAK_IF_REQUIRED(1, FALSE);
1351                                         continue;
1352                                 } else {
1353                                         cont = TRUE;
1354                                         break;
1355                                 }
1356                         }
1357
1358                         if (cur_len > 0) {
1359                                 Xstrndup_a(part_str, srcp, cur_len, );
1360                                 out_str = conv_codeset_strdup
1361                                         (part_str, cur_encoding, out_encoding);
1362                                 if (!out_str) {
1363                                         g_warning("conv_encode_header(): code conversion failed\n");
1364                                         conv_unreadable_8bit(part_str);
1365                                         out_str = g_strdup(part_str);
1366                                 }
1367                                 out_str_len = strlen(out_str);
1368
1369                                 if (use_base64)
1370                                         out_enc_str_len = B64LEN(out_str_len);
1371                                 else
1372                                         out_enc_str_len =
1373                                                 qp_get_q_encoding_len(out_str);
1374
1375                                 Xalloca(enc_str, out_enc_str_len + 1, );
1376                                 if (use_base64)
1377                                         base64_encode(enc_str, out_str, out_str_len);
1378                                 else
1379                                         qp_q_encode(enc_str, out_str);
1380
1381                                 g_free(out_str);
1382
1383                                 /* output MIME-encoded string block */
1384                                 mime_block_len = mimestr_len + strlen(enc_str);
1385                                 g_snprintf(destp, mime_block_len + 1,
1386                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1387                                            out_encoding, mimesep_enc, enc_str);
1388                                 destp += mime_block_len;
1389                                 srcp += cur_len;
1390
1391                                 left -= mime_block_len;
1392                         }
1393
1394                         LBREAK_IF_REQUIRED(cont, FALSE);
1395
1396                         if (cur_len == 0)
1397                                 break;
1398                 }
1399         }
1400
1401         *destp = '\0';
1402 }
1403
1404 #undef LBREAK_IF_REQUIRED