inital gtk2 patch
[claws.git] / src / procmime.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 "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <locale.h>
30 #include <ctype.h>
31
32 #include "intl.h"
33 #include "procmime.h"
34 #include "procheader.h"
35 #include "base64.h"
36 #include "quoted-printable.h"
37 #include "uuencode.h"
38 #include "unmime.h"
39 #include "html.h"
40 #include "enriched.h"
41 #include "codeconv.h"
42 #include "utils.h"
43 #include "prefs_common.h"
44
45 #if USE_GPGME
46 #  include "rfc2015.h"
47 #endif
48
49 #include "prefs_gtk.h"
50
51 static GHashTable *procmime_get_mime_type_table (void);
52
53 MimeInfo *procmime_mimeinfo_new(void)
54 {
55         MimeInfo *mimeinfo;
56
57         mimeinfo = g_new0(MimeInfo, 1);
58         mimeinfo->mime_type     = MIME_UNKNOWN;
59         mimeinfo->encoding_type = ENC_UNKNOWN;
60
61         return mimeinfo;
62 }
63
64 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
65 {
66         while (mimeinfo != NULL) {
67                 MimeInfo *next;
68
69                 g_free(mimeinfo->encoding);
70                 g_free(mimeinfo->content_type);
71                 g_free(mimeinfo->charset);
72                 g_free(mimeinfo->name);
73                 g_free(mimeinfo->boundary);
74                 g_free(mimeinfo->content_disposition);
75                 g_free(mimeinfo->filename);
76                 g_free(mimeinfo->description);
77 #if USE_GPGME
78                 g_free(mimeinfo->plaintextfile);
79                 g_free(mimeinfo->sigstatus);
80                 g_free(mimeinfo->sigstatus_full);
81 #endif
82
83                 procmime_mimeinfo_free_all(mimeinfo->sub);
84                 procmime_mimeinfo_free_all(mimeinfo->children);
85 #if USE_GPGME
86                 procmime_mimeinfo_free_all(mimeinfo->plaintext);
87 #endif
88
89                 next = mimeinfo->next;
90                 g_free(mimeinfo);
91                 mimeinfo = next;
92         }
93 }
94
95 MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
96 {
97         MimeInfo *child = parent->children;
98
99         if (!child)
100                 parent->children = mimeinfo;
101         else {
102                 while (child->next != NULL)
103                         child = child->next;
104
105                 child->next = mimeinfo;
106         }
107
108         mimeinfo->parent = parent;
109         mimeinfo->level = parent->level + 1;
110
111         return mimeinfo;
112 }
113
114 void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
115 {
116         MimeInfo *parent = old->parent;
117         MimeInfo *child;
118
119         g_return_if_fail(parent != NULL);
120         g_return_if_fail(new->next == NULL);
121
122         for (child = parent->children; child && child != old;
123              child = child->next)
124                 ;
125         if (!child) {
126                 g_warning("oops: parent can't find it's own child");
127                 return;
128         }
129         procmime_mimeinfo_free_all(old);
130
131         if (child == parent->children) {
132                 new->next = parent->children->next;
133                 parent->children = new;
134         } else {
135                 new->next = child->next;
136                 child = new;
137         }
138 }
139
140 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
141 {
142         if (!mimeinfo) return NULL;
143
144         if (mimeinfo->children)
145                 return mimeinfo->children;
146         if (mimeinfo->sub)
147                 return mimeinfo->sub;
148         if (mimeinfo->next)
149                 return mimeinfo->next;
150
151         if (mimeinfo->main) {
152                 mimeinfo = mimeinfo->main;
153                 if (mimeinfo->next)
154                         return mimeinfo->next;
155         }
156
157         for (mimeinfo = mimeinfo->parent; mimeinfo != NULL;
158              mimeinfo = mimeinfo->parent) {
159                 if (mimeinfo->next)
160                         return mimeinfo->next;
161                 if (mimeinfo->main) {
162                         mimeinfo = mimeinfo->main;
163                         if (mimeinfo->next)
164                                 return mimeinfo->next;
165                 }
166         }
167
168         return NULL;
169 }
170
171 #if 0
172 void procmime_dump_mimeinfo(MimeInfo *mimeinfo)
173 {
174         gint i;
175
176         g_print("\n");
177
178         for (; mimeinfo != NULL; mimeinfo = procmime_mimeinfo_next(mimeinfo)) {
179                 for (i = 0; i < mimeinfo->level; i++)
180                         g_print("  ");
181                 g_print("%s%s\n", mimeinfo->main ? "sub: " : "",
182                         mimeinfo->content_type);
183         }
184 }
185 #endif
186
187 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
188 {
189         FILE *fp;
190         MimeInfo *mimeinfo;
191
192         g_return_val_if_fail(msginfo != NULL, NULL);
193
194 #if USE_GPGME
195         if ((fp = procmsg_open_message_decrypted(msginfo, &mimeinfo)) == NULL)
196                 return NULL;
197 #else
198         if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
199         mimeinfo = procmime_scan_mime_header(fp);
200 #endif
201
202         if (mimeinfo) {
203                 mimeinfo->size = get_left_file_size(fp);
204                 if (mimeinfo->mime_type == MIME_MULTIPART ||
205                     mimeinfo->mime_type == MIME_MESSAGE_RFC822)
206                         procmime_scan_multipart_message(mimeinfo, fp);
207         }
208
209         fclose(fp);
210
211         return mimeinfo;
212 }
213
214 void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp)
215 {
216         gchar *p;
217         gchar *boundary;
218         gint boundary_len = 0;
219         gchar buf[BUFFSIZE];
220         glong fpos, prev_fpos;
221
222         g_return_if_fail(mimeinfo != NULL);
223         g_return_if_fail(mimeinfo->mime_type == MIME_MULTIPART ||
224                          mimeinfo->mime_type == MIME_MESSAGE_RFC822);
225
226         if (mimeinfo->mime_type == MIME_MULTIPART) {
227                 g_return_if_fail(mimeinfo->boundary != NULL);
228                 g_return_if_fail(mimeinfo->sub == NULL);
229         }
230         g_return_if_fail(fp != NULL);
231
232         boundary = mimeinfo->boundary;
233
234         if (boundary) {
235                 boundary_len = strlen(boundary);
236
237                 /* look for first boundary */
238                 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
239                         if (IS_BOUNDARY(buf, boundary, boundary_len)) break;
240                 if (!p) return;
241         } else if (mimeinfo->parent && mimeinfo->parent->boundary) {
242                 boundary = mimeinfo->parent->boundary;
243                 boundary_len = strlen(boundary);
244         }
245
246         if ((fpos = ftell(fp)) < 0) {
247                 perror("ftell");
248                 return;
249         }
250
251         for (;;) {
252                 MimeInfo *partinfo;
253                 gboolean eom = FALSE;
254                 gint len;
255
256                 prev_fpos = fpos;
257                 debug_print("prev_fpos: %ld\n", fpos);
258
259                 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
260                         MimeInfo *sub;
261
262                         mimeinfo->sub = sub = procmime_scan_mime_header(fp);
263                         if (!sub) break;
264
265                         sub->level = mimeinfo->level + 1;
266                         sub->parent = mimeinfo->parent;
267                         sub->main = mimeinfo;
268
269                         partinfo = sub;
270                 } else {
271                         partinfo = procmime_scan_mime_header(fp);
272                         if (!partinfo) break;
273                         procmime_mimeinfo_insert(mimeinfo, partinfo);
274                         debug_print("content-type: %s\n",
275                                     partinfo->content_type);
276                 }
277
278                 if (partinfo->mime_type == MIME_MULTIPART ||
279                     partinfo->mime_type == MIME_MESSAGE_RFC822) {
280                         if (partinfo->level < 8)
281                                 procmime_scan_multipart_message(partinfo, fp);
282                 }
283
284                 /* look for next boundary */
285                 buf[0] = '\0';
286                 while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
287                         if (IS_BOUNDARY(buf, boundary, boundary_len)) {
288                                 if (buf[2 + boundary_len]     == '-' &&
289                                     buf[2 + boundary_len + 1] == '-')
290                                         eom = TRUE;
291                                 break;
292                         }
293                 }
294                 if (p == NULL) {
295                         /* broken MIME, or single part MIME message */
296                         buf[0] = '\0';
297                         eom = TRUE;
298                 }
299                 debug_print("boundary: %s\n", buf);
300
301                 fpos = ftell(fp);
302                 debug_print("fpos: %ld\n", fpos);
303
304                 len = strlen(buf);
305                 partinfo->size = fpos - prev_fpos - len;
306                 debug_print("partinfo->size: %d\n", partinfo->size);
307                 if (partinfo->sub && !partinfo->sub->sub &&
308                     !partinfo->sub->children) {
309                         partinfo->sub->size =
310                                 fpos - partinfo->sub->fpos - strlen(buf);
311                         debug_print("partinfo->sub->size: %d\n",
312                                     partinfo->sub->size);
313                 }
314
315                 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
316                         if (len > 0 && fseek(fp, fpos - len, SEEK_SET) < 0)
317                                 perror("fseek");
318                         break;
319                 }
320
321                 if (eom) break;
322         }
323 }
324
325 void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding)
326 {
327         gchar *buf;
328
329         Xstrdup_a(buf, encoding, return);
330
331         g_free(mimeinfo->encoding);
332
333         mimeinfo->encoding = g_strdup(g_strstrip(buf));
334         if (!strcasecmp(buf, "7bit"))
335                 mimeinfo->encoding_type = ENC_7BIT;
336         else if (!strcasecmp(buf, "8bit"))
337                 mimeinfo->encoding_type = ENC_8BIT;
338         else if (!strcasecmp(buf, "quoted-printable"))
339                 mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE;
340         else if (!strcasecmp(buf, "base64"))
341                 mimeinfo->encoding_type = ENC_BASE64;
342         else if (!strcasecmp(buf, "x-uuencode"))
343                 mimeinfo->encoding_type = ENC_X_UUENCODE;
344         else
345                 mimeinfo->encoding_type = ENC_UNKNOWN;
346
347 }
348
349 void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type)
350 {
351         gchar *delim, *p, *cnttype;
352         gchar *buf;
353
354         if (conv_get_current_charset() == C_EUC_JP &&
355             strchr(content_type, '\033')) {
356                 gint len;
357                 len = strlen(content_type) * 2 + 1;
358                 Xalloca(buf, len, return);
359                 conv_jistoeuc(buf, len, content_type);
360         } else
361                 Xstrdup_a(buf, content_type, return);
362
363         g_free(mimeinfo->content_type);
364         g_free(mimeinfo->charset);
365         /* g_free(mimeinfo->name); */
366         mimeinfo->content_type = NULL;
367         mimeinfo->charset      = NULL;
368         /* mimeinfo->name      = NULL; */
369
370         if ((delim = strchr(buf, ';'))) *delim = '\0';
371         mimeinfo->content_type = cnttype = g_strdup(g_strstrip(buf));
372
373         mimeinfo->mime_type = procmime_scan_mime_type(cnttype);
374
375         if (!delim) return;
376         p = delim + 1;
377
378         for (;;) {
379                 gchar *eq;
380                 gchar *attr, *value;
381
382                 if ((delim = strchr(p, ';'))) *delim = '\0';
383
384                 if (!(eq = strchr(p, '='))) break;
385
386                 *eq = '\0';
387                 attr = p;
388                 g_strstrip(attr);
389                 value = eq + 1;
390                 g_strstrip(value);
391
392                 if (*value == '"')
393                         extract_quote(value, '"');
394                 else {
395                         eliminate_parenthesis(value, '(', ')');
396                         g_strstrip(value);
397                 }
398
399                 if (*value) {
400                         if (!strcasecmp(attr, "charset"))
401                                 mimeinfo->charset = g_strdup(value);
402                         else if (!strcasecmp(attr, "name")) {
403                                 gchar *tmp;
404                                 size_t len;
405
406                                 len = strlen(value) + 1;
407                                 Xalloca(tmp, len, return);
408                                 conv_unmime_header(tmp, len, value, NULL);
409                                 g_free(mimeinfo->name);
410                                 /*pgp signatures should NOT have a name */
411                                 if (mimeinfo->content_type 
412                                 &&  strcasecmp(mimeinfo->content_type, "application/pgp-signature"))
413                                         mimeinfo->name = g_strdup(tmp);
414                         } else if (!strcasecmp(attr, "boundary"))
415                                 mimeinfo->boundary = g_strdup(value);
416                 }
417
418                 if (!delim) break;
419                 p = delim + 1;
420         }
421
422         if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary)
423                 mimeinfo->mime_type = MIME_TEXT;
424 }
425
426 void procmime_scan_content_disposition(MimeInfo *mimeinfo,
427                                        const gchar *content_disposition)
428 {
429         gchar *delim, *p, *dispos;
430         gchar *buf;
431
432         if (conv_get_current_charset() == C_EUC_JP &&
433             strchr(content_disposition, '\033')) {
434                 gint len;
435                 len = strlen(content_disposition) * 2 + 1;
436                 Xalloca(buf, len, return);
437                 conv_jistoeuc(buf, len, content_disposition);
438         } else
439                 Xstrdup_a(buf, content_disposition, return);
440
441         if ((delim = strchr(buf, ';'))) *delim = '\0';
442         mimeinfo->content_disposition = dispos = g_strdup(g_strstrip(buf));
443
444         if (!delim) return;
445         p = delim + 1;
446
447         for (;;) {
448                 gchar *eq;
449                 gchar *attr, *value;
450
451                 if ((delim = strchr(p, ';'))) *delim = '\0';
452
453                 if (!(eq = strchr(p, '='))) break;
454
455                 *eq = '\0';
456                 attr = p;
457                 g_strstrip(attr);
458                 value = eq + 1;
459                 g_strstrip(value);
460
461                 if (*value == '"')
462                         extract_quote(value, '"');
463                 else {
464                         eliminate_parenthesis(value, '(', ')');
465                         g_strstrip(value);
466                 }
467
468                 if (*value) {
469                         if (!strcasecmp(attr, "filename")) {
470                                 gchar *tmp;
471                                 size_t len;
472
473                                 len = strlen(value) + 1;
474                                 Xalloca(tmp, len, return);
475                                 conv_unmime_header(tmp, len, value, NULL);
476                                 g_free(mimeinfo->filename);
477                                 /*pgp signatures should NOT have a name */
478                                 if (mimeinfo->content_type 
479                                 &&  strcasecmp(mimeinfo->content_type, "application/pgp-signature"))
480                                         mimeinfo->filename = g_strdup(tmp);
481                                 break;
482                         }
483                 }
484
485                 if (!delim) break;
486                 p = delim + 1;
487         }
488 }
489
490 void procmime_scan_content_description(MimeInfo *mimeinfo,
491                                        const gchar *content_description)
492 {
493         gchar *buf;
494
495         gchar *tmp;
496         size_t blen;
497
498         if (conv_get_current_charset() == C_EUC_JP &&
499             strchr(content_description, '\033')) {
500                 gint len;
501                 len = strlen(content_description) * 2 + 1;
502                 Xalloca(buf, len, return);
503                 conv_jistoeuc(buf, len, content_description);
504         } else
505                 Xstrdup_a(buf, content_description, return);
506         
507         blen = strlen(buf) + 1;
508         Xalloca(tmp, blen, return);
509         conv_unmime_header(tmp, blen, buf, NULL);
510         /*pgp signatures should NOT have a name */
511         if (mimeinfo->content_type 
512         &&  strcasecmp(mimeinfo->content_type, "application/pgp-signature"))
513                 mimeinfo->description = g_strdup(tmp);
514 }
515
516 void procmime_scan_subject(MimeInfo *mimeinfo,
517                            const gchar *subject)
518 {
519         gchar *buf;
520
521         gchar *tmp;
522         size_t blen;
523
524         if (conv_get_current_charset() == C_EUC_JP &&
525             strchr(subject, '\033')) {
526                 gint len;
527                 len = strlen(subject) * 2 + 1;
528                 Xalloca(buf, len, return);
529                 conv_jistoeuc(buf, len, subject);
530         } else
531                 Xstrdup_a(buf, subject, return);
532         
533         blen = strlen(buf) + 1;
534         Xalloca(tmp, blen, return);
535         conv_unmime_header(tmp, blen, buf, NULL);
536         g_free(mimeinfo->name);
537         mimeinfo->name = g_strdup(tmp);
538 }
539
540 enum
541 {
542         H_CONTENT_TRANSFER_ENCODING = 0,
543         H_CONTENT_TYPE              = 1,
544         H_CONTENT_DISPOSITION       = 2,
545         H_CONTENT_DESCRIPTION       = 3,
546         H_SUBJECT                   = 4
547 };
548
549 MimeInfo *procmime_scan_mime_header(FILE *fp)
550 {
551         static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:",
552                                                           NULL, FALSE},
553                                        {"Content-Type:", NULL, TRUE},
554                                        {"Content-Disposition:",
555                                                           NULL, TRUE},
556                                        {"Content-description:",
557                                                           NULL, TRUE},
558                                        {"Subject:",
559                                                           NULL, TRUE},
560                                        {NULL,             NULL, FALSE}};
561         gchar buf[BUFFSIZE];
562         gint hnum;
563         HeaderEntry *hp;
564         MimeInfo *mimeinfo;
565
566         g_return_val_if_fail(fp != NULL, NULL);
567
568         mimeinfo = procmime_mimeinfo_new();
569         mimeinfo->mime_type = MIME_TEXT;
570         mimeinfo->encoding_type = ENC_7BIT;
571         mimeinfo->fpos = ftell(fp);
572
573         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
574                != -1) {
575                 hp = hentry + hnum;
576
577                 if (H_CONTENT_TRANSFER_ENCODING == hnum) {
578                         procmime_scan_encoding
579                                 (mimeinfo, buf + strlen(hp->name));
580                 } else if (H_CONTENT_TYPE == hnum) {
581                         procmime_scan_content_type
582                                 (mimeinfo, buf + strlen(hp->name));
583                 } else if (H_CONTENT_DISPOSITION == hnum) {
584                         procmime_scan_content_disposition
585                                 (mimeinfo, buf + strlen(hp->name));
586                 } else if (H_CONTENT_DESCRIPTION == hnum) {
587                         procmime_scan_content_description
588                                 (mimeinfo, buf + strlen(hp->name));
589                 } else if (H_SUBJECT == hnum) {
590                         procmime_scan_subject
591                                 (mimeinfo, buf + strlen(hp->name));
592                 }
593         }
594
595         if (!mimeinfo->content_type)
596                         mimeinfo->content_type = g_strdup("text/plain");
597
598         return mimeinfo;
599 }
600
601 FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
602 {
603         gchar buf[BUFFSIZE];
604         gchar *boundary = NULL;
605         gint boundary_len = 0;
606         gboolean tmp_file = FALSE;
607
608         g_return_val_if_fail(infp != NULL, NULL);
609         g_return_val_if_fail(mimeinfo != NULL, NULL);
610
611         if (!outfp) {
612                 outfp = my_tmpfile();
613                 if (!outfp) {
614                         perror("tmpfile");
615                         return NULL;
616                 }
617                 tmp_file = TRUE;
618         }
619
620         if (mimeinfo->parent && mimeinfo->parent->boundary) {
621                 boundary = mimeinfo->parent->boundary;
622                 boundary_len = strlen(boundary);
623         }
624
625         if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) {
626                 while (fgets(buf, sizeof(buf), infp) != NULL &&
627                        (!boundary ||
628                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
629                         gint len;
630                         len = qp_decode_line(buf);
631                         fwrite(buf, len, 1, outfp);
632                 }
633         } else if (mimeinfo->encoding_type == ENC_BASE64) {
634                 gchar outbuf[BUFFSIZE];
635                 gint len;
636                 Base64Decoder *decoder;
637
638                 decoder = base64_decoder_new();
639                 while (fgets(buf, sizeof(buf), infp) != NULL &&
640                        (!boundary ||
641                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
642                         len = base64_decoder_decode(decoder, buf, outbuf);
643                         if (len < 0) {
644                                 g_warning("Bad BASE64 content\n");
645                                 break;
646                         }
647                         fwrite(outbuf, sizeof(gchar), len, outfp);
648                 }
649                 base64_decoder_free(decoder);
650         } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) {
651                 gchar outbuf[BUFFSIZE];
652                 gint len;
653                 gboolean flag = FALSE;
654
655                 while (fgets(buf, sizeof(buf), infp) != NULL &&
656                        (!boundary ||
657                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
658                         if(!flag && strncmp(buf,"begin ", 6)) continue;
659
660                         if (flag) {
661                                 len = fromuutobits(outbuf, buf);
662                                 if (len <= 0) {
663                                         if (len < 0) 
664                                                 g_warning("Bad UUENCODE content(%d)\n", len);
665                                         break;
666                                 }
667                                 fwrite(outbuf, sizeof(gchar), len, outfp);
668                         } else
669                                 flag = TRUE;
670                 }
671         } else {
672                 while (fgets(buf, sizeof(buf), infp) != NULL &&
673                        (!boundary ||
674                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
675                         fputs(buf, outfp);
676                 }
677         }
678
679         if (tmp_file) rewind(outfp);
680         return outfp;
681 }
682
683 gint procmime_get_part(const gchar *outfile, const gchar *infile,
684                        MimeInfo *mimeinfo)
685 {
686         FILE *infp, *outfp;
687         gchar buf[BUFFSIZE];
688
689         g_return_val_if_fail(outfile != NULL, -1);
690         g_return_val_if_fail(infile != NULL, -1);
691         g_return_val_if_fail(mimeinfo != NULL, -1);
692
693         if ((infp = fopen(infile, "rb")) == NULL) {
694                 FILE_OP_ERROR(infile, "fopen");
695                 return -1;
696         }
697         if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
698                 FILE_OP_ERROR(infile, "fseek");
699                 fclose(infp);
700                 return -1;
701         }
702         if ((outfp = fopen(outfile, "wb")) == NULL) {
703                 FILE_OP_ERROR(outfile, "fopen");
704                 fclose(infp);
705                 return -1;
706         }
707
708         while (fgets(buf, sizeof(buf), infp) != NULL)
709                 if (buf[0] == '\r' || buf[0] == '\n') break;
710
711         procmime_decode_content(outfp, infp, mimeinfo);
712
713         fclose(infp);
714         if (fclose(outfp) == EOF) {
715                 FILE_OP_ERROR(outfile, "fclose");
716                 unlink(outfile);
717                 return -1;
718         }
719
720         return 0;
721 }
722
723 struct ContentRenderer {
724         char * content_type;
725         char * renderer;
726 };
727
728 static GList * renderer_list = NULL;
729
730 static struct ContentRenderer *
731 content_renderer_new(char * content_type, char * renderer)
732 {
733         struct ContentRenderer * cr;
734
735         cr = g_new(struct ContentRenderer, 1);
736         if (cr == NULL)
737                 return NULL;
738
739         cr->content_type = g_strdup(content_type);
740         cr->renderer = g_strdup(renderer);
741
742         return cr;
743 }
744
745 static void content_renderer_free(struct ContentRenderer * cr)
746 {
747         g_free(cr->content_type);
748         g_free(cr->renderer);
749         g_free(cr);
750 }
751
752 void renderer_read_config(void)
753 {
754         gchar buf[BUFFSIZE];
755         FILE * f;
756         gchar * rcpath;
757
758         g_list_foreach(renderer_list, (GFunc) content_renderer_free, NULL);
759         renderer_list = NULL;
760
761         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
762         f = fopen(rcpath, "rb");
763         g_free(rcpath);
764         
765         if (f == NULL)
766                 return;
767
768         while (fgets(buf, BUFFSIZE, f)) {
769                 char * p;
770                 struct ContentRenderer * cr;
771
772                 strretchomp(buf);
773                 p = strchr(buf, ' ');
774                 if (p == NULL)
775                         continue;
776                 * p = 0;
777
778                 cr = content_renderer_new(buf, p + 1);
779                 if (cr == NULL)
780                         continue;
781
782                 renderer_list = g_list_append(renderer_list, cr);
783         }
784
785         fclose(f);
786 }
787
788 void renderer_write_config(void)
789 {
790         gchar * rcpath;
791         PrefFile *pfile;
792         GList * cur;
793
794         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
795         
796         if ((pfile = prefs_write_open(rcpath)) == NULL) {
797                 g_warning("failed to write configuration to file\n");
798                 g_free(rcpath);
799                 return;
800         }
801
802         g_free(rcpath);
803
804         for(cur = renderer_list ; cur != NULL ; cur = cur->next) {
805                 struct ContentRenderer * renderer;
806                 renderer = cur->data;
807                 fprintf(pfile->fp, "%s %s\n", renderer->content_type,
808                         renderer->renderer);
809         }
810
811         if (prefs_file_close(pfile) < 0) {
812                 g_warning("failed to write configuration to file\n");
813                 return;
814         }
815 }
816
817 FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp)
818 {
819         FILE *tmpfp, *outfp;
820         gchar *src_codeset;
821         gboolean conv_fail = FALSE;
822         gchar buf[BUFFSIZE];
823         gchar *str;
824         struct ContentRenderer * renderer;
825         GList * cur;
826
827         g_return_val_if_fail(mimeinfo != NULL, NULL);
828         g_return_val_if_fail(infp != NULL, NULL);
829         g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
830                              mimeinfo->mime_type == MIME_TEXT_HTML ||
831                              mimeinfo->mime_type == MIME_TEXT_ENRICHED, NULL);
832
833         if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
834                 perror("fseek");
835                 return NULL;
836         }
837
838         while (fgets(buf, sizeof(buf), infp) != NULL)
839                 if (buf[0] == '\r' || buf[0] == '\n') break;
840
841         tmpfp = procmime_decode_content(NULL, infp, mimeinfo);
842         if (!tmpfp)
843                 return NULL;
844
845         if ((outfp = my_tmpfile()) == NULL) {
846                 perror("tmpfile");
847                 fclose(tmpfp);
848                 return NULL;
849         }
850
851         src_codeset = prefs_common.force_charset
852                 ? prefs_common.force_charset : mimeinfo->charset;
853
854         renderer = NULL;
855
856         for(cur = renderer_list ; cur != NULL ; cur = cur->next) {
857                 struct ContentRenderer * cr;
858                 cr = cur->data;
859                 if (g_strcasecmp(cr->content_type,
860                                  mimeinfo->content_type) == 0) {
861                         renderer = cr;
862                         break;
863                 }
864         }
865
866         if (renderer != NULL) {
867                 FILE * p;
868                 int oldout;
869                 
870                 oldout = dup(1);
871                 
872                 dup2(fileno(outfp), 1);
873                 
874                 p = popen(renderer->renderer, "w");
875                 if (p != NULL) {
876                         size_t count;
877                         
878                         while ((count =
879                                 fread(buf, sizeof(char), sizeof(buf),
880                                       tmpfp)) > 0)
881                                 fwrite(buf, sizeof(char), count, p);
882                         pclose(p);
883                 }
884                 
885                 dup2(oldout, 1);
886         } else if (mimeinfo->mime_type == MIME_TEXT) {
887                 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
888 #warning FIXME_GTK2
889                         str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
890                         if (str) {
891                                 fputs(str, outfp);
892                                 g_free(str);
893                         } else {
894                                 conv_fail = TRUE;
895                                 fputs(buf, outfp);
896                         }
897                 }
898         } else if (mimeinfo->mime_type == MIME_TEXT_HTML) {
899                 HTMLParser *parser;
900                 CodeConverter *conv;
901
902                 conv = conv_code_converter_new(src_codeset);
903                 parser = html_parser_new(tmpfp, conv);
904                 while ((str = html_parse(parser)) != NULL) {
905                         fputs(str, outfp);
906                 }
907                 html_parser_destroy(parser);
908                 conv_code_converter_destroy(conv);
909         } else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED) {
910                 ERTFParser *parser;
911                 CodeConverter *conv;
912
913                 conv = conv_code_converter_new(src_codeset);
914                 parser = ertf_parser_new(tmpfp, conv);
915                 while ((str = ertf_parse(parser)) != NULL) {
916                         fputs(str, outfp);
917                 }
918                 ertf_parser_destroy(parser);
919                 conv_code_converter_destroy(conv);
920         }
921
922         if (conv_fail)
923                 g_warning("procmime_get_text_content(): Code conversion failed.\n");
924
925         fclose(tmpfp);
926         rewind(outfp);
927
928         return outfp;
929 }
930
931 /* search the first text part of (multipart) MIME message,
932    decode, convert it and output to outfp. */
933 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
934 {
935         FILE *infp, *outfp = NULL;
936         MimeInfo *mimeinfo, *partinfo;
937
938         g_return_val_if_fail(msginfo != NULL, NULL);
939
940         mimeinfo = procmime_scan_message(msginfo);
941         if (!mimeinfo) return NULL;
942
943         if ((infp = procmsg_open_message(msginfo)) == NULL) {
944                 procmime_mimeinfo_free_all(mimeinfo);
945                 return NULL;
946         }
947
948         partinfo = mimeinfo;
949         while (partinfo && partinfo->mime_type != MIME_TEXT)
950                 partinfo = procmime_mimeinfo_next(partinfo);
951         if (!partinfo) {
952                 partinfo = mimeinfo;
953                 while (partinfo && partinfo->mime_type != MIME_TEXT_HTML &&
954                                 partinfo->mime_type != MIME_TEXT_ENRICHED)
955                         partinfo = procmime_mimeinfo_next(partinfo);
956         }
957         
958
959         if (partinfo)
960                 outfp = procmime_get_text_content(partinfo, infp);
961
962         fclose(infp);
963         procmime_mimeinfo_free_all(mimeinfo);
964
965         return outfp;
966 }
967
968 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
969                                    const gchar *str, gboolean case_sens)
970 {
971
972         FILE *infp, *outfp;
973         gchar buf[BUFFSIZE];
974         gchar *(* StrFindFunc) (const gchar *haystack, const gchar *needle);
975
976         g_return_val_if_fail(mimeinfo != NULL, FALSE);
977         g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
978                              mimeinfo->mime_type == MIME_TEXT_HTML ||
979                              mimeinfo->mime_type == MIME_TEXT_ENRICHED, FALSE);
980         g_return_val_if_fail(str != NULL, FALSE);
981
982         if ((infp = fopen(filename, "rb")) == NULL) {
983                 FILE_OP_ERROR(filename, "fopen");
984                 return FALSE;
985         }
986
987         outfp = procmime_get_text_content(mimeinfo, infp);
988         fclose(infp);
989
990         if (!outfp)
991                 return FALSE;
992
993         if (case_sens)
994                 StrFindFunc = strstr;
995         else
996                 StrFindFunc = strcasestr;
997
998         while (fgets(buf, sizeof(buf), outfp) != NULL) {
999                 if (StrFindFunc(buf, str) != NULL) {
1000                         fclose(outfp);
1001                         return TRUE;
1002                 }
1003         }
1004
1005         fclose(outfp);
1006
1007         return FALSE;
1008 }
1009
1010 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
1011                               gboolean case_sens)
1012 {
1013         MimeInfo *mimeinfo;
1014         MimeInfo *partinfo;
1015         gchar *filename;
1016         gboolean found = FALSE;
1017
1018         g_return_val_if_fail(msginfo != NULL, FALSE);
1019         g_return_val_if_fail(str != NULL, FALSE);
1020
1021         filename = procmsg_get_message_file(msginfo);
1022         if (!filename) return FALSE;
1023         mimeinfo = procmime_scan_message(msginfo);
1024
1025         for (partinfo = mimeinfo; partinfo != NULL;
1026              partinfo = procmime_mimeinfo_next(partinfo)) {
1027                 if (partinfo->mime_type == MIME_TEXT ||
1028                     partinfo->mime_type == MIME_TEXT_HTML ||
1029                     partinfo->mime_type == MIME_TEXT_ENRICHED) {
1030                         if (procmime_find_string_part
1031                                 (partinfo, filename, str, case_sens) == TRUE) {
1032                                 found = TRUE;
1033                                 break;
1034                         }
1035                 }
1036         }
1037
1038         procmime_mimeinfo_free_all(mimeinfo);
1039         g_free(filename);
1040
1041         return found;
1042 }
1043
1044 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
1045 {
1046         static guint32 id = 0;
1047         const gchar *base;
1048         gchar *filename;
1049         gchar f_prefix[10];
1050
1051         g_return_val_if_fail(mimeinfo != NULL, NULL);
1052
1053         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
1054
1055         if (MIME_TEXT_HTML == mimeinfo->mime_type)
1056                 base = "mimetmp.html";
1057         else {
1058                 gchar *tmp;
1059                 base = mimeinfo->filename ? mimeinfo->filename
1060                         : mimeinfo->name ? mimeinfo->name : "mimetmp";
1061                 base = g_basename(base);
1062                 if (*base == '\0') base = "mimetmp";
1063                 Xstrdup_a(tmp, base, return NULL);
1064                 subst_for_filename(tmp);
1065                 base = tmp;
1066         }
1067
1068         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1069                                f_prefix, base, NULL);
1070
1071         return filename;
1072 }
1073
1074 ContentType procmime_scan_mime_type(const gchar *mime_type)
1075 {
1076         ContentType type;
1077
1078         if (!strncasecmp(mime_type, "text/html", 9))
1079                 type = MIME_TEXT_HTML;
1080         else if (!strncasecmp(mime_type, "text/enriched", 13))
1081                 type = MIME_TEXT_ENRICHED;
1082         else if (!strncasecmp(mime_type, "text/", 5))
1083                 type = MIME_TEXT;
1084         else if (!strncasecmp(mime_type, "message/rfc822", 14))
1085                 type = MIME_MESSAGE_RFC822;
1086         else if (!strncasecmp(mime_type, "message/", 8))
1087                 type = MIME_TEXT;
1088         else if (!strncasecmp(mime_type, "application/octet-stream", 24))
1089                 type = MIME_APPLICATION_OCTET_STREAM;
1090         else if (!strncasecmp(mime_type, "application/", 12))
1091                 type = MIME_APPLICATION;
1092         else if (!strncasecmp(mime_type, "multipart/", 10))
1093                 type = MIME_MULTIPART;
1094         else if (!strncasecmp(mime_type, "image/", 6))
1095                 type = MIME_IMAGE;
1096         else if (!strncasecmp(mime_type, "audio/", 6))
1097                 type = MIME_AUDIO;
1098         else if (!strcasecmp(mime_type, "text"))
1099                 type = MIME_TEXT;
1100         else
1101                 type = MIME_UNKNOWN;
1102
1103         return type;
1104 }
1105
1106 static GList *mime_type_list = NULL;
1107
1108 gchar *procmime_get_mime_type(const gchar *filename)
1109 {
1110         static GHashTable *mime_type_table = NULL;
1111         MimeType *mime_type;
1112         const gchar *p;
1113         gchar *ext;
1114
1115         if (!mime_type_table) {
1116                 mime_type_table = procmime_get_mime_type_table();
1117                 if (!mime_type_table) return NULL;
1118         }
1119
1120         filename = g_basename(filename);
1121         p = strrchr(filename, '.');
1122         if (!p) return NULL;
1123
1124         Xstrdup_a(ext, p + 1, return NULL);
1125         g_strdown(ext);
1126         mime_type = g_hash_table_lookup(mime_type_table, ext);
1127         if (mime_type) {
1128                 gchar *str;
1129
1130                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
1131                                   NULL);
1132                 return str;
1133         }
1134
1135         return NULL;
1136 }
1137
1138 static guint procmime_str_hash(gconstpointer gptr)
1139 {
1140         guint hash_result = 0;
1141         const char *str;
1142
1143         for (str = gptr; str && *str; str++) {
1144                 if (isupper(*str)) hash_result += (*str + ' ');
1145                 else hash_result += *str;
1146         }
1147
1148         return hash_result;
1149 }
1150
1151 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
1152 {
1153         const char *str1 = gptr1;
1154         const char *str2 = gptr2;
1155
1156         return !strcasecmp(str1, str2);
1157 }
1158
1159 static GHashTable *procmime_get_mime_type_table(void)
1160 {
1161         GHashTable *table = NULL;
1162         GList *cur;
1163         MimeType *mime_type;
1164         gchar **exts;
1165
1166         if (!mime_type_list) {
1167                 mime_type_list = procmime_get_mime_type_list();
1168                 if (!mime_type_list) return NULL;
1169         }
1170
1171         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
1172
1173         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1174                 gint i;
1175                 gchar *key;
1176
1177                 mime_type = (MimeType *)cur->data;
1178
1179                 if (!mime_type->extension) continue;
1180
1181                 exts = g_strsplit(mime_type->extension, " ", 16);
1182                 for (i = 0; exts[i] != NULL; i++) {
1183                         /* make the key case insensitive */
1184                         g_strdown(exts[i]);
1185                         /* use previously dup'd key on overwriting */
1186                         if (g_hash_table_lookup(table, exts[i]))
1187                                 key = exts[i];
1188                         else
1189                                 key = g_strdup(exts[i]);
1190                         g_hash_table_insert(table, key, mime_type);
1191                 }
1192                 g_strfreev(exts);
1193         }
1194
1195         return table;
1196 }
1197
1198 GList *procmime_get_mime_type_list(void)
1199 {
1200         GList *list = NULL;
1201         FILE *fp;
1202         gchar buf[BUFFSIZE];
1203         gchar *p, *delim;
1204         MimeType *mime_type;
1205
1206         if (mime_type_list) 
1207                 return mime_type_list;
1208
1209         if ((fp = fopen("/etc/mime.types", "rb")) == NULL) {
1210                 if ((fp = fopen(SYSCONFDIR "/mime.types", "rb")) == NULL) {
1211                         FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
1212                         return NULL;
1213                 }
1214         }
1215
1216         while (fgets(buf, sizeof(buf), fp) != NULL) {
1217                 p = strchr(buf, '#');
1218                 if (p) *p = '\0';
1219                 g_strstrip(buf);
1220
1221                 p = buf;
1222                 while (*p && !isspace(*p)) p++;
1223                 if (*p) {
1224                         *p = '\0';
1225                         p++;
1226                 }
1227                 delim = strchr(buf, '/');
1228                 if (delim == NULL) continue;
1229                 *delim = '\0';
1230
1231                 mime_type = g_new(MimeType, 1);
1232                 mime_type->type = g_strdup(buf);
1233                 mime_type->sub_type = g_strdup(delim + 1);
1234
1235                 while (*p && isspace(*p)) p++;
1236                 if (*p)
1237                         mime_type->extension = g_strdup(p);
1238                 else
1239                         mime_type->extension = NULL;
1240
1241                 list = g_list_append(list, mime_type);
1242         }
1243
1244         fclose(fp);
1245
1246         if (!list)
1247                 g_warning("Can't read mime.types\n");
1248
1249         return list;
1250 }
1251
1252 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1253 {
1254         if (!charset)
1255                 return ENC_8BIT;
1256         else if (!strncasecmp(charset, "ISO-2022-", 9) ||
1257                  !strcasecmp(charset, "US-ASCII"))
1258                 return ENC_7BIT;
1259         else if (!strcasecmp(charset, "ISO-8859-5") ||
1260                  !strncasecmp(charset, "KOI8-", 5) ||
1261                  !strcasecmp(charset, "Windows-1251"))
1262                 return ENC_8BIT;
1263         else if (!strncasecmp(charset, "ISO-8859-", 9))
1264                 return ENC_QUOTED_PRINTABLE;
1265         else
1266                 return ENC_8BIT;
1267 }
1268
1269 EncodingType procmime_get_encoding_for_file(const gchar *file)
1270 {
1271         FILE *fp;
1272         guchar buf[BUFSIZ];
1273         size_t len;
1274
1275         if ((fp = fopen(file, "rb")) == NULL) {
1276                 FILE_OP_ERROR(file, "fopen");
1277                 return ENC_UNKNOWN;
1278         }
1279
1280         while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
1281                 guchar *p;
1282                 gint i;
1283
1284                 for (p = buf, i = 0; i < len; p++, i++) {
1285                         if (*p & 0x80) {
1286                                 fclose(fp);
1287                                 return ENC_BASE64;
1288                         }
1289                 }
1290         }
1291
1292         fclose(fp);
1293         return ENC_7BIT;
1294 }
1295
1296 struct EncodingTable 
1297 {
1298         gchar *str;
1299         EncodingType enc_type;
1300 };
1301
1302 struct EncodingTable encoding_table[] = {
1303         {"7bit", ENC_7BIT},
1304         {"8bit", ENC_8BIT},
1305         {"binary", ENC_BINARY},
1306         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1307         {"base64", ENC_BASE64},
1308         {"x-uuencode", ENC_UNKNOWN},
1309         {NULL, ENC_UNKNOWN},
1310 };
1311
1312 const gchar *procmime_get_encoding_str(EncodingType encoding)
1313 {
1314         struct EncodingTable *enc_table;
1315         
1316         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1317                 if (enc_table->enc_type == encoding)
1318                         return enc_table->str;
1319         }
1320         return NULL;
1321 }