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