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