2006-03-01 [colin] 2.0.0cvs95
[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
122         g_hash_table_foreach_remove(mimeinfo->typeparameters,
123                 procmime_mimeinfo_parameters_destroy, NULL);
124         g_hash_table_destroy(mimeinfo->typeparameters);
125         g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
126                 procmime_mimeinfo_parameters_destroy, NULL);
127         g_hash_table_destroy(mimeinfo->dispositionparameters);
128
129         if (mimeinfo->privacy)
130                 privacy_free_privacydata(mimeinfo->privacy);
131
132         g_free(mimeinfo);
133
134         return FALSE;
135 }
136
137 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
138 {
139         GNode *node;
140
141         if (!mimeinfo)
142                 return;
143
144         node = mimeinfo->node;
145         g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
146
147         g_node_destroy(node);
148 }
149
150 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
151 {
152         g_return_val_if_fail(mimeinfo != NULL, NULL);
153         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
154
155         if (mimeinfo->node->parent == NULL)
156                 return NULL;
157         return (MimeInfo *) mimeinfo->node->parent->data;
158 }
159
160 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
161 {
162         g_return_val_if_fail(mimeinfo != NULL, NULL);
163         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
164
165         if (mimeinfo->node->children)
166                 return (MimeInfo *) mimeinfo->node->children->data;
167         if (mimeinfo->node->next)
168                 return (MimeInfo *) mimeinfo->node->next->data;
169
170         if (mimeinfo->node->parent == NULL)
171                 return NULL;
172
173         while (mimeinfo->node->parent != NULL) {
174                 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
175                 if (mimeinfo->node->next)
176                         return (MimeInfo *) mimeinfo->node->next->data;
177         }
178
179         return NULL;
180 }
181
182 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
183 {
184         gchar *filename;
185         MimeInfo *mimeinfo;
186
187         filename = procmsg_get_message_file_path(msginfo);
188         if (!filename || !is_file_exist(filename)) {
189                 g_free(filename);
190                 filename = procmsg_get_message_file(msginfo);
191         }
192         if (!filename || !is_file_exist(filename)) 
193                 return NULL;
194
195         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
196             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
197                 mimeinfo = procmime_scan_file(filename);
198         else
199                 mimeinfo = procmime_scan_queue_file(filename);
200         g_free(filename);
201
202         return mimeinfo;
203 }
204
205 enum
206 {
207         H_CONTENT_TRANSFER_ENCODING = 0,
208         H_CONTENT_TYPE              = 1,
209         H_CONTENT_DISPOSITION       = 2,
210         H_CONTENT_DESCRIPTION       = 3,
211         H_SUBJECT                   = 4
212 };
213
214 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
215 {
216         const gchar *value;
217
218         g_return_val_if_fail(mimeinfo != NULL, NULL);
219         g_return_val_if_fail(name != NULL, NULL);
220
221         value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
222         if (value == NULL)
223                 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
224         
225         return value;
226 }
227
228 gboolean procmime_decode_content(MimeInfo *mimeinfo)
229 {
230         gchar buf[BUFFSIZE];
231         gint readend;
232         gchar *tmpfilename;
233         FILE *outfp, *infp;
234         struct stat statbuf;
235         gboolean tmp_file = FALSE;
236
237         EncodingType encoding = forced_encoding 
238                                 ? forced_encoding
239                                 : mimeinfo->encoding_type;
240                    
241         g_return_val_if_fail(mimeinfo != NULL, FALSE);
242
243         if (encoding == ENC_UNKNOWN ||
244             encoding == ENC_BINARY)
245                 return TRUE;
246
247         infp = g_fopen(mimeinfo->data.filename, "rb");
248         if (!infp) {
249                 perror("fopen");
250                 return FALSE;
251         }
252         fseek(infp, mimeinfo->offset, SEEK_SET);
253
254         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
255         if (!outfp) {
256                 perror("tmpfile");
257                 fclose(infp);
258                 return FALSE;
259         }
260         tmp_file = TRUE;
261         readend = mimeinfo->offset + mimeinfo->length;
262
263         if (encoding == ENC_QUOTED_PRINTABLE) {
264                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
265                         gint len;
266                         len = qp_decode_line(buf);
267                         fwrite(buf, 1, len, outfp);
268                 }
269         } else if (encoding == ENC_BASE64) {
270                 gchar outbuf[BUFFSIZE];
271                 gint len;
272                 Base64Decoder *decoder;
273                 gboolean got_error = FALSE;
274                 gboolean uncanonicalize = FALSE;
275                 FILE *tmpfp = outfp;
276
277                 if (mimeinfo->type == MIMETYPE_TEXT ||
278                     mimeinfo->type == MIMETYPE_MESSAGE) {
279                         uncanonicalize = TRUE;
280                         tmpfp = my_tmpfile();
281                         if (!tmpfp) {
282                                 perror("tmpfile");
283                                 if (tmp_file) fclose(outfp);
284                                 fclose(infp);
285                                 return FALSE;
286                         }
287                 }
288
289                 decoder = base64_decoder_new();
290                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
291                         len = base64_decoder_decode(decoder, buf, outbuf);
292                         if (len < 0 && !got_error) {
293                                 g_warning("Bad BASE64 content.\n");
294                                 fwrite(_("[Error decoding BASE64]\n"),
295                                         sizeof(gchar),
296                                         strlen(_("[Error decoding BASE64]\n")),
297                                         tmpfp);
298                                 got_error = TRUE;
299                                 continue;
300                         } else if (len >= 0) {
301                                 /* print out the error message only once 
302                                  * per block */
303                                 fwrite(outbuf, sizeof(gchar), len, tmpfp);
304                                 got_error = FALSE;
305                         }
306                 }
307                 base64_decoder_free(decoder);
308
309                 if (uncanonicalize) {
310                         rewind(tmpfp);
311                         while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
312                                 strcrchomp(buf);
313                                 fputs(buf, outfp);
314                         }
315                         fclose(tmpfp);
316                 }
317         } else if (encoding == ENC_X_UUENCODE) {
318                 gchar outbuf[BUFFSIZE];
319                 gint len;
320                 gboolean flag = FALSE;
321
322                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
323                         if (!flag && strncmp(buf,"begin ", 6)) continue;
324
325                         if (flag) {
326                                 len = fromuutobits(outbuf, buf);
327                                 if (len <= 0) {
328                                         if (len < 0) 
329                                                 g_warning("Bad UUENCODE content(%d)\n", len);
330                                         break;
331                                 }
332                                 fwrite(outbuf, sizeof(gchar), len, outfp);
333                         } else
334                                 flag = TRUE;
335                 }
336         } else {
337                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
338                         fputs(buf, outfp);
339                 }
340         }
341
342         fclose(outfp);
343         fclose(infp);
344
345         stat(tmpfilename, &statbuf);
346         if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
347                 g_unlink(mimeinfo->data.filename);
348         if (mimeinfo->data.filename != NULL)
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 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                              const gchar *filename,
1280                              guint offset,
1281                              guint length);
1282
1283 void procmime_parse_message_rfc822(MimeInfo *mimeinfo)
1284 {
1285         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1286                                 {"Content-Transfer-Encoding:",
1287                                                    NULL, FALSE},
1288                                 {"Content-Description:",
1289                                                    NULL, TRUE},
1290                                 {"Content-ID:",
1291                                                    NULL, TRUE},
1292                                 {"Content-Disposition:",
1293                                                    NULL, TRUE},
1294                                 {"MIME-Version:",
1295                                                    NULL, TRUE},
1296                                 {NULL,             NULL, FALSE}};
1297         guint content_start, i;
1298         FILE *fp;
1299         gchar *tmp;
1300
1301         procmime_decode_content(mimeinfo);
1302
1303         fp = g_fopen(mimeinfo->data.filename, "rb");
1304         if (fp == NULL) {
1305                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1306                 return;
1307         }
1308         fseek(fp, mimeinfo->offset, SEEK_SET);
1309         procheader_get_header_fields(fp, hentry);
1310         if (hentry[0].body != NULL) {
1311                 tmp = conv_unmime_header(hentry[0].body, NULL);
1312                 g_free(hentry[0].body);
1313                 hentry[0].body = tmp;
1314         }                
1315         if (hentry[2].body != NULL) {
1316                 tmp = conv_unmime_header(hentry[2].body, NULL);
1317                 g_free(hentry[2].body);
1318                 hentry[2].body = tmp;
1319         }                
1320         if (hentry[4].body != NULL) {
1321                 tmp = conv_unmime_header(hentry[4].body, NULL);
1322                 g_free(hentry[4].body);
1323                 hentry[4].body = tmp;
1324         }                
1325         content_start = ftell(fp);
1326         fclose(fp);
1327         
1328         procmime_parse_mimepart(mimeinfo,
1329                                 hentry[0].body, hentry[1].body,
1330                                 hentry[2].body, hentry[3].body,
1331                                 hentry[4].body,
1332                                 mimeinfo->data.filename, content_start,
1333                                 mimeinfo->length - (content_start - mimeinfo->offset));
1334         
1335         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1336                 g_free(hentry[i].body);
1337                 hentry[i].body = NULL;
1338         }
1339 }
1340
1341 void procmime_parse_multipart(MimeInfo *mimeinfo)
1342 {
1343         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1344                                 {"Content-Transfer-Encoding:",
1345                                                    NULL, FALSE},
1346                                 {"Content-Description:",
1347                                                    NULL, TRUE},
1348                                 {"Content-ID:",
1349                                                    NULL, TRUE},
1350                                 {"Content-Disposition:",
1351                                                    NULL, TRUE},
1352                                 {NULL,             NULL, FALSE}};
1353         gchar *p, *tmp;
1354         gchar *boundary;
1355         gint boundary_len = 0, lastoffset = -1, i;
1356         gchar buf[BUFFSIZE];
1357         FILE *fp;
1358         int result = 0;
1359
1360         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1361         if (!boundary)
1362                 return;
1363         boundary_len = strlen(boundary);
1364
1365         procmime_decode_content(mimeinfo);
1366
1367         fp = g_fopen(mimeinfo->data.filename, "rb");
1368         if (fp == NULL) {
1369                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1370                 return;
1371         }
1372         fseek(fp, mimeinfo->offset, SEEK_SET);
1373         while ((p = fgets(buf, sizeof(buf), fp)) != NULL && result == 0) {
1374                 if (ftell(fp) > (mimeinfo->offset + mimeinfo->length))
1375                         break;
1376
1377                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1378                         if (lastoffset != -1) {
1379                                 result = procmime_parse_mimepart(mimeinfo,
1380                                                         hentry[0].body, hentry[1].body,
1381                                                         hentry[2].body, hentry[3].body, 
1382                                                         hentry[4].body, 
1383                                                         mimeinfo->data.filename, lastoffset,
1384                                                         (ftell(fp) - strlen(buf)) - lastoffset - 1);
1385                         }
1386                         
1387                         if (buf[2 + boundary_len]     == '-' &&
1388                             buf[2 + boundary_len + 1] == '-')
1389                                 break;
1390
1391                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1392                                 g_free(hentry[i].body);
1393                                 hentry[i].body = NULL;
1394                         }
1395                         procheader_get_header_fields(fp, hentry);
1396                         if (hentry[0].body != NULL) {
1397                                 tmp = conv_unmime_header(hentry[0].body, NULL);
1398                                 g_free(hentry[0].body);
1399                                 hentry[0].body = tmp;
1400                         }                
1401                         if (hentry[2].body != NULL) {
1402                                 tmp = conv_unmime_header(hentry[2].body, NULL);
1403                                 g_free(hentry[2].body);
1404                                 hentry[2].body = tmp;
1405                         }                
1406                         if (hentry[4].body != NULL) {
1407                                 tmp = conv_unmime_header(hentry[4].body, NULL);
1408                                 g_free(hentry[4].body);
1409                                 hentry[4].body = tmp;
1410                         }                
1411                         lastoffset = ftell(fp);
1412                 }
1413         }
1414         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1415                 g_free(hentry[i].body);
1416                 hentry[i].body = NULL;
1417         }
1418         fclose(fp);
1419 }
1420
1421 static void parse_parameters(const gchar *parameters, GHashTable *table)
1422 {
1423         gchar *params, *param, *next;
1424         GSList *convlist = NULL, *concatlist = NULL, *cur;
1425
1426         params = g_strdup(parameters);
1427         param = params;
1428         next = params;
1429         for (; next != NULL; param = next) {
1430                 gchar *attribute, *value, *tmp;
1431                 gint len;
1432                 gboolean convert = FALSE;
1433
1434                 next = strchr_with_skip_quote(param, '"', ';');
1435                 if (next != NULL) {
1436                         next[0] = '\0';
1437                         next++;
1438                 }
1439
1440                 g_strstrip(param);
1441
1442                 attribute = param;
1443                 value = strchr(attribute, '=');
1444                 if (value == NULL)
1445                         continue;
1446
1447                 value[0] = '\0';
1448                 value++;
1449                 while (value[0] == ' ')
1450                         value++;
1451
1452                 g_strdown(attribute);
1453
1454                 len = strlen(attribute);
1455                 if (attribute[len - 1] == '*') {
1456                         gchar *srcpos, *dstpos, *endpos;
1457
1458                         convert = TRUE;
1459                         attribute[len - 1] = '\0';
1460
1461                         srcpos = value;
1462                         dstpos = value;
1463                         endpos = value + strlen(value);
1464                         while (srcpos < endpos) {
1465                                 if (*srcpos != '%')
1466                                         *dstpos = *srcpos;
1467                                 else {
1468                                         guchar dstvalue;
1469
1470                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1471                                                 *dstpos = '?';
1472                                         else
1473                                                 *dstpos = dstvalue;
1474                                         srcpos += 2;
1475                                 }
1476                                 srcpos++;
1477                                 dstpos++;
1478                         }
1479                         *dstpos = '\0';
1480                 } else {
1481                         if (value[0] == '"')
1482                                 extract_quote(value, '"');
1483                         else if ((tmp = strchr(value, ' ')) != NULL)
1484                                 *tmp = '\0';
1485                 }
1486
1487                 if (attribute) {
1488                         while (attribute[0] == ' ')
1489                                 attribute++;
1490                         while (attribute[strlen(attribute)-1] == ' ') 
1491                                 attribute[strlen(attribute)-1] = '\0';
1492                 } 
1493                 if (value) {
1494                         while (value[0] == ' ')
1495                                 value++;
1496                         while (value[strlen(value)-1] == ' ') 
1497                                 value[strlen(value)-1] = '\0';
1498                 }               
1499                 if (strrchr(attribute, '*') != NULL) {
1500                         gchar *tmpattr;
1501
1502                         tmpattr = g_strdup(attribute);
1503                         tmp = strrchr(tmpattr, '*');
1504                         tmp[0] = '\0';
1505
1506                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1507                             (g_slist_find_custom(concatlist, attribute, g_str_equal) == NULL))
1508                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1509
1510                         if (convert && (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL))
1511                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1512
1513                         g_free(tmpattr);
1514                 } else if (convert) {
1515                         if (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL)
1516                                 convlist = g_slist_prepend(convlist, g_strdup(attribute));
1517                 }
1518
1519                 if (g_hash_table_lookup(table, attribute) == NULL)
1520                         g_hash_table_insert(table, g_strdup(attribute), g_strdup(value));
1521         }
1522
1523         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1524                 gchar *attribute, *attrwnum, *partvalue;
1525                 gint n = 0;
1526                 GString *value;
1527
1528                 attribute = (gchar *) cur->data;
1529                 value = g_string_sized_new(64);
1530
1531                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1532                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1533                         g_string_append(value, partvalue);
1534
1535                         g_free(attrwnum);
1536                         n++;
1537                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1538                 }
1539                 g_free(attrwnum);
1540
1541                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1542                 g_string_free(value, TRUE);
1543         }
1544         slist_free_strings(concatlist);
1545         g_slist_free(concatlist);
1546
1547         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1548                 gchar *attribute, *key, *value;
1549                 gchar *charset, *lang, *oldvalue, *newvalue;
1550
1551                 attribute = (gchar *) cur->data;
1552                 if (!g_hash_table_lookup_extended(
1553                         table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
1554                         continue;
1555
1556                 charset = value;
1557                 lang = strchr(charset, '\'');
1558                 if (lang == NULL)
1559                         continue;
1560                 lang[0] = '\0';
1561                 lang++;
1562                 oldvalue = strchr(lang, '\'');
1563                 if (oldvalue == NULL)
1564                         continue;
1565                 oldvalue[0] = '\0';
1566                 oldvalue++;
1567
1568                 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1569
1570                 g_hash_table_remove(table, attribute);
1571                 g_free(key);
1572                 g_free(value);
1573
1574                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1575         }
1576         slist_free_strings(convlist);
1577         g_slist_free(convlist);
1578
1579         g_free(params);
1580 }       
1581
1582 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1583 {
1584         g_return_if_fail(content_type != NULL);
1585         g_return_if_fail(mimeinfo != NULL);
1586
1587         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1588          * if it's not available we use the default Content-Type */
1589         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1590                 mimeinfo->type = MIMETYPE_TEXT;
1591                 mimeinfo->subtype = g_strdup("plain");
1592                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1593                                        "charset") == NULL) {
1594                         g_hash_table_insert(mimeinfo->typeparameters,
1595                                     g_strdup("charset"),
1596                                     g_strdup(
1597                                         conv_get_locale_charset_str_no_utf8()));
1598                 }
1599         } else {
1600                 gchar *type, *subtype, *params;
1601
1602                 type = g_strdup(content_type);
1603                 subtype = strchr(type, '/') + 1;
1604                 *(subtype - 1) = '\0';
1605                 if ((params = strchr(subtype, ';')) != NULL) {
1606                         params[0] = '\0';
1607                         params++;
1608                 }
1609
1610                 mimeinfo->type = procmime_get_media_type(type);
1611                 mimeinfo->subtype = g_strdup(subtype);
1612
1613                 /* Get mimeinfo->typeparameters */
1614                 if (params != NULL)
1615                         parse_parameters(params, mimeinfo->typeparameters);
1616
1617                 g_free(type);
1618         }
1619 }
1620
1621 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1622 {
1623         gchar *tmp, *params;
1624
1625         g_return_if_fail(content_disposition != NULL);
1626         g_return_if_fail(mimeinfo != NULL);
1627
1628         tmp = g_strdup(content_disposition);
1629         if ((params = strchr(tmp, ';')) != NULL) {
1630                 params[0] = '\0';
1631                 params++;
1632         }       
1633         g_strstrip(tmp);
1634
1635         if (!g_ascii_strcasecmp(tmp, "inline")) 
1636                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1637         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1638                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1639         else
1640                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1641         
1642         if (params != NULL)
1643                 parse_parameters(params, mimeinfo->dispositionparameters);
1644
1645         g_free(tmp);
1646 }
1647
1648
1649 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1650 {
1651         struct EncodingTable *enc_table;
1652         
1653         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1654                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1655                         mimeinfo->encoding_type = enc_table->enc_type;
1656                         return;
1657                 }
1658         }
1659         mimeinfo->encoding_type = ENC_UNKNOWN;
1660         return;
1661 }
1662
1663 int procmime_parse_mimepart(MimeInfo *parent,
1664                              gchar *content_type,
1665                              gchar *content_encoding,
1666                              gchar *content_description,
1667                              gchar *content_id,
1668                              gchar *content_disposition,
1669                              const gchar *filename,
1670                              guint offset,
1671                              guint length)
1672 {
1673         MimeInfo *mimeinfo;
1674
1675         /* Create MimeInfo */
1676         mimeinfo = procmime_mimeinfo_new();
1677         mimeinfo->content = MIMECONTENT_FILE;
1678         if (parent != NULL) {
1679                 if (g_node_depth(parent->node) > 32) {
1680                         /* 32 is an arbitrary value
1681                          * this avoids DOSsing ourselves 
1682                          * with enormous messages
1683                          */
1684                         procmime_mimeinfo_free_all(mimeinfo);
1685                         return -1;                      
1686                 }
1687                 g_node_append(parent->node, mimeinfo->node);
1688         }
1689         mimeinfo->data.filename = g_strdup(filename);
1690         mimeinfo->offset = offset;
1691         mimeinfo->length = length;
1692
1693         if (content_type != NULL) {
1694                 procmime_parse_content_type(content_type, mimeinfo);
1695         } else {
1696                 mimeinfo->type = MIMETYPE_TEXT;
1697                 mimeinfo->subtype = g_strdup("plain");
1698                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1699                                        "charset") == NULL) {
1700                         g_hash_table_insert(mimeinfo->typeparameters,
1701                                     g_strdup("charset"),
1702                                     g_strdup(
1703                                         conv_get_locale_charset_str_no_utf8()));
1704                 }
1705         }
1706
1707         if (content_encoding != NULL) {
1708                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1709         } else {
1710                 mimeinfo->encoding_type = ENC_UNKNOWN;
1711         }
1712
1713         if (content_description != NULL)
1714                 mimeinfo->description = g_strdup(content_description);
1715         else
1716                 mimeinfo->description = NULL;
1717
1718         if (content_id != NULL)
1719                 mimeinfo->id = g_strdup(content_id);
1720         else
1721                 mimeinfo->id = NULL;
1722
1723         if (content_disposition != NULL) 
1724                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1725         else
1726                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1727
1728         /* Call parser for mime type */
1729         switch (mimeinfo->type) {
1730                 case MIMETYPE_MESSAGE:
1731                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1732                                 procmime_parse_message_rfc822(mimeinfo);
1733                         }
1734                         break;
1735                         
1736                 case MIMETYPE_MULTIPART:
1737                         procmime_parse_multipart(mimeinfo);
1738                         break;
1739                         
1740                 default:
1741                         break;
1742         }
1743
1744         return 0;
1745 }
1746
1747 static gchar *typenames[] = {
1748     "text",
1749     "image",
1750     "audio",
1751     "video",
1752     "application",
1753     "message",
1754     "multipart",
1755     "unknown",
1756 };
1757
1758 static gboolean output_func(GNode *node, gpointer data)
1759 {
1760         guint i, depth;
1761         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1762
1763         depth = g_node_depth(node);
1764         for (i = 0; i < depth; i++)
1765                 printf("    ");
1766         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1767
1768         return FALSE;
1769 }
1770
1771 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1772 {
1773         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1774 }
1775
1776 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1777 {
1778         MimeInfo *mimeinfo;
1779         struct stat buf;
1780
1781         stat(filename, &buf);
1782
1783         mimeinfo = procmime_mimeinfo_new();
1784         mimeinfo->content = MIMECONTENT_FILE;
1785         mimeinfo->encoding_type = ENC_UNKNOWN;
1786         mimeinfo->type = MIMETYPE_MESSAGE;
1787         mimeinfo->subtype = g_strdup("rfc822");
1788         mimeinfo->data.filename = g_strdup(filename);
1789         mimeinfo->offset = offset;
1790         mimeinfo->length = buf.st_size - offset;
1791
1792         procmime_parse_message_rfc822(mimeinfo);
1793         if (debug_get_mode())
1794                 output_mime_structure(mimeinfo, 0);
1795
1796         return mimeinfo;
1797 }
1798
1799 MimeInfo *procmime_scan_file(const gchar *filename)
1800 {
1801         MimeInfo *mimeinfo;
1802
1803         g_return_val_if_fail(filename != NULL, NULL);
1804
1805         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1806
1807         return mimeinfo;
1808 }
1809
1810 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1811 {
1812         FILE *fp;
1813         MimeInfo *mimeinfo;
1814         gchar buf[BUFFSIZE];
1815         gint offset = 0;
1816
1817         g_return_val_if_fail(filename != NULL, NULL);
1818
1819         /* Open file */
1820         if ((fp = g_fopen(filename, "rb")) == NULL)
1821                 return NULL;
1822         /* Skip queue header */
1823         while (fgets(buf, sizeof(buf), fp) != NULL)
1824                 if (buf[0] == '\r' || buf[0] == '\n') break;
1825         offset = ftell(fp);
1826         fclose(fp);
1827
1828         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1829
1830         return mimeinfo;
1831 }
1832
1833 typedef enum {
1834     ENC_AS_TOKEN,
1835     ENC_AS_QUOTED_STRING,
1836     ENC_AS_EXTENDED,
1837     ENC_TO_ASCII,
1838 } EncodeAs;
1839
1840 typedef struct _ParametersData {
1841         FILE *fp;
1842         guint len;
1843         guint ascii_only;
1844 } ParametersData;
1845
1846 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1847 {
1848         gchar *param = key;
1849         gchar *val = value, *valpos, *tmp;
1850         ParametersData *pdata = (ParametersData *)user_data;
1851         GString *buf = g_string_new("");
1852
1853         EncodeAs encas = ENC_AS_TOKEN;
1854
1855         for (valpos = val; *valpos != 0; valpos++) {
1856                 if (!IS_ASCII(*valpos) || *valpos == '"') {
1857                         encas = ENC_AS_EXTENDED;
1858                         break;
1859                 }
1860             
1861                 /* CTLs */
1862                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
1863                         encas = ENC_AS_QUOTED_STRING;
1864                         continue;
1865                 }
1866
1867                 /* tspecials + SPACE */
1868                 switch (*valpos) {
1869                 case ' ':
1870                 case '(': 
1871                 case ')':
1872                 case '<':
1873                 case '>':
1874                 case '@':
1875                 case ',':
1876                 case ';':
1877                 case ':':
1878                 case '\\':
1879                 case '\'':
1880                 case '/':
1881                 case '[':
1882                 case ']':
1883                 case '?':
1884                 case '=':
1885                         encas = ENC_AS_QUOTED_STRING;
1886                         continue;
1887                 }
1888         }
1889         
1890         if (encas == ENC_AS_EXTENDED && pdata->ascii_only == TRUE) 
1891                 encas = ENC_TO_ASCII;
1892
1893         switch (encas) {
1894         case ENC_AS_TOKEN:
1895                 g_string_append_printf(buf, "%s=%s", param, val);
1896                 break;
1897
1898         case ENC_TO_ASCII:
1899                 tmp = g_strdup(val);
1900                 g_strcanon(tmp, 
1901                         " ()<>@,';:\\/[]?=.0123456789"
1902                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1903                         "abcdefghijklmnopqrstuvwxyz",
1904                         '_');
1905                 g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
1906                 g_free(tmp);
1907                 break;
1908
1909         case ENC_AS_QUOTED_STRING:
1910                 g_string_append_printf(buf, "%s=\"%s\"", param, val);
1911                 break;
1912
1913         case ENC_AS_EXTENDED:
1914                 if (!g_utf8_validate(val, -1, NULL))
1915                         g_string_append_printf(buf, "%s*=%s''", param,
1916                                 conv_get_locale_charset_str());
1917                 else
1918                         g_string_append_printf(buf, "%s*=%s''", param,
1919                                 CS_INTERNAL);
1920                 for (valpos = val; *valpos != '\0'; valpos++) {
1921                         if (IS_ASCII(*valpos) && isalnum(*valpos)) {
1922                                 g_string_append_printf(buf, "%c", *valpos);
1923                         } else {
1924                                 gchar hexstr[3] = "XX";
1925                                 get_hex_str(hexstr, *valpos);
1926                                 g_string_append_printf(buf, "%%%s", hexstr);
1927                         }
1928                 }
1929                 break;          
1930         }
1931         
1932         if (buf->str && strlen(buf->str)) {
1933                 if (pdata->len + strlen(buf->str) + 2 > 76) {
1934                         fprintf(pdata->fp, ";\n %s", buf->str);
1935                         pdata->len = strlen(buf->str) + 1;
1936                 } else {
1937                         fprintf(pdata->fp, "; %s", buf->str);
1938                         pdata->len += strlen(buf->str) + 2;
1939                 }
1940         }
1941         g_string_free(buf, TRUE);
1942 }
1943
1944 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1945 {
1946         struct TypeTable *type_table;
1947         ParametersData *pdata = g_new0(ParametersData, 1);
1948         debug_print("procmime_write_mime_header\n");
1949         
1950         pdata->fp = fp;
1951         pdata->ascii_only = FALSE;
1952
1953         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1954                 if (mimeinfo->type == type_table->type) {
1955                         gchar *buf = g_strdup_printf(
1956                                 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1957                         fprintf(fp, "%s", buf);
1958                         pdata->len = strlen(buf);
1959                         pdata->ascii_only = TRUE;
1960                         g_free(buf);
1961                         break;
1962                 }
1963         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
1964         g_free(pdata);
1965
1966         fprintf(fp, "\n");
1967
1968         if (mimeinfo->encoding_type != ENC_UNKNOWN)
1969                 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1970
1971         if (mimeinfo->description != NULL)
1972                 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1973
1974         if (mimeinfo->id != NULL)
1975                 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1976
1977         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
1978                 ParametersData *pdata = g_new0(ParametersData, 1);
1979                 gchar *buf = NULL;
1980                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
1981                         buf = g_strdup("Content-Disposition: inline");
1982                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
1983                         buf = g_strdup("Content-Disposition: attachment");
1984                 else
1985                         buf = g_strdup("Content-Disposition: unknown");
1986
1987                 fprintf(fp, "%s", buf);
1988                 pdata->len = strlen(buf);
1989                 g_free(buf);
1990
1991                 pdata->fp = fp;
1992                 pdata->ascii_only = FALSE;
1993
1994                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
1995                 g_free(pdata);
1996                 fprintf(fp, "\n");
1997         }
1998
1999         fprintf(fp, "\n");
2000 }
2001
2002 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
2003 {
2004         FILE *infp;
2005         GNode *childnode;
2006         MimeInfo *child;
2007         gchar buf[BUFFSIZE];
2008         gboolean skip = FALSE;;
2009
2010         debug_print("procmime_write_message_rfc822\n");
2011
2012         /* write header */
2013         switch (mimeinfo->content) {
2014         case MIMECONTENT_FILE:
2015                 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2016                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2017                         return -1;
2018                 }
2019                 fseek(infp, mimeinfo->offset, SEEK_SET);
2020                 while (fgets(buf, sizeof(buf), infp) == buf) {
2021                         if (buf[0] == '\n' && buf[1] == '\0')
2022                                 break;
2023                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
2024                                 continue;
2025                         if (g_ascii_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
2026                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
2027                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
2028                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
2029                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
2030                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
2031                                 skip = TRUE;
2032                                 continue;
2033                         }
2034                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
2035                         skip = FALSE;
2036                 }
2037                 fclose(infp);
2038                 break;
2039
2040         case MIMECONTENT_MEM:
2041                 fwrite(mimeinfo->data.mem, 
2042                                 sizeof(gchar), 
2043                                 strlen(mimeinfo->data.mem), 
2044                                 fp);
2045                 break;
2046
2047         default:
2048                 break;
2049         }
2050
2051         childnode = mimeinfo->node->children;
2052         if (childnode == NULL)
2053                 return -1;
2054
2055         child = (MimeInfo *) childnode->data;
2056         fprintf(fp, "Mime-Version: 1.0\n");
2057         procmime_write_mime_header(child, fp);
2058         return procmime_write_mimeinfo(child, fp);
2059 }
2060
2061 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
2062 {
2063         FILE *infp;
2064         GNode *childnode;
2065         gchar *boundary, *str, *str2;
2066         gchar buf[BUFFSIZE];
2067         gboolean firstboundary;
2068
2069         debug_print("procmime_write_multipart\n");
2070
2071         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
2072
2073         switch (mimeinfo->content) {
2074         case MIMECONTENT_FILE:
2075                 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2076                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2077                         return -1;
2078                 }
2079                 fseek(infp, mimeinfo->offset, SEEK_SET);
2080                 while (fgets(buf, sizeof(buf), infp) == buf) {
2081                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
2082                                 break;
2083                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
2084                 }
2085                 fclose(infp);
2086                 break;
2087
2088         case MIMECONTENT_MEM:
2089                 str = g_strdup(mimeinfo->data.mem);
2090                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
2091                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
2092                         *(str2 - 2) = '\0';
2093                 fwrite(str, sizeof(gchar), strlen(str), fp);
2094                 g_free(str);
2095                 break;
2096
2097         default:
2098                 break;
2099         }
2100
2101         childnode = mimeinfo->node->children;
2102         firstboundary = TRUE;
2103         while (childnode != NULL) {
2104                 MimeInfo *child = childnode->data;
2105
2106                 if (firstboundary)
2107                         firstboundary = FALSE;
2108                 else
2109                         fprintf(fp, "\n");
2110                 fprintf(fp, "--%s\n", boundary);
2111
2112                 procmime_write_mime_header(child, fp);
2113                 if (procmime_write_mimeinfo(child, fp) < 0)
2114                         return -1;
2115
2116                 childnode = g_node_next_sibling(childnode);
2117         }       
2118         fprintf(fp, "\n--%s--\n", boundary);
2119
2120         return 0;
2121 }
2122
2123 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
2124 {
2125         FILE *infp;
2126
2127         debug_print("procmime_write_mimeinfo\n");
2128
2129         if (G_NODE_IS_LEAF(mimeinfo->node)) {
2130                 switch (mimeinfo->content) {
2131                 case MIMECONTENT_FILE:
2132                         if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2133                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2134                                 return -1;
2135                         }
2136                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
2137                         fclose(infp);
2138                         return 0;
2139
2140                 case MIMECONTENT_MEM:
2141                         fwrite(mimeinfo->data.mem, 
2142                                         sizeof(gchar), 
2143                                         strlen(mimeinfo->data.mem), 
2144                                         fp);
2145                         return 0;
2146
2147                 default:
2148                         return 0;
2149                 }
2150         } else {
2151                 /* Call writer for mime type */
2152                 switch (mimeinfo->type) {
2153                 case MIMETYPE_MESSAGE:
2154                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2155                                 return procmime_write_message_rfc822(mimeinfo, fp);
2156                         }
2157                         break;
2158                         
2159                 case MIMETYPE_MULTIPART:
2160                         return procmime_write_multipart(mimeinfo, fp);
2161                         
2162                 default:
2163                         break;
2164                 }
2165
2166                 return -1;
2167         }
2168
2169         return 0;
2170 }
2171
2172 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
2173 {
2174         gchar *base;
2175
2176         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
2177                 base = g_strdup("mimetmp.html");
2178         else {
2179                 const gchar *basetmp;
2180                 gchar *basename;
2181
2182                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
2183                 if (basetmp == NULL)
2184                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
2185                 if (basetmp == NULL)
2186                         basetmp = "mimetmp";
2187                 basename = g_path_get_basename(basetmp);
2188                 if (*basename == '\0') {
2189                         g_free(basename);
2190                         basename = g_strdup("mimetmp");
2191                 }
2192                 base = conv_filename_from_utf8(basename);
2193                 g_free(basename);
2194                 subst_for_shellsafe_filename(base);
2195         }
2196         
2197         return base;
2198 }
2199