2006-04-27 [paul] 2.1.1cvs37
[claws.git] / src / procmime.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <glib/gi18n.h>
29 #include <stdio.h>
30 #include <string.h>
31 #if HAVE_LOCALE_H
32 #  include <locale.h>
33 #endif
34 #include <ctype.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38
39 #include "procmime.h"
40 #include "procheader.h"
41 #include "base64.h"
42 #include "quoted-printable.h"
43 #include "uuencode.h"
44 #include "unmime.h"
45 #include "html.h"
46 #include "enriched.h"
47 #include "codeconv.h"
48 #include "utils.h"
49 #include "prefs_common.h"
50 #include "prefs_gtk.h"
51
52 static GHashTable *procmime_get_mime_type_table (void);
53
54 MimeInfo *procmime_mimeinfo_new(void)
55 {
56         MimeInfo *mimeinfo;
57
58         mimeinfo = g_new0(MimeInfo, 1);
59         mimeinfo->content        = MIMECONTENT_EMPTY;
60         mimeinfo->data.filename  = NULL;
61
62         mimeinfo->type           = MIMETYPE_UNKNOWN;
63         mimeinfo->encoding_type  = ENC_UNKNOWN;
64         mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
65
66         mimeinfo->disposition    = DISPOSITIONTYPE_UNKNOWN;
67         mimeinfo->dispositionparameters 
68                                  = g_hash_table_new(g_str_hash, g_str_equal);
69
70         mimeinfo->node           = g_node_new(mimeinfo);
71         
72         return mimeinfo;
73 }
74
75 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
76 {
77         g_free(key);
78         g_free(value);
79         
80         return TRUE;
81 }
82
83 static gchar *forced_charset = NULL;
84
85 void procmime_force_charset(const gchar *str)
86 {
87         g_free(forced_charset);
88         forced_charset = NULL;
89         if (str)
90                 forced_charset = g_strdup(str);
91 }
92
93 static EncodingType forced_encoding = 0;
94
95 void procmime_force_encoding(EncodingType encoding)
96 {
97         forced_encoding = encoding;
98 }
99
100 static gboolean free_func(GNode *node, gpointer data)
101 {
102         MimeInfo *mimeinfo = (MimeInfo *) node->data;
103
104         switch (mimeinfo->content) {
105         case MIMECONTENT_FILE:
106                 if (mimeinfo->tmp)
107                         g_unlink(mimeinfo->data.filename);
108                 g_free(mimeinfo->data.filename);
109                 break;
110
111         case MIMECONTENT_MEM:
112                 if (mimeinfo->tmp)
113                         g_free(mimeinfo->data.mem);
114         default:
115                 break;
116         }
117
118         g_free(mimeinfo->subtype);
119         g_free(mimeinfo->description);
120         g_free(mimeinfo->id);
121         g_free(mimeinfo->location);
122
123         g_hash_table_foreach_remove(mimeinfo->typeparameters,
124                 procmime_mimeinfo_parameters_destroy, NULL);
125         g_hash_table_destroy(mimeinfo->typeparameters);
126         g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
127                 procmime_mimeinfo_parameters_destroy, NULL);
128         g_hash_table_destroy(mimeinfo->dispositionparameters);
129
130         if (mimeinfo->privacy)
131                 privacy_free_privacydata(mimeinfo->privacy);
132
133         g_free(mimeinfo);
134
135         return FALSE;
136 }
137
138 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
139 {
140         GNode *node;
141
142         if (!mimeinfo)
143                 return;
144
145         node = mimeinfo->node;
146         g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
147
148         g_node_destroy(node);
149 }
150
151 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
152 {
153         g_return_val_if_fail(mimeinfo != NULL, NULL);
154         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
155
156         if (mimeinfo->node->parent == NULL)
157                 return NULL;
158         return (MimeInfo *) mimeinfo->node->parent->data;
159 }
160
161 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
162 {
163         g_return_val_if_fail(mimeinfo != NULL, NULL);
164         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
165
166         if (mimeinfo->node->children)
167                 return (MimeInfo *) mimeinfo->node->children->data;
168         if (mimeinfo->node->next)
169                 return (MimeInfo *) mimeinfo->node->next->data;
170
171         if (mimeinfo->node->parent == NULL)
172                 return NULL;
173
174         while (mimeinfo->node->parent != NULL) {
175                 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
176                 if (mimeinfo->node->next)
177                         return (MimeInfo *) mimeinfo->node->next->data;
178         }
179
180         return NULL;
181 }
182
183 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
184 {
185         gchar *filename;
186         MimeInfo *mimeinfo;
187
188         filename = procmsg_get_message_file_path(msginfo);
189         if (!filename || !is_file_exist(filename)) {
190                 g_free(filename);
191                 filename = procmsg_get_message_file(msginfo);
192         }
193         if (!filename || !is_file_exist(filename)) 
194                 return NULL;
195
196         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
197             !folder_has_parent_of_type(msginfo->folder, 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         const gchar *value;
218
219         g_return_val_if_fail(mimeinfo != NULL, NULL);
220         g_return_val_if_fail(name != NULL, NULL);
221
222         value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
223         if (value == NULL)
224                 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
225         
226         return value;
227 }
228
229 gboolean procmime_decode_content(MimeInfo *mimeinfo)
230 {
231         gchar buf[BUFFSIZE];
232         gint readend;
233         gchar *tmpfilename;
234         FILE *outfp, *infp;
235         struct stat statbuf;
236         gboolean tmp_file = FALSE;
237
238         EncodingType encoding = forced_encoding 
239                                 ? forced_encoding
240                                 : mimeinfo->encoding_type;
241                    
242         g_return_val_if_fail(mimeinfo != NULL, FALSE);
243
244         if (encoding == ENC_UNKNOWN ||
245             encoding == ENC_BINARY)
246                 return TRUE;
247
248         infp = g_fopen(mimeinfo->data.filename, "rb");
249         if (!infp) {
250                 perror("fopen");
251                 return FALSE;
252         }
253         fseek(infp, mimeinfo->offset, SEEK_SET);
254
255         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
256         if (!outfp) {
257                 perror("tmpfile");
258                 fclose(infp);
259                 return FALSE;
260         }
261         tmp_file = TRUE;
262         readend = mimeinfo->offset + mimeinfo->length;
263
264         if (encoding == ENC_QUOTED_PRINTABLE) {
265                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
266                         gint len;
267                         len = qp_decode_line(buf);
268                         fwrite(buf, 1, len, outfp);
269                 }
270         } else if (encoding == ENC_BASE64) {
271                 gchar outbuf[BUFFSIZE];
272                 gint len;
273                 Base64Decoder *decoder;
274                 gboolean got_error = FALSE;
275                 gboolean uncanonicalize = FALSE;
276                 FILE *tmpfp = outfp;
277
278                 if (mimeinfo->type == MIMETYPE_TEXT ||
279                     mimeinfo->type == MIMETYPE_MESSAGE) {
280                         uncanonicalize = TRUE;
281                         tmpfp = my_tmpfile();
282                         if (!tmpfp) {
283                                 perror("tmpfile");
284                                 if (tmp_file) fclose(outfp);
285                                 fclose(infp);
286                                 return FALSE;
287                         }
288                 }
289
290                 decoder = base64_decoder_new();
291                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
292                         len = base64_decoder_decode(decoder, buf, outbuf);
293                         if (len < 0 && !got_error) {
294                                 g_warning("Bad BASE64 content.\n");
295                                 fwrite(_("[Error decoding BASE64]\n"),
296                                         sizeof(gchar),
297                                         strlen(_("[Error decoding BASE64]\n")),
298                                         tmpfp);
299                                 got_error = TRUE;
300                                 continue;
301                         } else if (len >= 0) {
302                                 /* print out the error message only once 
303                                  * per block */
304                                 fwrite(outbuf, sizeof(gchar), len, tmpfp);
305                                 got_error = FALSE;
306                         }
307                 }
308                 base64_decoder_free(decoder);
309
310                 if (uncanonicalize) {
311                         rewind(tmpfp);
312                         while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
313                                 strcrchomp(buf);
314                                 fputs(buf, outfp);
315                         }
316                         fclose(tmpfp);
317                 }
318         } else if (encoding == ENC_X_UUENCODE) {
319                 gchar outbuf[BUFFSIZE];
320                 gint len;
321                 gboolean flag = FALSE;
322
323                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
324                         if (!flag && strncmp(buf,"begin ", 6)) continue;
325
326                         if (flag) {
327                                 len = fromuutobits(outbuf, buf);
328                                 if (len <= 0) {
329                                         if (len < 0) 
330                                                 g_warning("Bad UUENCODE content(%d)\n", len);
331                                         break;
332                                 }
333                                 fwrite(outbuf, sizeof(gchar), len, outfp);
334                         } else
335                                 flag = TRUE;
336                 }
337         } else {
338                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
339                         fputs(buf, outfp);
340                 }
341         }
342
343         fclose(outfp);
344         fclose(infp);
345
346         stat(tmpfilename, &statbuf);
347         if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
348                 g_unlink(mimeinfo->data.filename);
349         g_free(mimeinfo->data.filename);
350         mimeinfo->data.filename = tmpfilename;
351         mimeinfo->tmp = TRUE;
352         mimeinfo->offset = 0;
353         mimeinfo->length = statbuf.st_size;
354         mimeinfo->encoding_type = ENC_BINARY;
355
356         return TRUE;
357 }
358
359 #define B64_LINE_SIZE           57
360 #define B64_BUFFSIZE            77
361
362 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
363 {
364         FILE *infp = NULL, *outfp;
365         gint len;
366         gchar *tmpfilename;
367         struct stat statbuf;
368
369         if (mimeinfo->content == MIMECONTENT_EMPTY)
370                 return TRUE;
371
372         if (mimeinfo->encoding_type != ENC_UNKNOWN &&
373             mimeinfo->encoding_type != ENC_BINARY &&
374             mimeinfo->encoding_type != ENC_7BIT &&
375             mimeinfo->encoding_type != ENC_8BIT)
376                 if(!procmime_decode_content(mimeinfo))
377                         return FALSE;
378
379         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
380         if (!outfp) {
381                 perror("tmpfile");
382                 return FALSE;
383         }
384
385         if (mimeinfo->content == MIMECONTENT_FILE) {
386                 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
387                         g_warning("Can't open file %s\n", mimeinfo->data.filename);
388                         return FALSE;
389                 }
390         } else if (mimeinfo->content == MIMECONTENT_MEM) {
391                 infp = str_open_as_stream(mimeinfo->data.mem);
392                 if (infp == NULL)
393                         return FALSE;
394         }
395
396         if (encoding == ENC_BASE64) {
397                 gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
398                 FILE *tmp_fp = infp;
399                 gchar *tmp_file = NULL;
400
401                 if (mimeinfo->type == MIMETYPE_TEXT ||
402                      mimeinfo->type == MIMETYPE_MESSAGE) {
403                         if (mimeinfo->content == MIMECONTENT_FILE) {
404                                 tmp_file = get_tmp_file();
405                                 if (canonicalize_file(mimeinfo->data.filename, tmp_file) < 0) {
406                                         g_free(tmp_file);
407                                         fclose(infp);
408                                         return FALSE;
409                                 }
410                                 if ((tmp_fp = g_fopen(tmp_file, "rb")) == NULL) {
411                                         FILE_OP_ERROR(tmp_file, "fopen");
412                                         g_unlink(tmp_file);
413                                         g_free(tmp_file);
414                                         fclose(infp);
415                                         return FALSE;
416                                 }
417                         } else {
418                                 gchar *out = canonicalize_str(mimeinfo->data.mem);
419                                 fclose(infp);
420                                 infp = str_open_as_stream(out);
421                                 tmp_fp = infp;
422                                 g_free(out);
423                                 if (infp == NULL)
424                                         return FALSE;
425                         }
426                 }
427
428                 while ((len = fread(inbuf, sizeof(gchar),
429                                     B64_LINE_SIZE, tmp_fp))
430                        == B64_LINE_SIZE) {
431                         base64_encode(outbuf, inbuf, B64_LINE_SIZE);
432                         fputs(outbuf, outfp);
433                         fputc('\n', outfp);
434                 }
435                 if (len > 0 && feof(tmp_fp)) {
436                         base64_encode(outbuf, inbuf, len);
437                         fputs(outbuf, outfp);
438                         fputc('\n', outfp);
439                 }
440
441                 if (tmp_file) {
442                         fclose(tmp_fp);
443                         g_unlink(tmp_file);
444                         g_free(tmp_file);
445                 }
446         } else if (encoding == ENC_QUOTED_PRINTABLE) {
447                 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
448
449                 while (fgets(inbuf, sizeof(inbuf), infp) != NULL) {
450                         qp_encode_line(outbuf, inbuf);
451
452                         if (!strncmp("From ", outbuf, sizeof("From ")-1)) {
453                                 gchar *tmpbuf = outbuf;
454                                 
455                                 tmpbuf += sizeof("From ")-1;
456                                 
457                                 fputs("=46rom ", outfp);
458                                 fputs(tmpbuf, outfp);
459                         } else 
460                                 fputs(outbuf, outfp);
461                 }
462         } else {
463                 gchar buf[BUFFSIZE];
464
465                 while (fgets(buf, sizeof(buf), infp) != NULL) {
466                         strcrchomp(buf);
467                         fputs(buf, outfp);
468                 }
469         }
470
471         fclose(outfp);
472         fclose(infp);
473
474         if (mimeinfo->content == MIMECONTENT_FILE) {
475                 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
476                         g_unlink(mimeinfo->data.filename);
477                 g_free(mimeinfo->data.filename);
478         } else if (mimeinfo->content == MIMECONTENT_MEM) {
479                 if (mimeinfo->tmp && (mimeinfo->data.mem != NULL))
480                         g_free(mimeinfo->data.mem);
481         }
482
483         stat(tmpfilename, &statbuf);
484         mimeinfo->content = MIMECONTENT_FILE;
485         mimeinfo->data.filename = tmpfilename;
486         mimeinfo->tmp = TRUE;
487         mimeinfo->offset = 0;
488         mimeinfo->length = statbuf.st_size;
489         mimeinfo->encoding_type = encoding;
490
491         return TRUE;
492 }
493
494 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
495 {
496         FILE *infp, *outfp;
497         gchar buf[BUFFSIZE];
498         gint restlength, readlength;
499
500         g_return_val_if_fail(outfile != NULL, -1);
501         g_return_val_if_fail(mimeinfo != NULL, -1);
502
503         if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
504                 return -1;
505
506         if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
507                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
508                 return -1;
509         }
510         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
511                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
512                 fclose(infp);
513                 return -1;
514         }
515         if ((outfp = g_fopen(outfile, "wb")) == NULL) {
516                 FILE_OP_ERROR(outfile, "fopen");
517                 fclose(infp);
518                 return -1;
519         }
520
521         restlength = mimeinfo->length;
522
523         while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
524                 fwrite(buf, 1, readlength, outfp);
525                 restlength -= readlength;
526         }
527
528         fclose(infp);
529         if (fclose(outfp) == EOF) {
530                 FILE_OP_ERROR(outfile, "fclose");
531                 g_unlink(outfile);
532                 return -1;
533         }
534
535         return 0;
536 }
537
538 struct ContentRenderer {
539         char * content_type;
540         char * renderer;
541 };
542
543 static GList * renderer_list = NULL;
544
545 static struct ContentRenderer *
546 content_renderer_new(char * content_type, char * renderer)
547 {
548         struct ContentRenderer * cr;
549
550         cr = g_new(struct ContentRenderer, 1);
551         if (cr == NULL)
552                 return NULL;
553
554         cr->content_type = g_strdup(content_type);
555         cr->renderer = g_strdup(renderer);
556
557         return cr;
558 }
559
560 static void content_renderer_free(struct ContentRenderer * cr)
561 {
562         g_free(cr->content_type);
563         g_free(cr->renderer);
564         g_free(cr);
565 }
566
567 void renderer_read_config(void)
568 {
569         gchar buf[BUFFSIZE];
570         FILE * f;
571         gchar * rcpath;
572
573         g_list_foreach(renderer_list, (GFunc) content_renderer_free, NULL);
574         renderer_list = NULL;
575
576         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
577         f = g_fopen(rcpath, "rb");
578         g_free(rcpath);
579         
580         if (f == NULL)
581                 return;
582
583         while (fgets(buf, BUFFSIZE, f)) {
584                 char * p;
585                 struct ContentRenderer * cr;
586
587                 strretchomp(buf);
588                 p = strchr(buf, ' ');
589                 if (p == NULL)
590                         continue;
591                 * p = 0;
592
593                 cr = content_renderer_new(buf, p + 1);
594                 if (cr == NULL)
595                         continue;
596
597                 renderer_list = g_list_append(renderer_list, cr);
598         }
599
600         fclose(f);
601 }
602
603 void renderer_write_config(void)
604 {
605         gchar * rcpath;
606         PrefFile *pfile;
607         GList * cur;
608
609         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
610         
611         if ((pfile = prefs_write_open(rcpath)) == NULL) {
612                 g_warning("failed to write configuration to file\n");
613                 g_free(rcpath);
614                 return;
615         }
616
617         g_free(rcpath);
618
619         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
620                 struct ContentRenderer * renderer;
621                 renderer = cur->data;
622                 fprintf(pfile->fp, "%s %s\n", renderer->content_type,
623                         renderer->renderer);
624         }
625
626         if (prefs_file_close(pfile) < 0) {
627                 g_warning("failed to write configuration to file\n");
628                 return;
629         }
630 }
631
632 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
633 {
634         FILE *tmpfp, *outfp;
635         const gchar *src_codeset;
636         gboolean conv_fail = FALSE;
637         gchar buf[BUFFSIZE];
638         gchar *str;
639         struct ContentRenderer * renderer;
640         GList * cur;
641         gchar *tmpfile, *content_type;
642     
643         g_return_val_if_fail(mimeinfo != NULL, NULL);
644
645         if (!procmime_decode_content(mimeinfo))
646                 return NULL;
647
648         tmpfile = procmime_get_tmp_file_name(mimeinfo);
649         if (tmpfile == NULL)
650                 return NULL;
651
652         if (procmime_get_part(tmpfile, mimeinfo) < 0) {
653                 g_free(tmpfile);
654                 return NULL;
655         }
656
657         tmpfp = g_fopen(tmpfile, "rb");
658         if (tmpfp == NULL) {
659                 g_free(tmpfile);
660                 return NULL;
661         }
662
663         if ((outfp = my_tmpfile()) == NULL) {
664                 perror("tmpfile");
665                 fclose(tmpfp);
666                 g_free(tmpfile);
667                 return NULL;
668         }
669
670         src_codeset = forced_charset
671                       ? forced_charset : 
672                       procmime_mimeinfo_get_parameter(mimeinfo, "charset");
673
674         renderer = NULL;
675
676         content_type = procmime_get_content_type_str(mimeinfo->type,
677                                                      mimeinfo->subtype);
678         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
679                 struct ContentRenderer * cr;
680
681                 cr = cur->data;
682                 if (g_ascii_strcasecmp(cr->content_type, content_type) == 0) {
683                         renderer = cr;
684                         break;
685                 }
686         }
687         g_free(content_type);
688
689         if (renderer != NULL) {
690                 FILE * p;
691                 int oldout;
692                 
693                 oldout = dup(1);
694                 
695                 dup2(fileno(outfp), 1);
696
697                 p = popen(renderer->renderer, "w");
698                 if (p != NULL) {
699                         size_t count;
700                         
701                         while ((count =
702                                 fread(buf, sizeof(char), sizeof(buf),
703                                       tmpfp)) > 0)
704                                 fwrite(buf, sizeof(char), count, p);
705                         pclose(p);
706                 }
707                 
708                 dup2(oldout, 1);
709 /* CodeConverter seems to have no effect here */
710         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
711                 SC_HTMLParser *parser;
712                 CodeConverter *conv;
713
714                 conv = conv_code_converter_new(src_codeset);
715                 parser = sc_html_parser_new(tmpfp, conv);
716                 while ((str = sc_html_parse(parser)) != NULL) {
717                         fputs(str, outfp);
718                 }
719                 sc_html_parser_destroy(parser);
720                 conv_code_converter_destroy(conv);
721         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
722                 ERTFParser *parser;
723                 CodeConverter *conv;
724
725                 conv = conv_code_converter_new(src_codeset);
726                 parser = ertf_parser_new(tmpfp, conv);
727                 while ((str = ertf_parse(parser)) != NULL) {
728                         fputs(str, outfp);
729                 }
730                 ertf_parser_destroy(parser);
731                 conv_code_converter_destroy(conv);
732         } else if (mimeinfo->type == MIMETYPE_TEXT) {
733                 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
734                         str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
735                         if (str) {
736                                 fputs(str, outfp);
737                                 g_free(str);
738                         } else {
739                                 conv_fail = TRUE;
740                                 fputs(buf, outfp);
741                         }
742                 }
743         }
744
745         if (conv_fail)
746                 g_warning("procmime_get_text_content(): Code conversion failed.\n");
747
748         fclose(tmpfp);
749         rewind(outfp);
750         g_unlink(tmpfile);
751         g_free(tmpfile);
752
753         return outfp;
754 }
755
756 /* search the first text part of (multipart) MIME message,
757    decode, convert it and output to outfp. */
758 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
759 {
760         FILE *outfp = NULL;
761         MimeInfo *mimeinfo, *partinfo;
762
763         g_return_val_if_fail(msginfo != NULL, NULL);
764
765         mimeinfo = procmime_scan_message(msginfo);
766         if (!mimeinfo) return NULL;
767
768         partinfo = mimeinfo;
769         while (partinfo && partinfo->type != MIMETYPE_TEXT) {
770                 partinfo = procmime_mimeinfo_next(partinfo);
771         }
772         if (partinfo)
773                 outfp = procmime_get_text_content(partinfo);
774
775         procmime_mimeinfo_free_all(mimeinfo);
776
777         return outfp;
778 }
779
780
781 static gboolean find_encrypted_func(GNode *node, gpointer data)
782 {
783         MimeInfo *mimeinfo = (MimeInfo *) node->data;
784         MimeInfo **encinfo = (MimeInfo **) data;
785         
786         if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
787                 *encinfo = mimeinfo;
788                 return TRUE;
789         }
790         
791         return FALSE;
792 }
793
794 static MimeInfo *find_encrypted_part(MimeInfo *rootinfo)
795 {
796         MimeInfo *encinfo = NULL;
797
798         g_node_traverse(rootinfo->node, G_IN_ORDER, G_TRAVERSE_ALL, -1,
799                 find_encrypted_func, &encinfo);
800         
801         return encinfo;
802 }
803
804 /* search the first encrypted text part of (multipart) MIME message,
805    decode, convert it and output to outfp. */
806 FILE *procmime_get_first_encrypted_text_content(MsgInfo *msginfo)
807 {
808         FILE *outfp = NULL;
809         MimeInfo *mimeinfo, *partinfo, *encinfo;
810
811         g_return_val_if_fail(msginfo != NULL, NULL);
812
813         mimeinfo = procmime_scan_message(msginfo);
814         if (!mimeinfo) {
815                 return NULL;
816         }
817
818         partinfo = mimeinfo;
819         if ((encinfo = find_encrypted_part(partinfo)) != NULL) {
820                 debug_print("decrypting message part\n");
821                 if (privacy_mimeinfo_decrypt(encinfo) < 0)
822                         return NULL;
823         }
824         partinfo = mimeinfo;
825         while (partinfo && partinfo->type != MIMETYPE_TEXT) {
826                 partinfo = procmime_mimeinfo_next(partinfo);
827         }
828
829         if (partinfo)
830                 outfp = procmime_get_text_content(partinfo);
831
832         procmime_mimeinfo_free_all(mimeinfo);
833
834         return outfp;
835 }
836
837 gboolean procmime_msginfo_is_encrypted(MsgInfo *msginfo)
838 {
839         MimeInfo *mimeinfo, *partinfo;
840         gboolean result = FALSE;
841
842         g_return_val_if_fail(msginfo != NULL, FALSE);
843
844         mimeinfo = procmime_scan_message(msginfo);
845         if (!mimeinfo) {
846                 return FALSE;
847         }
848
849         partinfo = mimeinfo;
850         result = (find_encrypted_part(partinfo) != NULL);
851         procmime_mimeinfo_free_all(mimeinfo);
852
853         return result;
854 }
855
856 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
857                                    const gchar *str, StrFindFunc find_func)
858 {
859         FILE *outfp;
860         gchar buf[BUFFSIZE];
861
862         g_return_val_if_fail(mimeinfo != NULL, FALSE);
863         g_return_val_if_fail(mimeinfo->type == MIMETYPE_TEXT, FALSE);
864         g_return_val_if_fail(str != NULL, FALSE);
865         g_return_val_if_fail(find_func != NULL, FALSE);
866
867         outfp = procmime_get_text_content(mimeinfo);
868
869         if (!outfp)
870                 return FALSE;
871
872         while (fgets(buf, sizeof(buf), outfp) != NULL) {
873                 strretchomp(buf);
874                 if (find_func(buf, str)) {
875                         fclose(outfp);
876                         return TRUE;
877                 }
878         }
879
880         fclose(outfp);
881
882         return FALSE;
883 }
884
885 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
886                               StrFindFunc find_func)
887 {
888         MimeInfo *mimeinfo;
889         MimeInfo *partinfo;
890         gchar *filename;
891         gboolean found = FALSE;
892
893         g_return_val_if_fail(msginfo != NULL, FALSE);
894         g_return_val_if_fail(str != NULL, FALSE);
895         g_return_val_if_fail(find_func != NULL, FALSE);
896
897         filename = procmsg_get_message_file(msginfo);
898         if (!filename) return FALSE;
899         mimeinfo = procmime_scan_message(msginfo);
900
901         for (partinfo = mimeinfo; partinfo != NULL;
902              partinfo = procmime_mimeinfo_next(partinfo)) {
903                 if (partinfo->type == MIMETYPE_TEXT) {
904                         if (procmime_find_string_part
905                                 (partinfo, filename, str, find_func) == TRUE) {
906                                 found = TRUE;
907                                 break;
908                         }
909                 }
910         }
911
912         procmime_mimeinfo_free_all(mimeinfo);
913         g_free(filename);
914
915         return found;
916 }
917
918 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
919 {
920         static guint32 id = 0;
921         gchar *base;
922         gchar *filename;
923         gchar f_prefix[10];
924
925         g_return_val_if_fail(mimeinfo != NULL, NULL);
926
927         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
928
929         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
930                 base = g_strdup("mimetmp.html");
931         else {
932                 const gchar *basetmp;
933
934                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
935                 if (basetmp == NULL)
936                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
937                 if (basetmp == NULL)
938                         basetmp = "mimetmp";
939                 basetmp = g_path_get_basename(basetmp);
940                 if (*basetmp == '\0') 
941                         basetmp = g_strdup("mimetmp");
942                 base = conv_filename_from_utf8(basetmp);
943                 g_free((gchar*)basetmp);
944                 subst_for_shellsafe_filename(base);
945         }
946
947         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
948                                f_prefix, base, NULL);
949
950         g_free(base);
951         
952         return filename;
953 }
954
955 static GList *mime_type_list = NULL;
956
957 gchar *procmime_get_mime_type(const gchar *filename)
958 {
959         static GHashTable *mime_type_table = NULL;
960         MimeType *mime_type;
961         const gchar *p;
962         gchar *ext = NULL;
963         gchar *base;
964
965         if (!mime_type_table) {
966                 mime_type_table = procmime_get_mime_type_table();
967                 if (!mime_type_table) return NULL;
968         }
969
970         base = g_path_get_basename(filename);
971         if ((p = strrchr(base, '.')) != NULL)
972                 Xstrdup_a(ext, p + 1, p = NULL );
973         g_free(base);
974         if (!p) return NULL;
975
976         g_strdown(ext);
977         mime_type = g_hash_table_lookup(mime_type_table, ext);
978         if (mime_type) {
979                 gchar *str;
980
981                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
982                                   NULL);
983                 return str;
984         }
985
986         return NULL;
987 }
988
989 static guint procmime_str_hash(gconstpointer gptr)
990 {
991         guint hash_result = 0;
992         const char *str;
993
994         for (str = gptr; str && *str; str++) {
995                 if (isupper(*str)) hash_result += (*str + ' ');
996                 else hash_result += *str;
997         }
998
999         return hash_result;
1000 }
1001
1002 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
1003 {
1004         const char *str1 = gptr1;
1005         const char *str2 = gptr2;
1006
1007         return !g_utf8_collate(str1, str2);
1008 }
1009
1010 static GHashTable *procmime_get_mime_type_table(void)
1011 {
1012         GHashTable *table = NULL;
1013         GList *cur;
1014         MimeType *mime_type;
1015         gchar **exts;
1016
1017         if (!mime_type_list) {
1018                 mime_type_list = procmime_get_mime_type_list();
1019                 if (!mime_type_list) return NULL;
1020         }
1021
1022         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
1023
1024         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1025                 gint i;
1026                 gchar *key;
1027
1028                 mime_type = (MimeType *)cur->data;
1029
1030                 if (!mime_type->extension) continue;
1031
1032                 exts = g_strsplit(mime_type->extension, " ", 16);
1033                 for (i = 0; exts[i] != NULL; i++) {
1034                         /* make the key case insensitive */
1035                         g_strdown(exts[i]);
1036                         /* use previously dup'd key on overwriting */
1037                         if (g_hash_table_lookup(table, exts[i]))
1038                                 key = exts[i];
1039                         else
1040                                 key = g_strdup(exts[i]);
1041                         g_hash_table_insert(table, key, mime_type);
1042                 }
1043                 g_strfreev(exts);
1044         }
1045
1046         return table;
1047 }
1048
1049 GList *procmime_get_mime_type_list(void)
1050 {
1051         GList *list = NULL;
1052         FILE *fp;
1053         gchar buf[BUFFSIZE];
1054         gchar *p;
1055         gchar *delim;
1056         MimeType *mime_type;
1057         gboolean fp_is_glob_file = TRUE;
1058
1059         if (mime_type_list) 
1060                 return mime_type_list;
1061         
1062         if ((fp = g_fopen("/usr/share/mime/globs", "rb")) == NULL) {
1063                 fp_is_glob_file = FALSE;
1064                 if ((fp = g_fopen("/etc/mime.types", "rb")) == NULL) {
1065                         if ((fp = g_fopen(SYSCONFDIR "/mime.types", "rb")) 
1066                                 == NULL) {
1067                                 FILE_OP_ERROR(SYSCONFDIR "/mime.types", 
1068                                         "fopen");
1069                                 return NULL;
1070                         }
1071                 }
1072         }
1073
1074         while (fgets(buf, sizeof(buf), fp) != NULL) {
1075                 p = strchr(buf, '#');
1076                 if (p) *p = '\0';
1077                 g_strstrip(buf);
1078
1079                 p = buf;
1080                 
1081                 if (fp_is_glob_file) {
1082                         while (*p && !g_ascii_isspace(*p) && (*p!=':')) p++;
1083                 } else {
1084                         while (*p && !g_ascii_isspace(*p)) p++;
1085                 }
1086
1087                 if (*p) {
1088                         *p = '\0';
1089                         p++;
1090                 }
1091                 delim = strchr(buf, '/');
1092                 if (delim == NULL) continue;
1093                 *delim = '\0';
1094
1095                 mime_type = g_new(MimeType, 1);
1096                 mime_type->type = g_strdup(buf);
1097                 mime_type->sub_type = g_strdup(delim + 1);
1098
1099                 if (fp_is_glob_file) {
1100                         while (*p && (g_ascii_isspace(*p)||(*p=='*')||(*p=='.'))) p++;
1101                 } else {
1102                         while (*p && g_ascii_isspace(*p)) p++;
1103                 }
1104
1105                 if (*p)
1106                         mime_type->extension = g_strdup(p);
1107                 else
1108                         mime_type->extension = NULL;
1109
1110                 list = g_list_append(list, mime_type);
1111         }
1112
1113         fclose(fp);
1114
1115         if (!list)
1116                 g_warning("Can't read mime.types\n");
1117
1118         return list;
1119 }
1120
1121 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1122 {
1123         if (!charset)
1124                 return ENC_8BIT;
1125         else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
1126                  !g_ascii_strcasecmp(charset, "US-ASCII"))
1127                 return ENC_7BIT;
1128         else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
1129                  !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
1130                  !g_ascii_strcasecmp(charset, "Windows-1251"))
1131                 return ENC_8BIT;
1132         else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
1133                 return ENC_QUOTED_PRINTABLE;
1134         else
1135                 return ENC_8BIT;
1136 }
1137
1138 EncodingType procmime_get_encoding_for_text_file(const gchar *file, gboolean *has_binary)
1139 {
1140         FILE *fp;
1141         guchar buf[BUFFSIZE];
1142         size_t len;
1143         size_t octet_chars = 0;
1144         size_t total_len = 0;
1145         gfloat octet_percentage;
1146         gboolean force_b64 = FALSE;
1147
1148         if ((fp = g_fopen(file, "rb")) == NULL) {
1149                 FILE_OP_ERROR(file, "fopen");
1150                 return ENC_UNKNOWN;
1151         }
1152
1153         while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
1154                 guchar *p;
1155                 gint i;
1156
1157                 for (p = buf, i = 0; i < len; ++p, ++i) {
1158                         if (*p & 0x80)
1159                                 ++octet_chars;
1160                         if (*p == '\0') {
1161                                 force_b64 = TRUE;
1162                                 *has_binary = TRUE;
1163                         }
1164                 }
1165                 total_len += len;
1166         }
1167
1168         fclose(fp);
1169         
1170         if (total_len > 0)
1171                 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1172         else
1173                 octet_percentage = 0.0;
1174
1175         debug_print("procmime_get_encoding_for_text_file(): "
1176                     "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len,
1177                     100.0 * octet_percentage);
1178
1179         if (octet_percentage > 0.20 || force_b64) {
1180                 debug_print("using BASE64\n");
1181                 return ENC_BASE64;
1182         } else if (octet_chars > 0) {
1183                 debug_print("using quoted-printable\n");
1184                 return ENC_QUOTED_PRINTABLE;
1185         } else {
1186                 debug_print("using 7bit\n");
1187                 return ENC_7BIT;
1188         }
1189 }
1190
1191 struct EncodingTable 
1192 {
1193         gchar *str;
1194         EncodingType enc_type;
1195 };
1196
1197 struct EncodingTable encoding_table[] = {
1198         {"7bit", ENC_7BIT},
1199         {"8bit", ENC_8BIT},
1200         {"binary", ENC_BINARY},
1201         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1202         {"base64", ENC_BASE64},
1203         {"x-uuencode", ENC_UNKNOWN},
1204         {NULL, ENC_UNKNOWN},
1205 };
1206
1207 const gchar *procmime_get_encoding_str(EncodingType encoding)
1208 {
1209         struct EncodingTable *enc_table;
1210         
1211         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1212                 if (enc_table->enc_type == encoding)
1213                         return enc_table->str;
1214         }
1215         return NULL;
1216 }
1217
1218 /* --- NEW MIME STUFF --- */
1219 struct TypeTable
1220 {
1221         gchar *str;
1222         MimeMediaType type;
1223 };
1224
1225 static struct TypeTable mime_type_table[] = {
1226         {"text", MIMETYPE_TEXT},
1227         {"image", MIMETYPE_IMAGE},
1228         {"audio", MIMETYPE_AUDIO},
1229         {"video", MIMETYPE_VIDEO},
1230         {"application", MIMETYPE_APPLICATION},
1231         {"message", MIMETYPE_MESSAGE},
1232         {"multipart", MIMETYPE_MULTIPART},
1233         {NULL, 0},
1234 };
1235
1236 const gchar *procmime_get_media_type_str(MimeMediaType type)
1237 {
1238         struct TypeTable *type_table;
1239         
1240         for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1241                 if (type_table->type == type)
1242                         return type_table->str;
1243         }
1244         return NULL;
1245 }
1246
1247 MimeMediaType procmime_get_media_type(const gchar *str)
1248 {
1249         struct TypeTable *typetablearray;
1250
1251         for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1252                 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1253                         return typetablearray->type;
1254
1255         return MIMETYPE_UNKNOWN;
1256 }
1257
1258 /*!
1259  *\brief        Safe wrapper for content type string.
1260  *
1261  *\return       const gchar * Pointer to content type string. 
1262  */
1263 gchar *procmime_get_content_type_str(MimeMediaType type,
1264                                            const char *subtype)
1265 {
1266         const gchar *type_str = NULL;
1267
1268         if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1269                 return g_strdup("unknown");
1270         return g_strdup_printf("%s/%s", type_str, subtype);
1271 }
1272
1273 static int procmime_parse_mimepart(MimeInfo *parent,
1274                              gchar *content_type,
1275                              gchar *content_encoding,
1276                              gchar *content_description,
1277                              gchar *content_id,
1278                              gchar *content_disposition,
1279                              gchar *content_location,
1280                              const gchar *filename,
1281                              guint offset,
1282                              guint length);
1283
1284 void procmime_parse_message_rfc822(MimeInfo *mimeinfo)
1285 {
1286         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1287                                 {"Content-Transfer-Encoding:",
1288                                                    NULL, FALSE},
1289                                 {"Content-Description:",
1290                                                    NULL, TRUE},
1291                                 {"Content-ID:",
1292                                                    NULL, TRUE},
1293                                 {"Content-Disposition:",
1294                                                    NULL, TRUE},
1295                                 {"Content-Location:",
1296                                                    NULL, TRUE},
1297                                 {"MIME-Version:",
1298                                                    NULL, TRUE},
1299                                 {NULL,             NULL, FALSE}};
1300         guint content_start, i;
1301         FILE *fp;
1302         gchar *tmp;
1303
1304         procmime_decode_content(mimeinfo);
1305
1306         fp = g_fopen(mimeinfo->data.filename, "rb");
1307         if (fp == NULL) {
1308                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1309                 return;
1310         }
1311         fseek(fp, mimeinfo->offset, SEEK_SET);
1312         procheader_get_header_fields(fp, hentry);
1313         if (hentry[0].body != NULL) {
1314                 tmp = conv_unmime_header(hentry[0].body, NULL);
1315                 g_free(hentry[0].body);
1316                 hentry[0].body = tmp;
1317         }                
1318         if (hentry[2].body != NULL) {
1319                 tmp = conv_unmime_header(hentry[2].body, NULL);
1320                 g_free(hentry[2].body);
1321                 hentry[2].body = tmp;
1322         }                
1323         if (hentry[4].body != NULL) {
1324                 tmp = conv_unmime_header(hentry[4].body, NULL);
1325                 g_free(hentry[4].body);
1326                 hentry[4].body = tmp;
1327         }                
1328         if (hentry[5].body != NULL) {
1329                 tmp = conv_unmime_header(hentry[5].body, NULL);
1330                 g_free(hentry[5].body);
1331                 hentry[5].body = tmp;
1332         }                
1333         content_start = ftell(fp);
1334         fclose(fp);
1335         
1336         procmime_parse_mimepart(mimeinfo,
1337                                 hentry[0].body, hentry[1].body,
1338                                 hentry[2].body, hentry[3].body,
1339                                 hentry[4].body, hentry[5].body,
1340                                 mimeinfo->data.filename, content_start,
1341                                 mimeinfo->length - (content_start - mimeinfo->offset));
1342         
1343         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1344                 g_free(hentry[i].body);
1345                 hentry[i].body = NULL;
1346         }
1347 }
1348
1349 void procmime_parse_multipart(MimeInfo *mimeinfo)
1350 {
1351         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1352                                 {"Content-Transfer-Encoding:",
1353                                                    NULL, FALSE},
1354                                 {"Content-Description:",
1355                                                    NULL, TRUE},
1356                                 {"Content-ID:",
1357                                                    NULL, TRUE},
1358                                 {"Content-Disposition:",
1359                                                    NULL, TRUE},
1360                                 {"Content-Location:",
1361                                                    NULL, TRUE},
1362                                 {NULL,             NULL, FALSE}};
1363         gchar *p, *tmp;
1364         gchar *boundary;
1365         gint boundary_len = 0, lastoffset = -1, i;
1366         gchar buf[BUFFSIZE];
1367         FILE *fp;
1368         int result = 0;
1369
1370         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1371         if (!boundary)
1372                 return;
1373         boundary_len = strlen(boundary);
1374
1375         procmime_decode_content(mimeinfo);
1376
1377         fp = g_fopen(mimeinfo->data.filename, "rb");
1378         if (fp == NULL) {
1379                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1380                 return;
1381         }
1382         fseek(fp, mimeinfo->offset, SEEK_SET);
1383         while ((p = fgets(buf, sizeof(buf), fp)) != NULL && result == 0) {
1384                 if (ftell(fp) > (mimeinfo->offset + mimeinfo->length))
1385                         break;
1386
1387                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1388                         if (lastoffset != -1) {
1389                                 result = procmime_parse_mimepart(mimeinfo,
1390                                                         hentry[0].body, hentry[1].body,
1391                                                         hentry[2].body, hentry[3].body, 
1392                                                         hentry[4].body, hentry[5].body,
1393                                                         mimeinfo->data.filename, lastoffset,
1394                                                         (ftell(fp) - strlen(buf)) - lastoffset - 1);
1395                         }
1396                         
1397                         if (buf[2 + boundary_len]     == '-' &&
1398                             buf[2 + boundary_len + 1] == '-')
1399                                 break;
1400
1401                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1402                                 g_free(hentry[i].body);
1403                                 hentry[i].body = NULL;
1404                         }
1405                         procheader_get_header_fields(fp, hentry);
1406                         if (hentry[0].body != NULL) {
1407                                 tmp = conv_unmime_header(hentry[0].body, NULL);
1408                                 g_free(hentry[0].body);
1409                                 hentry[0].body = tmp;
1410                         }                
1411                         if (hentry[2].body != NULL) {
1412                                 tmp = conv_unmime_header(hentry[2].body, NULL);
1413                                 g_free(hentry[2].body);
1414                                 hentry[2].body = tmp;
1415                         }                
1416                         if (hentry[4].body != NULL) {
1417                                 tmp = conv_unmime_header(hentry[4].body, NULL);
1418                                 g_free(hentry[4].body);
1419                                 hentry[4].body = tmp;
1420                         }                
1421                         if (hentry[5].body != NULL) {
1422                                 tmp = conv_unmime_header(hentry[5].body, NULL);
1423                                 g_free(hentry[5].body);
1424                                 hentry[5].body = tmp;
1425                         }                
1426                         lastoffset = ftell(fp);
1427                 }
1428         }
1429         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1430                 g_free(hentry[i].body);
1431                 hentry[i].body = NULL;
1432         }
1433         fclose(fp);
1434 }
1435
1436 static void parse_parameters(const gchar *parameters, GHashTable *table)
1437 {
1438         gchar *params, *param, *next;
1439         GSList *convlist = NULL, *concatlist = NULL, *cur;
1440
1441         params = g_strdup(parameters);
1442         param = params;
1443         next = params;
1444         for (; next != NULL; param = next) {
1445                 gchar *attribute, *value, *tmp;
1446                 gint len;
1447                 gboolean convert = FALSE;
1448
1449                 next = strchr_with_skip_quote(param, '"', ';');
1450                 if (next != NULL) {
1451                         next[0] = '\0';
1452                         next++;
1453                 }
1454
1455                 g_strstrip(param);
1456
1457                 attribute = param;
1458                 value = strchr(attribute, '=');
1459                 if (value == NULL)
1460                         continue;
1461
1462                 value[0] = '\0';
1463                 value++;
1464                 while (value[0] == ' ')
1465                         value++;
1466
1467                 g_strdown(attribute);
1468
1469                 len = strlen(attribute);
1470                 if (attribute[len - 1] == '*') {
1471                         gchar *srcpos, *dstpos, *endpos;
1472
1473                         convert = TRUE;
1474                         attribute[len - 1] = '\0';
1475
1476                         srcpos = value;
1477                         dstpos = value;
1478                         endpos = value + strlen(value);
1479                         while (srcpos < endpos) {
1480                                 if (*srcpos != '%')
1481                                         *dstpos = *srcpos;
1482                                 else {
1483                                         guchar dstvalue;
1484
1485                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1486                                                 *dstpos = '?';
1487                                         else
1488                                                 *dstpos = dstvalue;
1489                                         srcpos += 2;
1490                                 }
1491                                 srcpos++;
1492                                 dstpos++;
1493                         }
1494                         *dstpos = '\0';
1495                 } else {
1496                         if (value[0] == '"')
1497                                 extract_quote(value, '"');
1498                         else if ((tmp = strchr(value, ' ')) != NULL)
1499                                 *tmp = '\0';
1500                 }
1501
1502                 if (attribute) {
1503                         while (attribute[0] == ' ')
1504                                 attribute++;
1505                         while (attribute[strlen(attribute)-1] == ' ') 
1506                                 attribute[strlen(attribute)-1] = '\0';
1507                 } 
1508                 if (value) {
1509                         while (value[0] == ' ')
1510                                 value++;
1511                         while (value[strlen(value)-1] == ' ') 
1512                                 value[strlen(value)-1] = '\0';
1513                 }               
1514                 if (strrchr(attribute, '*') != NULL) {
1515                         gchar *tmpattr;
1516
1517                         tmpattr = g_strdup(attribute);
1518                         tmp = strrchr(tmpattr, '*');
1519                         tmp[0] = '\0';
1520
1521                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1522                             (g_slist_find_custom(concatlist, attribute, g_str_equal) == NULL))
1523                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1524
1525                         if (convert && (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL))
1526                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1527
1528                         g_free(tmpattr);
1529                 } else if (convert) {
1530                         if (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL)
1531                                 convlist = g_slist_prepend(convlist, g_strdup(attribute));
1532                 }
1533
1534                 if (g_hash_table_lookup(table, attribute) == NULL)
1535                         g_hash_table_insert(table, g_strdup(attribute), g_strdup(value));
1536         }
1537
1538         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1539                 gchar *attribute, *attrwnum, *partvalue;
1540                 gint n = 0;
1541                 GString *value;
1542
1543                 attribute = (gchar *) cur->data;
1544                 value = g_string_sized_new(64);
1545
1546                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1547                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1548                         g_string_append(value, partvalue);
1549
1550                         g_free(attrwnum);
1551                         n++;
1552                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1553                 }
1554                 g_free(attrwnum);
1555
1556                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1557                 g_string_free(value, TRUE);
1558         }
1559         slist_free_strings(concatlist);
1560         g_slist_free(concatlist);
1561
1562         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1563                 gchar *attribute, *key, *value;
1564                 gchar *charset, *lang, *oldvalue, *newvalue;
1565
1566                 attribute = (gchar *) cur->data;
1567                 if (!g_hash_table_lookup_extended(
1568                         table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
1569                         continue;
1570
1571                 charset = value;
1572                 lang = strchr(charset, '\'');
1573                 if (lang == NULL)
1574                         continue;
1575                 lang[0] = '\0';
1576                 lang++;
1577                 oldvalue = strchr(lang, '\'');
1578                 if (oldvalue == NULL)
1579                         continue;
1580                 oldvalue[0] = '\0';
1581                 oldvalue++;
1582
1583                 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1584
1585                 g_hash_table_remove(table, attribute);
1586                 g_free(key);
1587                 g_free(value);
1588
1589                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1590         }
1591         slist_free_strings(convlist);
1592         g_slist_free(convlist);
1593
1594         g_free(params);
1595 }       
1596
1597 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1598 {
1599         g_return_if_fail(content_type != NULL);
1600         g_return_if_fail(mimeinfo != NULL);
1601
1602         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1603          * if it's not available we use the default Content-Type */
1604         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1605                 mimeinfo->type = MIMETYPE_TEXT;
1606                 mimeinfo->subtype = g_strdup("plain");
1607                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1608                                        "charset") == NULL) {
1609                         g_hash_table_insert(mimeinfo->typeparameters,
1610                                     g_strdup("charset"),
1611                                     g_strdup(
1612                                         conv_get_locale_charset_str_no_utf8()));
1613                 }
1614         } else {
1615                 gchar *type, *subtype, *params;
1616
1617                 type = g_strdup(content_type);
1618                 subtype = strchr(type, '/') + 1;
1619                 *(subtype - 1) = '\0';
1620                 if ((params = strchr(subtype, ';')) != NULL) {
1621                         params[0] = '\0';
1622                         params++;
1623                 }
1624
1625                 mimeinfo->type = procmime_get_media_type(type);
1626                 mimeinfo->subtype = g_strdup(subtype);
1627
1628                 /* Get mimeinfo->typeparameters */
1629                 if (params != NULL)
1630                         parse_parameters(params, mimeinfo->typeparameters);
1631
1632                 g_free(type);
1633         }
1634 }
1635
1636 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1637 {
1638         gchar *tmp, *params;
1639
1640         g_return_if_fail(content_disposition != NULL);
1641         g_return_if_fail(mimeinfo != NULL);
1642
1643         tmp = g_strdup(content_disposition);
1644         if ((params = strchr(tmp, ';')) != NULL) {
1645                 params[0] = '\0';
1646                 params++;
1647         }       
1648         g_strstrip(tmp);
1649
1650         if (!g_ascii_strcasecmp(tmp, "inline")) 
1651                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1652         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1653                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1654         else
1655                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1656         
1657         if (params != NULL)
1658                 parse_parameters(params, mimeinfo->dispositionparameters);
1659
1660         g_free(tmp);
1661 }
1662
1663
1664 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1665 {
1666         struct EncodingTable *enc_table;
1667         
1668         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1669                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1670                         mimeinfo->encoding_type = enc_table->enc_type;
1671                         return;
1672                 }
1673         }
1674         mimeinfo->encoding_type = ENC_UNKNOWN;
1675         return;
1676 }
1677
1678 static int procmime_parse_mimepart(MimeInfo *parent,
1679                              gchar *content_type,
1680                              gchar *content_encoding,
1681                              gchar *content_description,
1682                              gchar *content_id,
1683                              gchar *content_disposition,
1684                              gchar *content_location,
1685                              const gchar *filename,
1686                              guint offset,
1687                              guint length)
1688 {
1689         MimeInfo *mimeinfo;
1690
1691         /* Create MimeInfo */
1692         mimeinfo = procmime_mimeinfo_new();
1693         mimeinfo->content = MIMECONTENT_FILE;
1694         if (parent != NULL) {
1695                 if (g_node_depth(parent->node) > 32) {
1696                         /* 32 is an arbitrary value
1697                          * this avoids DOSsing ourselves 
1698                          * with enormous messages
1699                          */
1700                         procmime_mimeinfo_free_all(mimeinfo);
1701                         return -1;                      
1702                 }
1703                 g_node_append(parent->node, mimeinfo->node);
1704         }
1705         mimeinfo->data.filename = g_strdup(filename);
1706         mimeinfo->offset = offset;
1707         mimeinfo->length = length;
1708
1709         if (content_type != NULL) {
1710                 procmime_parse_content_type(content_type, mimeinfo);
1711         } else {
1712                 mimeinfo->type = MIMETYPE_TEXT;
1713                 mimeinfo->subtype = g_strdup("plain");
1714                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1715                                        "charset") == NULL) {
1716                         g_hash_table_insert(mimeinfo->typeparameters,
1717                                     g_strdup("charset"),
1718                                     g_strdup(
1719                                         conv_get_locale_charset_str_no_utf8()));
1720                 }
1721         }
1722
1723         if (content_encoding != NULL) {
1724                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1725         } else {
1726                 mimeinfo->encoding_type = ENC_UNKNOWN;
1727         }
1728
1729         if (content_description != NULL)
1730                 mimeinfo->description = g_strdup(content_description);
1731         else
1732                 mimeinfo->description = NULL;
1733
1734         if (content_id != NULL)
1735                 mimeinfo->id = g_strdup(content_id);
1736         else
1737                 mimeinfo->id = NULL;
1738
1739         if (content_location != NULL)
1740                 mimeinfo->location = g_strdup(content_location);
1741         else
1742                 mimeinfo->location = NULL;
1743
1744         if (content_disposition != NULL) 
1745                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1746         else
1747                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1748
1749         /* Call parser for mime type */
1750         switch (mimeinfo->type) {
1751                 case MIMETYPE_MESSAGE:
1752                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1753                                 procmime_parse_message_rfc822(mimeinfo);
1754                         }
1755                         break;
1756                         
1757                 case MIMETYPE_MULTIPART:
1758                         procmime_parse_multipart(mimeinfo);
1759                         break;
1760                         
1761                 default:
1762                         break;
1763         }
1764
1765         return 0;
1766 }
1767
1768 static gchar *typenames[] = {
1769     "text",
1770     "image",
1771     "audio",
1772     "video",
1773     "application",
1774     "message",
1775     "multipart",
1776     "unknown",
1777 };
1778
1779 static gboolean output_func(GNode *node, gpointer data)
1780 {
1781         guint i, depth;
1782         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1783
1784         depth = g_node_depth(node);
1785         for (i = 0; i < depth; i++)
1786                 printf("    ");
1787         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1788
1789         return FALSE;
1790 }
1791
1792 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1793 {
1794         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1795 }
1796
1797 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1798 {
1799         MimeInfo *mimeinfo;
1800         struct stat buf;
1801
1802         stat(filename, &buf);
1803
1804         mimeinfo = procmime_mimeinfo_new();
1805         mimeinfo->content = MIMECONTENT_FILE;
1806         mimeinfo->encoding_type = ENC_UNKNOWN;
1807         mimeinfo->type = MIMETYPE_MESSAGE;
1808         mimeinfo->subtype = g_strdup("rfc822");
1809         mimeinfo->data.filename = g_strdup(filename);
1810         mimeinfo->offset = offset;
1811         mimeinfo->length = buf.st_size - offset;
1812
1813         procmime_parse_message_rfc822(mimeinfo);
1814         if (debug_get_mode())
1815                 output_mime_structure(mimeinfo, 0);
1816
1817         return mimeinfo;
1818 }
1819
1820 MimeInfo *procmime_scan_file(const gchar *filename)
1821 {
1822         MimeInfo *mimeinfo;
1823
1824         g_return_val_if_fail(filename != NULL, NULL);
1825
1826         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1827
1828         return mimeinfo;
1829 }
1830
1831 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1832 {
1833         FILE *fp;
1834         MimeInfo *mimeinfo;
1835         gchar buf[BUFFSIZE];
1836         gint offset = 0;
1837
1838         g_return_val_if_fail(filename != NULL, NULL);
1839
1840         /* Open file */
1841         if ((fp = g_fopen(filename, "rb")) == NULL)
1842                 return NULL;
1843         /* Skip queue header */
1844         while (fgets(buf, sizeof(buf), fp) != NULL)
1845                 if (buf[0] == '\r' || buf[0] == '\n') break;
1846         offset = ftell(fp);
1847         fclose(fp);
1848
1849         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1850
1851         return mimeinfo;
1852 }
1853
1854 typedef enum {
1855     ENC_AS_TOKEN,
1856     ENC_AS_QUOTED_STRING,
1857     ENC_AS_EXTENDED,
1858     ENC_TO_ASCII,
1859 } EncodeAs;
1860
1861 typedef struct _ParametersData {
1862         FILE *fp;
1863         guint len;
1864         guint ascii_only;
1865 } ParametersData;
1866
1867 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1868 {
1869         gchar *param = key;
1870         gchar *val = value, *valpos, *tmp;
1871         ParametersData *pdata = (ParametersData *)user_data;
1872         GString *buf = g_string_new("");
1873
1874         EncodeAs encas = ENC_AS_TOKEN;
1875
1876         for (valpos = val; *valpos != 0; valpos++) {
1877                 if (!IS_ASCII(*valpos) || *valpos == '"') {
1878                         encas = ENC_AS_EXTENDED;
1879                         break;
1880                 }
1881             
1882                 /* CTLs */
1883                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
1884                         encas = ENC_AS_QUOTED_STRING;
1885                         continue;
1886                 }
1887
1888                 /* tspecials + SPACE */
1889                 switch (*valpos) {
1890                 case ' ':
1891                 case '(': 
1892                 case ')':
1893                 case '<':
1894                 case '>':
1895                 case '@':
1896                 case ',':
1897                 case ';':
1898                 case ':':
1899                 case '\\':
1900                 case '\'':
1901                 case '/':
1902                 case '[':
1903                 case ']':
1904                 case '?':
1905                 case '=':
1906                         encas = ENC_AS_QUOTED_STRING;
1907                         continue;
1908                 }
1909         }
1910         
1911         if (encas == ENC_AS_EXTENDED && pdata->ascii_only == TRUE) 
1912                 encas = ENC_TO_ASCII;
1913
1914         switch (encas) {
1915         case ENC_AS_TOKEN:
1916                 g_string_append_printf(buf, "%s=%s", param, val);
1917                 break;
1918
1919         case ENC_TO_ASCII:
1920                 tmp = g_strdup(val);
1921                 g_strcanon(tmp, 
1922                         " ()<>@,';:\\/[]?=.0123456789"
1923                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1924                         "abcdefghijklmnopqrstuvwxyz",
1925                         '_');
1926                 g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
1927                 g_free(tmp);
1928                 break;
1929
1930         case ENC_AS_QUOTED_STRING:
1931                 g_string_append_printf(buf, "%s=\"%s\"", param, val);
1932                 break;
1933
1934         case ENC_AS_EXTENDED:
1935                 if (!g_utf8_validate(val, -1, NULL))
1936                         g_string_append_printf(buf, "%s*=%s''", param,
1937                                 conv_get_locale_charset_str());
1938                 else
1939                         g_string_append_printf(buf, "%s*=%s''", param,
1940                                 CS_INTERNAL);
1941                 for (valpos = val; *valpos != '\0'; valpos++) {
1942                         if (IS_ASCII(*valpos) && isalnum(*valpos)) {
1943                                 g_string_append_printf(buf, "%c", *valpos);
1944                         } else {
1945                                 gchar hexstr[3] = "XX";
1946                                 get_hex_str(hexstr, *valpos);
1947                                 g_string_append_printf(buf, "%%%s", hexstr);
1948                         }
1949                 }
1950                 break;          
1951         }
1952         
1953         if (buf->str && strlen(buf->str)) {
1954                 if (pdata->len + strlen(buf->str) + 2 > 76) {
1955                         fprintf(pdata->fp, ";\n %s", buf->str);
1956                         pdata->len = strlen(buf->str) + 1;
1957                 } else {
1958                         fprintf(pdata->fp, "; %s", buf->str);
1959                         pdata->len += strlen(buf->str) + 2;
1960                 }
1961         }
1962         g_string_free(buf, TRUE);
1963 }
1964
1965 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1966 {
1967         struct TypeTable *type_table;
1968         ParametersData *pdata = g_new0(ParametersData, 1);
1969         debug_print("procmime_write_mime_header\n");
1970         
1971         pdata->fp = fp;
1972         pdata->ascii_only = FALSE;
1973
1974         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1975                 if (mimeinfo->type == type_table->type) {
1976                         gchar *buf = g_strdup_printf(
1977                                 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1978                         fprintf(fp, "%s", buf);
1979                         pdata->len = strlen(buf);
1980                         pdata->ascii_only = TRUE;
1981                         g_free(buf);
1982                         break;
1983                 }
1984         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
1985         g_free(pdata);
1986
1987         fprintf(fp, "\n");
1988
1989         if (mimeinfo->encoding_type != ENC_UNKNOWN)
1990                 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1991
1992         if (mimeinfo->description != NULL)
1993                 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1994
1995         if (mimeinfo->id != NULL)
1996                 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1997
1998         if (mimeinfo->location != NULL)
1999                 fprintf(fp, "Content-Location: %s\n", mimeinfo->location);
2000
2001         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
2002                 ParametersData *pdata = g_new0(ParametersData, 1);
2003                 gchar *buf = NULL;
2004                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
2005                         buf = g_strdup("Content-Disposition: inline");
2006                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
2007                         buf = g_strdup("Content-Disposition: attachment");
2008                 else
2009                         buf = g_strdup("Content-Disposition: unknown");
2010
2011                 fprintf(fp, "%s", buf);
2012                 pdata->len = strlen(buf);
2013                 g_free(buf);
2014
2015                 pdata->fp = fp;
2016                 pdata->ascii_only = FALSE;
2017
2018                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
2019                 g_free(pdata);
2020                 fprintf(fp, "\n");
2021         }
2022
2023         fprintf(fp, "\n");
2024 }
2025
2026 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
2027 {
2028         FILE *infp;
2029         GNode *childnode;
2030         MimeInfo *child;
2031         gchar buf[BUFFSIZE];
2032         gboolean skip = FALSE;;
2033
2034         debug_print("procmime_write_message_rfc822\n");
2035
2036         /* write header */
2037         switch (mimeinfo->content) {
2038         case MIMECONTENT_FILE:
2039                 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2040                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2041                         return -1;
2042                 }
2043                 fseek(infp, mimeinfo->offset, SEEK_SET);
2044                 while (fgets(buf, sizeof(buf), infp) == buf) {
2045                         if (buf[0] == '\n' && buf[1] == '\0')
2046                                 break;
2047                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
2048                                 continue;
2049                         if (g_ascii_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
2050                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
2051                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
2052                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
2053                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
2054                             g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 ||
2055                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
2056                                 skip = TRUE;
2057                                 continue;
2058                         }
2059                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
2060                         skip = FALSE;
2061                 }
2062                 fclose(infp);
2063                 break;
2064
2065         case MIMECONTENT_MEM:
2066                 fwrite(mimeinfo->data.mem, 
2067                                 sizeof(gchar), 
2068                                 strlen(mimeinfo->data.mem), 
2069                                 fp);
2070                 break;
2071
2072         default:
2073                 break;
2074         }
2075
2076         childnode = mimeinfo->node->children;
2077         if (childnode == NULL)
2078                 return -1;
2079
2080         child = (MimeInfo *) childnode->data;
2081         fprintf(fp, "Mime-Version: 1.0\n");
2082         procmime_write_mime_header(child, fp);
2083         return procmime_write_mimeinfo(child, fp);
2084 }
2085
2086 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
2087 {
2088         FILE *infp;
2089         GNode *childnode;
2090         gchar *boundary, *str, *str2;
2091         gchar buf[BUFFSIZE];
2092         gboolean firstboundary;
2093
2094         debug_print("procmime_write_multipart\n");
2095
2096         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
2097
2098         switch (mimeinfo->content) {
2099         case MIMECONTENT_FILE:
2100                 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2101                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2102                         return -1;
2103                 }
2104                 fseek(infp, mimeinfo->offset, SEEK_SET);
2105                 while (fgets(buf, sizeof(buf), infp) == buf) {
2106                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
2107                                 break;
2108                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
2109                 }
2110                 fclose(infp);
2111                 break;
2112
2113         case MIMECONTENT_MEM:
2114                 str = g_strdup(mimeinfo->data.mem);
2115                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
2116                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
2117                         *(str2 - 2) = '\0';
2118                 fwrite(str, sizeof(gchar), strlen(str), fp);
2119                 g_free(str);
2120                 break;
2121
2122         default:
2123                 break;
2124         }
2125
2126         childnode = mimeinfo->node->children;
2127         firstboundary = TRUE;
2128         while (childnode != NULL) {
2129                 MimeInfo *child = childnode->data;
2130
2131                 if (firstboundary)
2132                         firstboundary = FALSE;
2133                 else
2134                         fprintf(fp, "\n");
2135                 fprintf(fp, "--%s\n", boundary);
2136
2137                 procmime_write_mime_header(child, fp);
2138                 if (procmime_write_mimeinfo(child, fp) < 0)
2139                         return -1;
2140
2141                 childnode = g_node_next_sibling(childnode);
2142         }       
2143         fprintf(fp, "\n--%s--\n", boundary);
2144
2145         return 0;
2146 }
2147
2148 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
2149 {
2150         FILE *infp;
2151
2152         debug_print("procmime_write_mimeinfo\n");
2153
2154         if (G_NODE_IS_LEAF(mimeinfo->node)) {
2155                 switch (mimeinfo->content) {
2156                 case MIMECONTENT_FILE:
2157                         if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2158                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2159                                 return -1;
2160                         }
2161                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
2162                         fclose(infp);
2163                         return 0;
2164
2165                 case MIMECONTENT_MEM:
2166                         fwrite(mimeinfo->data.mem, 
2167                                         sizeof(gchar), 
2168                                         strlen(mimeinfo->data.mem), 
2169                                         fp);
2170                         return 0;
2171
2172                 default:
2173                         return 0;
2174                 }
2175         } else {
2176                 /* Call writer for mime type */
2177                 switch (mimeinfo->type) {
2178                 case MIMETYPE_MESSAGE:
2179                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2180                                 return procmime_write_message_rfc822(mimeinfo, fp);
2181                         }
2182                         break;
2183                         
2184                 case MIMETYPE_MULTIPART:
2185                         return procmime_write_multipart(mimeinfo, fp);
2186                         
2187                 default:
2188                         break;
2189                 }
2190
2191                 return -1;
2192         }
2193
2194         return 0;
2195 }
2196
2197 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
2198 {
2199         gchar *base;
2200
2201         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
2202                 base = g_strdup("mimetmp.html");
2203         else {
2204                 const gchar *basetmp;
2205                 gchar *basename;
2206
2207                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
2208                 if (basetmp == NULL)
2209                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
2210                 if (basetmp == NULL)
2211                         basetmp = "mimetmp";
2212                 basename = g_path_get_basename(basetmp);
2213                 if (*basename == '\0') {
2214                         g_free(basename);
2215                         basename = g_strdup("mimetmp");
2216                 }
2217                 base = conv_filename_from_utf8(basename);
2218                 g_free(basename);
2219                 subst_for_shellsafe_filename(base);
2220         }
2221         
2222         return base;
2223 }
2224