sync with sylpheed 0.6.3cvs8
[claws.git] / src / procmime.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 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 "uuencode.h"
37 #include "unmime.h"
38 #include "html.h"
39 #include "codeconv.h"
40 #include "utils.h"
41 #include "prefs_common.h"
42
43 #if USE_GPGME
44 #  include "rfc2015.h"
45 #endif
46
47 static GHashTable *procmime_get_mime_type_table (void);
48
49 MimeInfo *procmime_mimeinfo_new(void)
50 {
51         MimeInfo *mimeinfo;
52
53         mimeinfo = g_new0(MimeInfo, 1);
54         mimeinfo->mime_type     = MIME_UNKNOWN;
55         mimeinfo->encoding_type = ENC_UNKNOWN;
56
57         return mimeinfo;
58 }
59
60 void procmime_mimeinfo_free(MimeInfo *mimeinfo)
61 {
62         if (!mimeinfo) return;
63
64         g_free(mimeinfo->encoding);
65         g_free(mimeinfo->content_type);
66         g_free(mimeinfo->charset);
67         g_free(mimeinfo->name);
68         g_free(mimeinfo->boundary);
69         g_free(mimeinfo->content_disposition);
70         g_free(mimeinfo->filename);
71 #if USE_GPGME
72         g_free(mimeinfo->plaintextfile);
73         g_free(mimeinfo->sigstatus);
74         g_free(mimeinfo->sigstatus_full);
75 #endif
76
77         procmime_mimeinfo_free(mimeinfo->sub);
78
79         g_free(mimeinfo);
80 }
81
82 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
83 {
84         while (mimeinfo != NULL) {
85                 MimeInfo *next;
86
87                 g_free(mimeinfo->encoding);
88                 g_free(mimeinfo->content_type);
89                 g_free(mimeinfo->charset);
90                 g_free(mimeinfo->name);
91                 g_free(mimeinfo->boundary);
92                 g_free(mimeinfo->content_disposition);
93                 g_free(mimeinfo->filename);
94 #if USE_GPGME
95                 g_free(mimeinfo->plaintextfile);
96                 g_free(mimeinfo->sigstatus);
97                 g_free(mimeinfo->sigstatus_full);
98 #endif
99
100                 procmime_mimeinfo_free_all(mimeinfo->sub);
101                 procmime_mimeinfo_free_all(mimeinfo->children);
102 #if USE_GPGME
103                 procmime_mimeinfo_free_all(mimeinfo->plaintext);
104 #endif
105
106                 next = mimeinfo->next;
107                 g_free(mimeinfo);
108                 mimeinfo = next;
109         }
110 }
111
112 MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
113 {
114         MimeInfo *child = parent->children;
115
116         if (!child)
117                 parent->children = mimeinfo;
118         else {
119                 while (child->next != NULL)
120                         child = child->next;
121
122                 child->next = mimeinfo;
123         }
124
125         mimeinfo->parent = parent;
126         mimeinfo->level = parent->level + 1;
127
128         return mimeinfo;
129 }
130
131 void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
132 {
133         MimeInfo *parent = old->parent;
134         MimeInfo *child;
135
136         g_return_if_fail(parent != NULL);
137         g_return_if_fail(new->next == NULL);
138
139         for (child = parent->children; child && child != old;
140              child = child->next)
141                 ;
142         if (!child) {
143                 g_warning("oops: parent can't find it's own child");
144                 return;
145         }
146         procmime_mimeinfo_free_all(old);
147
148         if (child == parent->children) {
149                 new->next = parent->children->next;
150                 parent->children = new;
151         } else {
152                 new->next = child->next;
153                 child = new;
154         }
155 }
156
157 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
158 {
159         if (!mimeinfo) return NULL;
160
161         if (mimeinfo->children)
162                 return mimeinfo->children;
163         if (mimeinfo->sub)
164                 return mimeinfo->sub;
165         if (mimeinfo->next)
166                 return mimeinfo->next;
167
168         for (mimeinfo = mimeinfo->parent; mimeinfo != NULL;
169              mimeinfo = mimeinfo->parent) {
170                 if (mimeinfo->next)
171                         return mimeinfo->next;
172                 if (mimeinfo->main) {
173                         mimeinfo = mimeinfo->main;
174                         if (mimeinfo->next)
175                                 return mimeinfo->next;
176                 }
177         }
178
179         return NULL;
180 }
181
182 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
183 {
184         FILE *fp;
185         MimeInfo *mimeinfo;
186
187         g_return_val_if_fail(msginfo != NULL, NULL);
188
189         if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
190         mimeinfo = procmime_scan_mime_header(fp);
191
192         if (mimeinfo) {
193                 if (mimeinfo->mime_type != MIME_MULTIPART) {
194                         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
195                                 perror("fseek");
196                 }
197                 if (mimeinfo->mime_type != MIME_TEXT)
198                         procmime_scan_multipart_message(mimeinfo, fp);
199         }
200
201 #if USE_GPGME
202         if (prefs_common.auto_check_signatures)
203                 rfc2015_check_signature(mimeinfo, fp);
204 #endif
205         fclose(fp);
206
207         return mimeinfo;
208 }
209
210 void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp)
211 {
212         gchar *p;
213         gchar *boundary;
214         gint boundary_len = 0;
215         gchar buf[BUFFSIZE];
216         glong fpos, prev_fpos;
217         gint npart;
218
219         g_return_if_fail(mimeinfo != NULL);
220         g_return_if_fail(mimeinfo->mime_type != MIME_TEXT);
221
222         if (mimeinfo->mime_type == MIME_MULTIPART) {
223                 g_return_if_fail(mimeinfo->boundary != NULL);
224                 g_return_if_fail(mimeinfo->sub == NULL);
225         }
226         g_return_if_fail(fp != NULL);
227
228         boundary = mimeinfo->boundary;
229
230         if (boundary) {
231                 boundary_len = strlen(boundary);
232
233                 /* look for first boundary */
234                 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
235                         if (IS_BOUNDARY(buf, boundary, boundary_len)) break;
236                 if (!p) return;
237         }
238
239         if ((fpos = ftell(fp)) < 0) {
240                 perror("ftell");
241                 return;
242         }
243
244         for (npart = 0;; npart++) {
245                 MimeInfo *partinfo;
246                 gboolean eom = FALSE;
247
248                 prev_fpos = fpos;
249
250                 partinfo = procmime_scan_mime_header(fp);
251                 if (!partinfo) break;
252                 procmime_mimeinfo_insert(mimeinfo, partinfo);
253
254                 if (partinfo->mime_type == MIME_MULTIPART) {
255                         if (partinfo->level < 8)
256                                 procmime_scan_multipart_message(partinfo, fp);
257                 } else if (partinfo->mime_type == MIME_MESSAGE_RFC822) {
258                         MimeInfo *sub;
259
260                         partinfo->sub = sub = procmime_scan_mime_header(fp);
261                         if (!sub) break;
262                         sub->level = partinfo->level + 1;
263                         sub->parent = partinfo->parent;
264                         sub->main = partinfo;
265
266                         if (sub->mime_type == MIME_MULTIPART) {
267                                 if (sub->level < 8)
268                                         procmime_scan_multipart_message
269                                                 (sub, fp);
270                         }
271                 }
272
273                 /* look for next boundary */
274                 buf[0] = '\0';
275                 while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
276                         if (IS_BOUNDARY(buf, boundary, boundary_len)) {
277                                 if (buf[2 + boundary_len]     == '-' &&
278                                     buf[2 + boundary_len + 1] == '-')
279                                         eom = TRUE;
280                                 break;
281                         }
282                 }
283                 if (p == NULL)
284                         eom = TRUE;     /* broken MIME message */
285                 fpos = ftell(fp);
286
287                 partinfo->size = fpos - prev_fpos - strlen(buf);
288
289                 if (eom) break;
290         }
291 }
292
293 void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding)
294 {
295         gchar *buf;
296
297         Xstrdup_a(buf, encoding, return);
298
299         g_free(mimeinfo->encoding);
300
301         mimeinfo->encoding = g_strdup(g_strstrip(buf));
302         if (!strcasecmp(buf, "7bit"))
303                 mimeinfo->encoding_type = ENC_7BIT;
304         else if (!strcasecmp(buf, "8bit"))
305                 mimeinfo->encoding_type = ENC_8BIT;
306         else if (!strcasecmp(buf, "quoted-printable"))
307                 mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE;
308         else if (!strcasecmp(buf, "base64"))
309                 mimeinfo->encoding_type = ENC_BASE64;
310         else if (!strcasecmp(buf, "x-uuencode"))
311                 mimeinfo->encoding_type = ENC_X_UUENCODE;
312         else
313                 mimeinfo->encoding_type = ENC_UNKNOWN;
314
315 }
316
317 void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type)
318 {
319         gchar *delim, *p, *cnttype;
320         gchar *buf;
321
322         if (conv_get_current_charset() == C_EUC_JP &&
323             strchr(content_type, '\033')) {
324                 gint len;
325                 len = strlen(content_type) * 2 + 1;
326                 Xalloca(buf, len, return);
327                 conv_jistoeuc(buf, len, content_type);
328         } else
329                 Xstrdup_a(buf, content_type, return);
330
331         g_free(mimeinfo->content_type);
332         g_free(mimeinfo->charset);
333         g_free(mimeinfo->name);
334         mimeinfo->content_type = NULL;
335         mimeinfo->charset      = NULL;
336         mimeinfo->name         = NULL;
337
338         if ((delim = strchr(buf, ';'))) *delim = '\0';
339         mimeinfo->content_type = cnttype = g_strdup(g_strstrip(buf));
340
341         mimeinfo->mime_type = procmime_scan_mime_type(cnttype);
342
343         if (!delim) return;
344         p = delim + 1;
345
346         for (;;) {
347                 gchar *eq;
348                 gchar *attr, *value;
349
350                 if ((delim = strchr(p, ';'))) *delim = '\0';
351
352                 if (!(eq = strchr(p, '='))) break;
353
354                 *eq = '\0';
355                 attr = p;
356                 g_strstrip(attr);
357                 value = eq + 1;
358                 g_strstrip(value);
359
360                 if (*value == '"')
361                         extract_quote(value, '"');
362                 else {
363                         eliminate_parenthesis(value, '(', ')');
364                         g_strstrip(value);
365                 }
366
367                 if (*value) {
368                         if (!strcasecmp(attr, "charset"))
369                                 mimeinfo->charset = g_strdup(value);
370                         else if (!strcasecmp(attr, "name")) {
371                                 gchar *tmp;
372                                 size_t len;
373
374                                 len = strlen(value) + 1;
375                                 Xalloca(tmp, len, return);
376                                 conv_unmime_header(tmp, len, value, NULL);
377                                 mimeinfo->name = g_strdup(tmp);
378                         } else if (!strcasecmp(attr, "boundary"))
379                                 mimeinfo->boundary = g_strdup(value);
380                 }
381
382                 if (!delim) break;
383                 p = delim + 1;
384         }
385
386         if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary)
387                 mimeinfo->mime_type = MIME_TEXT;
388 }
389
390 void procmime_scan_content_disposition(MimeInfo *mimeinfo,
391                                        const gchar *content_disposition)
392 {
393         gchar *delim, *p, *dispos;
394         gchar *buf;
395
396         if (conv_get_current_charset() == C_EUC_JP &&
397             strchr(content_disposition, '\033')) {
398                 gint len;
399                 len = strlen(content_disposition) * 2 + 1;
400                 Xalloca(buf, len, return);
401                 conv_jistoeuc(buf, len, content_disposition);
402         } else
403                 Xstrdup_a(buf, content_disposition, return);
404
405         if ((delim = strchr(buf, ';'))) *delim = '\0';
406         mimeinfo->content_disposition = dispos = g_strdup(g_strstrip(buf));
407
408         if (!delim) return;
409         p = delim + 1;
410
411         for (;;) {
412                 gchar *eq;
413                 gchar *attr, *value;
414
415                 if ((delim = strchr(p, ';'))) *delim = '\0';
416
417                 if (!(eq = strchr(p, '='))) break;
418
419                 *eq = '\0';
420                 attr = p;
421                 g_strstrip(attr);
422                 value = eq + 1;
423                 g_strstrip(value);
424
425                 if (*value == '"')
426                         extract_quote(value, '"');
427                 else {
428                         eliminate_parenthesis(value, '(', ')');
429                         g_strstrip(value);
430                 }
431
432                 if (*value) {
433                         if (!strcasecmp(attr, "filename")) {
434                                 gchar *tmp;
435                                 size_t len;
436
437                                 len = strlen(value) + 1;
438                                 Xalloca(tmp, len, return);
439                                 conv_unmime_header(tmp, len, value, NULL);
440                                 g_free(mimeinfo->filename);
441                                 mimeinfo->filename = g_strdup(tmp);
442                                 break;
443                         }
444                 }
445
446                 if (!delim) break;
447                 p = delim + 1;
448         }
449 }
450
451 enum
452 {
453         H_CONTENT_TRANSFER_ENCODING = 0,
454         H_CONTENT_TYPE              = 1,
455         H_CONTENT_DISPOSITION       = 2
456 };
457
458 MimeInfo *procmime_scan_mime_header(FILE *fp)
459 {
460         static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:",
461                                                           NULL, FALSE},
462                                        {"Content-Type:", NULL, TRUE},
463                                        {"Content-Disposition:",
464                                                           NULL, TRUE},
465                                        {NULL,             NULL, FALSE}};
466         gchar buf[BUFFSIZE];
467         gint hnum;
468         HeaderEntry *hp;
469         MimeInfo *mimeinfo;
470
471         g_return_val_if_fail(fp != NULL, NULL);
472
473         mimeinfo = procmime_mimeinfo_new();
474         mimeinfo->mime_type = MIME_TEXT;
475         mimeinfo->encoding_type = ENC_7BIT;
476         mimeinfo->fpos = ftell(fp);
477
478         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
479                != -1) {
480                 hp = hentry + hnum;
481
482                 if (H_CONTENT_TRANSFER_ENCODING == hnum) {
483                         procmime_scan_encoding
484                                 (mimeinfo, buf + strlen(hp->name));
485                 } else if (H_CONTENT_TYPE == hnum) {
486                         procmime_scan_content_type
487                                 (mimeinfo, buf + strlen(hp->name));
488                 } else if (H_CONTENT_DISPOSITION == hnum) {
489                         procmime_scan_content_disposition
490                                 (mimeinfo, buf + strlen(hp->name));
491                 }
492         }
493
494         if (mimeinfo->mime_type == MIME_APPLICATION_OCTET_STREAM &&
495             mimeinfo->name) {
496                 const gchar *type;
497                 type = procmime_get_mime_type(mimeinfo->name);
498                 if (type)
499                         mimeinfo->mime_type = procmime_scan_mime_type(type);
500         }
501
502         if (!mimeinfo->content_type)
503                 mimeinfo->content_type = g_strdup("text/plain");
504
505         return mimeinfo;
506 }
507
508 FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
509 {
510         gchar buf[BUFFSIZE];
511         gchar *boundary = NULL;
512         gint boundary_len = 0;
513         gboolean tmp_file = FALSE;
514
515         g_return_val_if_fail(infp != NULL, NULL);
516         g_return_val_if_fail(mimeinfo != NULL, NULL);
517
518         if (!outfp) {
519                 outfp = my_tmpfile();
520                 if (!outfp) {
521                         perror("tmpfile");
522                         return NULL;
523                 }
524                 tmp_file = TRUE;
525         }
526
527         if (mimeinfo->parent && mimeinfo->parent->boundary) {
528                 boundary = mimeinfo->parent->boundary;
529                 boundary_len = strlen(boundary);
530         }
531
532         if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) {
533                 gboolean softline = FALSE;
534
535                 while (fgets(buf, sizeof(buf), infp) != NULL &&
536                        (!boundary ||
537                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
538                         guchar *p = buf;
539
540                         softline = DoOneQPLine(&p, FALSE, softline);
541                         fwrite(buf, p - (guchar *)buf, 1, outfp);
542                 }
543         } else if (mimeinfo->encoding_type == ENC_BASE64) {
544                 gchar outbuf[BUFFSIZE];
545                 gint len;
546                 Base64Decoder *decoder;
547
548                 decoder = base64_decoder_new();
549                 while (fgets(buf, sizeof(buf), infp) != NULL &&
550                        (!boundary ||
551                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
552                         len = base64_decoder_decode(decoder, buf, outbuf);
553                         if (len < 0) {
554                                 g_warning("Bad BASE64 content\n");
555                                 break;
556                         }
557                         fwrite(outbuf, sizeof(gchar), len, outfp);
558                 }
559                 base64_decoder_free(decoder);
560         } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) {
561                 gchar outbuf[BUFFSIZE];
562                 gint len;
563                 gboolean flag = FALSE;
564
565                 while (fgets(buf, sizeof(buf), infp) != NULL &&
566                        (!boundary ||
567                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
568                         if(!flag && strncmp(buf,"begin ", 6)) continue;
569
570                         if (flag) {
571                                 len = fromuutobits(outbuf, buf);
572                                 if (len <= 0) {
573                                         if (len < 0) 
574                                                 g_warning("Bad UUENCODE content(%d)\n", len);
575                                         break;
576                                 }
577                                 fwrite(outbuf, sizeof(gchar), len, outfp);
578                         } else
579                                 flag = TRUE;
580                 }
581         } else {
582                 while (fgets(buf, sizeof(buf), infp) != NULL &&
583                        (!boundary ||
584                         !IS_BOUNDARY(buf, boundary, boundary_len))) {
585                         fputs(buf, outfp);
586                 }
587         }
588
589         if (tmp_file) rewind(outfp);
590         return outfp;
591 }
592
593 gint procmime_get_part(const gchar *outfile, const gchar *infile,
594                        MimeInfo *mimeinfo)
595 {
596         FILE *infp, *outfp;
597         gchar buf[BUFFSIZE];
598
599         g_return_val_if_fail(outfile != NULL, -1);
600         g_return_val_if_fail(infile != NULL, -1);
601         g_return_val_if_fail(mimeinfo != NULL, -1);
602
603         if ((infp = fopen(infile, "r")) == NULL) {
604                 FILE_OP_ERROR(infile, "fopen");
605                 return -1;
606         }
607         if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
608                 FILE_OP_ERROR(infile, "fseek");
609                 fclose(infp);
610                 return -1;
611         }
612         if ((outfp = fopen(outfile, "w")) == NULL) {
613                 FILE_OP_ERROR(outfile, "fopen");
614                 fclose(infp);
615                 return -1;
616         }
617
618         while (fgets(buf, sizeof(buf), infp) != NULL)
619                 if (buf[0] == '\r' || buf[0] == '\n') break;
620
621         procmime_decode_content(outfp, infp, mimeinfo);
622
623         fclose(infp);
624         if (fclose(outfp) == EOF) {
625                 FILE_OP_ERROR(outfile, "fclose");
626                 unlink(outfile);
627                 return -1;
628         }
629
630         return 0;
631 }
632
633 FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp)
634 {
635         FILE *tmpfp, *outfp;
636         gchar *src_codeset;
637         gboolean conv_fail = FALSE;
638         gchar buf[BUFFSIZE];
639         gchar *str;
640
641         g_return_val_if_fail(mimeinfo != NULL, NULL);
642         g_return_val_if_fail(infp != NULL, NULL);
643         g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
644                              mimeinfo->mime_type == MIME_TEXT_HTML, NULL);
645
646         if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
647                 perror("fseek");
648                 return NULL;
649         }
650
651         while (fgets(buf, sizeof(buf), infp) != NULL)
652                 if (buf[0] == '\r' || buf[0] == '\n') break;
653
654         tmpfp = procmime_decode_content(NULL, infp, mimeinfo);
655         if (!tmpfp)
656                 return NULL;
657
658         if ((outfp = my_tmpfile()) == NULL) {
659                 perror("tmpfile");
660                 fclose(tmpfp);
661                 return NULL;
662         }
663
664         src_codeset = prefs_common.force_charset
665                 ? prefs_common.force_charset : mimeinfo->charset;
666
667         if (mimeinfo->mime_type == MIME_TEXT) {
668                 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
669                         str = conv_codeset_strdup(buf, src_codeset, NULL);
670                         if (str) {
671                                 fputs(str, outfp);
672                                 g_free(str);
673                         } else {
674                                 conv_fail = TRUE;
675                                 fputs(buf, outfp);
676                         }
677                 }
678         } else if (mimeinfo->mime_type == MIME_TEXT_HTML) {
679                 HTMLParser *parser;
680                 CodeConverter *conv;
681
682                 conv = conv_code_converter_new(src_codeset);
683                 parser = html_parser_new(tmpfp, conv);
684                 while ((str = html_parse(parser)) != NULL) {
685                         fputs(str, outfp);
686                 }
687                 html_parser_destroy(parser);
688                 conv_code_converter_destroy(conv);
689         }
690
691         if (conv_fail)
692                 g_warning(_("procmime_get_text_content(): Code conversion failed.\n"));
693
694         fclose(tmpfp);
695         rewind(outfp);
696
697         return outfp;
698 }
699
700 /* search the first text part of (multipart) MIME message,
701    decode, convert it and output to outfp. */
702 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
703 {
704         FILE *infp, *outfp = NULL;
705         MimeInfo *mimeinfo, *partinfo;
706
707         g_return_val_if_fail(msginfo != NULL, NULL);
708
709         mimeinfo = procmime_scan_message(msginfo);
710         if (!mimeinfo) return NULL;
711
712         if ((infp = procmsg_open_message(msginfo)) == NULL) {
713                 procmime_mimeinfo_free_all(mimeinfo);
714                 return NULL;
715         }
716
717         partinfo = mimeinfo;
718         while (partinfo && partinfo->mime_type != MIME_TEXT)
719                 partinfo = procmime_mimeinfo_next(partinfo);
720         if (!partinfo) {
721                 partinfo = mimeinfo;
722                 while (partinfo && partinfo->mime_type != MIME_TEXT_HTML)
723                         partinfo = procmime_mimeinfo_next(partinfo);
724         }
725
726         if (partinfo)
727                 outfp = procmime_get_text_content(partinfo, infp);
728
729         fclose(infp);
730         procmime_mimeinfo_free_all(mimeinfo);
731
732         return outfp;
733 }
734
735 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
736                                    const gchar *str, gboolean case_sens)
737 {
738
739         FILE *infp, *outfp;
740         gchar buf[BUFFSIZE];
741         gchar *(* StrFindFunc) (const gchar *haystack, const gchar *needle);
742
743         g_return_val_if_fail(mimeinfo != NULL, FALSE);
744         g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
745                              mimeinfo->mime_type == MIME_TEXT_HTML, FALSE);
746         g_return_val_if_fail(str != NULL, FALSE);
747
748         if ((infp = fopen(filename, "r")) == NULL) {
749                 FILE_OP_ERROR(filename, "fopen");
750                 return FALSE;
751         }
752
753         outfp = procmime_get_text_content(mimeinfo, infp);
754         fclose(infp);
755
756         if (!outfp)
757                 return FALSE;
758
759         if (case_sens)
760                 StrFindFunc = strstr;
761         else
762                 StrFindFunc = strcasestr;
763
764         while (fgets(buf, sizeof(buf), outfp) != NULL) {
765                 if (StrFindFunc(buf, str) != NULL) {
766                         fclose(outfp);
767                         return TRUE;
768                 }
769         }
770
771         fclose(outfp);
772
773         return FALSE;
774 }
775
776 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
777                               gboolean case_sens)
778 {
779         MimeInfo *mimeinfo;
780         MimeInfo *partinfo;
781         gchar *filename;
782         gboolean found = FALSE;
783
784         g_return_val_if_fail(msginfo != NULL, FALSE);
785         g_return_val_if_fail(str != NULL, FALSE);
786
787         filename = procmsg_get_message_file(msginfo);
788         if (!filename) return FALSE;
789         mimeinfo = procmime_scan_message(msginfo);
790
791         for (partinfo = mimeinfo; partinfo != NULL;
792              partinfo = procmime_mimeinfo_next(partinfo)) {
793                 if (partinfo->mime_type == MIME_TEXT ||
794                     partinfo->mime_type == MIME_TEXT_HTML) {
795                         if (procmime_find_string_part
796                                 (partinfo, filename, str, case_sens) == TRUE) {
797                                 found = TRUE;
798                                 break;
799                         }
800                 }
801         }
802
803         procmime_mimeinfo_free_all(mimeinfo);
804         g_free(filename);
805
806         return found;
807 }
808
809 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
810 {
811         static guint32 id = 0;
812         gchar *base;
813         gchar *filename;
814         gchar f_prefix[10];
815
816         g_return_val_if_fail(mimeinfo != NULL, NULL);
817
818         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
819
820         if (MIME_TEXT_HTML == mimeinfo->mime_type)
821                 base = "mimetmp.html";
822         else {
823                 base = mimeinfo->filename ? mimeinfo->filename
824                         : mimeinfo->name ? mimeinfo->name : "mimetmp";
825                 base = g_basename(base);
826                 if (*base == '\0') base = "mimetmp";
827         }
828
829         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
830                                f_prefix, base, NULL);
831
832         return filename;
833 }
834
835 ContentType procmime_scan_mime_type(const gchar *mime_type)
836 {
837         ContentType type;
838
839         if (!strncasecmp(mime_type, "text/html", 9))
840                 type = MIME_TEXT_HTML;
841         else if (!strncasecmp(mime_type, "text/", 5))
842                 type = MIME_TEXT;
843         else if (!strncasecmp(mime_type, "message/rfc822", 14))
844                 type = MIME_MESSAGE_RFC822;
845         else if (!strncasecmp(mime_type, "message/", 8))
846                 type = MIME_TEXT;
847         else if (!strncasecmp(mime_type, "application/octet-stream", 24))
848                 type = MIME_APPLICATION_OCTET_STREAM;
849         else if (!strncasecmp(mime_type, "application/", 12))
850                 type = MIME_APPLICATION;
851         else if (!strncasecmp(mime_type, "multipart/", 10))
852                 type = MIME_MULTIPART;
853         else if (!strncasecmp(mime_type, "image/", 6))
854                 type = MIME_IMAGE;
855         else if (!strncasecmp(mime_type, "audio/", 6))
856                 type = MIME_AUDIO;
857         else if (!strcasecmp(mime_type, "text"))
858                 type = MIME_TEXT;
859         else
860                 type = MIME_UNKNOWN;
861
862         return type;
863 }
864
865 static GList *mime_type_list = NULL;
866
867 gchar *procmime_get_mime_type(const gchar *filename)
868 {
869         static GHashTable *mime_type_table = NULL;
870         MimeType *mime_type;
871         const gchar *ext, *p;
872
873         if (!mime_type_table) {
874                 mime_type_table = procmime_get_mime_type_table();
875                 if (!mime_type_table) return NULL;
876         }
877
878         filename = g_basename(filename);
879         p = strrchr(filename, '.');
880         if (p)
881                 ext = p + 1;
882         else
883                 return NULL;
884
885         mime_type = g_hash_table_lookup(mime_type_table, ext);
886         if (mime_type) {
887                 gchar *str;
888
889                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
890                                   NULL);
891                 return str;
892         }
893
894         return NULL;
895 }
896
897 static guint procmime_str_hash(gconstpointer gptr)
898 {
899         guint hash_result = 0;
900         const char *str;
901
902         for (str = gptr; str && *str; str++) {
903                 if (isupper(*str)) hash_result += (*str + ' ');
904                 else hash_result += *str;
905         }
906
907         return hash_result;
908 }
909
910 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
911 {
912         const char *str1 = gptr1;
913         const char *str2 = gptr2;
914
915         return !strcasecmp(str1, str2);
916 }
917
918 static GHashTable *procmime_get_mime_type_table(void)
919 {
920         GHashTable *table = NULL;
921         GList *cur;
922         MimeType *mime_type;
923         gchar **exts;
924
925         if (!mime_type_list) {
926                 mime_type_list = procmime_get_mime_type_list();
927                 if (!mime_type_list) return NULL;
928         }
929
930         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
931
932         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
933                 gint i;
934
935                 mime_type = (MimeType *)cur->data;
936
937                 if (!mime_type->extension) continue;
938
939                 exts = g_strsplit(mime_type->extension, " ", 16);
940                 for (i = 0; exts[i] != NULL; i++)
941                         g_hash_table_insert(table, g_strdup(exts[i]),
942                                             mime_type);
943                 g_strfreev(exts);
944         }
945
946         return table;
947 }
948
949 GList *procmime_get_mime_type_list(void)
950 {
951         GList *list = NULL;
952         FILE *fp;
953         gchar buf[BUFFSIZE];
954         gchar *p, *delim;
955         MimeType *mime_type;
956
957         if (mime_type_list) 
958                 return mime_type_list;
959
960         if ((fp = fopen("/etc/mime.types", "r")) == NULL) {
961                 if ((fp = fopen(SYSCONFDIR "/mime.types", "r")) == NULL) {
962                         FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
963                         return NULL;
964                 }
965         }
966
967         while (fgets(buf, sizeof(buf), fp) != NULL) {
968                 p = strchr(buf, '#');
969                 if (p) *p = '\0';
970                 g_strstrip(buf);
971
972                 p = buf;
973                 while (*p && !isspace(*p)) p++;
974                 if (*p) {
975                         *p = '\0';
976                         p++;
977                 }
978                 delim = strchr(buf, '/');
979                 if (delim == NULL) continue;
980                 *delim = '\0';
981
982                 mime_type = g_new(MimeType, 1);
983                 mime_type->type = g_strdup(buf);
984                 mime_type->sub_type = g_strdup(delim + 1);
985
986                 while (*p && isspace(*p)) p++;
987                 if (*p)
988                         mime_type->extension = g_strdup(p);
989                 else
990                         mime_type->extension = NULL;
991
992                 list = g_list_append(list, mime_type);
993         }
994
995         fclose(fp);
996
997         if (!list)
998                 g_warning("Can't read mime.types\n");
999
1000         return list;
1001 }
1002
1003 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1004 {
1005         if (!charset)
1006                 return ENC_8BIT;
1007         else if (!strncasecmp(charset, "ISO-2022-", 9) ||
1008                  !strcasecmp(charset, "US-ASCII"))
1009                 return ENC_7BIT;
1010         else
1011                 return ENC_8BIT;
1012                 /* return ENC_BASE64; */
1013                 /* return ENC_QUOTED_PRINTABLE; */
1014 }
1015
1016 const gchar *procmime_get_encoding_str(EncodingType encoding)
1017 {
1018         static const gchar *encoding_str[] = {
1019                 "7bit", "8bit", "quoted-printable", "base64", "x-uuencode",
1020                 NULL
1021         };
1022
1023         if (encoding >= ENC_7BIT && encoding <= ENC_UNKNOWN)
1024                 return encoding_str[encoding];
1025         else
1026                 return NULL;
1027 }