2004-10-18 [christoph] 0.9.12cvs127
[claws.git] / src / procmime.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto & The Sylpheed-Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <stdio.h>
27 #include <glib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <locale.h>
31 #include <ctype.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35
36 #include "intl.h"
37 #include "procmime.h"
38 #include "procheader.h"
39 #include "base64.h"
40 #include "quoted-printable.h"
41 #include "uuencode.h"
42 #include "unmime.h"
43 #include "html.h"
44 #include "enriched.h"
45 #include "codeconv.h"
46 #include "utils.h"
47 #include "prefs_common.h"
48
49 #include "prefs_gtk.h"
50
51 static GHashTable *procmime_get_mime_type_table (void);
52
53 MimeInfo *procmime_mimeinfo_new(void)
54 {
55         MimeInfo *mimeinfo;
56
57         mimeinfo = g_new0(MimeInfo, 1);
58         mimeinfo->content        = MIMECONTENT_EMPTY;
59         mimeinfo->data.filename  = NULL;
60
61         mimeinfo->type           = MIMETYPE_UNKNOWN;
62         mimeinfo->encoding_type  = ENC_UNKNOWN;
63         mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
64
65         mimeinfo->disposition    = DISPOSITIONTYPE_UNKNOWN;
66         mimeinfo->dispositionparameters 
67                                  = g_hash_table_new(g_str_hash, g_str_equal);
68
69         mimeinfo->node           = g_node_new(mimeinfo);
70         
71         return mimeinfo;
72 }
73
74 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
75 {
76         g_free(key);
77         g_free(value);
78         
79         return TRUE;
80 }
81
82 static gchar *forced_charset = NULL;
83
84 void procmime_force_charset(const gchar *str)
85 {
86         g_free(forced_charset);
87         forced_charset = NULL;
88         if (str)
89                 forced_charset = g_strdup(str);
90 }
91
92 static EncodingType forced_encoding = 0;
93
94 void procmime_force_encoding(EncodingType encoding)
95 {
96         forced_encoding = encoding;
97 }
98
99 static gboolean free_func(GNode *node, gpointer data)
100 {
101         MimeInfo *mimeinfo = (MimeInfo *) node->data;
102
103         switch (mimeinfo->content) {
104         case MIMECONTENT_FILE:
105                 if (mimeinfo->tmp)
106                         unlink(mimeinfo->data.filename);
107                 g_free(mimeinfo->data.filename);
108                 break;
109
110         case MIMECONTENT_MEM:
111                 if (mimeinfo->tmp)
112                         g_free(mimeinfo->data.mem);
113         default:
114                 break;
115         }
116
117         g_free(mimeinfo->subtype);
118         g_free(mimeinfo->description);
119         g_free(mimeinfo->id);
120
121         g_hash_table_foreach_remove(mimeinfo->typeparameters,
122                 procmime_mimeinfo_parameters_destroy, NULL);
123         g_hash_table_destroy(mimeinfo->typeparameters);
124         g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
125                 procmime_mimeinfo_parameters_destroy, NULL);
126         g_hash_table_destroy(mimeinfo->dispositionparameters);
127
128         if (mimeinfo->privacy)
129                 privacy_free_privacydata(mimeinfo->privacy);
130
131         g_free(mimeinfo);
132
133         return FALSE;
134 }
135
136 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
137 {
138         GNode *node;
139
140         g_return_if_fail(mimeinfo);
141
142         node = mimeinfo->node;
143         g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
144
145         g_node_destroy(node);
146 }
147
148 #if 0 /* UNUSED */
149 MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
150 {
151         MimeInfo *child = parent->children;
152
153         if (!child)
154                 parent->children = mimeinfo;
155         else {
156                 while (child->next != NULL)
157                         child = child->next;
158
159                 child->next = mimeinfo;
160         }
161
162         mimeinfo->parent = parent;
163         mimeinfo->level = parent->level + 1;
164
165         return mimeinfo;
166 }
167
168 void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
169 {
170         MimeInfo *parent = old->parent;
171         MimeInfo *child;
172
173         g_return_if_fail(parent != NULL);
174         g_return_if_fail(new->next == NULL);
175
176         for (child = parent->children; child && child != old;
177              child = child->next)
178                 ;
179         if (!child) {
180                 g_warning("oops: parent can't find it's own child");
181                 return;
182         }
183         procmime_mimeinfo_free_all(old);
184
185         if (child == parent->children) {
186                 new->next = parent->children->next;
187                 parent->children = new;
188         } else {
189                 new->next = child->next;
190                 child = new;
191         }
192 }
193 #endif
194
195 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
196 {
197         g_return_val_if_fail(mimeinfo != NULL, NULL);
198         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
199
200         if (mimeinfo->node->parent == NULL)
201                 return NULL;
202         return (MimeInfo *) mimeinfo->node->parent->data;
203 }
204
205 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
206 {
207         g_return_val_if_fail(mimeinfo != NULL, NULL);
208         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
209
210         if (mimeinfo->node->children)
211                 return (MimeInfo *) mimeinfo->node->children->data;
212         if (mimeinfo->node->next)
213                 return (MimeInfo *) mimeinfo->node->next->data;
214
215         if (mimeinfo->node->parent == NULL)
216                 return NULL;
217
218         while (mimeinfo->node->parent != NULL) {
219                 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
220                 if (mimeinfo->node->next)
221                         return (MimeInfo *) mimeinfo->node->next->data;
222         }
223
224         return NULL;
225 }
226
227 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
228 {
229         gchar *filename;
230         MimeInfo *mimeinfo;
231
232         filename = procmsg_get_message_file(msginfo);
233         if (!filename)
234                 return NULL;
235         if (msginfo->folder->stype != F_QUEUE && 
236             msginfo->folder->stype != F_DRAFT)
237                 mimeinfo = procmime_scan_file(filename);
238         else
239                 mimeinfo = procmime_scan_queue_file(filename);
240         g_free(filename);
241
242         return mimeinfo;
243 }
244
245 enum
246 {
247         H_CONTENT_TRANSFER_ENCODING = 0,
248         H_CONTENT_TYPE              = 1,
249         H_CONTENT_DISPOSITION       = 2,
250         H_CONTENT_DESCRIPTION       = 3,
251         H_SUBJECT                   = 4
252 };
253
254 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
255 {
256         const gchar *value;
257
258         g_return_val_if_fail(mimeinfo != NULL, NULL);
259         g_return_val_if_fail(name != NULL, NULL);
260
261         value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
262         if (value == NULL)
263                 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
264         
265         return value;
266 }
267
268 gboolean procmime_decode_content(MimeInfo *mimeinfo)
269 {
270         gchar buf[BUFFSIZE];
271         gint readend;
272         gchar *tmpfilename;
273         gchar *mimetmpdir;
274         FILE *outfp, *infp;
275         struct stat statbuf;
276
277         EncodingType encoding = forced_encoding 
278                                 ? forced_encoding
279                                 : mimeinfo->encoding_type;
280                    
281         g_return_val_if_fail(mimeinfo != NULL, FALSE);
282
283         if (encoding == ENC_UNKNOWN ||
284             encoding == ENC_BINARY)
285                 return TRUE;
286
287         infp = fopen(mimeinfo->data.filename, "rb");
288         if (!infp) {
289                 perror("fopen");
290                 return FALSE;
291         }
292         fseek(infp, mimeinfo->offset, SEEK_SET);
293
294         mimetmpdir = get_mime_tmp_dir();
295         outfp = get_tmpfile_in_dir(mimetmpdir, &tmpfilename);
296         if (!outfp) {
297                 perror("tmpfile");
298                 return FALSE;
299         }
300
301         readend = mimeinfo->offset + mimeinfo->length;
302
303         if (encoding == ENC_QUOTED_PRINTABLE) {
304                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
305                         gint len;
306                         len = qp_decode_line(buf);
307                         fwrite(buf, len, 1, outfp);
308                 }
309         } else if (encoding == ENC_BASE64) {
310                 gchar outbuf[BUFFSIZE];
311                 gint len;
312                 Base64Decoder *decoder;
313
314                 decoder = base64_decoder_new();
315                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
316                         len = base64_decoder_decode(decoder, buf, outbuf);
317                         if (len < 0) {
318                                 g_warning("Bad BASE64 content\n");
319                                 break;
320                         }
321                         fwrite(outbuf, sizeof(gchar), len, outfp);
322                 }
323                 base64_decoder_free(decoder);
324         } else if (encoding == ENC_X_UUENCODE) {
325                 gchar outbuf[BUFFSIZE];
326                 gint len;
327                 gboolean flag = FALSE;
328
329                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
330                         if (!flag && strncmp(buf,"begin ", 6)) continue;
331
332                         if (flag) {
333                                 len = fromuutobits(outbuf, buf);
334                                 if (len <= 0) {
335                                         if (len < 0) 
336                                                 g_warning("Bad UUENCODE content(%d)\n", len);
337                                         break;
338                                 }
339                                 fwrite(outbuf, sizeof(gchar), len, outfp);
340                         } else
341                                 flag = TRUE;
342                 }
343         } else {
344                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
345                         fputs(buf, outfp);
346                 }
347         }
348
349         fclose(outfp);
350         fclose(infp);
351
352         stat(tmpfilename, &statbuf);
353         if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
354                 unlink(mimeinfo->data.filename);
355         if (mimeinfo->data.filename != NULL)
356                 g_free(mimeinfo->data.filename);
357         mimeinfo->data.filename = tmpfilename;
358         mimeinfo->tmp = TRUE;
359         mimeinfo->offset = 0;
360         mimeinfo->length = statbuf.st_size;
361         mimeinfo->encoding_type = ENC_BINARY;
362
363         return TRUE;
364 }
365
366 #define B64_LINE_SIZE           57
367 #define B64_BUFFSIZE            77
368
369 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
370 {
371         FILE *infp, *outfp;
372         gint len;
373         gchar *tmpfilename;
374         gchar *mimetmpdir;
375         struct stat statbuf;
376
377         if (mimeinfo->encoding_type != ENC_UNKNOWN ||
378             mimeinfo->encoding_type != ENC_BINARY ||
379             mimeinfo->encoding_type != ENC_7BIT ||
380             mimeinfo->encoding_type != ENC_8BIT)
381                 if(!procmime_decode_content(mimeinfo))
382                         return FALSE;
383
384         mimetmpdir = get_mime_tmp_dir();
385         outfp = get_tmpfile_in_dir(mimetmpdir, &tmpfilename);
386         if (!outfp) {
387                 perror("tmpfile");
388                 return FALSE;
389         }
390
391         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
392                 g_warning("Can't open file %s\n", mimeinfo->data.filename);
393                 return FALSE;
394         }
395
396         if (encoding == ENC_BASE64) {
397                 gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
398
399                 while ((len = fread(inbuf, sizeof(gchar),
400                                     B64_LINE_SIZE, infp))
401                        == B64_LINE_SIZE) {
402                         base64_encode(outbuf, inbuf, B64_LINE_SIZE);
403                         fputs(outbuf, outfp);
404                         fputc('\n', outfp);
405                 }
406                 if (len > 0 && feof(infp)) {
407                         base64_encode(outbuf, inbuf, len);
408                         fputs(outbuf, outfp);
409                         fputc('\n', outfp);
410                 }
411         } else if (encoding == ENC_QUOTED_PRINTABLE) {
412                 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
413
414                 while (fgets(inbuf, sizeof(inbuf), infp) != NULL) {
415                         qp_encode_line(outbuf, inbuf);
416                         fputs(outbuf, outfp);
417                 }
418         } else {
419                 gchar buf[BUFFSIZE];
420
421                 while (fgets(buf, sizeof(buf), infp) != NULL) {
422                         strcrchomp(buf);
423                         fputs(buf, outfp);
424                 }
425         }
426
427         fclose(outfp);
428         fclose(infp);
429
430         stat(tmpfilename, &statbuf);
431         if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
432                 unlink(mimeinfo->data.filename);
433         g_free(mimeinfo->data.filename);
434         mimeinfo->data.filename = tmpfilename;
435         mimeinfo->tmp = TRUE;
436         mimeinfo->offset = 0;
437         mimeinfo->length = statbuf.st_size;
438         mimeinfo->encoding_type = encoding;
439
440         return TRUE;
441 }
442
443 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
444 {
445         FILE *infp, *outfp;
446         gchar buf[BUFFSIZE];
447         gint restlength, readlength;
448
449         g_return_val_if_fail(outfile != NULL, -1);
450         g_return_val_if_fail(mimeinfo != NULL, -1);
451
452         if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
453                 return -1;
454
455         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
456                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
457                 return -1;
458         }
459         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
460                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
461                 fclose(infp);
462                 return -1;
463         }
464         if ((outfp = fopen(outfile, "wb")) == NULL) {
465                 FILE_OP_ERROR(outfile, "fopen");
466                 fclose(infp);
467                 return -1;
468         }
469
470         restlength = mimeinfo->length;
471
472         while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
473                 fwrite(buf, 1, readlength, outfp);
474                 restlength -= readlength;
475         }
476
477         fclose(infp);
478         if (fclose(outfp) == EOF) {
479                 FILE_OP_ERROR(outfile, "fclose");
480                 unlink(outfile);
481                 return -1;
482         }
483
484         return 0;
485 }
486
487 struct ContentRenderer {
488         char * content_type;
489         char * renderer;
490 };
491
492 static GList * renderer_list = NULL;
493
494 static struct ContentRenderer *
495 content_renderer_new(char * content_type, char * renderer)
496 {
497         struct ContentRenderer * cr;
498
499         cr = g_new(struct ContentRenderer, 1);
500         if (cr == NULL)
501                 return NULL;
502
503         cr->content_type = g_strdup(content_type);
504         cr->renderer = g_strdup(renderer);
505
506         return cr;
507 }
508
509 static void content_renderer_free(struct ContentRenderer * cr)
510 {
511         g_free(cr->content_type);
512         g_free(cr->renderer);
513         g_free(cr);
514 }
515
516 void renderer_read_config(void)
517 {
518         gchar buf[BUFFSIZE];
519         FILE * f;
520         gchar * rcpath;
521
522         g_list_foreach(renderer_list, (GFunc) content_renderer_free, NULL);
523         renderer_list = NULL;
524
525         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
526         f = fopen(rcpath, "rb");
527         g_free(rcpath);
528         
529         if (f == NULL)
530                 return;
531
532         while (fgets(buf, BUFFSIZE, f)) {
533                 char * p;
534                 struct ContentRenderer * cr;
535
536                 strretchomp(buf);
537                 p = strchr(buf, ' ');
538                 if (p == NULL)
539                         continue;
540                 * p = 0;
541
542                 cr = content_renderer_new(buf, p + 1);
543                 if (cr == NULL)
544                         continue;
545
546                 renderer_list = g_list_append(renderer_list, cr);
547         }
548
549         fclose(f);
550 }
551
552 void renderer_write_config(void)
553 {
554         gchar * rcpath;
555         PrefFile *pfile;
556         GList * cur;
557
558         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
559         
560         if ((pfile = prefs_write_open(rcpath)) == NULL) {
561                 g_warning("failed to write configuration to file\n");
562                 g_free(rcpath);
563                 return;
564         }
565
566         g_free(rcpath);
567
568         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
569                 struct ContentRenderer * renderer;
570                 renderer = cur->data;
571                 fprintf(pfile->fp, "%s %s\n", renderer->content_type,
572                         renderer->renderer);
573         }
574
575         if (prefs_file_close(pfile) < 0) {
576                 g_warning("failed to write configuration to file\n");
577                 return;
578         }
579 }
580
581 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
582 {
583         FILE *tmpfp, *outfp;
584         const gchar *src_codeset;
585         gboolean conv_fail = FALSE;
586         gchar buf[BUFFSIZE];
587         gchar *str;
588         struct ContentRenderer * renderer;
589         GList * cur;
590         gchar *tmpfile, *content_type;
591     
592         g_return_val_if_fail(mimeinfo != NULL, NULL);
593
594         if (!procmime_decode_content(mimeinfo))
595                 return NULL;
596
597         tmpfile = procmime_get_tmp_file_name(mimeinfo);
598         if (tmpfile == NULL)
599                 return NULL;
600
601         if (procmime_get_part(tmpfile, mimeinfo) < 0) {
602                 g_free(tmpfile);
603                 return NULL;
604         }
605
606         tmpfp = fopen(tmpfile, "rb");
607         if (tmpfp == NULL) {
608                 g_free(tmpfile);
609                 return NULL;
610         }
611
612         if ((outfp = my_tmpfile()) == NULL) {
613                 perror("tmpfile");
614                 fclose(tmpfp);
615                 g_free(tmpfile);
616                 return NULL;
617         }
618
619         src_codeset = forced_charset
620                       ? forced_charset : 
621                       procmime_mimeinfo_get_parameter(mimeinfo, "charset");
622
623         renderer = NULL;
624
625         content_type = procmime_get_content_type_str(mimeinfo->type,
626                                                      mimeinfo->subtype);
627         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
628                 struct ContentRenderer * cr;
629
630                 cr = cur->data;
631                 if (g_strcasecmp(cr->content_type, content_type) == 0) {
632                         renderer = cr;
633                         break;
634                 }
635         }
636         g_free(content_type);
637
638         if (renderer != NULL) {
639                 FILE * p;
640                 int oldout;
641                 
642                 oldout = dup(1);
643                 
644                 dup2(fileno(outfp), 1);
645                 
646                 p = popen(renderer->renderer, "w");
647                 if (p != NULL) {
648                         size_t count;
649                         
650                         while ((count =
651                                 fread(buf, sizeof(char), sizeof(buf),
652                                       tmpfp)) > 0)
653                                 fwrite(buf, sizeof(char), count, p);
654                         pclose(p);
655                 }
656                 
657                 dup2(oldout, 1);
658         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_strcasecmp(mimeinfo->subtype, "html")) {
659                 HTMLParser *parser;
660                 CodeConverter *conv;
661
662                 conv = conv_code_converter_new(src_codeset);
663                 parser = html_parser_new(tmpfp, conv);
664                 while ((str = html_parse(parser)) != NULL) {
665                         fputs(str, outfp);
666                 }
667                 html_parser_destroy(parser);
668                 conv_code_converter_destroy(conv);
669         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_strcasecmp(mimeinfo->subtype, "enriched")) {
670                 ERTFParser *parser;
671                 CodeConverter *conv;
672
673                 conv = conv_code_converter_new(src_codeset);
674                 parser = ertf_parser_new(tmpfp, conv);
675                 while ((str = ertf_parse(parser)) != NULL) {
676                         fputs(str, outfp);
677                 }
678                 ertf_parser_destroy(parser);
679                 conv_code_converter_destroy(conv);
680         } else if (mimeinfo->type == MIMETYPE_TEXT) {
681                 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
682                         str = conv_codeset_strdup(buf, src_codeset, NULL);
683                         if (str) {
684                                 fputs(str, outfp);
685                                 g_free(str);
686                         } else {
687                                 conv_fail = TRUE;
688                                 fputs(buf, outfp);
689                         }
690                 }
691         }
692
693         if (conv_fail)
694                 g_warning("procmime_get_text_content(): Code conversion failed.\n");
695
696         fclose(tmpfp);
697         rewind(outfp);
698         unlink(tmpfile);
699         g_free(tmpfile);
700
701         return outfp;
702 }
703
704 /* search the first text part of (multipart) MIME message,
705    decode, convert it and output to outfp. */
706 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
707 {
708         FILE *outfp = NULL;
709         MimeInfo *mimeinfo, *partinfo;
710
711         g_return_val_if_fail(msginfo != NULL, NULL);
712
713         mimeinfo = procmime_scan_message(msginfo);
714         if (!mimeinfo) return NULL;
715
716         partinfo = mimeinfo;
717         while (partinfo && partinfo->type != MIMETYPE_TEXT)
718                 partinfo = procmime_mimeinfo_next(partinfo);
719
720         if (partinfo)
721                 outfp = procmime_get_text_content(partinfo);
722
723         procmime_mimeinfo_free_all(mimeinfo);
724
725         return outfp;
726 }
727
728 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
729                                    const gchar *str, gboolean case_sens)
730 {
731         FILE *outfp;
732         gchar buf[BUFFSIZE];
733         gchar *(* StrFindFunc) (const gchar *haystack, const gchar *needle);
734
735         g_return_val_if_fail(mimeinfo != NULL, FALSE);
736         g_return_val_if_fail(mimeinfo->type == MIMETYPE_TEXT, FALSE);
737         g_return_val_if_fail(str != NULL, FALSE);
738
739         outfp = procmime_get_text_content(mimeinfo);
740
741         if (!outfp)
742                 return FALSE;
743
744         if (case_sens)
745                 StrFindFunc = strstr;
746         else
747                 StrFindFunc = strcasestr;
748
749         while (fgets(buf, sizeof(buf), outfp) != NULL) {
750                 if (StrFindFunc(buf, str) != NULL) {
751                         fclose(outfp);
752                         return TRUE;
753                 }
754         }
755
756         fclose(outfp);
757
758         return FALSE;
759 }
760
761 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
762                               gboolean case_sens)
763 {
764         MimeInfo *mimeinfo;
765         MimeInfo *partinfo;
766         gchar *filename;
767         gboolean found = FALSE;
768
769         g_return_val_if_fail(msginfo != NULL, FALSE);
770         g_return_val_if_fail(str != NULL, FALSE);
771
772         filename = procmsg_get_message_file(msginfo);
773         if (!filename) return FALSE;
774         mimeinfo = procmime_scan_message(msginfo);
775
776         for (partinfo = mimeinfo; partinfo != NULL;
777              partinfo = procmime_mimeinfo_next(partinfo)) {
778                 if (partinfo->type == MIMETYPE_TEXT) {
779                         if (procmime_find_string_part
780                                 (partinfo, filename, str, case_sens) == TRUE) {
781                                 found = TRUE;
782                                 break;
783                         }
784                 }
785         }
786
787         procmime_mimeinfo_free_all(mimeinfo);
788         g_free(filename);
789
790         return found;
791 }
792
793 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
794 {
795         static guint32 id = 0;
796         gchar *base;
797         gchar *filename;
798         gchar f_prefix[10];
799
800         g_return_val_if_fail(mimeinfo != NULL, NULL);
801
802         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
803
804         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_strcasecmp(mimeinfo->subtype, "html"))
805                 base = "mimetmp.html";
806         else {
807                 const gchar *basetmp;
808
809                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
810                 if (basetmp == NULL)
811                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
812                 if (basetmp == NULL)
813                         basetmp = "mimetmp";
814                 base = g_basename(basetmp);
815                 if (*base == '\0') base = "mimetmp";
816                 Xstrdup_a(base, base, return NULL);
817                 subst_for_shellsafe_filename(base);
818         }
819
820         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
821                                f_prefix, base, NULL);
822
823         return filename;
824 }
825
826 static GList *mime_type_list = NULL;
827
828 gchar *procmime_get_mime_type(const gchar *filename)
829 {
830         static GHashTable *mime_type_table = NULL;
831         MimeType *mime_type;
832         const gchar *p;
833         gchar *ext;
834
835         if (!mime_type_table) {
836                 mime_type_table = procmime_get_mime_type_table();
837                 if (!mime_type_table) return NULL;
838         }
839
840         filename = g_basename(filename);
841         p = strrchr(filename, '.');
842         if (!p) return NULL;
843
844         Xstrdup_a(ext, p + 1, return NULL);
845         g_strdown(ext);
846         mime_type = g_hash_table_lookup(mime_type_table, ext);
847         if (mime_type) {
848                 gchar *str;
849
850                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
851                                   NULL);
852                 return str;
853         }
854
855         return NULL;
856 }
857
858 static guint procmime_str_hash(gconstpointer gptr)
859 {
860         guint hash_result = 0;
861         const char *str;
862
863         for (str = gptr; str && *str; str++) {
864                 if (isupper(*str)) hash_result += (*str + ' ');
865                 else hash_result += *str;
866         }
867
868         return hash_result;
869 }
870
871 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
872 {
873         const char *str1 = gptr1;
874         const char *str2 = gptr2;
875
876         return !g_strcasecmp(str1, str2);
877 }
878
879 static GHashTable *procmime_get_mime_type_table(void)
880 {
881         GHashTable *table = NULL;
882         GList *cur;
883         MimeType *mime_type;
884         gchar **exts;
885
886         if (!mime_type_list) {
887                 mime_type_list = procmime_get_mime_type_list();
888                 if (!mime_type_list) return NULL;
889         }
890
891         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
892
893         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
894                 gint i;
895                 gchar *key;
896
897                 mime_type = (MimeType *)cur->data;
898
899                 if (!mime_type->extension) continue;
900
901                 exts = g_strsplit(mime_type->extension, " ", 16);
902                 for (i = 0; exts[i] != NULL; i++) {
903                         /* make the key case insensitive */
904                         g_strdown(exts[i]);
905                         /* use previously dup'd key on overwriting */
906                         if (g_hash_table_lookup(table, exts[i]))
907                                 key = exts[i];
908                         else
909                                 key = g_strdup(exts[i]);
910                         g_hash_table_insert(table, key, mime_type);
911                 }
912                 g_strfreev(exts);
913         }
914
915         return table;
916 }
917
918 GList *procmime_get_mime_type_list(void)
919 {
920         GList *list = NULL;
921         FILE *fp;
922         gchar buf[BUFFSIZE];
923         guchar *p;
924         gchar *delim;
925         MimeType *mime_type;
926
927         if (mime_type_list) 
928                 return mime_type_list;
929
930         if ((fp = fopen("/etc/mime.types", "rb")) == NULL) {
931                 if ((fp = fopen(SYSCONFDIR "/mime.types", "rb")) == NULL) {
932                         FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
933                         return NULL;
934                 }
935         }
936
937         while (fgets(buf, sizeof(buf), fp) != NULL) {
938                 p = strchr(buf, '#');
939                 if (p) *p = '\0';
940                 g_strstrip(buf);
941
942                 p = buf;
943                 while (*p && !isspace(*p)) p++;
944                 if (*p) {
945                         *p = '\0';
946                         p++;
947                 }
948                 delim = strchr(buf, '/');
949                 if (delim == NULL) continue;
950                 *delim = '\0';
951
952                 mime_type = g_new(MimeType, 1);
953                 mime_type->type = g_strdup(buf);
954                 mime_type->sub_type = g_strdup(delim + 1);
955
956                 while (*p && isspace(*p)) p++;
957                 if (*p)
958                         mime_type->extension = g_strdup(p);
959                 else
960                         mime_type->extension = NULL;
961
962                 list = g_list_append(list, mime_type);
963         }
964
965         fclose(fp);
966
967         if (!list)
968                 g_warning("Can't read mime.types\n");
969
970         return list;
971 }
972
973 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
974 {
975         if (!charset)
976                 return ENC_8BIT;
977         else if (!g_strncasecmp(charset, "ISO-2022-", 9) ||
978                  !g_strcasecmp(charset, "US-ASCII"))
979                 return ENC_7BIT;
980         else if (!g_strcasecmp(charset, "ISO-8859-5") ||
981                  !g_strncasecmp(charset, "KOI8-", 5) ||
982                  !g_strcasecmp(charset, "Windows-1251"))
983                 return ENC_8BIT;
984         else if (!g_strncasecmp(charset, "ISO-8859-", 9))
985                 return ENC_QUOTED_PRINTABLE;
986         else
987                 return ENC_8BIT;
988 }
989
990 EncodingType procmime_get_encoding_for_file(const gchar *file)
991 {
992         FILE *fp;
993         guchar buf[BUFSIZ];
994         size_t len;
995
996         if ((fp = fopen(file, "rb")) == NULL) {
997                 FILE_OP_ERROR(file, "fopen");
998                 return ENC_UNKNOWN;
999         }
1000
1001         while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
1002                 guchar *p;
1003                 gint i;
1004
1005                 for (p = buf, i = 0; i < len; p++, i++) {
1006                         if (*p & 0x80) {
1007                                 fclose(fp);
1008                                 return ENC_BASE64;
1009                         }
1010                 }
1011         }
1012
1013         fclose(fp);
1014         return ENC_7BIT;
1015 }
1016
1017 struct EncodingTable 
1018 {
1019         gchar *str;
1020         EncodingType enc_type;
1021 };
1022
1023 struct EncodingTable encoding_table[] = {
1024         {"7bit", ENC_7BIT},
1025         {"8bit", ENC_8BIT},
1026         {"binary", ENC_BINARY},
1027         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1028         {"base64", ENC_BASE64},
1029         {"x-uuencode", ENC_UNKNOWN},
1030         {NULL, ENC_UNKNOWN},
1031 };
1032
1033 const gchar *procmime_get_encoding_str(EncodingType encoding)
1034 {
1035         struct EncodingTable *enc_table;
1036         
1037         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1038                 if (enc_table->enc_type == encoding)
1039                         return enc_table->str;
1040         }
1041         return NULL;
1042 }
1043
1044 /* --- NEW MIME STUFF --- */
1045 struct TypeTable
1046 {
1047         gchar *str;
1048         MimeMediaType type;
1049 };
1050
1051 static struct TypeTable mime_type_table[] = {
1052         {"text", MIMETYPE_TEXT},
1053         {"image", MIMETYPE_IMAGE},
1054         {"audio", MIMETYPE_AUDIO},
1055         {"video", MIMETYPE_VIDEO},
1056         {"application", MIMETYPE_APPLICATION},
1057         {"message", MIMETYPE_MESSAGE},
1058         {"multipart", MIMETYPE_MULTIPART},
1059         {NULL, 0},
1060 };
1061
1062 const gchar *procmime_get_media_type_str(MimeMediaType type)
1063 {
1064         struct TypeTable *type_table;
1065         
1066         for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1067                 if (type_table->type == type)
1068                         return type_table->str;
1069         }
1070         return NULL;
1071 }
1072
1073 MimeMediaType procmime_get_media_type(const gchar *str)
1074 {
1075         struct TypeTable *typetablearray;
1076
1077         for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1078                 if (g_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1079                         return typetablearray->type;
1080
1081         return MIMETYPE_UNKNOWN;
1082 }
1083
1084 /*!
1085  *\brief        Safe wrapper for content type string.
1086  *
1087  *\return       const gchar * Pointer to content type string. 
1088  */
1089 gchar *procmime_get_content_type_str(MimeMediaType type,
1090                                            const char *subtype)
1091 {
1092         const gchar *type_str = NULL;
1093
1094         if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1095                 return g_strdup("unknown");
1096         return g_strdup_printf("%s/%s", type_str, subtype);
1097 }
1098
1099 void procmime_parse_mimepart(MimeInfo *parent,
1100                              gchar *content_type,
1101                              gchar *content_encoding,
1102                              gchar *content_description,
1103                              gchar *content_id,
1104                              gchar *content_disposition,
1105                              const gchar *filename,
1106                              guint offset,
1107                              guint length);
1108
1109 void procmime_parse_message_rfc822(MimeInfo *mimeinfo)
1110 {
1111         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1112                                 {"Content-Transfer-Encoding:",
1113                                                    NULL, FALSE},
1114                                 {"Content-Description:",
1115                                                    NULL, TRUE},
1116                                 {"Content-ID:",
1117                                                    NULL, TRUE},
1118                                 {"Content-Disposition:",
1119                                                    NULL, TRUE},
1120                                 {"MIME-Version:",
1121                                                    NULL, TRUE},
1122                                 {NULL,             NULL, FALSE}};
1123         guint content_start, i;
1124         FILE *fp;
1125         gint mime_major, mime_minor;
1126
1127         procmime_decode_content(mimeinfo);
1128
1129         fp = fopen(mimeinfo->data.filename, "rb");
1130         if (fp == NULL) {
1131                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1132                 return;
1133         }
1134         fseek(fp, mimeinfo->offset, SEEK_SET);
1135         procheader_get_header_fields(fp, hentry);
1136         if (hentry[0].body != NULL)
1137                 conv_unmime_header_overwrite(hentry[0].body);
1138         if (hentry[2].body != NULL)
1139                 conv_unmime_header_overwrite(hentry[2].body);
1140         if (hentry[4].body != NULL)
1141                 conv_unmime_header_overwrite(hentry[4].body);
1142         content_start = ftell(fp);
1143         fclose(fp);
1144
1145         if ((hentry[5].body != NULL) &&
1146             (sscanf(hentry[5].body, "%d.%d", &mime_major, &mime_minor) == 2) &&
1147             (mime_major == 1) && (mime_minor == 0)) {
1148                 procmime_parse_mimepart(mimeinfo,
1149                                         hentry[0].body, hentry[1].body,
1150                                         hentry[2].body, hentry[3].body, 
1151                                         hentry[4].body, 
1152                                         mimeinfo->data.filename, content_start,
1153                                         mimeinfo->length - (content_start - mimeinfo->offset));
1154         } else {
1155                 MimeInfo *subinfo;
1156
1157                 subinfo = procmime_mimeinfo_new();
1158                 subinfo->encoding_type = ENC_UNKNOWN;
1159                 subinfo->type = MIMETYPE_TEXT;
1160                 subinfo->subtype = g_strdup("plain");
1161                 subinfo->data.filename = g_strdup(mimeinfo->data.filename);
1162                 subinfo->offset = content_start;
1163                 subinfo->length = mimeinfo->length - (content_start - mimeinfo->offset);
1164
1165                 g_node_append(mimeinfo->node, subinfo->node);
1166         }
1167         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1168                 g_free(hentry[i].body);
1169                 hentry[i].body = NULL;
1170         }
1171 }
1172
1173 void procmime_parse_multipart(MimeInfo *mimeinfo)
1174 {
1175         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1176                                 {"Content-Transfer-Encoding:",
1177                                                    NULL, FALSE},
1178                                 {"Content-Description:",
1179                                                    NULL, TRUE},
1180                                 {"Content-ID:",
1181                                                    NULL, TRUE},
1182                                 {"Content-Disposition:",
1183                                                    NULL, TRUE},
1184                                 {NULL,             NULL, FALSE}};
1185         gchar *p;
1186         gchar *boundary;
1187         gint boundary_len = 0, lastoffset = -1, i;
1188         gchar buf[BUFFSIZE];
1189         FILE *fp;
1190
1191         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1192         if (!boundary)
1193                 return;
1194         boundary_len = strlen(boundary);
1195
1196         procmime_decode_content(mimeinfo);
1197
1198         fp = fopen(mimeinfo->data.filename, "rb");
1199         if (fp == NULL) {
1200                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1201                 return;
1202         }
1203         fseek(fp, mimeinfo->offset, SEEK_SET);
1204         while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
1205                 if (ftell(fp) > (mimeinfo->offset + mimeinfo->length))
1206                         break;
1207
1208                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1209                         if (lastoffset != -1) {
1210                                 procmime_parse_mimepart(mimeinfo,
1211                                                         hentry[0].body, hentry[1].body,
1212                                                         hentry[2].body, hentry[3].body, 
1213                                                         hentry[4].body, 
1214                                                         mimeinfo->data.filename, lastoffset,
1215                                                         (ftell(fp) - strlen(buf)) - lastoffset - 1);
1216                         }
1217                         
1218                         if (buf[2 + boundary_len]     == '-' &&
1219                             buf[2 + boundary_len + 1] == '-')
1220                                 break;
1221
1222                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1223                                 g_free(hentry[i].body);
1224                                 hentry[i].body = NULL;
1225                         }
1226                         procheader_get_header_fields(fp, hentry);
1227                         if (hentry[0].body != NULL)
1228                                 conv_unmime_header_overwrite(hentry[0].body);
1229                         if (hentry[2].body != NULL)
1230                                 conv_unmime_header_overwrite(hentry[2].body);
1231                         if (hentry[4].body != NULL)
1232                                 conv_unmime_header_overwrite(hentry[4].body);
1233                         lastoffset = ftell(fp);
1234                 }
1235         }
1236         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1237                 g_free(hentry[i].body);
1238                 hentry[i].body = NULL;
1239         }
1240         fclose(fp);
1241 }
1242
1243 static void add_to_mimeinfo_parameters(gchar **parts, GHashTable *table)
1244 {
1245         gchar **strarray;
1246
1247         for (strarray = parts; *strarray != NULL; strarray++) {
1248                 gchar **parameters_parts;
1249
1250                 parameters_parts = g_strsplit(*strarray, "=", 1);
1251                 if ((parameters_parts[0] != NULL) && (parameters_parts[1] != NULL)) {
1252                         gchar *firstspace;
1253
1254                         g_strstrip(parameters_parts[0]);
1255                         g_strstrip(parameters_parts[1]);
1256                         g_strdown(parameters_parts[0]);
1257                         if (parameters_parts[1][0] == '"')
1258                                 extract_quote(parameters_parts[1], '"');
1259                         else if ((firstspace = strchr(parameters_parts[1], ' ')) != NULL)
1260                                 *firstspace = '\0';
1261                         if (g_hash_table_lookup(table, parameters_parts[0]) == NULL)
1262                                 g_hash_table_insert(table,
1263                                                     g_strdup(parameters_parts[0]),
1264                                                     g_strdup(parameters_parts[1]));
1265                 }
1266                 g_strfreev(parameters_parts);
1267         }
1268 }       
1269
1270 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1271 {
1272         gchar **content_type_parts;
1273         gchar **strarray;
1274         gchar *str;
1275         
1276         g_return_if_fail(content_type != NULL);
1277         g_return_if_fail(mimeinfo != NULL);
1278
1279         /* Split content type into parts and remove trailing
1280            and leading whitespaces from all strings */
1281         content_type_parts = g_strsplit(content_type, ";", 0);
1282         for (strarray = content_type_parts; *strarray != NULL; strarray++) {
1283                 g_strstrip(*strarray);
1284         }
1285
1286         /* Get mimeinfo->type and mimeinfo->subtype */
1287         str = content_type_parts[0];
1288         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1289          * if it's not available we use the default Content-Type */
1290         if ((str == NULL) || (str[0] == '\0') || (strchr(str, '/') == NULL)) {
1291                 mimeinfo->type = MIMETYPE_TEXT;
1292                 mimeinfo->subtype = g_strdup("plain");
1293                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1294                                        "charset") == NULL)
1295                         g_hash_table_insert(mimeinfo->typeparameters,
1296                                             g_strdup("charset"),
1297                                             g_strdup("us-ascii"));
1298         } else {
1299                 gchar *type, *subtype;
1300
1301                 type = g_strdup(str);
1302                 subtype = strchr(type, '/') + 1;
1303                 *(subtype - 1) = '\0';
1304
1305                 mimeinfo->type = procmime_get_media_type(type);
1306                 mimeinfo->subtype = g_strdup(subtype);
1307
1308                 g_free(type);
1309
1310                 /* Get mimeinfo->parmeters */
1311                 add_to_mimeinfo_parameters(&content_type_parts[1], mimeinfo->typeparameters);
1312         }
1313
1314         g_strfreev(content_type_parts);
1315 }
1316
1317 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1318 {
1319         gchar **content_disp_parts;
1320         gchar **strarray;
1321         gchar *str;
1322
1323         g_return_if_fail(content_disposition != NULL);
1324         g_return_if_fail(mimeinfo != NULL);
1325
1326         /* Split into parts and remove trailing
1327            and leading whitespaces from all strings */
1328         content_disp_parts = g_strsplit(content_disposition, ";", 0);
1329         for (strarray = content_disp_parts; *strarray != NULL; strarray++) {
1330                 g_strstrip(*strarray);
1331         }
1332         /* Get mimeinfo->disposition */
1333         str = content_disp_parts[0];
1334         if (str == NULL) {
1335                 g_strfreev(content_disp_parts);
1336                 return;
1337         }
1338         if (!g_strcasecmp(str, "inline")) 
1339                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1340         else if (!g_strcasecmp(str, "attachment"))
1341                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1342         else
1343                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1344         
1345         add_to_mimeinfo_parameters(&content_disp_parts[1], mimeinfo->dispositionparameters);
1346         g_strfreev(content_disp_parts);
1347 }
1348
1349
1350 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1351 {
1352         struct EncodingTable *enc_table;
1353         
1354         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1355                 if (g_strcasecmp(enc_table->str, content_encoding) == 0) {
1356                         mimeinfo->encoding_type = enc_table->enc_type;
1357                         return;
1358                 }
1359         }
1360         mimeinfo->encoding_type = ENC_UNKNOWN;
1361         return;
1362 }
1363
1364 void procmime_parse_mimepart(MimeInfo *parent,
1365                              gchar *content_type,
1366                              gchar *content_encoding,
1367                              gchar *content_description,
1368                              gchar *content_id,
1369                              gchar *content_disposition,
1370                              const gchar *filename,
1371                              guint offset,
1372                              guint length)
1373 {
1374         MimeInfo *mimeinfo;
1375
1376         /* Create MimeInfo */
1377         mimeinfo = procmime_mimeinfo_new();
1378         mimeinfo->content = MIMECONTENT_FILE;
1379         if (parent != NULL)
1380                 g_node_append(parent->node, mimeinfo->node);
1381         mimeinfo->data.filename = g_strdup(filename);
1382         mimeinfo->offset = offset;
1383         mimeinfo->length = length;
1384
1385         if (content_type != NULL) {
1386                 procmime_parse_content_type(content_type, mimeinfo);
1387         } else {
1388                 mimeinfo->type = MIMETYPE_TEXT;
1389                 mimeinfo->subtype = g_strdup("plain");
1390                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1391                                        "charset") == NULL)
1392                         g_hash_table_insert(mimeinfo->typeparameters, g_strdup("charset"), g_strdup("us-ascii"));
1393         }
1394
1395         if (content_encoding != NULL) {
1396                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1397         } else {
1398                 mimeinfo->encoding_type = ENC_UNKNOWN;
1399         }
1400
1401         if (content_description != NULL)
1402                 mimeinfo->description = g_strdup(content_description);
1403         else
1404                 mimeinfo->description = NULL;
1405
1406         if (content_id != NULL)
1407                 mimeinfo->id = g_strdup(content_id);
1408         else
1409                 mimeinfo->id = NULL;
1410
1411         if (content_disposition != NULL) 
1412                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1413         else
1414                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1415
1416         /* Call parser for mime type */
1417         switch (mimeinfo->type) {
1418                 case MIMETYPE_MESSAGE:
1419                         if (g_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1420                                 procmime_parse_message_rfc822(mimeinfo);
1421                         }
1422                         break;
1423                         
1424                 case MIMETYPE_MULTIPART:
1425                         procmime_parse_multipart(mimeinfo);
1426                         break;
1427                         
1428                 default:
1429                         break;
1430         }
1431 }
1432
1433 static gchar *typenames[] = {
1434     "text",
1435     "image",
1436     "audio",
1437     "video",
1438     "application",
1439     "message",
1440     "multipart",
1441     "unknown",
1442 };
1443
1444 static gboolean output_func(GNode *node, gpointer data)
1445 {
1446         guint i, depth;
1447         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1448
1449         depth = g_node_depth(node);
1450         for (i = 0; i < depth; i++)
1451                 printf("    ");
1452         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1453
1454         return FALSE;
1455 }
1456
1457 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1458 {
1459         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1460 }
1461
1462 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1463 {
1464         MimeInfo *mimeinfo;
1465         struct stat buf;
1466
1467         stat(filename, &buf);
1468
1469         mimeinfo = procmime_mimeinfo_new();
1470         mimeinfo->content = MIMECONTENT_FILE;
1471         mimeinfo->encoding_type = ENC_UNKNOWN;
1472         mimeinfo->type = MIMETYPE_MESSAGE;
1473         mimeinfo->subtype = g_strdup("rfc822");
1474         mimeinfo->data.filename = g_strdup(filename);
1475         mimeinfo->offset = offset;
1476         mimeinfo->length = buf.st_size - offset;
1477
1478         procmime_parse_message_rfc822(mimeinfo);
1479         if (debug_get_mode())
1480                 output_mime_structure(mimeinfo, 0);
1481
1482         return mimeinfo;
1483 }
1484
1485 MimeInfo *procmime_scan_file(const gchar *filename)
1486 {
1487         MimeInfo *mimeinfo;
1488
1489         g_return_val_if_fail(filename != NULL, NULL);
1490
1491         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1492
1493         return mimeinfo;
1494 }
1495
1496 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1497 {
1498         FILE *fp;
1499         MimeInfo *mimeinfo;
1500         gchar buf[BUFFSIZE];
1501         gint offset = 0;
1502
1503         g_return_val_if_fail(filename != NULL, NULL);
1504
1505         /* Open file */
1506         if ((fp = fopen(filename, "rb")) == NULL)
1507                 return NULL;
1508         /* Skip queue header */
1509         while (fgets(buf, sizeof(buf), fp) != NULL)
1510                 if (buf[0] == '\r' || buf[0] == '\n') break;
1511         offset = ftell(fp);
1512         fclose(fp);
1513
1514         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1515
1516         return mimeinfo;
1517 }
1518
1519 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1520 {
1521         gchar *param = key;
1522         gchar *val = value;
1523         FILE *fp = user_data;
1524
1525         /* FIXME: better encoding of parameters */
1526         fprintf(fp, "; %s=", param);
1527         if (strchr(val, ' ') != NULL)
1528                 fprintf(fp, "\"%s\"", val);
1529         else
1530                 fprintf(fp, "%s", val);
1531 }
1532
1533 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1534 {
1535         struct TypeTable *type_table;
1536
1537         debug_print("procmime_write_mime_header\n");
1538
1539         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1540                 if (mimeinfo->type == type_table->type) {
1541                         fprintf(fp, "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1542                         break;
1543                 }
1544         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, fp);
1545         fprintf(fp, "\n");
1546
1547         if (mimeinfo->encoding_type != ENC_UNKNOWN)
1548                 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1549
1550         if (mimeinfo->description != NULL)
1551                 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1552
1553         if (mimeinfo->id != NULL)
1554                 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1555
1556         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
1557                 fprintf(fp, "Content-Disposition: ");
1558                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
1559                         fprintf(fp, "inline");
1560                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
1561                         fprintf(fp, "attachment");
1562                 else
1563                         fprintf(fp, "unknown");
1564
1565                 /* FIXME: linebreaks after too many parameters */
1566                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, fp);
1567                 fprintf(fp, "\n");
1568         }
1569
1570         fprintf(fp, "\n");
1571 }
1572
1573 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
1574 {
1575         FILE *infp;
1576         GNode *childnode;
1577         MimeInfo *child;
1578         gchar buf[BUFFSIZE];
1579         gboolean skip = FALSE;;
1580
1581         debug_print("procmime_write_message_rfc822\n");
1582
1583         /* write header */
1584         switch (mimeinfo->content) {
1585         case MIMECONTENT_FILE:
1586                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1587                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1588                         return -1;
1589                 }
1590                 fseek(infp, mimeinfo->offset, SEEK_SET);
1591                 while (fgets(buf, sizeof(buf), infp) == buf) {
1592                         if (buf[0] == '\n' && buf[1] == '\0')
1593                                 break;
1594                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
1595                                 continue;
1596                         if (g_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
1597                             g_strncasecmp(buf, "Content-Type:", 13) == 0 ||
1598                             g_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
1599                             g_strncasecmp(buf, "Content-Description:", 20) == 0 ||
1600                             g_strncasecmp(buf, "Content-ID:", 11) == 0 ||
1601                             g_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
1602                                 skip = TRUE;
1603                                 continue;
1604                         }
1605                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1606                         skip = FALSE;
1607                 }
1608                 fclose(infp);
1609                 break;
1610
1611         case MIMECONTENT_MEM:
1612                 fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1613                 break;
1614
1615         default:
1616                 break;
1617         }
1618
1619         childnode = mimeinfo->node->children;
1620         if (childnode == NULL)
1621                 return -1;
1622
1623         child = (MimeInfo *) childnode->data;
1624         fprintf(fp, "Mime-Version: 1.0\n");
1625         procmime_write_mime_header(child, fp);
1626         return procmime_write_mimeinfo(child, fp);
1627 }
1628
1629 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
1630 {
1631         FILE *infp;
1632         GNode *childnode;
1633         gchar *boundary, *str, *str2;
1634         gchar buf[BUFFSIZE];
1635         gboolean firstboundary;
1636
1637         debug_print("procmime_write_multipart\n");
1638
1639         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1640
1641         switch (mimeinfo->content) {
1642         case MIMECONTENT_FILE:
1643                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1644                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1645                         return -1;
1646                 }
1647                 fseek(infp, mimeinfo->offset, SEEK_SET);
1648                 while (fgets(buf, sizeof(buf), infp) == buf) {
1649                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
1650                                 break;
1651                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1652                 }
1653                 fclose(infp);
1654                 break;
1655
1656         case MIMECONTENT_MEM:
1657                 str = g_strdup(mimeinfo->data.mem);
1658                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
1659                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
1660                         *(str2 - 2) = '\0';
1661                 fwrite(str, strlen(str), sizeof(gchar), fp);
1662                 g_free(str);
1663                 break;
1664
1665         default:
1666                 break;
1667         }
1668
1669         childnode = mimeinfo->node->children;
1670         firstboundary = TRUE;
1671         while (childnode != NULL) {
1672                 MimeInfo *child = childnode->data;
1673
1674                 if (firstboundary)
1675                         firstboundary = FALSE;
1676                 else
1677                         fprintf(fp, "\n");
1678                 fprintf(fp, "--%s\n", boundary);
1679
1680                 procmime_write_mime_header(child, fp);
1681                 if (procmime_write_mimeinfo(child, fp) < 0)
1682                         return -1;
1683
1684                 childnode = g_node_next_sibling(childnode);
1685         }       
1686         fprintf(fp, "\n--%s--\n", boundary);
1687
1688         return 0;
1689 }
1690
1691 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
1692 {
1693         FILE *infp;
1694
1695         debug_print("procmime_write_mimeinfo\n");
1696
1697         if (G_NODE_IS_LEAF(mimeinfo->node)) {
1698                 switch (mimeinfo->content) {
1699                 case MIMECONTENT_FILE:
1700                         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1701                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1702                                 return -1;
1703                         }
1704                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
1705                         fclose(infp);
1706                         return 0;
1707
1708                 case MIMECONTENT_MEM:
1709                         fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1710                         return 0;
1711
1712                 default:
1713                         return 0;
1714                 }
1715         } else {
1716                 /* Call writer for mime type */
1717                 switch (mimeinfo->type) {
1718                 case MIMETYPE_MESSAGE:
1719                         if (g_strcasecmp(mimeinfo->subtype, "rfc822") == 0)
1720                                 return procmime_write_message_rfc822(mimeinfo, fp);
1721                         break;
1722                         
1723                 case MIMETYPE_MULTIPART:
1724                         return procmime_write_multipart(mimeinfo, fp);
1725                         
1726                 default:
1727                         break;
1728                 }
1729
1730                 return -1;
1731         }
1732
1733         return 0;
1734 }