3d7fd9eec0c419f51ccb11f0723f0cfd9b33c122
[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 == 0xff)
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                 if (!strcasecmp(dest_code, CS_US_ASCII))
705                         dest_code = CS_ISO_8859_1;
706         }
707
708         /* don't convert if current codeset is US-ASCII */
709         if (!strcasecmp(dest_code, CS_US_ASCII))
710                 return g_strdup(inbuf);
711
712         /* don't convert if src and dest codeset are identical */
713         if (!strcasecmp(src_code, dest_code))
714                 return g_strdup(inbuf);
715
716         return conv_iconv_strdup(inbuf, src_code, dest_code);
717 #else
718         return g_strdup(inbuf);
719 #endif /* HAVE_ICONV */
720 }
721
722 CodeConvFunc conv_get_code_conv_func(const gchar *src_charset_str,
723                                      const gchar *dest_charset_str)
724 {
725         CodeConvFunc code_conv = conv_noconv;
726         CharSet src_charset;
727         CharSet dest_charset;
728
729         if (!src_charset_str)
730                 src_charset = conv_get_current_charset();
731         else
732                 src_charset = conv_get_charset_from_str(src_charset_str);
733
734         /* auto detection mode */
735         if (!src_charset_str && !dest_charset_str) {
736                 if (src_charset == C_EUC_JP || src_charset == C_SHIFT_JIS)
737                         return conv_anytodisp;
738                 else
739                         return conv_noconv;
740         }
741
742         dest_charset = conv_get_charset_from_str(dest_charset_str);
743
744         if (dest_charset == C_US_ASCII)
745                 return conv_ustodisp;
746
747         switch (src_charset) {
748         case C_ISO_2022_JP:
749         case C_ISO_2022_JP_2:
750                 if (dest_charset == C_AUTO)
751                         code_conv = conv_jistodisp;
752                 else if (dest_charset == C_EUC_JP)
753                         code_conv = conv_jistoeuc;
754                 break;
755         case C_US_ASCII:
756                 if (dest_charset == C_AUTO)
757                         code_conv = conv_ustodisp;
758                 break;
759         case C_ISO_8859_1:
760 #if !HAVE_ICONV
761         case C_ISO_8859_2:
762         case C_ISO_8859_4:
763         case C_ISO_8859_5:
764         case C_ISO_8859_7:
765         case C_ISO_8859_8:
766         case C_ISO_8859_9:
767         case C_ISO_8859_11:
768         case C_ISO_8859_13:
769         case C_ISO_8859_15:
770 #endif
771                 if (dest_charset == C_AUTO)
772                         code_conv = conv_latintodisp;
773                 break;
774         case C_SHIFT_JIS:
775                 if (dest_charset == C_AUTO)
776                         code_conv = conv_sjistodisp;
777                 else if (dest_charset == C_EUC_JP)
778                         code_conv = conv_sjistoeuc;
779                 break;
780         case C_EUC_JP:
781                 if (dest_charset == C_AUTO)
782                         code_conv = conv_euctodisp;
783                 else if (dest_charset == C_ISO_2022_JP ||
784                          dest_charset == C_ISO_2022_JP_2)
785                         code_conv = conv_euctojis;
786                 break;
787         default:
788                 break;
789         }
790
791         return code_conv;
792 }
793
794 #if HAVE_ICONV
795 gchar *conv_iconv_strdup(const gchar *inbuf,
796                          const gchar *src_code, const gchar *dest_code)
797 {
798         iconv_t cd;
799         const gchar *inbuf_p;
800         gchar *outbuf;
801         gchar *outbuf_p;
802         gint in_size;
803         gint in_left;
804         gint out_size;
805         gint out_left;
806         gint n_conv;
807
808         cd = iconv_open(dest_code, src_code);
809         if (cd == (iconv_t)-1)
810                 return NULL;
811
812         inbuf_p = inbuf;
813         in_size = strlen(inbuf) + 1;
814         in_left = in_size;
815         out_size = in_size * 2;
816         outbuf = g_malloc(out_size);
817         outbuf_p = outbuf;
818         out_left = out_size;
819
820         while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left,
821                                &outbuf_p, &out_left)) < 0) {
822                 if (EILSEQ == errno) {
823                         g_free(outbuf);
824                         outbuf = NULL;
825                         break;
826                 } else if (EINVAL == errno) {
827                         g_free(outbuf);
828                         outbuf = NULL;
829                         break;
830                 } else if (E2BIG == errno) {
831                         out_size *= 2;
832                         outbuf = g_realloc(outbuf, out_size);
833                         inbuf_p = inbuf;
834                         in_left = in_size;
835                         outbuf_p = outbuf;
836                         out_left = out_size;
837                 } else {
838                         g_warning("conv_iconv_strdup(): %s\n",
839                                   g_strerror(errno));
840                         *outbuf_p = '\0';
841                         break;
842                 }
843         }
844
845         if (outbuf) {
846                 iconv(cd, NULL, NULL, &outbuf_p, &out_left);
847                 outbuf = g_realloc(outbuf, strlen(outbuf) + 1);
848         }
849
850         iconv_close(cd);
851
852         return outbuf;
853 }
854 #endif /* HAVE_ICONV */
855
856 static const struct {
857         CharSet charset;
858         gchar *const name;
859 } charsets[] = {
860         {C_US_ASCII,            CS_US_ASCII},
861         {C_US_ASCII,            CS_ANSI_X3_4_1968},
862         {C_UTF_8,               CS_UTF_8},
863         {C_ISO_8859_1,          CS_ISO_8859_1},
864         {C_ISO_8859_2,          CS_ISO_8859_2},
865         {C_ISO_8859_4,          CS_ISO_8859_4},
866         {C_ISO_8859_5,          CS_ISO_8859_5},
867         {C_ISO_8859_7,          CS_ISO_8859_7},
868         {C_ISO_8859_8,          CS_ISO_8859_8},
869         {C_ISO_8859_9,          CS_ISO_8859_9},
870         {C_ISO_8859_11,         CS_ISO_8859_11},
871         {C_ISO_8859_13,         CS_ISO_8859_13},
872         {C_ISO_8859_15,         CS_ISO_8859_15},
873         {C_BALTIC,              CS_BALTIC},
874         {C_CP1251,              CS_CP1251},
875         {C_WINDOWS_1251,        CS_WINDOWS_1251},
876         {C_KOI8_R,              CS_KOI8_R},
877         {C_KOI8_U,              CS_KOI8_U},
878         {C_ISO_2022_JP,         CS_ISO_2022_JP},
879         {C_ISO_2022_JP_2,       CS_ISO_2022_JP_2},
880         {C_EUC_JP,              CS_EUC_JP},
881         {C_EUC_JP,              CS_EUCJP},
882         {C_SHIFT_JIS,           CS_SHIFT_JIS},
883         {C_SHIFT_JIS,           CS_SHIFT__JIS},
884         {C_SHIFT_JIS,           CS_SJIS},
885         {C_ISO_2022_KR,         CS_ISO_2022_KR},
886         {C_EUC_KR,              CS_EUC_KR},
887         {C_ISO_2022_CN,         CS_ISO_2022_CN},
888         {C_EUC_CN,              CS_EUC_CN},
889         {C_GB2312,              CS_GB2312},
890         {C_EUC_TW,              CS_EUC_TW},
891         {C_BIG5,                CS_BIG5},
892         {C_TIS_620,             CS_TIS_620},
893         {C_WINDOWS_874,         CS_WINDOWS_874},
894 };
895
896 static const struct {
897         gchar *const locale;
898         CharSet charset;
899         CharSet out_charset;
900 } locale_table[] = {
901         {"ja_JP.eucJP"  , C_EUC_JP      , C_ISO_2022_JP},
902         {"ja_JP.ujis"   , C_EUC_JP      , C_ISO_2022_JP},
903         {"ja_JP.EUC"    , C_EUC_JP      , C_ISO_2022_JP},
904         {"ja_JP.SJIS"   , C_SHIFT_JIS   , C_ISO_2022_JP},
905         {"ja_JP.JIS"    , C_ISO_2022_JP , C_ISO_2022_JP},
906         {"ja_JP"        , C_EUC_JP      , C_ISO_2022_JP},
907         {"ko_KR"        , C_EUC_KR      , C_EUC_KR},
908         {"zh_CN.GB2312" , C_GB2312      , C_GB2312},
909         {"zh_CN"        , C_GB2312      , C_GB2312},
910         {"zh_TW.eucTW"  , C_EUC_TW      , C_BIG5},
911         {"zh_TW.Big5"   , C_BIG5        , C_BIG5},
912         {"zh_TW"        , C_BIG5        , C_BIG5},
913
914         {"ru_RU.KOI8-R" , C_KOI8_R      , C_KOI8_R},
915         {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
916         {"ru_RU"        , C_ISO_8859_5  , C_KOI8_R},
917         {"ru_UA"        , C_KOI8_U      , C_KOI8_U},
918         {"uk_UA"        , C_KOI8_U      , C_KOI8_U},
919         {"be_BY"        , C_WINDOWS_1251, C_WINDOWS_1251},
920         {"bg_BG"        , C_WINDOWS_1251, C_WINDOWS_1251},
921
922         {"en_US"        , C_ISO_8859_1  , C_ISO_8859_1},
923         {"ca_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
924         {"da_DK"        , C_ISO_8859_1  , C_ISO_8859_1},
925         {"de_DE"        , C_ISO_8859_1  , C_ISO_8859_1},
926         {"nl_NL"        , C_ISO_8859_1  , C_ISO_8859_1},
927         {"et_EE"        , C_ISO_8859_1  , C_ISO_8859_1},
928         {"fi_FI"        , C_ISO_8859_1  , C_ISO_8859_1},
929         {"fr_FR"        , C_ISO_8859_1  , C_ISO_8859_1},
930         {"is_IS"        , C_ISO_8859_1  , C_ISO_8859_1},
931         {"it_IT"        , C_ISO_8859_1  , C_ISO_8859_1},
932         {"no_NO"        , C_ISO_8859_1  , C_ISO_8859_1},
933         {"pt_PT"        , C_ISO_8859_1  , C_ISO_8859_1},
934         {"pt_BR"        , C_ISO_8859_1  , C_ISO_8859_1},
935         {"es_ES"        , C_ISO_8859_1  , C_ISO_8859_1},
936         {"sv_SE"        , C_ISO_8859_1  , C_ISO_8859_1},
937
938         {"hr_HR"        , C_ISO_8859_2  , C_ISO_8859_2},
939         {"hu_HU"        , C_ISO_8859_2  , C_ISO_8859_2},
940         {"pl_PL"        , C_ISO_8859_2  , C_ISO_8859_2},
941         {"ro_RO"        , C_ISO_8859_2  , C_ISO_8859_2},
942         {"sk_SK"        , C_ISO_8859_2  , C_ISO_8859_2},
943         {"sl_SI"        , C_ISO_8859_2  , C_ISO_8859_2},
944         {"el_GR"        , C_ISO_8859_7  , C_ISO_8859_7},
945         {"iw_IL"        , C_ISO_8859_8  , C_ISO_8859_8},
946         {"tr_TR"        , C_ISO_8859_9  , C_ISO_8859_9},
947
948         {"th_TH"        , C_TIS_620     , C_TIS_620},
949         /* {"th_TH"     , C_WINDOWS_874}, */
950         /* {"th_TH"     , C_ISO_8859_11}, */
951
952         {"lt_LT.iso88594"       , C_ISO_8859_4  , C_ISO_8859_4},
953         {"lt_LT.ISO8859-4"      , C_ISO_8859_4  , C_ISO_8859_4},
954         {"lt_LT.ISO_8859-4"     , C_ISO_8859_4  , C_ISO_8859_4},
955         {"lt_LT"                , C_ISO_8859_13 , C_ISO_8859_13},
956         {"lv_LV"                , C_ISO_8859_13 , C_ISO_8859_13},
957
958         {"C"                    , C_US_ASCII    , C_US_ASCII},
959         {"POSIX"                , C_US_ASCII    , C_US_ASCII},
960         {"ANSI_X3.4-1968"       , C_US_ASCII    , C_US_ASCII},
961 };
962
963 const gchar *conv_get_charset_str(CharSet charset)
964 {
965         gint i;
966
967         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
968                 if (charsets[i].charset == charset)
969                         return charsets[i].name;
970         }
971
972         return NULL;
973 }
974
975 CharSet conv_get_charset_from_str(const gchar *charset)
976 {
977         gint i;
978
979         if (!charset) return C_AUTO;
980
981         for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
982                 if (!strcasecmp(charsets[i].name, charset))
983                         return charsets[i].charset;
984         }
985
986         return C_AUTO;
987 }
988
989 CharSet conv_get_current_charset(void)
990 {
991         static CharSet cur_charset = -1;
992         const gchar *cur_locale;
993         const gchar *p;
994         gint i;
995
996         if (cur_charset != -1)
997                 return cur_charset;
998
999         cur_locale = conv_get_current_locale();
1000         if (!cur_locale) {
1001                 cur_charset = C_US_ASCII;
1002                 return cur_charset;
1003         }
1004
1005         if (strcasestr(cur_locale, "UTF-8")) {
1006                 cur_charset = C_UTF_8;
1007                 return cur_charset;
1008         }
1009
1010         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1011                 cur_charset = C_ISO_8859_15;
1012                 return cur_charset;
1013         }
1014
1015         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1016                 const gchar *p;
1017
1018                 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1019                    "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1020                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1021                                  strlen(locale_table[i].locale))) {
1022                         cur_charset = locale_table[i].charset;
1023                         return cur_charset;
1024                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1025                          !strchr(p + 1, '.')) {
1026                         if (strlen(cur_locale) == 2 &&
1027                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1028                                 cur_charset = locale_table[i].charset;
1029                                 return cur_charset;
1030                         }
1031                 }
1032         }
1033
1034         cur_charset = C_AUTO;
1035         return cur_charset;
1036 }
1037
1038 const gchar *conv_get_current_charset_str(void)
1039 {
1040         static const gchar *codeset = NULL;
1041
1042         if (!codeset)
1043                 codeset = conv_get_charset_str(conv_get_current_charset());
1044
1045         return codeset ? codeset : CS_US_ASCII;
1046 }
1047
1048 CharSet conv_get_outgoing_charset(void)
1049 {
1050         static CharSet out_charset = -1;
1051         const gchar *cur_locale;
1052         const gchar *p;
1053         gint i;
1054
1055         if (out_charset != -1)
1056                 return out_charset;
1057
1058         cur_locale = conv_get_current_locale();
1059         if (!cur_locale) {
1060                 out_charset = C_AUTO;
1061                 return out_charset;
1062         }
1063
1064         if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1065                 out_charset = C_ISO_8859_15;
1066                 return out_charset;
1067         }
1068
1069         for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1070                 const gchar *p;
1071
1072                 if (!strncasecmp(cur_locale, locale_table[i].locale,
1073                                  strlen(locale_table[i].locale))) {
1074                         out_charset = locale_table[i].out_charset;
1075                         break;
1076                 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1077                          !strchr(p + 1, '.')) {
1078                         if (strlen(cur_locale) == 2 &&
1079                             !strncasecmp(cur_locale, locale_table[i].locale, 2)) {
1080                                 out_charset = locale_table[i].out_charset;
1081                                 break;
1082                         }
1083                 }
1084         }
1085
1086 #if !HAVE_ICONV
1087         /* encoding conversion without iconv() is only supported
1088            on Japanese locale for now */
1089         if (out_charset == C_ISO_2022_JP)
1090                 return out_charset;
1091         else
1092                 return conv_get_current_charset();
1093 #endif
1094
1095         return out_charset;
1096 }
1097
1098 const gchar *conv_get_outgoing_charset_str(void)
1099 {
1100         CharSet out_charset;
1101         const gchar *str;
1102
1103         if (prefs_common.outgoing_charset) {
1104                 if (!isalpha(prefs_common.outgoing_charset[0])) {
1105                         g_free(prefs_common.outgoing_charset);
1106                         prefs_common.outgoing_charset = g_strdup(CS_AUTO);
1107                 } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0)
1108                         return prefs_common.outgoing_charset;
1109         }
1110
1111         out_charset = conv_get_outgoing_charset();
1112         str = conv_get_charset_str(out_charset);
1113
1114         return str ? str : CS_US_ASCII;
1115 }
1116
1117 const gchar *conv_get_current_locale(void)
1118 {
1119         gchar *cur_locale;
1120
1121         cur_locale = g_getenv("LC_ALL");
1122         if (!cur_locale) cur_locale = g_getenv("LC_CTYPE");
1123         if (!cur_locale) cur_locale = g_getenv("LANG");
1124         if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL);
1125
1126         debug_print("current locale: %s\n",
1127                     cur_locale ? cur_locale : "(none)");
1128
1129         return cur_locale;
1130 }
1131
1132 void conv_unmime_header_overwrite(gchar *str)
1133 {
1134         gchar *buf;
1135         gint buflen;
1136         CharSet cur_charset;
1137
1138         cur_charset = conv_get_current_charset();
1139
1140         if (cur_charset == C_EUC_JP) {
1141                 buflen = strlen(str) * 2 + 1;
1142                 Xalloca(buf, buflen, return);
1143                 conv_anytodisp(buf, buflen, str);
1144                 unmime_header(str, buf);
1145         } else {
1146                 buflen = strlen(str) + 1;
1147                 Xalloca(buf, buflen, return);
1148                 unmime_header(buf, str);
1149                 strncpy2(str, buf, buflen);
1150         }
1151 }
1152
1153 void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str,
1154                         const gchar *charset)
1155 {
1156         CharSet cur_charset;
1157
1158         cur_charset = conv_get_current_charset();
1159
1160         if (cur_charset == C_EUC_JP) {
1161                 gchar *buf;
1162                 gint buflen;
1163
1164                 buflen = strlen(str) * 2 + 1;
1165                 Xalloca(buf, buflen, return);
1166                 conv_anytodisp(buf, buflen, str);
1167                 unmime_header(outbuf, buf);
1168         } else
1169                 unmime_header(outbuf, str);
1170 }
1171
1172 #define MAX_LINELEN     76
1173 #define MIMESEP_BEGIN   "=?"
1174 #define MIMESEP_END     "?="
1175
1176 #define B64LEN(len)     ((len) / 3 * 4 + ((len) % 3 ? 4 : 0))
1177
1178 #define LBREAK_IF_REQUIRED(cond, plaintext)                     \
1179 {                                                               \
1180         if (len - (destp - dest) < MAX_LINELEN + 2) {           \
1181                 *destp = '\0';                                  \
1182                 return;                                         \
1183         }                                                       \
1184                                                                 \
1185         if ((cond) && *srcp) {                                  \
1186                 if (destp > dest && isspace(*(destp - 1)))      \
1187                         destp--;                                \
1188                 else if (plaintext && isspace(*srcp))           \
1189                         srcp++;                                 \
1190                 if (*srcp) {                                    \
1191                         *destp++ = '\n';                        \
1192                         *destp++ = ' ';                         \
1193                         left = MAX_LINELEN - 1;                 \
1194                 }                                               \
1195         }                                                       \
1196 }
1197
1198 void conv_encode_header(gchar *dest, gint len, const gchar *src,
1199                         gint header_len)
1200 {
1201         const gchar *cur_encoding;
1202         const gchar *out_encoding;
1203         gint mimestr_len;
1204         gchar *mimesep_enc;
1205         gint left;
1206         const gchar *srcp = src;
1207         gchar *destp = dest;
1208         gboolean use_base64;
1209
1210         if (MB_CUR_MAX > 1) {
1211                 use_base64 = TRUE;
1212                 mimesep_enc = "?B?";
1213         } else {
1214                 use_base64 = FALSE;
1215                 mimesep_enc = "?Q?";
1216         }
1217
1218         cur_encoding = conv_get_current_charset_str();
1219         if (!strcmp(cur_encoding, CS_US_ASCII))
1220                 cur_encoding = CS_ISO_8859_1;
1221         out_encoding = conv_get_outgoing_charset_str();
1222         if (!strcmp(out_encoding, CS_US_ASCII))
1223                 out_encoding = CS_ISO_8859_1;
1224
1225         mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
1226                 strlen(mimesep_enc) + strlen(MIMESEP_END);
1227
1228         left = MAX_LINELEN - header_len;
1229
1230         while (*srcp) {
1231                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1232
1233                 while (isspace(*srcp)) {
1234                         *destp++ = *srcp++;
1235                         left--;
1236                         LBREAK_IF_REQUIRED(left <= 0, TRUE);
1237                 }
1238
1239                 /* output as it is if the next word is ASCII string */
1240                 if (!is_next_nonascii(srcp)) {
1241                         gint word_len;
1242
1243                         word_len = get_next_word_len(srcp);
1244                         LBREAK_IF_REQUIRED(left < word_len, TRUE);
1245                         while (word_len > 0) {
1246                                 LBREAK_IF_REQUIRED(left <= 0, TRUE);
1247                                 *destp++ = *srcp++;
1248                                 left--;
1249                                 word_len--;
1250                         }
1251
1252                         continue;
1253                 }
1254
1255                 while (1) {
1256                         gint mb_len = 0;
1257                         gint cur_len = 0;
1258                         gchar *part_str;
1259                         gchar *out_str;
1260                         gchar *enc_str;
1261                         const gchar *p = srcp;
1262                         gint out_str_len;
1263                         gint out_enc_str_len;
1264                         gint mime_block_len;
1265                         gboolean cont = FALSE;
1266
1267                         while (*p != '\0') {
1268                                 if (isspace(*p) && !is_next_nonascii(p + 1))
1269                                         break;
1270
1271                                 if (MB_CUR_MAX > 1) {
1272                                         mb_len = mblen(p, MB_CUR_MAX);
1273                                         if (mb_len < 0) {
1274                                                 g_warning("conv_encode_header(): invalid multibyte character encountered\n");
1275                                                 mb_len = 1;
1276                                         }
1277                                 } else
1278                                         mb_len = 1;
1279
1280                                 Xstrndup_a(part_str, srcp, cur_len + mb_len, );
1281                                 out_str = conv_codeset_strdup
1282                                         (part_str, cur_encoding, out_encoding);
1283                                 if (!out_str) {
1284                                         g_warning("conv_encode_header(): code conversion failed\n");
1285                                         out_str = g_strdup(out_str);
1286                                 }
1287                                 out_str_len = strlen(out_str);
1288
1289                                 if (use_base64)
1290                                         out_enc_str_len = B64LEN(out_str_len);
1291                                 else
1292                                         out_enc_str_len =
1293                                                 qp_get_q_encoding_len(out_str);
1294
1295                                 g_free(out_str);
1296
1297                                 if (mimestr_len + out_enc_str_len <= left) {
1298                                         cur_len += mb_len;
1299                                         p += mb_len;
1300                                 } else if (cur_len == 0) {
1301                                         LBREAK_IF_REQUIRED(1, FALSE);
1302                                         continue;
1303                                 } else {
1304                                         cont = TRUE;
1305                                         break;
1306                                 }
1307                         }
1308
1309                         if (cur_len > 0) {
1310                                 Xstrndup_a(part_str, srcp, cur_len, );
1311                                 out_str = conv_codeset_strdup
1312                                         (part_str, cur_encoding, out_encoding);
1313                                 if (!out_str) {
1314                                         g_warning("conv_encode_header(): code conversion failed\n");
1315                                         out_str = g_strdup(out_str);
1316                                 }
1317                                 out_str_len = strlen(out_str);
1318
1319                                 if (use_base64)
1320                                         out_enc_str_len = B64LEN(out_str_len);
1321                                 else
1322                                         out_enc_str_len =
1323                                                 qp_get_q_encoding_len(out_str);
1324
1325                                 Xalloca(enc_str, out_enc_str_len + 1, );
1326                                 if (use_base64)
1327                                         base64_encode(enc_str, out_str, out_str_len);
1328                                 else
1329                                         qp_q_encode(enc_str, out_str);
1330
1331                                 g_free(out_str);
1332
1333                                 /* output MIME-encoded string block */
1334                                 mime_block_len = mimestr_len + strlen(enc_str);
1335                                 g_snprintf(destp, mime_block_len + 1,
1336                                            MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
1337                                            out_encoding, mimesep_enc, enc_str);
1338                                 destp += mime_block_len;
1339                                 srcp += cur_len;
1340
1341                                 left -= mime_block_len;
1342                         }
1343
1344                         LBREAK_IF_REQUIRED(cont, FALSE);
1345
1346                         if (cur_len == 0)
1347                                 break;
1348                 }
1349         }
1350
1351         *destp = '\0';
1352 }
1353
1354 #undef LBREAK_IF_REQUIRED