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