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