fix quoting in reply to format=flowed message
[claws.git] / src / procmime.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 Hiroyuki Yamamoto & The Claws Mail 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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #define _GNU_SOURCE
25 #include <stdio.h>
26
27 #include "defs.h"
28
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <string.h>
32 #if HAVE_LOCALE_H
33 #  include <locale.h>
34 #endif
35 #include <ctype.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #include <errno.h>
40
41 #include "procmime.h"
42 #include "procheader.h"
43 #include "quoted-printable.h"
44 #include "uuencode.h"
45 #include "unmime.h"
46 #include "html.h"
47 #include "enriched.h"
48 #include "codeconv.h"
49 #include "utils.h"
50 #include "prefs_common.h"
51 #include "prefs_gtk.h"
52 #include "alertpanel.h"
53 #include "timing.h"
54 #include "privacy.h"
55
56 static GHashTable *procmime_get_mime_type_table (void);
57 static MimeInfo *procmime_scan_file_short(const gchar *filename);
58 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename);
59 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan);
60
61 MimeInfo *procmime_mimeinfo_new(void)
62 {
63         MimeInfo *mimeinfo;
64
65         mimeinfo = g_new0(MimeInfo, 1);
66         mimeinfo->content        = MIMECONTENT_EMPTY;
67         mimeinfo->data.filename  = NULL;
68
69         mimeinfo->type           = MIMETYPE_UNKNOWN;
70         mimeinfo->encoding_type  = ENC_UNKNOWN;
71         mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
72
73         mimeinfo->disposition    = DISPOSITIONTYPE_UNKNOWN;
74         mimeinfo->dispositionparameters 
75                                  = g_hash_table_new(g_str_hash, g_str_equal);
76
77         mimeinfo->node           = g_node_new(mimeinfo);
78         
79         return mimeinfo;
80 }
81
82 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
83 {
84         g_free(key);
85         g_free(value);
86         
87         return TRUE;
88 }
89
90 static gchar *forced_charset = NULL;
91
92 void procmime_force_charset(const gchar *str)
93 {
94         g_free(forced_charset);
95         forced_charset = NULL;
96         if (str)
97                 forced_charset = g_strdup(str);
98 }
99
100 static EncodingType forced_encoding = 0;
101
102 void procmime_force_encoding(EncodingType encoding)
103 {
104         forced_encoding = encoding;
105 }
106
107 static gboolean free_func(GNode *node, gpointer data)
108 {
109         MimeInfo *mimeinfo = (MimeInfo *) node->data;
110
111         switch (mimeinfo->content) {
112         case MIMECONTENT_FILE:
113                 if (mimeinfo->tmp)
114                         claws_unlink(mimeinfo->data.filename);
115                 g_free(mimeinfo->data.filename);
116                 break;
117
118         case MIMECONTENT_MEM:
119                 if (mimeinfo->tmp)
120                         g_free(mimeinfo->data.mem);
121         default:
122                 break;
123         }
124
125         g_free(mimeinfo->subtype);
126         g_free(mimeinfo->description);
127         g_free(mimeinfo->id);
128         g_free(mimeinfo->location);
129
130         g_hash_table_foreach_remove(mimeinfo->typeparameters,
131                 procmime_mimeinfo_parameters_destroy, NULL);
132         g_hash_table_destroy(mimeinfo->typeparameters);
133         g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
134                 procmime_mimeinfo_parameters_destroy, NULL);
135         g_hash_table_destroy(mimeinfo->dispositionparameters);
136
137         if (mimeinfo->privacy)
138                 privacy_free_privacydata(mimeinfo->privacy);
139
140         return FALSE;
141 }
142
143 void procmime_mimeinfo_free_all(MimeInfo **mimeinfo_ptr)
144 {
145         MimeInfo *mimeinfo = *mimeinfo_ptr;
146         GNode *node;
147
148         if (!mimeinfo)
149                 return;
150
151         node = mimeinfo->node;
152         g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
153
154         g_node_destroy(node);
155
156         g_free(mimeinfo);
157         *mimeinfo_ptr = NULL;
158 }
159
160 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
161 {
162         cm_return_val_if_fail(mimeinfo != NULL, NULL);
163         cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
164
165         if (mimeinfo->node->parent == NULL)
166                 return NULL;
167         return (MimeInfo *) mimeinfo->node->parent->data;
168 }
169
170 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
171 {
172         cm_return_val_if_fail(mimeinfo != NULL, NULL);
173         cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
174
175         if (mimeinfo->node->children)
176                 return (MimeInfo *) mimeinfo->node->children->data;
177         if (mimeinfo->node->next)
178                 return (MimeInfo *) mimeinfo->node->next->data;
179
180         if (mimeinfo->node->parent == NULL)
181                 return NULL;
182
183         while (mimeinfo->node->parent != NULL) {
184                 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
185                 if (mimeinfo->node->next)
186                         return (MimeInfo *) mimeinfo->node->next->data;
187         }
188
189         return NULL;
190 }
191
192 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
193 {
194         gchar *filename;
195         MimeInfo *mimeinfo;
196
197         filename = procmsg_get_message_file_path(msginfo);
198         if (!filename || !is_file_exist(filename)) {
199                 g_free(filename);
200                 return NULL;
201         }
202
203         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
204             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
205                 mimeinfo = procmime_scan_file(filename);
206         else
207                 mimeinfo = procmime_scan_queue_file(filename);
208         g_free(filename);
209
210         return mimeinfo;
211 }
212
213 MimeInfo *procmime_scan_message_short(MsgInfo *msginfo)
214 {
215         gchar *filename;
216         MimeInfo *mimeinfo;
217
218         filename = procmsg_get_message_file_path(msginfo);
219         if (!filename || !is_file_exist(filename)) {
220                 g_free(filename);
221                 return NULL;
222         }
223
224         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
225             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
226                 mimeinfo = procmime_scan_file_short(filename);
227         else
228                 mimeinfo = procmime_scan_queue_file_short(filename);
229         g_free(filename);
230
231         return mimeinfo;
232 }
233
234 enum
235 {
236         H_CONTENT_TRANSFER_ENCODING = 0,
237         H_CONTENT_TYPE              = 1,
238         H_CONTENT_DISPOSITION       = 2,
239         H_CONTENT_DESCRIPTION       = 3,
240         H_SUBJECT                   = 4
241 };
242
243 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
244 {
245         const gchar *value;
246
247         cm_return_val_if_fail(mimeinfo != NULL, NULL);
248         cm_return_val_if_fail(name != NULL, NULL);
249
250         value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
251         if (value == NULL)
252                 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
253         
254         return value;
255 }
256
257 #ifdef HAVE_FGETS_UNLOCKED
258 #define SC_FGETS fgets_unlocked
259 #define SC_FPUTS fputs_unlocked
260 #define SC_FPUTC fputc_unlocked
261 #define SC_FREAD fread_unlocked
262 #define SC_FWRITE fwrite_unlocked
263 #define SC_FEOF feof_unlocked
264 #define SC_FERROR ferror_unlocked
265
266 static FILE *procmime_fopen(const gchar *file, const gchar *mode)
267 {
268         FILE *fp = g_fopen(file, mode);
269         if (!fp)
270                 return NULL;
271         flockfile(fp);
272         return fp;
273 }
274 static int procmime_fclose(FILE *fp)
275 {
276         funlockfile(fp);
277         return fclose(fp);
278 }
279 #else
280 #define SC_FGETS fgets
281 #define SC_FPUTS fputs
282 #define SC_FPUTC fputc
283 #define SC_FREAD fread
284 #define SC_FWRITE fwrite
285 #define SC_FEOF feof
286 #define SC_FERROR ferror
287 #define procmime_fopen g_fopen
288 #define procmime_fclose fclose
289 #endif
290
291 #define FLUSH_LASTLINE() {                                                      \
292         if (*lastline != '\0') {                                                \
293                 gint llen = 0;                                                  \
294                 strretchomp(lastline);                                          \
295                 llen = strlen(lastline);                                        \
296                 if (lastline[llen-1] == ' ' && strcmp(lastline,"-- ") &&        \
297                     strcmp(lastline,"> ")) {                                    \
298                         /* this is flowed */                                    \
299                         if (delsp)                                              \
300                                 lastline[llen-1] = '\0';                        \
301                         if (SC_FPUTS(lastline, outfp) == EOF)                   \
302                                 err = TRUE;                                     \
303                 } else {                                                        \
304                         if (SC_FPUTS(lastline, outfp) == EOF)                   \
305                                 err = TRUE;                                     \
306                         if (SC_FPUTS("\n", outfp) == EOF)                               \
307                                 err = TRUE;                                     \
308                 }                                                               \
309         }                                                                       \
310         strcpy(lastline, buf);                                                  \
311 }
312
313 gboolean procmime_decode_content(MimeInfo *mimeinfo)
314 {
315         gchar buf[BUFFSIZE];
316         gint readend;
317         gchar *tmpfilename;
318         FILE *outfp, *infp;
319         GStatBuf statbuf;
320         gboolean tmp_file = FALSE;
321         gboolean flowed = FALSE;
322         gboolean delsp = FALSE; 
323         gboolean err = FALSE;
324         gint state = 0;
325         guint save = 0;
326
327         cm_return_val_if_fail(mimeinfo != NULL, FALSE);
328
329         EncodingType encoding = forced_encoding 
330                                 ? forced_encoding
331                                 : mimeinfo->encoding_type;
332         gchar lastline[BUFFSIZE];
333         memset(lastline, 0, BUFFSIZE);
334
335         if (prefs_common.respect_flowed_format &&
336             mimeinfo->type == MIMETYPE_TEXT && 
337             !strcasecmp(mimeinfo->subtype, "plain")) {
338                 if (procmime_mimeinfo_get_parameter(mimeinfo, "format") != NULL &&
339                     !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "format"),"flowed"))
340                         flowed = TRUE;
341                 if (flowed &&
342                     procmime_mimeinfo_get_parameter(mimeinfo, "delsp") != NULL &&
343                     !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "delsp"),"yes"))
344                         delsp = TRUE;
345         }
346         
347         if (!flowed && (
348              encoding == ENC_UNKNOWN ||
349              encoding == ENC_BINARY ||
350              encoding == ENC_7BIT ||
351              encoding == ENC_8BIT
352             ))
353                 return TRUE;
354
355         if (mimeinfo->type == MIMETYPE_MULTIPART || mimeinfo->type == MIMETYPE_MESSAGE)
356                 return TRUE;
357
358         if (mimeinfo->data.filename == NULL)
359                 return FALSE;
360
361         infp = procmime_fopen(mimeinfo->data.filename, "rb");
362         if (!infp) {
363                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
364                 return FALSE;
365         }
366         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
367                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
368                 procmime_fclose(infp);
369                 return FALSE;
370         }
371
372         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
373         if (!outfp) {
374                 perror("tmpfile");
375                 procmime_fclose(infp);
376                 return FALSE;
377         }
378 #ifdef HAVE_FGETS_UNLOCKED
379         flockfile(outfp);
380 #endif
381         tmp_file = TRUE;
382         readend = mimeinfo->offset + mimeinfo->length;
383
384         *buf = '\0';
385         if (encoding == ENC_QUOTED_PRINTABLE) {
386                 while ((ftell(infp) < readend) && (SC_FGETS(buf, sizeof(buf), infp) != NULL)) {
387                         gint len;
388                         len = qp_decode_line(buf);
389                         buf[len] = '\0';
390                         if (!flowed) {
391                                 if (SC_FWRITE(buf, 1, len, outfp) < len)
392                                         err = TRUE;
393                         } else {
394                                 FLUSH_LASTLINE();
395                         }
396                 }
397                 if (flowed)
398                         FLUSH_LASTLINE();
399         } else if (encoding == ENC_BASE64) {
400                 gchar outbuf[BUFFSIZE];
401                 gint len, inlen, inread;
402                 gboolean got_error = FALSE;
403                 gboolean uncanonicalize = FALSE;
404                 FILE *tmpfp = NULL;
405                 gboolean null_bytes = FALSE;
406                 gboolean starting = TRUE;
407
408                 if (mimeinfo->type == MIMETYPE_TEXT ||
409                     mimeinfo->type == MIMETYPE_MESSAGE) {
410                         uncanonicalize = TRUE;
411                         tmpfp = my_tmpfile();
412                         if (!tmpfp) {
413                                 perror("my_tmpfile");
414                                 if (tmp_file) 
415                                         procmime_fclose(outfp);
416                                 procmime_fclose(infp);
417                                 return FALSE;
418                         }
419 #ifdef HAVE_FGETS_UNLOCKED
420                         flockfile(tmpfp);
421 #endif
422                 } else
423                         tmpfp = outfp;
424
425                 while ((inlen = MIN(readend - ftell(infp), sizeof(buf))) > 0 && !err) {
426                         inread = SC_FREAD(buf, 1, inlen, infp);
427                         len = g_base64_decode_step(buf, inlen, outbuf, &state, &save);
428                         if (uncanonicalize == TRUE && strlen(outbuf) < len && starting) {
429                                 uncanonicalize = FALSE;
430                                 null_bytes = TRUE;
431                         }
432                         starting = FALSE;
433                         if (((inread != inlen) || len < 0) && !got_error) {
434                                 g_warning("Bad BASE64 content.");
435                                 if (SC_FWRITE(_("[Error decoding BASE64]\n"),
436                                         sizeof(gchar),
437                                         strlen(_("[Error decoding BASE64]\n")),
438                                         tmpfp) < strlen(_("[Error decoding BASE64]\n")))
439                                         g_warning("error decoding BASE64");
440                                 got_error = TRUE;
441                                 continue;
442                         } else if (len >= 0) {
443                                 /* print out the error message only once 
444                                  * per block */
445                                 if (null_bytes) {
446                                         /* we won't uncanonicalize, output to outfp directly */
447                                         if (SC_FWRITE(outbuf, sizeof(gchar), len, outfp) < len)
448                                                 err = TRUE;
449                                 } else {
450                                         if (SC_FWRITE(outbuf, sizeof(gchar), len, tmpfp) < len)
451                                                 err = TRUE;
452                                 }
453                                 got_error = FALSE;
454                         }
455                 }
456
457                 if (uncanonicalize) {
458                         rewind(tmpfp);
459                         while (SC_FGETS(buf, sizeof(buf), tmpfp) != NULL) {
460                                 strcrchomp(buf);
461                                 if (SC_FPUTS(buf, outfp) == EOF)
462                                         err = TRUE;
463                         }
464                 }
465                 if (tmpfp != outfp)
466                         procmime_fclose(tmpfp);
467         } else if (encoding == ENC_X_UUENCODE) {
468                 gchar outbuf[BUFFSIZE];
469                 gint len;
470                 gboolean flag = FALSE;
471
472                 while ((ftell(infp) < readend) && (SC_FGETS(buf, sizeof(buf), infp) != NULL)) {
473                         if (!flag && strncmp(buf,"begin ", 6)) continue;
474
475                         if (flag) {
476                                 len = fromuutobits(outbuf, buf);
477                                 if (len <= 0) {
478                                         if (len < 0) 
479                                                 g_warning("Bad UUENCODE content (%d)", len);
480                                         break;
481                                 }
482                                 if (SC_FWRITE(outbuf, sizeof(gchar), len, outfp) < len)
483                                         err = TRUE;
484                         } else
485                                 flag = TRUE;
486                 }
487         } else {
488                 while ((ftell(infp) < readend) && (SC_FGETS(buf, sizeof(buf), infp) != NULL)) {
489                         if (!flowed) {
490                                 if (SC_FPUTS(buf, outfp) == EOF)
491                                         err = TRUE;
492                         } else {
493                                 FLUSH_LASTLINE();
494                         }
495                 }
496                 if (flowed)
497                         FLUSH_LASTLINE();
498                 if (err == TRUE)
499                         g_warning("write error");
500         }
501
502         procmime_fclose(outfp);
503         procmime_fclose(infp);
504
505         if (err == TRUE) {
506                 return FALSE;
507         }
508
509         if (g_stat(tmpfilename, &statbuf) < 0) {
510                 FILE_OP_ERROR(tmpfilename, "stat");
511                 return FALSE;
512         }
513
514         if (mimeinfo->tmp)
515                 claws_unlink(mimeinfo->data.filename);
516         g_free(mimeinfo->data.filename);
517         mimeinfo->data.filename = tmpfilename;
518         mimeinfo->tmp = TRUE;
519         mimeinfo->offset = 0;
520         mimeinfo->length = statbuf.st_size;
521         mimeinfo->encoding_type = ENC_BINARY;
522
523         return TRUE;
524 }
525
526 #define B64_LINE_SIZE           57
527 #define B64_BUFFSIZE            77
528
529 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
530 {
531         FILE *infp = NULL, *outfp;
532         gint len;
533         gchar *tmpfilename;
534         GStatBuf statbuf;
535         gboolean err = FALSE;
536
537         if (mimeinfo->content == MIMECONTENT_EMPTY)
538                 return TRUE;
539
540         if (mimeinfo->encoding_type != ENC_UNKNOWN &&
541             mimeinfo->encoding_type != ENC_BINARY &&
542             mimeinfo->encoding_type != ENC_7BIT &&
543             mimeinfo->encoding_type != ENC_8BIT)
544                 if(!procmime_decode_content(mimeinfo))
545                         return FALSE;
546
547         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
548         if (!outfp) {
549                 perror("tmpfile");
550                 return FALSE;
551         }
552 #ifdef HAVE_FGETS_UNLOCKED
553         flockfile(outfp);
554 #endif
555
556         if (mimeinfo->content == MIMECONTENT_FILE && mimeinfo->data.filename) {
557                 if ((infp = procmime_fopen(mimeinfo->data.filename, "rb")) == NULL) {
558                         g_warning("Can't open file %s", mimeinfo->data.filename);
559                         procmime_fclose(outfp);
560                         return FALSE;
561                 }
562         } else if (mimeinfo->content == MIMECONTENT_MEM) {
563                 infp = str_open_as_stream(mimeinfo->data.mem);
564                 if (infp == NULL) {
565                         procmime_fclose(outfp);
566                         return FALSE;
567                 }
568 #ifdef HAVE_FGETS_UNLOCKED
569                 flockfile(infp);
570 #endif
571         } else {
572                 procmime_fclose(outfp);
573                 g_warning("Unknown mimeinfo");
574                 return FALSE;
575         }
576
577         if (encoding == ENC_BASE64) {
578                 gchar inbuf[B64_LINE_SIZE], *out;
579                 FILE *tmp_fp = infp;
580                 gchar *tmp_file = NULL;
581
582                 if (mimeinfo->type == MIMETYPE_TEXT ||
583                      mimeinfo->type == MIMETYPE_MESSAGE) {
584                         if (mimeinfo->content == MIMECONTENT_FILE) {
585                                 tmp_file = get_tmp_file();
586                                 if (canonicalize_file(mimeinfo->data.filename, tmp_file) < 0) {
587                                         g_free(tmp_file);
588                                         procmime_fclose(infp);
589                                         procmime_fclose(outfp);
590                                         return FALSE;
591                                 }
592                                 if ((tmp_fp = procmime_fopen(tmp_file, "rb")) == NULL) {
593                                         FILE_OP_ERROR(tmp_file, "fopen");
594                                         claws_unlink(tmp_file);
595                                         g_free(tmp_file);
596                                         procmime_fclose(infp);
597                                         procmime_fclose(outfp);
598                                         return FALSE;
599                                 }
600                         } else {
601                                 gchar *out = canonicalize_str(mimeinfo->data.mem);
602                                 procmime_fclose(infp);
603                                 infp = str_open_as_stream(out);
604                                 tmp_fp = infp;
605                                 g_free(out);
606                                 if (infp == NULL) {
607                                         procmime_fclose(outfp);
608                                         return FALSE;
609                                 }
610 #ifdef HAVE_FGETS_UNLOCKED
611                                 flockfile(infp);
612 #endif
613                         }
614                 }
615
616                 while ((len = SC_FREAD(inbuf, sizeof(gchar),
617                                     B64_LINE_SIZE, tmp_fp))
618                        == B64_LINE_SIZE) {
619                         out = g_base64_encode(inbuf, B64_LINE_SIZE);
620                         if (SC_FPUTS(out, outfp) == EOF)
621                                 err = TRUE;
622                         g_free(out);
623                         if (SC_FPUTC('\n', outfp) == EOF)
624                                 err = TRUE;
625                 }
626                 if (len > 0 && SC_FEOF(tmp_fp)) {
627                         out = g_base64_encode(inbuf, len);
628                         if (SC_FPUTS(out, outfp) == EOF)
629                                 err = TRUE;
630                         g_free(out);
631                         if (SC_FPUTC('\n', outfp) == EOF)
632                                 err = TRUE;
633                 }
634
635                 if (tmp_file) {
636                         procmime_fclose(tmp_fp);
637                         claws_unlink(tmp_file);
638                         g_free(tmp_file);
639                 }
640         } else if (encoding == ENC_QUOTED_PRINTABLE) {
641                 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
642
643                 while (SC_FGETS(inbuf, sizeof(inbuf), infp) != NULL) {
644                         qp_encode_line(outbuf, inbuf);
645
646                         if (!strncmp("From ", outbuf, sizeof("From ")-1)) {
647                                 gchar *tmpbuf = outbuf;
648                                 
649                                 tmpbuf += sizeof("From ")-1;
650                                 
651                                 if (SC_FPUTS("=46rom ", outfp) == EOF)
652                                         err = TRUE;
653                                 if (SC_FPUTS(tmpbuf, outfp) == EOF)
654                                         err = TRUE;
655                         } else {
656                                 if (SC_FPUTS(outbuf, outfp) == EOF)
657                                         err = TRUE;
658                         }
659                 }
660         } else {
661                 gchar buf[BUFFSIZE];
662
663                 while (SC_FGETS(buf, sizeof(buf), infp) != NULL) {
664                         strcrchomp(buf);
665                         if (SC_FPUTS(buf, outfp) == EOF)
666                                 err = TRUE;
667                 }
668         }
669
670         procmime_fclose(outfp);
671         procmime_fclose(infp);
672
673         if (err == TRUE)
674                 return FALSE;
675
676         if (mimeinfo->content == MIMECONTENT_FILE) {
677                 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
678                         claws_unlink(mimeinfo->data.filename);
679                 g_free(mimeinfo->data.filename);
680         } else if (mimeinfo->content == MIMECONTENT_MEM) {
681                 if (mimeinfo->tmp && (mimeinfo->data.mem != NULL))
682                         g_free(mimeinfo->data.mem);
683         }
684
685         if (g_stat(tmpfilename, &statbuf) < 0) {
686                 FILE_OP_ERROR(tmpfilename, "stat");
687                 return FALSE;
688         }
689         mimeinfo->content = MIMECONTENT_FILE;
690         mimeinfo->data.filename = tmpfilename;
691         mimeinfo->tmp = TRUE;
692         mimeinfo->offset = 0;
693         mimeinfo->length = statbuf.st_size;
694         mimeinfo->encoding_type = encoding;
695
696         return TRUE;
697 }
698
699 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
700 {
701         FILE *infp, *outfp;
702         gchar buf[BUFFSIZE];
703         gint restlength, readlength;
704         gint saved_errno = 0;
705
706         cm_return_val_if_fail(outfile != NULL, -1);
707         cm_return_val_if_fail(mimeinfo != NULL, -1);
708
709         if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
710                 return -EINVAL;
711
712         if ((infp = procmime_fopen(mimeinfo->data.filename, "rb")) == NULL) {
713                 saved_errno = errno;
714                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
715                 return -(saved_errno);
716         }
717         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
718                 saved_errno = errno;
719                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
720                 procmime_fclose(infp);
721                 return -(saved_errno);
722         }
723         if ((outfp = procmime_fopen(outfile, "wb")) == NULL) {
724                 saved_errno = errno;
725                 FILE_OP_ERROR(outfile, "fopen");
726                 procmime_fclose(infp);
727                 return -(saved_errno);
728         }
729
730         restlength = mimeinfo->length;
731
732         while ((restlength > 0) && ((readlength = SC_FREAD(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
733                 if (SC_FWRITE(buf, 1, readlength, outfp) != readlength) {
734                         saved_errno = errno;
735                         procmime_fclose(infp);
736                         procmime_fclose(outfp);
737                         return -(saved_errno);
738                 }
739                 restlength -= readlength;
740         }
741
742         procmime_fclose(infp);
743         if (procmime_fclose(outfp) == EOF) {
744                 saved_errno = errno;
745                 FILE_OP_ERROR(outfile, "fclose");
746                 claws_unlink(outfile);
747                 return -(saved_errno);
748         }
749
750         return 0;
751 }
752
753 gboolean procmime_scan_text_content(MimeInfo *mimeinfo,
754                 gboolean (*scan_callback)(const gchar *str, gpointer cb_data),
755                 gpointer cb_data) 
756 {
757         FILE *tmpfp;
758         const gchar *src_codeset;
759         gboolean conv_fail = FALSE;
760         gchar buf[BUFFSIZE];
761         gchar *str;
762         gchar *tmpfile;
763         gboolean scan_ret = FALSE;
764
765         cm_return_val_if_fail(mimeinfo != NULL, TRUE);
766         cm_return_val_if_fail(scan_callback != NULL, TRUE);
767
768         if (!procmime_decode_content(mimeinfo))
769                 return TRUE;
770
771         tmpfile = procmime_get_tmp_file_name(mimeinfo);
772         if (tmpfile == NULL)
773                 return TRUE;
774
775         if (procmime_get_part(tmpfile, mimeinfo) < 0) {
776                 g_free(tmpfile);
777                 return TRUE;
778         }
779
780         tmpfp = procmime_fopen(tmpfile, "rb");
781         if (tmpfp == NULL) {
782                 g_free(tmpfile);
783                 return TRUE;
784         }
785
786         src_codeset = forced_charset
787                       ? forced_charset : 
788                       procmime_mimeinfo_get_parameter(mimeinfo, "charset");
789
790         /* use supersets transparently when possible */
791         if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_ISO_8859_1))
792                 src_codeset = CS_WINDOWS_1252;
793         else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_GBK))
794                 src_codeset = CS_GB18030;
795         else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GBK))
796                 src_codeset = CS_GB18030;
797         else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GB2312))
798                 src_codeset = CS_GB18030;
799         else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_VIET_VPS))
800                 src_codeset = CS_WINDOWS_874;
801
802         if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
803                 SC_HTMLParser *parser;
804                 CodeConverter *conv;
805
806                 conv = conv_code_converter_new(src_codeset);
807                 parser = sc_html_parser_new(tmpfp, conv);
808                 while ((str = sc_html_parse(parser)) != NULL) {
809                         if ((scan_ret = scan_callback(str, cb_data)) == TRUE)
810                                 break;
811                 }
812                 sc_html_parser_destroy(parser);
813                 conv_code_converter_destroy(conv);
814         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
815                 ERTFParser *parser;
816                 CodeConverter *conv;
817
818                 conv = conv_code_converter_new(src_codeset);
819                 parser = ertf_parser_new(tmpfp, conv);
820                 while ((str = ertf_parse(parser)) != NULL) {
821                         if ((scan_ret = scan_callback(str, cb_data)) == TRUE)
822                                 break;
823                 }
824                 ertf_parser_destroy(parser);
825                 conv_code_converter_destroy(conv);
826         } else if (mimeinfo->type == MIMETYPE_TEXT && mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
827                 while (SC_FGETS(buf, sizeof(buf), tmpfp) != NULL) {
828                         str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
829                         if (str) {
830                                 if ((scan_ret = scan_callback(str, cb_data)) == TRUE) {
831                                         g_free(str);
832                                         break;
833                                 }
834                                 g_free(str);
835                         } else {
836                                 conv_fail = TRUE;
837                                 if ((scan_ret = scan_callback(buf, cb_data)) == TRUE)
838                                         break;
839                         }
840                 }
841         }
842
843         if (conv_fail)
844                 g_warning("procmime_get_text_content(): Code conversion failed.");
845
846         procmime_fclose(tmpfp);
847         claws_unlink(tmpfile);
848         g_free(tmpfile);
849
850         return scan_ret;
851 }
852
853 static gboolean scan_fputs_cb(const gchar *str, gpointer fp)
854 {
855         if (SC_FPUTS(str, (FILE *)fp) == EOF)
856                 return TRUE;
857         
858         return FALSE;
859 }
860
861 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
862 {
863         FILE *outfp;
864         gboolean err;
865
866         if ((outfp = my_tmpfile()) == NULL) {
867                 perror("my_tmpfile");
868                 return NULL;
869         }
870 #ifdef HAVE_FGETS_UNLOCKED
871         flockfile(outfp);
872 #endif
873
874         err = procmime_scan_text_content(mimeinfo, scan_fputs_cb, outfp);
875
876         rewind(outfp);
877         if (err == TRUE) {
878                 procmime_fclose(outfp);
879                 return NULL;
880         }
881 #ifdef HAVE_FGETS_UNLOCKED
882         funlockfile(outfp);
883 #endif
884         return outfp;
885
886 }
887
888 FILE *procmime_get_binary_content(MimeInfo *mimeinfo)
889 {
890         FILE *outfp;
891         gchar *tmpfile;
892
893         cm_return_val_if_fail(mimeinfo != NULL, NULL);
894
895         if (!procmime_decode_content(mimeinfo))
896                 return NULL;
897
898         tmpfile = procmime_get_tmp_file_name(mimeinfo);
899         if (tmpfile == NULL)
900                 return NULL;
901
902         if (procmime_get_part(tmpfile, mimeinfo) < 0) {
903                 g_free(tmpfile);
904                 return NULL;
905         }
906
907         outfp = procmime_fopen(tmpfile, "rb");
908         if (outfp == NULL) {
909                 g_unlink(tmpfile);
910                 g_free(tmpfile);
911                 return NULL;
912         }
913
914         g_unlink(tmpfile);
915         g_free(tmpfile);
916
917 #ifdef HAVE_FGETS_UNLOCKED
918         funlockfile(outfp);
919 #endif
920         return outfp;
921 }
922
923 /* search the first text part of (multipart) MIME message,
924    decode, convert it and output to outfp. */
925 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
926 {
927         FILE *outfp = NULL;
928         MimeInfo *mimeinfo, *partinfo;
929         gboolean empty_ok = FALSE, short_scan = TRUE;
930
931         cm_return_val_if_fail(msginfo != NULL, NULL);
932
933         /* first we try to short-scan (for speed), refusing empty parts */
934 scan_again:
935         if (short_scan)
936                 mimeinfo = procmime_scan_message_short(msginfo);
937         else
938                 mimeinfo = procmime_scan_message(msginfo);
939         if (!mimeinfo) return NULL;
940
941         partinfo = mimeinfo;
942         while (partinfo && (partinfo->type != MIMETYPE_TEXT ||
943                (partinfo->length == 0 && !empty_ok))) {
944                 partinfo = procmime_mimeinfo_next(partinfo);
945         }
946         if (partinfo)
947                 outfp = procmime_get_text_content(partinfo);
948         else if (!empty_ok && short_scan) {
949                 /* if short scan didn't find a non-empty part, rescan
950                  * fully for non-empty parts
951                  */
952                 short_scan = FALSE;
953                 procmime_mimeinfo_free_all(&mimeinfo);
954                 goto scan_again;
955         } else if (!empty_ok && !short_scan) {
956                 /* if full scan didn't find a non-empty part, rescan
957                  * accepting empty parts 
958                  */
959                 empty_ok = TRUE;
960                 procmime_mimeinfo_free_all(&mimeinfo);
961                 goto scan_again;
962         }
963         procmime_mimeinfo_free_all(&mimeinfo);
964
965         /* outfp already unlocked at this time */
966         return outfp;
967 }
968
969
970 static gboolean find_encrypted_func(GNode *node, gpointer data)
971 {
972         MimeInfo *mimeinfo = (MimeInfo *) node->data;
973         MimeInfo **encinfo = (MimeInfo **) data;
974         
975         if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
976                 *encinfo = mimeinfo;
977                 return TRUE;
978         }
979         
980         return FALSE;
981 }
982
983 static MimeInfo *find_encrypted_part(MimeInfo *rootinfo)
984 {
985         MimeInfo *encinfo = NULL;
986
987         g_node_traverse(rootinfo->node, G_IN_ORDER, G_TRAVERSE_ALL, -1,
988                 find_encrypted_func, &encinfo);
989         
990         return encinfo;
991 }
992
993 /* search the first encrypted text part of (multipart) MIME message,
994    decode, convert it and output to outfp. */
995 FILE *procmime_get_first_encrypted_text_content(MsgInfo *msginfo)
996 {
997         FILE *outfp = NULL;
998         MimeInfo *mimeinfo, *partinfo, *encinfo;
999
1000         cm_return_val_if_fail(msginfo != NULL, NULL);
1001
1002         mimeinfo = procmime_scan_message(msginfo);
1003         if (!mimeinfo) {
1004                 return NULL;
1005         }
1006
1007         partinfo = mimeinfo;
1008         if ((encinfo = find_encrypted_part(partinfo)) != NULL) {
1009                 debug_print("decrypting message part\n");
1010                 if (privacy_mimeinfo_decrypt(encinfo) < 0) {
1011                         alertpanel_error(_("Couldn't decrypt: %s"),
1012                                 privacy_get_error());
1013                         return NULL;
1014                 }
1015         }
1016         partinfo = mimeinfo;
1017         while (partinfo && partinfo->type != MIMETYPE_TEXT) {
1018                 partinfo = procmime_mimeinfo_next(partinfo);
1019                 if (privacy_mimeinfo_is_signed(partinfo))
1020                         procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
1021         }
1022
1023         if (partinfo)
1024                 outfp = procmime_get_text_content(partinfo);
1025
1026         procmime_mimeinfo_free_all(&mimeinfo);
1027
1028         /* outfp already unlocked at this time */
1029         return outfp;
1030 }
1031
1032 gboolean procmime_msginfo_is_encrypted(MsgInfo *msginfo)
1033 {
1034         MimeInfo *mimeinfo, *partinfo;
1035         gboolean result = FALSE;
1036
1037         cm_return_val_if_fail(msginfo != NULL, FALSE);
1038
1039         mimeinfo = procmime_scan_message(msginfo);
1040         if (!mimeinfo) {
1041                 return FALSE;
1042         }
1043
1044         partinfo = mimeinfo;
1045         result = (find_encrypted_part(partinfo) != NULL);
1046         procmime_mimeinfo_free_all(&mimeinfo);
1047
1048         return result;
1049 }
1050
1051 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
1052 {
1053         static guint32 id = 0;
1054         gchar *base;
1055         gchar *filename;
1056         gchar f_prefix[10];
1057
1058         cm_return_val_if_fail(mimeinfo != NULL, NULL);
1059
1060         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
1061
1062         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
1063                 base = g_strdup("mimetmp.html");
1064         else {
1065                 const gchar *basetmp;
1066
1067                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
1068                 if (basetmp == NULL)
1069                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
1070                 if (basetmp == NULL)
1071                         basetmp = "mimetmp";
1072                 basetmp = g_path_get_basename(basetmp);
1073                 if (*basetmp == '\0') 
1074                         basetmp = g_strdup("mimetmp");
1075                 base = conv_filename_from_utf8(basetmp);
1076                 g_free((gchar*)basetmp);
1077                 subst_for_shellsafe_filename(base);
1078         }
1079
1080         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1081                                f_prefix, base, NULL);
1082
1083         g_free(base);
1084         
1085         return filename;
1086 }
1087
1088 static GList *mime_type_list = NULL;
1089
1090 gchar *procmime_get_mime_type(const gchar *filename)
1091 {
1092         const gchar *p;
1093         gchar *ext = NULL;
1094         gchar *base;
1095 #ifndef G_OS_WIN32
1096         static GHashTable *mime_type_table = NULL;
1097         MimeType *mime_type;
1098
1099         if (!mime_type_table) {
1100                 mime_type_table = procmime_get_mime_type_table();
1101                 if (!mime_type_table) return NULL;
1102         }
1103 #endif
1104
1105         if (filename == NULL)
1106                 return NULL;
1107
1108         base = g_path_get_basename(filename);
1109         if ((p = strrchr(base, '.')) != NULL)
1110                 ext = g_utf8_strdown(p + 1, -1);
1111         else
1112                 ext = g_utf8_strdown(base, -1);
1113         g_free(base);
1114
1115 #ifndef G_OS_WIN32
1116         mime_type = g_hash_table_lookup(mime_type_table, ext);
1117         
1118         if (mime_type) {
1119                 gchar *str;
1120                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
1121                                   NULL);
1122                 debug_print("got type %s for %s\n", str, ext);
1123                 g_free(ext);
1124                 return str;
1125         }
1126         g_free(ext);
1127         return NULL;
1128 #else
1129         gchar *str = get_content_type_from_registry_with_ext(ext);
1130
1131         g_free(ext);
1132         return str;
1133 #endif
1134 }
1135
1136 static guint procmime_str_hash(gconstpointer gptr)
1137 {
1138         guint hash_result = 0;
1139         const char *str;
1140
1141         for (str = gptr; str && *str; str++) {
1142                 if (isupper(*str)) hash_result += (*str + ' ');
1143                 else hash_result += *str;
1144         }
1145
1146         return hash_result;
1147 }
1148
1149 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
1150 {
1151         const char *str1 = gptr1;
1152         const char *str2 = gptr2;
1153
1154         return !g_utf8_collate(str1, str2);
1155 }
1156
1157 static GHashTable *procmime_get_mime_type_table(void)
1158 {
1159         GHashTable *table = NULL;
1160         GList *cur;
1161         MimeType *mime_type;
1162         gchar **exts;
1163
1164         if (!mime_type_list) {
1165                 mime_type_list = procmime_get_mime_type_list();
1166                 if (!mime_type_list) return NULL;
1167         }
1168
1169         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
1170
1171         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1172                 gint i;
1173                 gchar *key;
1174
1175                 mime_type = (MimeType *)cur->data;
1176
1177                 if (!mime_type->extension) continue;
1178
1179                 exts = g_strsplit(mime_type->extension, " ", 16);
1180                 for (i = 0; exts[i] != NULL; i++) {
1181                         /* Don't overwrite previously inserted extension */
1182                         if (!g_hash_table_lookup(table, exts[i])) {
1183                                 key = g_strdup(exts[i]);
1184                                 g_hash_table_insert(table, key, mime_type);
1185                         }
1186                 }
1187                 g_strfreev(exts);
1188         }
1189
1190         return table;
1191 }
1192
1193 GList *procmime_get_mime_type_list(void)
1194 {
1195         GList *list = NULL;
1196         FILE *fp;
1197         gchar buf[BUFFSIZE];
1198         gchar *p;
1199         gchar *delim;
1200         MimeType *mime_type;
1201         gboolean fp_is_glob_file = TRUE;
1202
1203         if (mime_type_list) 
1204                 return mime_type_list;
1205         
1206 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
1207         if ((fp = procmime_fopen(DATAROOTDIR "/mime/globs", "rb")) == NULL) 
1208 #else
1209         if ((fp = procmime_fopen("/usr/share/mime/globs", "rb")) == NULL) 
1210 #endif
1211         {
1212                 fp_is_glob_file = FALSE;
1213                 if ((fp = procmime_fopen("/etc/mime.types", "rb")) == NULL) {
1214                         if ((fp = procmime_fopen(SYSCONFDIR "/mime.types", "rb")) 
1215                                 == NULL) {
1216                                 FILE_OP_ERROR(SYSCONFDIR "/mime.types", 
1217                                         "fopen");
1218                                 return NULL;
1219                         }
1220                 }
1221         }
1222
1223         while (SC_FGETS(buf, sizeof(buf), fp) != NULL) {
1224                 p = strchr(buf, '#');
1225                 if (p) *p = '\0';
1226                 g_strstrip(buf);
1227
1228                 p = buf;
1229                 
1230                 if (fp_is_glob_file) {
1231                         while (*p && !g_ascii_isspace(*p) && (*p!=':')) p++;
1232                 } else {
1233                         while (*p && !g_ascii_isspace(*p)) p++;
1234                 }
1235
1236                 if (*p) {
1237                         *p = '\0';
1238                         p++;
1239                 }
1240                 delim = strchr(buf, '/');
1241                 if (delim == NULL) continue;
1242                 *delim = '\0';
1243
1244                 mime_type = g_new(MimeType, 1);
1245                 mime_type->type = g_strdup(buf);
1246                 mime_type->sub_type = g_strdup(delim + 1);
1247
1248                 if (fp_is_glob_file) {
1249                         while (*p && (g_ascii_isspace(*p)||(*p=='*')||(*p=='.'))) p++;
1250                 } else {
1251                         while (*p && g_ascii_isspace(*p)) p++;
1252                 }
1253
1254                 if (*p)
1255                         mime_type->extension = g_utf8_strdown(p, -1);
1256                 else
1257                         mime_type->extension = NULL;
1258
1259                 list = g_list_append(list, mime_type);
1260         }
1261
1262         procmime_fclose(fp);
1263
1264         if (!list)
1265                 g_warning("Can't read mime.types");
1266
1267         return list;
1268 }
1269
1270 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1271 {
1272         if (!charset)
1273                 return ENC_8BIT;
1274         else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
1275                  !g_ascii_strcasecmp(charset, "US-ASCII"))
1276                 return ENC_7BIT;
1277         else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
1278                  !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
1279                  !g_ascii_strcasecmp(charset, "X-MAC-CYRILLIC") ||
1280                  !g_ascii_strcasecmp(charset, "MAC-CYRILLIC") ||
1281                  !g_ascii_strcasecmp(charset, "Windows-1251"))
1282                 return ENC_8BIT;
1283         else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
1284                 return ENC_QUOTED_PRINTABLE;
1285         else if (!g_ascii_strncasecmp(charset, "UTF-8", 5))
1286                 return ENC_QUOTED_PRINTABLE;
1287         else 
1288                 return ENC_8BIT;
1289 }
1290
1291 EncodingType procmime_get_encoding_for_text_file(const gchar *file, gboolean *has_binary)
1292 {
1293         FILE *fp;
1294         guchar buf[BUFFSIZE];
1295         size_t len;
1296         size_t octet_chars = 0;
1297         size_t total_len = 0;
1298         gfloat octet_percentage;
1299         gboolean force_b64 = FALSE;
1300
1301         if ((fp = procmime_fopen(file, "rb")) == NULL) {
1302                 FILE_OP_ERROR(file, "fopen");
1303                 return ENC_UNKNOWN;
1304         }
1305
1306         while ((len = SC_FREAD(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
1307                 guchar *p;
1308                 gint i;
1309
1310                 for (p = buf, i = 0; i < len; ++p, ++i) {
1311                         if (*p & 0x80)
1312                                 ++octet_chars;
1313                         if (*p == '\0') {
1314                                 force_b64 = TRUE;
1315                                 *has_binary = TRUE;
1316                         }
1317                 }
1318                 total_len += len;
1319         }
1320
1321         procmime_fclose(fp);
1322         
1323         if (total_len > 0)
1324                 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1325         else
1326                 octet_percentage = 0.0;
1327
1328         debug_print("procmime_get_encoding_for_text_file(): "
1329                     "8bit chars: %zd / %zd (%f%%)\n", octet_chars, total_len,
1330                     100.0 * octet_percentage);
1331
1332         if (octet_percentage > 0.20 || force_b64) {
1333                 debug_print("using BASE64\n");
1334                 return ENC_BASE64;
1335         } else if (octet_chars > 0) {
1336                 debug_print("using quoted-printable\n");
1337                 return ENC_QUOTED_PRINTABLE;
1338         } else {
1339                 debug_print("using 7bit\n");
1340                 return ENC_7BIT;
1341         }
1342 }
1343
1344 struct EncodingTable 
1345 {
1346         gchar *str;
1347         EncodingType enc_type;
1348 };
1349
1350 struct EncodingTable encoding_table[] = {
1351         {"7bit", ENC_7BIT},
1352         {"8bit", ENC_8BIT},
1353         {"binary", ENC_BINARY},
1354         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1355         {"base64", ENC_BASE64},
1356         {"x-uuencode", ENC_UNKNOWN},
1357         {NULL, ENC_UNKNOWN},
1358 };
1359
1360 const gchar *procmime_get_encoding_str(EncodingType encoding)
1361 {
1362         struct EncodingTable *enc_table;
1363         
1364         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1365                 if (enc_table->enc_type == encoding)
1366                         return enc_table->str;
1367         }
1368         return NULL;
1369 }
1370
1371 /* --- NEW MIME STUFF --- */
1372 struct TypeTable
1373 {
1374         gchar *str;
1375         MimeMediaType type;
1376 };
1377
1378 static struct TypeTable mime_type_table[] = {
1379         {"text", MIMETYPE_TEXT},
1380         {"image", MIMETYPE_IMAGE},
1381         {"audio", MIMETYPE_AUDIO},
1382         {"video", MIMETYPE_VIDEO},
1383         {"application", MIMETYPE_APPLICATION},
1384         {"message", MIMETYPE_MESSAGE},
1385         {"multipart", MIMETYPE_MULTIPART},
1386         {NULL, 0},
1387 };
1388
1389 const gchar *procmime_get_media_type_str(MimeMediaType type)
1390 {
1391         struct TypeTable *type_table;
1392         
1393         for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1394                 if (type_table->type == type)
1395                         return type_table->str;
1396         }
1397         return NULL;
1398 }
1399
1400 MimeMediaType procmime_get_media_type(const gchar *str)
1401 {
1402         struct TypeTable *typetablearray;
1403
1404         for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1405                 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1406                         return typetablearray->type;
1407
1408         return MIMETYPE_UNKNOWN;
1409 }
1410
1411 /*!
1412  *\brief        Safe wrapper for content type string.
1413  *
1414  *\return       const gchar * Pointer to content type string. 
1415  */
1416 gchar *procmime_get_content_type_str(MimeMediaType type,
1417                                            const char *subtype)
1418 {
1419         const gchar *type_str = NULL;
1420
1421         if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1422                 return g_strdup("unknown");
1423         return g_strdup_printf("%s/%s", type_str, subtype);
1424 }
1425
1426 static int procmime_parse_mimepart(MimeInfo *parent,
1427                              gchar *content_type,
1428                              gchar *content_encoding,
1429                              gchar *content_description,
1430                              gchar *content_id,
1431                              gchar *content_disposition,
1432                              gchar *content_location,
1433                              const gchar *original_msgid,
1434                              const gchar *disposition_notification_hdr,
1435                              const gchar *filename,
1436                              guint offset,
1437                              guint length,
1438                              gboolean short_scan);
1439
1440 static void procmime_parse_message_rfc822(MimeInfo *mimeinfo, gboolean short_scan)
1441 {
1442         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1443                                 {"Content-Transfer-Encoding:",
1444                                                    NULL, FALSE},
1445                                 {"Content-Description:",
1446                                                    NULL, TRUE},
1447                                 {"Content-ID:",
1448                                                    NULL, TRUE},
1449                                 {"Content-Disposition:",
1450                                                    NULL, TRUE},
1451                                 {"Content-Location:",
1452                                                    NULL, TRUE},
1453                                 {"MIME-Version:",
1454                                                    NULL, TRUE},
1455                                 {"Original-Message-ID:",
1456                                                    NULL, TRUE},
1457                                 {"Disposition:",
1458                                                    NULL, TRUE},
1459                                 {NULL,             NULL, FALSE}};
1460         guint content_start, i;
1461         FILE *fp;
1462         gchar *tmp;
1463         gint len = 0;
1464
1465         procmime_decode_content(mimeinfo);
1466
1467         fp = procmime_fopen(mimeinfo->data.filename, "rb");
1468         if (fp == NULL) {
1469                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1470                 return;
1471         }
1472         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1473                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1474                 procmime_fclose(fp);
1475                 return;
1476         }
1477         procheader_get_header_fields(fp, hentry);
1478         if (hentry[0].body != NULL) {
1479                 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);
1480                 g_free(hentry[0].body);
1481                 hentry[0].body = tmp;
1482         }                
1483         if (hentry[2].body != NULL) {
1484                 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);
1485                 g_free(hentry[2].body);
1486                 hentry[2].body = tmp;
1487         }                
1488         if (hentry[4].body != NULL) {
1489                 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);
1490                 g_free(hentry[4].body);
1491                 hentry[4].body = tmp;
1492         }                
1493         if (hentry[5].body != NULL) {
1494                 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);
1495                 g_free(hentry[5].body);
1496                 hentry[5].body = tmp;
1497         }                
1498         if (hentry[7].body != NULL) {
1499                 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);
1500                 g_free(hentry[7].body);
1501                 hentry[7].body = tmp;
1502         }
1503         if (hentry[8].body != NULL) {
1504                 tmp = conv_unmime_header(hentry[8].body, NULL, FALSE);
1505                 g_free(hentry[8].body);
1506                 hentry[8].body = tmp;
1507         }
1508   
1509         content_start = ftell(fp);
1510         procmime_fclose(fp);
1511         
1512         len = mimeinfo->length - (content_start - mimeinfo->offset);
1513         if (len < 0)
1514                 len = 0;
1515         procmime_parse_mimepart(mimeinfo,
1516                                 hentry[0].body, hentry[1].body,
1517                                 hentry[2].body, hentry[3].body,
1518                                 hentry[4].body, hentry[5].body,
1519                                 hentry[7].body, hentry[8].body, 
1520                                 mimeinfo->data.filename, content_start,
1521                                 len, short_scan);
1522         
1523         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1524                 g_free(hentry[i].body);
1525                 hentry[i].body = NULL;
1526         }
1527 }
1528
1529 static void procmime_parse_disposition_notification(MimeInfo *mimeinfo, 
1530                 const gchar *original_msgid, const gchar *disposition_notification_hdr,
1531                 gboolean short_scan)
1532 {
1533         HeaderEntry hentry[] = {{"Original-Message-ID:",  NULL, TRUE},
1534                                 {"Disposition:",          NULL, TRUE},
1535                                 {NULL,                    NULL, FALSE}};
1536         guint i;
1537         FILE *fp;
1538         gchar *orig_msg_id = NULL;
1539         gchar *disp = NULL;
1540
1541         procmime_decode_content(mimeinfo);
1542
1543         debug_print("parse disposition notification\n");
1544         fp = procmime_fopen(mimeinfo->data.filename, "rb");
1545         if (fp == NULL) {
1546                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1547                 return;
1548         }
1549         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1550                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1551                 procmime_fclose(fp);
1552                 return;
1553         }
1554
1555         if (original_msgid && disposition_notification_hdr) {
1556                 hentry[0].body = g_strdup(original_msgid);
1557                 hentry[1].body = g_strdup(disposition_notification_hdr);
1558         } else {
1559                 procheader_get_header_fields(fp, hentry);
1560         }
1561     
1562         procmime_fclose(fp);
1563
1564         if (!hentry[0].body || !hentry[1].body) {
1565                 debug_print("MsgId %s, Disp %s\n",
1566                         hentry[0].body ? hentry[0].body:"(nil)",
1567                         hentry[1].body ? hentry[1].body:"(nil)");
1568                 goto bail;
1569         }
1570
1571         orig_msg_id = g_strdup(hentry[0].body);
1572         disp = g_strdup(hentry[1].body);
1573
1574         extract_parenthesis(orig_msg_id, '<', '>');
1575         remove_space(orig_msg_id);
1576         
1577         if (strstr(disp, "displayed")) {
1578                 /* find sent message, if possible */
1579                 MsgInfo *info = NULL;
1580                 GList *flist;
1581                 debug_print("%s has been displayed.\n", orig_msg_id);
1582                 for (flist = folder_get_list(); flist != NULL; flist = g_list_next(flist)) {
1583                         FolderItem *outbox = ((Folder *)(flist->data))->outbox;
1584                         if (!outbox) {
1585                                 debug_print("skipping folder with no outbox...\n");
1586                                 continue;
1587                         }
1588                         info = folder_item_get_msginfo_by_msgid(outbox, orig_msg_id);
1589                         debug_print("%s %s in %s\n", info?"found":"didn't find", orig_msg_id, outbox->path);
1590                         if (info) {
1591                                 procmsg_msginfo_set_flags(info, MSG_RETRCPT_GOT, 0);
1592                                 procmsg_msginfo_free(&info);
1593                         }
1594                 }
1595         }
1596         g_free(orig_msg_id);
1597         g_free(disp);
1598 bail:
1599         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1600                 g_free(hentry[i].body);
1601                 hentry[i].body = NULL;
1602         }
1603 }
1604
1605 #define GET_HEADERS() {                                         \
1606         procheader_get_header_fields(fp, hentry);               \
1607         if (hentry[0].body != NULL) {                           \
1608                 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);  \
1609                 g_free(hentry[0].body);                         \
1610                 hentry[0].body = tmp;                           \
1611         }                                                       \
1612         if (hentry[2].body != NULL) {                           \
1613                 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);  \
1614                 g_free(hentry[2].body);                         \
1615                 hentry[2].body = tmp;                           \
1616         }                                                       \
1617         if (hentry[4].body != NULL) {                           \
1618                 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);  \
1619                 g_free(hentry[4].body);                         \
1620                 hentry[4].body = tmp;                           \
1621         }                                                       \
1622         if (hentry[5].body != NULL) {                           \
1623                 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);  \
1624                 g_free(hentry[5].body);                         \
1625                 hentry[5].body = tmp;                           \
1626         }                                                       \
1627         if (hentry[6].body != NULL) {                           \
1628                 tmp = conv_unmime_header(hentry[6].body, NULL, FALSE);  \
1629                 g_free(hentry[6].body);                         \
1630                 hentry[6].body = tmp;                           \
1631         }                                                       \
1632         if (hentry[7].body != NULL) {                           \
1633                 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);  \
1634                 g_free(hentry[7].body);                         \
1635                 hentry[7].body = tmp;                           \
1636         }                                                       \
1637 }
1638
1639 static void procmime_parse_multipart(MimeInfo *mimeinfo, gboolean short_scan)
1640 {
1641         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1642                                 {"Content-Transfer-Encoding:",
1643                                                    NULL, FALSE},
1644                                 {"Content-Description:",
1645                                                    NULL, TRUE},
1646                                 {"Content-ID:",
1647                                                    NULL, TRUE},
1648                                 {"Content-Disposition:",
1649                                                    NULL, TRUE},
1650                                 {"Content-Location:",
1651                                                    NULL, TRUE},
1652                                 {"Original-Message-ID:",
1653                                                    NULL, TRUE},
1654                                 {"Disposition:",
1655                                                    NULL, TRUE},
1656                                 {NULL,             NULL, FALSE}};
1657         gchar *tmp;
1658         gchar *boundary;
1659         gint boundary_len = 0, lastoffset = -1, i;
1660         gchar buf[BUFFSIZE];
1661         FILE *fp;
1662         int result = 0;
1663         gboolean start_found = FALSE;
1664         gboolean end_found = FALSE;
1665
1666         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1667         if (!boundary)
1668                 return;
1669         boundary_len = strlen(boundary);
1670
1671         procmime_decode_content(mimeinfo);
1672
1673         fp = procmime_fopen(mimeinfo->data.filename, "rb");
1674         if (fp == NULL) {
1675                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1676                 return;
1677         }
1678
1679         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1680                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1681                 procmime_fclose(fp);
1682                 return;
1683         }
1684
1685         while (SC_FGETS(buf, sizeof(buf), fp) != NULL && result == 0) {
1686                 if (ftell(fp) - 1 > (mimeinfo->offset + mimeinfo->length))
1687                         break;
1688
1689                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1690                         start_found = TRUE;
1691
1692                         if (lastoffset != -1) {
1693                                 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1694                                 if (len < 0)
1695                                         len = 0;
1696                                 result = procmime_parse_mimepart(mimeinfo,
1697                                                         hentry[0].body, hentry[1].body,
1698                                                         hentry[2].body, hentry[3].body, 
1699                                                         hentry[4].body, hentry[5].body,
1700                                                         hentry[6].body, hentry[7].body,
1701                                                         mimeinfo->data.filename, lastoffset,
1702                                                         len, short_scan);
1703                                 if (result == 1 && short_scan)
1704                                         break;
1705                                 
1706                         } 
1707                         
1708                         if (buf[2 + boundary_len]     == '-' &&
1709                             buf[2 + boundary_len + 1] == '-') {
1710                                 end_found = TRUE;
1711                                 break;
1712                         }
1713                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1714                                 g_free(hentry[i].body);
1715                                 hentry[i].body = NULL;
1716                         }
1717                         GET_HEADERS();
1718                         lastoffset = ftell(fp);
1719                 }
1720         }
1721         
1722         if (start_found && !end_found && lastoffset != -1) {
1723                 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1724
1725                 if (len >= 0) {
1726                         result = procmime_parse_mimepart(mimeinfo,
1727                                         hentry[0].body, hentry[1].body,
1728                                         hentry[2].body, hentry[3].body, 
1729                                         hentry[4].body, hentry[5].body,
1730                                         hentry[6].body, hentry[7].body,
1731                                         mimeinfo->data.filename, lastoffset,
1732                                         len, short_scan);
1733                 }
1734                 mimeinfo->broken = TRUE;
1735         }
1736         
1737         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1738                 g_free(hentry[i].body);
1739                 hentry[i].body = NULL;
1740         }
1741         procmime_fclose(fp);
1742 }
1743
1744 static void parse_parameters(const gchar *parameters, GHashTable *table)
1745 {
1746         gchar *params, *param, *next;
1747         GSList *convlist = NULL, *concatlist = NULL, *cur;
1748
1749         params = g_strdup(parameters);
1750         param = params;
1751         next = params;
1752         for (; next != NULL; param = next) {
1753                 gchar *attribute, *value, *tmp, *down_attr, *orig_down_attr;
1754                 gint len;
1755                 gboolean convert = FALSE;
1756
1757                 next = strchr_with_skip_quote(param, '"', ';');
1758                 if (next != NULL) {
1759                         next[0] = '\0';
1760                         next++;
1761                 }
1762
1763                 g_strstrip(param);
1764
1765                 attribute = param;
1766                 value = strchr(attribute, '=');
1767                 if (value == NULL)
1768                         continue;
1769
1770                 value[0] = '\0';
1771                 value++;
1772                 while (value[0] != '\0' && value[0] == ' ')
1773                         value++;
1774
1775                 down_attr = g_utf8_strdown(attribute, -1);
1776                 orig_down_attr = down_attr;
1777         
1778                 len = down_attr ? strlen(down_attr):0;
1779                 if (len > 0 && down_attr[len - 1] == '*') {
1780                         gchar *srcpos, *dstpos, *endpos;
1781
1782                         convert = TRUE;
1783                         down_attr[len - 1] = '\0';
1784
1785                         srcpos = value;
1786                         dstpos = value;
1787                         endpos = value + strlen(value);
1788                         while (srcpos < endpos) {
1789                                 if (*srcpos != '%')
1790                                         *dstpos = *srcpos;
1791                                 else {
1792                                         guchar dstvalue;
1793
1794                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1795                                                 *dstpos = '?';
1796                                         else
1797                                                 *dstpos = dstvalue;
1798                                         srcpos += 2;
1799                                 }
1800                                 srcpos++;
1801                                 dstpos++;
1802                         }
1803                         *dstpos = '\0';
1804                         if (value[0] == '"')
1805                                 extract_quote(value, '"');
1806                 } else {
1807                         if (value[0] == '"')
1808                                 extract_quote(value, '"');
1809                         else if ((tmp = strchr(value, ' ')) != NULL)
1810                                 *tmp = '\0';
1811                 }
1812
1813                 if (down_attr) {
1814                         while (down_attr[0] == ' ')
1815                                 down_attr++;
1816                         while (down_attr[strlen(down_attr)-1] == ' ') 
1817                                 down_attr[strlen(down_attr)-1] = '\0';
1818                 } 
1819
1820                 while (value[0] != '\0' && value[0] == ' ')
1821                         value++;
1822                 while (value[strlen(value)-1] == ' ') 
1823                         value[strlen(value)-1] = '\0';
1824
1825                 if (down_attr && strrchr(down_attr, '*') != NULL) {
1826                         gchar *tmpattr;
1827
1828                         tmpattr = g_strdup(down_attr);
1829                         tmp = strrchr(tmpattr, '*');
1830                         tmp[0] = '\0';
1831
1832                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1833                             (g_slist_find_custom(concatlist, down_attr, (GCompareFunc)g_strcmp0) == NULL))
1834                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1835
1836                         if (convert && (g_slist_find_custom(convlist, tmpattr, (GCompareFunc)g_strcmp0) == NULL))
1837                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1838
1839                         g_free(tmpattr);
1840                 } else if (convert) {
1841                         if (g_slist_find_custom(convlist, down_attr, (GCompareFunc)g_strcmp0) == NULL)
1842                                 convlist = g_slist_prepend(convlist, g_strdup(down_attr));
1843                 }
1844
1845                 if (g_hash_table_lookup(table, down_attr) == NULL)
1846                         g_hash_table_insert(table, g_strdup(down_attr), g_strdup(value));
1847                 g_free(orig_down_attr);
1848         }
1849
1850         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1851                 gchar *attribute, *attrwnum, *partvalue;
1852                 gint n = 0;
1853                 GString *value;
1854
1855                 attribute = (gchar *) cur->data;
1856                 value = g_string_sized_new(64);
1857
1858                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1859                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1860                         g_string_append(value, partvalue);
1861
1862                         g_hash_table_remove(table, attrwnum);
1863                         g_free(attrwnum);
1864                         n++;
1865                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1866                 }
1867                 g_free(attrwnum);
1868
1869                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1870                 g_string_free(value, TRUE);
1871         }
1872         slist_free_strings_full(concatlist);
1873
1874         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1875                 gchar *attribute, *key, *value;
1876                 gchar *charset, *lang, *oldvalue, *newvalue;
1877
1878                 attribute = (gchar *) cur->data;
1879                 if (!g_hash_table_lookup_extended(
1880                         table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
1881                         continue;
1882
1883                 charset = value;
1884                 if (charset == NULL)
1885                         continue;
1886                 lang = strchr(charset, '\'');
1887                 if (lang == NULL)
1888                         continue;
1889                 lang[0] = '\0';
1890                 lang++;
1891                 oldvalue = strchr(lang, '\'');
1892                 if (oldvalue == NULL)
1893                         continue;
1894                 oldvalue[0] = '\0';
1895                 oldvalue++;
1896
1897                 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1898
1899                 g_hash_table_remove(table, attribute);
1900                 g_free(key);
1901                 g_free(value);
1902
1903                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1904         }
1905         slist_free_strings_full(convlist);
1906
1907         g_free(params);
1908 }       
1909
1910 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1911 {
1912         cm_return_if_fail(content_type != NULL);
1913         cm_return_if_fail(mimeinfo != NULL);
1914
1915         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1916          * if it's not available we use the default Content-Type */
1917         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1918                 mimeinfo->type = MIMETYPE_TEXT;
1919                 mimeinfo->subtype = g_strdup("plain");
1920                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1921                                        "charset") == NULL) {
1922                         g_hash_table_insert(mimeinfo->typeparameters,
1923                                     g_strdup("charset"),
1924                                     g_strdup(
1925                                         conv_get_locale_charset_str_no_utf8()));
1926                 }
1927         } else {
1928                 gchar *type, *subtype, *params;
1929
1930                 type = g_strdup(content_type);
1931                 subtype = strchr(type, '/') + 1;
1932                 *(subtype - 1) = '\0';
1933                 if ((params = strchr(subtype, ';')) != NULL) {
1934                         params[0] = '\0';
1935                         params++;
1936                 }
1937
1938                 mimeinfo->type = procmime_get_media_type(type);
1939                 mimeinfo->subtype = g_strstrip(g_strdup(subtype));
1940
1941                 /* Get mimeinfo->typeparameters */
1942                 if (params != NULL)
1943                         parse_parameters(params, mimeinfo->typeparameters);
1944
1945                 g_free(type);
1946         }
1947 }
1948
1949 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1950 {
1951         gchar *tmp, *params;
1952
1953         cm_return_if_fail(content_disposition != NULL);
1954         cm_return_if_fail(mimeinfo != NULL);
1955
1956         tmp = g_strdup(content_disposition);
1957         if ((params = strchr(tmp, ';')) != NULL) {
1958                 params[0] = '\0';
1959                 params++;
1960         }       
1961         g_strstrip(tmp);
1962
1963         if (!g_ascii_strcasecmp(tmp, "inline")) 
1964                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1965         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1966                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1967         else
1968                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1969         
1970         if (params != NULL)
1971                 parse_parameters(params, mimeinfo->dispositionparameters);
1972
1973         g_free(tmp);
1974 }
1975
1976
1977 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1978 {
1979         struct EncodingTable *enc_table;
1980         
1981         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1982                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1983                         mimeinfo->encoding_type = enc_table->enc_type;
1984                         return;
1985                 }
1986         }
1987         mimeinfo->encoding_type = ENC_UNKNOWN;
1988         return;
1989 }
1990
1991 static GSList *registered_parsers = NULL;
1992
1993 static MimeParser *procmime_get_mimeparser_for_type(MimeMediaType type, const gchar *sub_type)
1994 {
1995         GSList *cur;
1996         for (cur = registered_parsers; cur; cur = cur->next) {
1997                 MimeParser *parser = (MimeParser *)cur->data;
1998                 if (parser->type == type && !strcmp2(parser->sub_type, sub_type))
1999                         return parser;
2000         }
2001         return NULL;
2002 }
2003
2004 void procmime_mimeparser_register(MimeParser *parser)
2005 {
2006         if (!procmime_get_mimeparser_for_type(parser->type, parser->sub_type))
2007                 registered_parsers = g_slist_append(registered_parsers, parser);
2008 }
2009
2010
2011 void procmime_mimeparser_unregister(MimeParser *parser) 
2012 {
2013         registered_parsers = g_slist_remove(registered_parsers, parser);
2014 }
2015
2016 static gboolean procmime_mimeparser_parse(MimeParser *parser, MimeInfo *mimeinfo)
2017 {
2018         cm_return_val_if_fail(parser->parse != NULL, FALSE);
2019         return parser->parse(parser, mimeinfo); 
2020 }
2021
2022 static int procmime_parse_mimepart(MimeInfo *parent,
2023                              gchar *content_type,
2024                              gchar *content_encoding,
2025                              gchar *content_description,
2026                              gchar *content_id,
2027                              gchar *content_disposition,
2028                              gchar *content_location,
2029                              const gchar *original_msgid,
2030                              const gchar *disposition_notification_hdr,
2031                              const gchar *filename,
2032                              guint offset,
2033                              guint length,
2034                              gboolean short_scan)
2035 {
2036         MimeInfo *mimeinfo;
2037         MimeParser *parser = NULL;
2038         gboolean parsed = FALSE;
2039         int result = 0;
2040
2041         /* Create MimeInfo */
2042         mimeinfo = procmime_mimeinfo_new();
2043         mimeinfo->content = MIMECONTENT_FILE;
2044
2045         if (parent != NULL) {
2046                 if (g_node_depth(parent->node) > 32) {
2047                         /* 32 is an arbitrary value
2048                          * this avoids DOSsing ourselves 
2049                          * with enormous messages
2050                          */
2051                         procmime_mimeinfo_free_all(&mimeinfo);
2052                         return -1;                      
2053                 }
2054                 g_node_append(parent->node, mimeinfo->node);
2055         }
2056         mimeinfo->data.filename = g_strdup(filename);
2057         mimeinfo->offset = offset;
2058         mimeinfo->length = length;
2059
2060         if (content_type != NULL) {
2061                 g_strchomp(content_type);
2062                 procmime_parse_content_type(content_type, mimeinfo);
2063         } else {
2064                 mimeinfo->type = MIMETYPE_TEXT;
2065                 mimeinfo->subtype = g_strdup("plain");
2066                 if (g_hash_table_lookup(mimeinfo->typeparameters,
2067                                        "charset") == NULL) {
2068                         g_hash_table_insert(mimeinfo->typeparameters,
2069                                     g_strdup("charset"),
2070                                     g_strdup(
2071                                         conv_get_locale_charset_str_no_utf8()));
2072                 }
2073         }
2074
2075         if (content_encoding != NULL) {
2076                 g_strchomp(content_encoding);
2077                 procmime_parse_content_encoding(content_encoding, mimeinfo);
2078         } else {
2079                 mimeinfo->encoding_type = ENC_UNKNOWN;
2080         }
2081
2082         if (content_description != NULL)
2083                 mimeinfo->description = g_strdup(content_description);
2084         else
2085                 mimeinfo->description = NULL;
2086
2087         if (content_id != NULL)
2088                 mimeinfo->id = g_strdup(content_id);
2089         else
2090                 mimeinfo->id = NULL;
2091
2092         if (content_location != NULL)
2093                 mimeinfo->location = g_strdup(content_location);
2094         else
2095                 mimeinfo->location = NULL;
2096
2097         if (content_disposition != NULL) {
2098                 g_strchomp(content_disposition);
2099                 procmime_parse_content_disposition(content_disposition, mimeinfo);
2100         } else
2101                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
2102
2103         /* Call parser for mime type */
2104         if ((parser = procmime_get_mimeparser_for_type(mimeinfo->type, mimeinfo->subtype)) != NULL) {
2105                 parsed = procmime_mimeparser_parse(parser, mimeinfo);
2106         } 
2107         if (!parsed) {
2108                 switch (mimeinfo->type) {
2109                 case MIMETYPE_TEXT:
2110                         if (g_ascii_strcasecmp(mimeinfo->subtype, "plain") == 0 && short_scan) {
2111                                 return 1;
2112                         }
2113                         break;
2114
2115                 case MIMETYPE_MESSAGE:
2116                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2117                                 procmime_parse_message_rfc822(mimeinfo, short_scan);
2118                         }
2119                         if (g_ascii_strcasecmp(mimeinfo->subtype, "disposition-notification") == 0) {
2120                                 procmime_parse_disposition_notification(mimeinfo, 
2121                                         original_msgid, disposition_notification_hdr, short_scan);
2122                         }
2123                         break;
2124                         
2125                 case MIMETYPE_MULTIPART:
2126                         procmime_parse_multipart(mimeinfo, short_scan);
2127                         break;
2128                 
2129                 case MIMETYPE_APPLICATION:
2130                         if (g_ascii_strcasecmp(mimeinfo->subtype, "octet-stream") == 0
2131                         && original_msgid && *original_msgid 
2132                         && disposition_notification_hdr && *disposition_notification_hdr) {
2133                                 procmime_parse_disposition_notification(mimeinfo, 
2134                                         original_msgid, disposition_notification_hdr, short_scan);
2135                         }
2136                         break;
2137                 default:
2138                         break;
2139                 }
2140         }
2141
2142         return result;
2143 }
2144
2145 static gchar *typenames[] = {
2146     "text",
2147     "image",
2148     "audio",
2149     "video",
2150     "application",
2151     "message",
2152     "multipart",
2153     "unknown",
2154 };
2155
2156 static gboolean output_func(GNode *node, gpointer data)
2157 {
2158         guint i, depth;
2159         MimeInfo *mimeinfo = (MimeInfo *) node->data;
2160
2161         depth = g_node_depth(node);
2162         for (i = 0; i < depth; i++)
2163                 g_print("    ");
2164         g_print("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
2165
2166         return FALSE;
2167 }
2168
2169 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
2170 {
2171         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
2172 }
2173
2174 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset, gboolean short_scan)
2175 {
2176         MimeInfo *mimeinfo;
2177         GStatBuf buf;
2178
2179         if (g_stat(filename, &buf) < 0) {
2180                 FILE_OP_ERROR(filename, "stat");
2181                 return NULL;
2182         }
2183
2184         mimeinfo = procmime_mimeinfo_new();
2185         mimeinfo->content = MIMECONTENT_FILE;
2186         mimeinfo->encoding_type = ENC_UNKNOWN;
2187         mimeinfo->type = MIMETYPE_MESSAGE;
2188         mimeinfo->subtype = g_strdup("rfc822");
2189         mimeinfo->data.filename = g_strdup(filename);
2190         mimeinfo->offset = offset;
2191         mimeinfo->length = buf.st_size - offset;
2192
2193         procmime_parse_message_rfc822(mimeinfo, short_scan);
2194         if (debug_get_mode())
2195                 output_mime_structure(mimeinfo, 0);
2196
2197         return mimeinfo;
2198 }
2199
2200 static MimeInfo *procmime_scan_file_full(const gchar *filename, gboolean short_scan)
2201 {
2202         MimeInfo *mimeinfo;
2203
2204         cm_return_val_if_fail(filename != NULL, NULL);
2205
2206         mimeinfo = procmime_scan_file_with_offset(filename, 0, short_scan);
2207
2208         return mimeinfo;
2209 }
2210
2211 MimeInfo *procmime_scan_file(const gchar *filename)
2212 {
2213         return procmime_scan_file_full(filename, FALSE);
2214 }
2215
2216 static MimeInfo *procmime_scan_file_short(const gchar *filename)
2217 {
2218         return procmime_scan_file_full(filename, TRUE);
2219 }
2220
2221 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan)
2222 {
2223         FILE *fp;
2224         MimeInfo *mimeinfo;
2225         gchar buf[BUFFSIZE];
2226         gint offset = 0;
2227
2228         cm_return_val_if_fail(filename != NULL, NULL);
2229
2230         /* Open file */
2231         if ((fp = procmime_fopen(filename, "rb")) == NULL)
2232                 return NULL;
2233         /* Skip queue header */
2234         while (SC_FGETS(buf, sizeof(buf), fp) != NULL) {
2235                 /* new way */
2236                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
2237                         strlen("X-Claws-End-Special-Headers:"))) ||
2238                    (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
2239                         strlen("X-Sylpheed-End-Special-Headers:"))))
2240                         break;
2241                 /* old way */
2242                 if (buf[0] == '\r' || buf[0] == '\n') break;
2243                 /* from other mailers */
2244                 if (!strncmp(buf, "Date: ", 6)
2245                 ||  !strncmp(buf, "To: ", 4)
2246                 ||  !strncmp(buf, "From: ", 6)
2247                 ||  !strncmp(buf, "Subject: ", 9)) {
2248                         rewind(fp);
2249                         break;
2250                 }
2251         }
2252         offset = ftell(fp);
2253         procmime_fclose(fp);
2254
2255         mimeinfo = procmime_scan_file_with_offset(filename, offset, short_scan);
2256
2257         return mimeinfo;
2258 }
2259
2260 MimeInfo *procmime_scan_queue_file(const gchar *filename)
2261 {
2262         return procmime_scan_queue_file_full(filename, FALSE);
2263 }
2264
2265 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename)
2266 {
2267         return procmime_scan_queue_file_full(filename, TRUE);
2268 }
2269
2270 typedef enum {
2271     ENC_AS_TOKEN,
2272     ENC_AS_QUOTED_STRING,
2273     ENC_AS_EXTENDED,
2274     ENC_AS_ENCWORD
2275 } EncodeAs;
2276
2277 typedef struct _ParametersData {
2278         FILE *fp;
2279         guint len;
2280         gint error;
2281 } ParametersData;
2282
2283 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
2284 {
2285         gchar *param = key;
2286         gchar *val = value, *valpos, *tmp;
2287         ParametersData *pdata = (ParametersData *)user_data;
2288         GString *buf = g_string_new("");
2289         gint len;
2290
2291         EncodeAs encas = ENC_AS_TOKEN;
2292
2293         for (valpos = val; *valpos != 0; valpos++) {
2294                 if (!IS_ASCII(*valpos)) {
2295                         encas = ENC_AS_ENCWORD;
2296                         break;
2297                 }
2298             
2299                 /* CTLs */
2300                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
2301                         encas = ENC_AS_QUOTED_STRING;
2302                         continue;
2303                 }
2304
2305                 /* tspecials + SPACE */
2306                 switch (*valpos) {
2307                 case ' ':
2308                 case '(': 
2309                 case ')':
2310                 case '<':
2311                 case '>':
2312                 case '@':
2313                 case ',':
2314                 case ';':
2315                 case ':':
2316                 case '\\':
2317                 case '"':
2318                 case '/':
2319                 case '[':
2320                 case ']':
2321                 case '?':
2322                 case '=':
2323                         encas = ENC_AS_QUOTED_STRING;
2324                         continue;
2325                 }
2326         }
2327         
2328         switch (encas) {
2329         case ENC_AS_TOKEN:
2330                 g_string_append_printf(buf, "%s=%s", param, val);
2331                 break;
2332
2333         case ENC_AS_QUOTED_STRING:
2334                 g_string_append_printf(buf, "%s=\"%s\"", param, val);
2335                 break;
2336
2337 #if 0 /* we don't use that for now */
2338         case ENC_AS_EXTENDED:
2339                 if (!g_utf8_validate(val, -1, NULL))
2340                         g_string_append_printf(buf, "%s*=%s''", param,
2341                                 conv_get_locale_charset_str());
2342                 else
2343                         g_string_append_printf(buf, "%s*=%s''", param,
2344                                 CS_INTERNAL);
2345                 for (valpos = val; *valpos != '\0'; valpos++) {
2346                         if (IS_ASCII(*valpos) && isalnum(*valpos)) {
2347                                 g_string_append_printf(buf, "%c", *valpos);
2348                         } else {
2349                                 gchar hexstr[3] = "XX";
2350                                 get_hex_str(hexstr, *valpos);
2351                                 g_string_append_printf(buf, "%%%s", hexstr);
2352                         }
2353                 }
2354                 break;
2355 #else
2356         case ENC_AS_EXTENDED:
2357                 debug_print("Unhandled ENC_AS_EXTENDED.\n");
2358                 break;
2359 #endif
2360         case ENC_AS_ENCWORD:
2361                 len = MAX(strlen(val)*6, 512);
2362                 tmp = g_malloc(len+1);
2363                 codeconv_set_strict(TRUE);
2364                 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2365                         prefs_common.outgoing_charset);
2366                 codeconv_set_strict(FALSE);
2367                 if (!tmp || !*tmp) {
2368                         codeconv_set_strict(TRUE);
2369                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2370                                 conv_get_outgoing_charset_str());
2371                         codeconv_set_strict(FALSE);
2372                 }
2373                 if (!tmp || !*tmp) {
2374                         codeconv_set_strict(TRUE);
2375                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2376                                 CS_UTF_8);
2377                         codeconv_set_strict(FALSE);
2378                 }
2379                 if (!tmp || !*tmp) {
2380                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2381                                 CS_UTF_8);
2382                 }
2383                 g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
2384                 g_free(tmp);
2385                 break;
2386
2387         }
2388         
2389         if (buf->str && strlen(buf->str)) {
2390                 tmp = strstr(buf->str, "\n");
2391                 if (tmp)
2392                         len = (tmp - buf->str);
2393                 else
2394                         len = strlen(buf->str);
2395                 if (pdata->len + len > 76) {
2396                         if (fprintf(pdata->fp, ";\n %s", buf->str) < 0)
2397                                 pdata->error = TRUE;
2398                         pdata->len = strlen(buf->str) + 1;
2399                 } else {
2400                         if (fprintf(pdata->fp, "; %s", buf->str) < 0)
2401                                 pdata->error = TRUE;
2402                         pdata->len += strlen(buf->str) + 2;
2403                 }
2404         }
2405         g_string_free(buf, TRUE);
2406 }
2407
2408 #define TRY(func) { \
2409         if (!(func)) { \
2410                 return -1; \
2411         } \
2412 }
2413
2414 int procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
2415 {
2416         struct TypeTable *type_table;
2417         ParametersData *pdata = g_new0(ParametersData, 1);
2418         debug_print("procmime_write_mime_header\n");
2419         
2420         pdata->fp = fp;
2421         pdata->error = FALSE;
2422         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
2423                 if (mimeinfo->type == type_table->type) {
2424                         gchar *buf = g_strdup_printf(
2425                                 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
2426                         if (fprintf(fp, "%s", buf) < 0) {
2427                                 g_free(buf);
2428                                 g_free(pdata);
2429                                 return -1;
2430                         }
2431                         pdata->len = strlen(buf);
2432                         g_free(buf);
2433                         break;
2434                 }
2435         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
2436         if (pdata->error == TRUE) {
2437                 g_free(pdata);
2438                 return -1;
2439         }
2440         g_free(pdata);
2441
2442         TRY(fprintf(fp, "\n") >= 0);
2443
2444         if (mimeinfo->encoding_type != ENC_UNKNOWN)
2445                 TRY(fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type)) >= 0);
2446
2447         if (mimeinfo->description != NULL)
2448                 TRY(fprintf(fp, "Content-Description: %s\n", mimeinfo->description) >= 0);
2449
2450         if (mimeinfo->id != NULL)
2451                 TRY(fprintf(fp, "Content-ID: %s\n", mimeinfo->id) >= 0);
2452
2453         if (mimeinfo->location != NULL)
2454                 TRY(fprintf(fp, "Content-Location: %s\n", mimeinfo->location) >= 0);
2455
2456         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
2457                 ParametersData *pdata = g_new0(ParametersData, 1);
2458                 gchar *buf = NULL;
2459                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
2460                         buf = g_strdup("Content-Disposition: inline");
2461                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
2462                         buf = g_strdup("Content-Disposition: attachment");
2463                 else
2464                         buf = g_strdup("Content-Disposition: unknown");
2465
2466                 if (fprintf(fp, "%s", buf) < 0) {
2467                         g_free(buf);
2468                         g_free(pdata);
2469                         return -1;
2470                 }
2471                 pdata->len = strlen(buf);
2472                 g_free(buf);
2473
2474                 pdata->fp = fp;
2475                 pdata->error = FALSE;
2476                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
2477                 if (pdata->error == TRUE) {
2478                         g_free(pdata);
2479                         return -1;
2480                 }
2481                 g_free(pdata);
2482                 TRY(fprintf(fp, "\n") >= 0);
2483         }
2484
2485         TRY(fprintf(fp, "\n") >= 0);
2486         
2487         return 0;
2488 }
2489
2490 static gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
2491 {
2492         FILE *infp;
2493         GNode *childnode;
2494         MimeInfo *child;
2495         gchar buf[BUFFSIZE];
2496         gboolean skip = FALSE;;
2497         size_t len;
2498
2499         debug_print("procmime_write_message_rfc822\n");
2500
2501         /* write header */
2502         switch (mimeinfo->content) {
2503         case MIMECONTENT_FILE:
2504                 if ((infp = procmime_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2505                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2506                         return -1;
2507                 }
2508                 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2509                         FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2510                         procmime_fclose(infp);
2511                         return -1;
2512                 }
2513                 while (SC_FGETS(buf, sizeof(buf), infp) == buf) {
2514                         strcrchomp(buf);
2515                         if (buf[0] == '\n' && buf[1] == '\0')
2516                                 break;
2517                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
2518                                 continue;
2519                         if (g_ascii_strncasecmp(buf, "MIME-Version:", 13) == 0 ||
2520                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
2521                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
2522                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
2523                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
2524                             g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 ||
2525                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
2526                                 skip = TRUE;
2527                                 continue;
2528                         }
2529                         len = strlen(buf);
2530                         if (SC_FWRITE(buf, sizeof(gchar), len, fp) < len) {
2531                                 g_warning("failed to dump %zd bytes from file", len);
2532                                 procmime_fclose(infp);
2533                                 return -1;
2534                         }
2535                         skip = FALSE;
2536                 }
2537                 procmime_fclose(infp);
2538                 break;
2539
2540         case MIMECONTENT_MEM:
2541                 len = strlen(mimeinfo->data.mem);
2542                 if (SC_FWRITE(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) {
2543                         g_warning("failed to dump %zd bytes from mem", len);
2544                         return -1;
2545                 }
2546                 break;
2547
2548         default:
2549                 break;
2550         }
2551
2552         childnode = mimeinfo->node->children;
2553         if (childnode == NULL)
2554                 return -1;
2555
2556         child = (MimeInfo *) childnode->data;
2557         if (fprintf(fp, "MIME-Version: 1.0\n") < 0) {
2558                 g_warning("failed to write mime version");
2559                 return -1;
2560         }
2561         if (procmime_write_mime_header(child, fp) < 0)
2562                 return -1;
2563         return procmime_write_mimeinfo(child, fp);
2564 }
2565
2566 static gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
2567 {
2568         FILE *infp;
2569         GNode *childnode;
2570         gchar *boundary, *str, *str2;
2571         gchar buf[BUFFSIZE];
2572         gboolean firstboundary;
2573         size_t len;
2574
2575         debug_print("procmime_write_multipart\n");
2576
2577         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
2578
2579         switch (mimeinfo->content) {
2580         case MIMECONTENT_FILE:
2581                 if ((infp = procmime_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2582                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2583                         return -1;
2584                 }
2585                 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2586                         FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2587                         procmime_fclose(infp);
2588                         return -1;
2589                 }
2590                 while (SC_FGETS(buf, sizeof(buf), infp) == buf) {
2591                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
2592                                 break;
2593                         len = strlen(buf);
2594                         if (SC_FWRITE(buf, sizeof(gchar), len, fp) < len) {
2595                                 g_warning("failed to write %zd", len);
2596                                 procmime_fclose(infp);
2597                                 return -1;
2598                         }
2599                 }
2600                 procmime_fclose(infp);
2601                 break;
2602
2603         case MIMECONTENT_MEM:
2604                 str = g_strdup(mimeinfo->data.mem);
2605                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
2606                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
2607                         *(str2 - 2) = '\0';
2608                 len = strlen(str);
2609                 if (SC_FWRITE(str, sizeof(gchar), len, fp) < len) {
2610                         g_warning("failed to write %zd from mem", len);
2611                         g_free(str);
2612                         return -1;
2613                 }
2614                 g_free(str);
2615                 break;
2616
2617         default:
2618                 break;
2619         }
2620
2621         childnode = mimeinfo->node->children;
2622         firstboundary = TRUE;
2623         while (childnode != NULL) {
2624                 MimeInfo *child = childnode->data;
2625
2626                 if (firstboundary)
2627                         firstboundary = FALSE;
2628                 else
2629                         TRY(fprintf(fp, "\n") >= 0);
2630                         
2631                 TRY(fprintf(fp, "--%s\n", boundary) >= 0);
2632
2633                 if (procmime_write_mime_header(child, fp) < 0)
2634                         return -1;
2635                 if (procmime_write_mimeinfo(child, fp) < 0)
2636                         return -1;
2637
2638                 childnode = g_node_next_sibling(childnode);
2639         }       
2640         TRY(fprintf(fp, "\n--%s--\n", boundary) >= 0);
2641
2642         return 0;
2643 }
2644
2645 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
2646 {
2647         FILE *infp;
2648         size_t len;
2649         debug_print("procmime_write_mimeinfo\n");
2650
2651         if (G_NODE_IS_LEAF(mimeinfo->node)) {
2652                 switch (mimeinfo->content) {
2653                 case MIMECONTENT_FILE:
2654                         if ((infp = procmime_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2655                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
2656                                 return -1;
2657                         }
2658                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
2659                         procmime_fclose(infp);
2660                         return 0;
2661
2662                 case MIMECONTENT_MEM:
2663                         len = strlen(mimeinfo->data.mem);
2664                         if (SC_FWRITE(mimeinfo->data.mem, sizeof(gchar), len, fp) < len)
2665                                 return -1;
2666                         return 0;
2667
2668                 default:
2669                         return 0;
2670                 }
2671         } else {
2672                 /* Call writer for mime type */
2673                 switch (mimeinfo->type) {
2674                 case MIMETYPE_MESSAGE:
2675                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2676                                 return procmime_write_message_rfc822(mimeinfo, fp);
2677                         }
2678                         break;
2679                         
2680                 case MIMETYPE_MULTIPART:
2681                         return procmime_write_multipart(mimeinfo, fp);
2682                         
2683                 default:
2684                         break;
2685                 }
2686
2687                 return -1;
2688         }
2689
2690         return 0;
2691 }
2692
2693 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
2694 {
2695         gchar *base;
2696
2697         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
2698                 base = g_strdup("mimetmp.html");
2699         else {
2700                 const gchar *basetmp;
2701                 gchar *basename;
2702
2703                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
2704                 if (basetmp == NULL)
2705                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
2706                 if (basetmp == NULL)
2707                         basetmp = "mimetmp";
2708                 basename = g_path_get_basename(basetmp);
2709                 if (*basename == '\0') {
2710                         g_free(basename);
2711                         basename = g_strdup("mimetmp");
2712                 }
2713                 base = conv_filename_from_utf8(basename);
2714                 g_free(basename);
2715                 subst_for_shellsafe_filename(base);
2716         }
2717         
2718         return base;
2719 }
2720