ClamAV plugin gui tweaks
[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                         str = conv_codeset_strdup(buf, src_codeset, NULL);
889                         if (str) {
890                                 fputs(str, outfp);
891                                 g_free(str);
892                         } else {
893                                 conv_fail = TRUE;
894                                 fputs(buf, outfp);
895                         }
896                 }
897         } else if (mimeinfo->mime_type == MIME_TEXT_HTML) {
898                 HTMLParser *parser;
899                 CodeConverter *conv;
900
901                 conv = conv_code_converter_new(src_codeset);
902                 parser = html_parser_new(tmpfp, conv);
903                 while ((str = html_parse(parser)) != NULL) {
904                         fputs(str, outfp);
905                 }
906                 html_parser_destroy(parser);
907                 conv_code_converter_destroy(conv);
908         } else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED) {
909                 ERTFParser *parser;
910                 CodeConverter *conv;
911
912                 conv = conv_code_converter_new(src_codeset);
913                 parser = ertf_parser_new(tmpfp, conv);
914                 while ((str = ertf_parse(parser)) != NULL) {
915                         fputs(str, outfp);
916                 }
917                 ertf_parser_destroy(parser);
918                 conv_code_converter_destroy(conv);
919         }
920
921         if (conv_fail)
922                 g_warning("procmime_get_text_content(): Code conversion failed.\n");
923
924         fclose(tmpfp);
925         rewind(outfp);
926
927         return outfp;
928 }
929
930 /* search the first text part of (multipart) MIME message,
931    decode, convert it and output to outfp. */
932 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
933 {
934         FILE *infp, *outfp = NULL;
935         MimeInfo *mimeinfo, *partinfo;
936
937         g_return_val_if_fail(msginfo != NULL, NULL);
938
939         mimeinfo = procmime_scan_message(msginfo);
940         if (!mimeinfo) return NULL;
941
942         if ((infp = procmsg_open_message(msginfo)) == NULL) {
943                 procmime_mimeinfo_free_all(mimeinfo);
944                 return NULL;
945         }
946
947         partinfo = mimeinfo;
948         while (partinfo && partinfo->mime_type != MIME_TEXT)
949                 partinfo = procmime_mimeinfo_next(partinfo);
950         if (!partinfo) {
951                 partinfo = mimeinfo;
952                 while (partinfo && partinfo->mime_type != MIME_TEXT_HTML &&
953                                 partinfo->mime_type != MIME_TEXT_ENRICHED)
954                         partinfo = procmime_mimeinfo_next(partinfo);
955         }
956         
957
958         if (partinfo)
959                 outfp = procmime_get_text_content(partinfo, infp);
960
961         fclose(infp);
962         procmime_mimeinfo_free_all(mimeinfo);
963
964         return outfp;
965 }
966
967 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
968                                    const gchar *str, gboolean case_sens)
969 {
970
971         FILE *infp, *outfp;
972         gchar buf[BUFFSIZE];
973         gchar *(* StrFindFunc) (const gchar *haystack, const gchar *needle);
974
975         g_return_val_if_fail(mimeinfo != NULL, FALSE);
976         g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
977                              mimeinfo->mime_type == MIME_TEXT_HTML ||
978                              mimeinfo->mime_type == MIME_TEXT_ENRICHED, FALSE);
979         g_return_val_if_fail(str != NULL, FALSE);
980
981         if ((infp = fopen(filename, "rb")) == NULL) {
982                 FILE_OP_ERROR(filename, "fopen");
983                 return FALSE;
984         }
985
986         outfp = procmime_get_text_content(mimeinfo, infp);
987         fclose(infp);
988
989         if (!outfp)
990                 return FALSE;
991
992         if (case_sens)
993                 StrFindFunc = strstr;
994         else
995                 StrFindFunc = strcasestr;
996
997         while (fgets(buf, sizeof(buf), outfp) != NULL) {
998                 if (StrFindFunc(buf, str) != NULL) {
999                         fclose(outfp);
1000                         return TRUE;
1001                 }
1002         }
1003
1004         fclose(outfp);
1005
1006         return FALSE;
1007 }
1008
1009 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
1010                               gboolean case_sens)
1011 {
1012         MimeInfo *mimeinfo;
1013         MimeInfo *partinfo;
1014         gchar *filename;
1015         gboolean found = FALSE;
1016
1017         g_return_val_if_fail(msginfo != NULL, FALSE);
1018         g_return_val_if_fail(str != NULL, FALSE);
1019
1020         filename = procmsg_get_message_file(msginfo);
1021         if (!filename) return FALSE;
1022         mimeinfo = procmime_scan_message(msginfo);
1023
1024         for (partinfo = mimeinfo; partinfo != NULL;
1025              partinfo = procmime_mimeinfo_next(partinfo)) {
1026                 if (partinfo->mime_type == MIME_TEXT ||
1027                     partinfo->mime_type == MIME_TEXT_HTML ||
1028                     partinfo->mime_type == MIME_TEXT_ENRICHED) {
1029                         if (procmime_find_string_part
1030                                 (partinfo, filename, str, case_sens) == TRUE) {
1031                                 found = TRUE;
1032                                 break;
1033                         }
1034                 }
1035         }
1036
1037         procmime_mimeinfo_free_all(mimeinfo);
1038         g_free(filename);
1039
1040         return found;
1041 }
1042
1043 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
1044 {
1045         static guint32 id = 0;
1046         gchar *base;
1047         gchar *filename;
1048         gchar f_prefix[10];
1049
1050         g_return_val_if_fail(mimeinfo != NULL, NULL);
1051
1052         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
1053
1054         if (MIME_TEXT_HTML == mimeinfo->mime_type)
1055                 base = "mimetmp.html";
1056         else {
1057                 base = mimeinfo->filename ? mimeinfo->filename
1058                         : mimeinfo->name ? mimeinfo->name : "mimetmp";
1059                 base = g_basename(base);
1060                 if (*base == '\0') base = "mimetmp";
1061                 Xstrdup_a(base, base, return NULL);
1062                 subst_for_filename(base);
1063         }
1064
1065         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1066                                f_prefix, base, NULL);
1067
1068         return filename;
1069 }
1070
1071 ContentType procmime_scan_mime_type(const gchar *mime_type)
1072 {
1073         ContentType type;
1074
1075         if (!strncasecmp(mime_type, "text/html", 9))
1076                 type = MIME_TEXT_HTML;
1077         else if (!strncasecmp(mime_type, "text/enriched", 13))
1078                 type = MIME_TEXT_ENRICHED;
1079         else if (!strncasecmp(mime_type, "text/", 5))
1080                 type = MIME_TEXT;
1081         else if (!strncasecmp(mime_type, "message/rfc822", 14))
1082                 type = MIME_MESSAGE_RFC822;
1083         else if (!strncasecmp(mime_type, "message/", 8))
1084                 type = MIME_TEXT;
1085         else if (!strncasecmp(mime_type, "application/octet-stream", 24))
1086                 type = MIME_APPLICATION_OCTET_STREAM;
1087         else if (!strncasecmp(mime_type, "application/", 12))
1088                 type = MIME_APPLICATION;
1089         else if (!strncasecmp(mime_type, "multipart/", 10))
1090                 type = MIME_MULTIPART;
1091         else if (!strncasecmp(mime_type, "image/", 6))
1092                 type = MIME_IMAGE;
1093         else if (!strncasecmp(mime_type, "audio/", 6))
1094                 type = MIME_AUDIO;
1095         else if (!strcasecmp(mime_type, "text"))
1096                 type = MIME_TEXT;
1097         else
1098                 type = MIME_UNKNOWN;
1099
1100         return type;
1101 }
1102
1103 static GList *mime_type_list = NULL;
1104
1105 gchar *procmime_get_mime_type(const gchar *filename)
1106 {
1107         static GHashTable *mime_type_table = NULL;
1108         MimeType *mime_type;
1109         const gchar *p;
1110         gchar *ext;
1111
1112         if (!mime_type_table) {
1113                 mime_type_table = procmime_get_mime_type_table();
1114                 if (!mime_type_table) return NULL;
1115         }
1116
1117         filename = g_basename(filename);
1118         p = strrchr(filename, '.');
1119         if (!p) return NULL;
1120
1121         Xstrdup_a(ext, p + 1, return NULL);
1122         g_strdown(ext);
1123         mime_type = g_hash_table_lookup(mime_type_table, ext);
1124         if (mime_type) {
1125                 gchar *str;
1126
1127                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
1128                                   NULL);
1129                 return str;
1130         }
1131
1132         return NULL;
1133 }
1134
1135 static guint procmime_str_hash(gconstpointer gptr)
1136 {
1137         guint hash_result = 0;
1138         const char *str;
1139
1140         for (str = gptr; str && *str; str++) {
1141                 if (isupper(*str)) hash_result += (*str + ' ');
1142                 else hash_result += *str;
1143         }
1144
1145         return hash_result;
1146 }
1147
1148 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
1149 {
1150         const char *str1 = gptr1;
1151         const char *str2 = gptr2;
1152
1153         return !strcasecmp(str1, str2);
1154 }
1155
1156 static GHashTable *procmime_get_mime_type_table(void)
1157 {
1158         GHashTable *table = NULL;
1159         GList *cur;
1160         MimeType *mime_type;
1161         gchar **exts;
1162
1163         if (!mime_type_list) {
1164                 mime_type_list = procmime_get_mime_type_list();
1165                 if (!mime_type_list) return NULL;
1166         }
1167
1168         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
1169
1170         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1171                 gint i;
1172                 gchar *key;
1173
1174                 mime_type = (MimeType *)cur->data;
1175
1176                 if (!mime_type->extension) continue;
1177
1178                 exts = g_strsplit(mime_type->extension, " ", 16);
1179                 for (i = 0; exts[i] != NULL; i++) {
1180                         /* make the key case insensitive */
1181                         g_strdown(exts[i]);
1182                         /* use previously dup'd key on overwriting */
1183                         if (g_hash_table_lookup(table, exts[i]))
1184                                 key = exts[i];
1185                         else
1186                                 key = g_strdup(exts[i]);
1187                         g_hash_table_insert(table, key, mime_type);
1188                 }
1189                 g_strfreev(exts);
1190         }
1191
1192         return table;
1193 }
1194
1195 GList *procmime_get_mime_type_list(void)
1196 {
1197         GList *list = NULL;
1198         FILE *fp;
1199         gchar buf[BUFFSIZE];
1200         gchar *p, *delim;
1201         MimeType *mime_type;
1202
1203         if (mime_type_list) 
1204                 return mime_type_list;
1205
1206         if ((fp = fopen("/etc/mime.types", "rb")) == NULL) {
1207                 if ((fp = fopen(SYSCONFDIR "/mime.types", "rb")) == NULL) {
1208                         FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
1209                         return NULL;
1210                 }
1211         }
1212
1213         while (fgets(buf, sizeof(buf), fp) != NULL) {
1214                 p = strchr(buf, '#');
1215                 if (p) *p = '\0';
1216                 g_strstrip(buf);
1217
1218                 p = buf;
1219                 while (*p && !isspace(*p)) p++;
1220                 if (*p) {
1221                         *p = '\0';
1222                         p++;
1223                 }
1224                 delim = strchr(buf, '/');
1225                 if (delim == NULL) continue;
1226                 *delim = '\0';
1227
1228                 mime_type = g_new(MimeType, 1);
1229                 mime_type->type = g_strdup(buf);
1230                 mime_type->sub_type = g_strdup(delim + 1);
1231
1232                 while (*p && isspace(*p)) p++;
1233                 if (*p)
1234                         mime_type->extension = g_strdup(p);
1235                 else
1236                         mime_type->extension = NULL;
1237
1238                 list = g_list_append(list, mime_type);
1239         }
1240
1241         fclose(fp);
1242
1243         if (!list)
1244                 g_warning("Can't read mime.types\n");
1245
1246         return list;
1247 }
1248
1249 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1250 {
1251         if (!charset)
1252                 return ENC_8BIT;
1253         else if (!strncasecmp(charset, "ISO-2022-", 9) ||
1254                  !strcasecmp(charset, "US-ASCII"))
1255                 return ENC_7BIT;
1256         else if (!strcasecmp(charset, "ISO-8859-5") ||
1257                  !strncasecmp(charset, "KOI8-", 5) ||
1258                  !strcasecmp(charset, "Windows-1251"))
1259                 return ENC_8BIT;
1260         else if (!strncasecmp(charset, "ISO-8859-", 9))
1261                 return ENC_QUOTED_PRINTABLE;
1262         else
1263                 return ENC_8BIT;
1264 }
1265
1266 EncodingType procmime_get_encoding_for_file(const gchar *file)
1267 {
1268         FILE *fp;
1269         guchar buf[BUFSIZ];
1270         size_t len;
1271
1272         if ((fp = fopen(file, "rb")) == NULL) {
1273                 FILE_OP_ERROR(file, "fopen");
1274                 return ENC_UNKNOWN;
1275         }
1276
1277         while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
1278                 guchar *p;
1279                 gint i;
1280
1281                 for (p = buf, i = 0; i < len; p++, i++) {
1282                         if (*p & 0x80) {
1283                                 fclose(fp);
1284                                 return ENC_BASE64;
1285                         }
1286                 }
1287         }
1288
1289         fclose(fp);
1290         return ENC_7BIT;
1291 }
1292
1293 struct EncodingTable 
1294 {
1295         gchar *str;
1296         EncodingType enc_type;
1297 };
1298
1299 struct EncodingTable encoding_table[] = {
1300         {"7bit", ENC_7BIT},
1301         {"8bit", ENC_8BIT},
1302         {"binary", ENC_BINARY},
1303         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1304         {"base64", ENC_BASE64},
1305         {"x-uuencode", ENC_UNKNOWN},
1306         {NULL, ENC_UNKNOWN},
1307 };
1308
1309 const gchar *procmime_get_encoding_str(EncodingType encoding)
1310 {
1311         struct EncodingTable *enc_table;
1312         
1313         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1314                 if (enc_table->enc_type == encoding)
1315                         return enc_table->str;
1316         }
1317         return NULL;
1318 }