* src/procmime.c
[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_shellsafe_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         str = content_type_parts[0];
1102         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1103          * if it's not available we use the default Content-Type */
1104         if ((str == NULL) || (str[0] == '\0') || (strchr(str, '/') == NULL)) {
1105                 mimeinfo->type = MIMETYPE_TEXT;
1106                 mimeinfo->subtype = g_strdup("plain");
1107                 if(g_hash_table_lookup(mimeinfo->parameters,
1108                                        "charset") == NULL)
1109                         g_hash_table_insert(mimeinfo->parameters,
1110                                             g_strdup("charset"),
1111                                             g_strdup("us-ascii"));
1112                 g_strfreev(content_type_parts);
1113                 return;
1114         } else {
1115                 mimeinfo->type = MIMETYPE_UNKNOWN;
1116                 for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++) {
1117                         if (g_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0 &&
1118                             str[strlen(typetablearray->str)] == '/') {
1119                                 mimeinfo->type = typetablearray->type;
1120                                 mimeinfo->subtype = g_strdup(str + strlen(typetablearray->str) + 1);
1121                                 break;
1122                         }
1123                 }
1124
1125                 /* Get mimeinfo->parmeters */
1126                 add_to_mimeinfo_parameters(&content_type_parts[1], mimeinfo);
1127         }
1128
1129         g_strfreev(content_type_parts);
1130 }
1131
1132 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1133 {
1134         gchar **content_disp_parts;
1135         gchar **strarray;
1136         gchar *str;
1137
1138         g_return_if_fail(content_disposition != NULL);
1139         g_return_if_fail(mimeinfo != NULL);
1140
1141         /* Split into parts and remove trailing
1142            and leading whitespaces from all strings */
1143         content_disp_parts = g_strsplit(content_disposition, ";", 0);
1144         for (strarray = content_disp_parts; *strarray != NULL; strarray++) {
1145                 g_strstrip(*strarray);
1146         }
1147         /* Get mimeinfo->disposition */
1148         str = content_disp_parts[0];
1149         if (str == NULL) {
1150                 g_strfreev(content_disp_parts);
1151                 return;
1152         }
1153         if (!g_strcasecmp(str, "inline")) 
1154                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1155         else if (!g_strcasecmp(str, "attachment"))
1156                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1157         else
1158                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1159         
1160         add_to_mimeinfo_parameters(&content_disp_parts[1], mimeinfo);
1161         g_strfreev(content_disp_parts);
1162 }
1163
1164
1165 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1166 {
1167         struct EncodingTable *enc_table;
1168         
1169         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1170                 if (g_strcasecmp(enc_table->str, content_encoding) == 0) {
1171                         mimeinfo->encoding_type = enc_table->enc_type;
1172                         return;
1173                 }
1174         }
1175         mimeinfo->encoding_type = ENC_UNKNOWN;
1176         return;
1177 }
1178
1179 void procmime_parse_mimepart(MimeInfo *parent,
1180                              gchar *content_type,
1181                              gchar *content_encoding,
1182                              gchar *content_description,
1183                              gchar *content_id,
1184                              gchar *content_disposition,
1185                              const gchar *filename,
1186                              guint offset,
1187                              guint length)
1188 {
1189         MimeInfo *mimeinfo;
1190
1191         /* Create MimeInfo */
1192         mimeinfo = procmime_mimeinfo_new();
1193         if (parent != NULL)
1194                 g_node_append(parent->node, mimeinfo->node);
1195         mimeinfo->filename = g_strdup(filename);
1196         mimeinfo->offset = offset;
1197         mimeinfo->length = length;
1198
1199         if (content_type != NULL) {
1200                 procmime_parse_content_type(content_type, mimeinfo);
1201         } else {
1202                 mimeinfo->type = MIMETYPE_TEXT;
1203                 mimeinfo->subtype = g_strdup("plain");
1204                 if(g_hash_table_lookup(mimeinfo->parameters,
1205                                        "charset") == NULL)
1206                         g_hash_table_insert(mimeinfo->parameters, g_strdup("charset"), g_strdup("us-ascii"));
1207         }
1208
1209         if (content_encoding != NULL) {
1210                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1211         } else {
1212                 mimeinfo->encoding_type = ENC_7BIT;
1213         }
1214
1215         if (content_description != NULL)
1216                 mimeinfo->description = g_strdup(content_description);
1217         else
1218                 mimeinfo->description = NULL;
1219
1220         if (content_id != NULL)
1221                 mimeinfo->id = g_strdup(content_id);
1222         else
1223                 mimeinfo->id = NULL;
1224
1225         if (content_disposition != NULL) 
1226                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1227         else
1228                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1229
1230         /* Call parser for mime type */
1231         switch (mimeinfo->type) {
1232                 case MIMETYPE_MESSAGE:
1233                         if (g_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1234                                 procmime_parse_message_rfc822(mimeinfo);
1235                         }
1236                         break;
1237                         
1238                 case MIMETYPE_MULTIPART:
1239                         procmime_parse_multipart(mimeinfo);
1240                         break;
1241                         
1242                 default:
1243                         break;
1244         }
1245 }
1246
1247 static gchar *typenames[] = {
1248     "text",
1249     "image",
1250     "audio",
1251     "video",
1252     "application",
1253     "message",
1254     "multipart",
1255     "unknown",
1256 };
1257
1258 static gboolean output_func(GNode *node, gpointer data)
1259 {
1260         guint i, depth;
1261         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1262
1263         depth = g_node_depth(node);
1264         for(i = 0; i < depth; i++)
1265                 printf("    ");
1266         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1267
1268         return FALSE;
1269 }
1270
1271 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1272 {
1273         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1274 }
1275
1276 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1277 {
1278         MimeInfo *mimeinfo;
1279         struct stat buf;
1280
1281         stat(filename, &buf);
1282
1283         mimeinfo = procmime_mimeinfo_new();
1284         mimeinfo->encoding_type = ENC_BINARY;
1285         mimeinfo->type = MIMETYPE_MESSAGE;
1286         mimeinfo->subtype = g_strdup("rfc822");
1287         mimeinfo->filename = g_strdup(filename);
1288         mimeinfo->offset = offset;
1289         mimeinfo->length = buf.st_size - offset;
1290
1291         procmime_parse_message_rfc822(mimeinfo);
1292         if (debug_get_mode())
1293                 output_mime_structure(mimeinfo, 0);
1294
1295         return mimeinfo;
1296 }
1297
1298 MimeInfo *procmime_scan_file(gchar *filename)
1299 {
1300         MimeInfo *mimeinfo;
1301
1302         g_return_val_if_fail(filename != NULL, NULL);
1303
1304         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1305
1306         return mimeinfo;
1307 }
1308
1309 MimeInfo *procmime_scan_queue_file(gchar *filename)
1310 {
1311         FILE *fp;
1312         MimeInfo *mimeinfo;
1313         gchar buf[BUFFSIZE];
1314         gint offset = 0;
1315
1316         g_return_val_if_fail(filename != NULL, NULL);
1317
1318         /* Open file */
1319         if((fp = fopen(filename, "rb")) == NULL)
1320                 return NULL;
1321         /* Skip queue header */
1322         while (fgets(buf, sizeof(buf), fp) != NULL)
1323                 if (buf[0] == '\r' || buf[0] == '\n') break;
1324         offset = ftell(fp);
1325         fclose(fp);
1326
1327         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1328
1329         return mimeinfo;
1330 }