recognise model/* mime type
[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         {"model", MIMETYPE_MODEL},
1328         {"application", MIMETYPE_APPLICATION},
1329         {"message", MIMETYPE_MESSAGE},
1330         {"multipart", MIMETYPE_MULTIPART},
1331         {NULL, 0},
1332 };
1333
1334 const gchar *procmime_get_media_type_str(MimeMediaType type)
1335 {
1336         struct TypeTable *type_table;
1337         
1338         for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1339                 if (type_table->type == type)
1340                         return type_table->str;
1341         }
1342         return NULL;
1343 }
1344
1345 MimeMediaType procmime_get_media_type(const gchar *str)
1346 {
1347         struct TypeTable *typetablearray;
1348
1349         for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1350                 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1351                         return typetablearray->type;
1352
1353         return MIMETYPE_UNKNOWN;
1354 }
1355
1356 /*!
1357  *\brief        Safe wrapper for content type string.
1358  *
1359  *\return       const gchar * Pointer to content type string. 
1360  */
1361 gchar *procmime_get_content_type_str(MimeMediaType type,
1362                                            const char *subtype)
1363 {
1364         const gchar *type_str = NULL;
1365
1366         if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1367                 return g_strdup("unknown");
1368         return g_strdup_printf("%s/%s", type_str, subtype);
1369 }
1370
1371 static int procmime_parse_mimepart(MimeInfo *parent,
1372                              gchar *content_type,
1373                              gchar *content_encoding,
1374                              gchar *content_description,
1375                              gchar *content_id,
1376                              gchar *content_disposition,
1377                              gchar *content_location,
1378                              const gchar *original_msgid,
1379                              const gchar *disposition_notification_hdr,
1380                              const gchar *filename,
1381                              guint offset,
1382                              guint length,
1383                              gboolean short_scan);
1384
1385 static void procmime_parse_message_rfc822(MimeInfo *mimeinfo, gboolean short_scan)
1386 {
1387         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1388                                 {"Content-Transfer-Encoding:",
1389                                                    NULL, FALSE},
1390                                 {"Content-Description:",
1391                                                    NULL, TRUE},
1392                                 {"Content-ID:",
1393                                                    NULL, TRUE},
1394                                 {"Content-Disposition:",
1395                                                    NULL, TRUE},
1396                                 {"Content-Location:",
1397                                                    NULL, TRUE},
1398                                 {"MIME-Version:",
1399                                                    NULL, TRUE},
1400                                 {"Original-Message-ID:",
1401                                                    NULL, TRUE},
1402                                 {"Disposition:",
1403                                                    NULL, TRUE},
1404                                 {NULL,             NULL, FALSE}};
1405         guint content_start, i;
1406         FILE *fp;
1407         gchar *tmp;
1408         gint len = 0;
1409
1410         procmime_decode_content(mimeinfo);
1411
1412         fp = claws_fopen(mimeinfo->data.filename, "rb");
1413         if (fp == NULL) {
1414                 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1415                 return;
1416         }
1417         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1418                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1419                 claws_fclose(fp);
1420                 return;
1421         }
1422         procheader_get_header_fields(fp, hentry);
1423         if (hentry[0].body != NULL) {
1424                 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);
1425                 g_free(hentry[0].body);
1426                 hentry[0].body = tmp;
1427         }                
1428         if (hentry[2].body != NULL) {
1429                 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);
1430                 g_free(hentry[2].body);
1431                 hentry[2].body = tmp;
1432         }                
1433         if (hentry[4].body != NULL) {
1434                 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);
1435                 g_free(hentry[4].body);
1436                 hentry[4].body = tmp;
1437         }                
1438         if (hentry[5].body != NULL) {
1439                 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);
1440                 g_free(hentry[5].body);
1441                 hentry[5].body = tmp;
1442         }                
1443         if (hentry[7].body != NULL) {
1444                 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);
1445                 g_free(hentry[7].body);
1446                 hentry[7].body = tmp;
1447         }
1448         if (hentry[8].body != NULL) {
1449                 tmp = conv_unmime_header(hentry[8].body, NULL, FALSE);
1450                 g_free(hentry[8].body);
1451                 hentry[8].body = tmp;
1452         }
1453   
1454         content_start = ftell(fp);
1455         claws_fclose(fp);
1456         
1457         len = mimeinfo->length - (content_start - mimeinfo->offset);
1458         if (len < 0)
1459                 len = 0;
1460         procmime_parse_mimepart(mimeinfo,
1461                                 hentry[0].body, hentry[1].body,
1462                                 hentry[2].body, hentry[3].body,
1463                                 hentry[4].body, hentry[5].body,
1464                                 hentry[7].body, hentry[8].body, 
1465                                 mimeinfo->data.filename, content_start,
1466                                 len, short_scan);
1467         
1468         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1469                 g_free(hentry[i].body);
1470                 hentry[i].body = NULL;
1471         }
1472 }
1473
1474 static void procmime_parse_disposition_notification(MimeInfo *mimeinfo, 
1475                 const gchar *original_msgid, const gchar *disposition_notification_hdr,
1476                 gboolean short_scan)
1477 {
1478         HeaderEntry hentry[] = {{"Original-Message-ID:",  NULL, TRUE},
1479                                 {"Disposition:",          NULL, TRUE},
1480                                 {NULL,                    NULL, FALSE}};
1481         guint i;
1482         FILE *fp;
1483         gchar *orig_msg_id = NULL;
1484         gchar *disp = NULL;
1485
1486         procmime_decode_content(mimeinfo);
1487
1488         debug_print("parse disposition notification\n");
1489         fp = claws_fopen(mimeinfo->data.filename, "rb");
1490         if (fp == NULL) {
1491                 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1492                 return;
1493         }
1494         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1495                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1496                 claws_fclose(fp);
1497                 return;
1498         }
1499
1500         if (original_msgid && disposition_notification_hdr) {
1501                 hentry[0].body = g_strdup(original_msgid);
1502                 hentry[1].body = g_strdup(disposition_notification_hdr);
1503         } else {
1504                 procheader_get_header_fields(fp, hentry);
1505         }
1506     
1507         claws_fclose(fp);
1508
1509         if (!hentry[0].body || !hentry[1].body) {
1510                 debug_print("MsgId %s, Disp %s\n",
1511                         hentry[0].body ? hentry[0].body:"(nil)",
1512                         hentry[1].body ? hentry[1].body:"(nil)");
1513                 goto bail;
1514         }
1515
1516         orig_msg_id = g_strdup(hentry[0].body);
1517         disp = g_strdup(hentry[1].body);
1518
1519         extract_parenthesis(orig_msg_id, '<', '>');
1520         remove_space(orig_msg_id);
1521         
1522         if (strstr(disp, "displayed")) {
1523                 /* find sent message, if possible */
1524                 MsgInfo *info = NULL;
1525                 GList *flist;
1526                 debug_print("%s has been displayed.\n", orig_msg_id);
1527                 for (flist = folder_get_list(); flist != NULL; flist = g_list_next(flist)) {
1528                         FolderItem *outbox = ((Folder *)(flist->data))->outbox;
1529                         if (!outbox) {
1530                                 debug_print("skipping folder with no outbox...\n");
1531                                 continue;
1532                         }
1533                         info = folder_item_get_msginfo_by_msgid(outbox, orig_msg_id);
1534                         debug_print("%s %s in %s\n", info?"found":"didn't find", orig_msg_id, outbox->path);
1535                         if (info) {
1536                                 procmsg_msginfo_set_flags(info, MSG_RETRCPT_GOT, 0);
1537                                 procmsg_msginfo_free(&info);
1538                         }
1539                 }
1540         }
1541         g_free(orig_msg_id);
1542         g_free(disp);
1543 bail:
1544         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1545                 g_free(hentry[i].body);
1546                 hentry[i].body = NULL;
1547         }
1548 }
1549
1550 #define GET_HEADERS() {                                         \
1551         procheader_get_header_fields(fp, hentry);               \
1552         if (hentry[0].body != NULL) {                           \
1553                 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);  \
1554                 g_free(hentry[0].body);                         \
1555                 hentry[0].body = tmp;                           \
1556         }                                                       \
1557         if (hentry[2].body != NULL) {                           \
1558                 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);  \
1559                 g_free(hentry[2].body);                         \
1560                 hentry[2].body = tmp;                           \
1561         }                                                       \
1562         if (hentry[4].body != NULL) {                           \
1563                 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);  \
1564                 g_free(hentry[4].body);                         \
1565                 hentry[4].body = tmp;                           \
1566         }                                                       \
1567         if (hentry[5].body != NULL) {                           \
1568                 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);  \
1569                 g_free(hentry[5].body);                         \
1570                 hentry[5].body = tmp;                           \
1571         }                                                       \
1572         if (hentry[6].body != NULL) {                           \
1573                 tmp = conv_unmime_header(hentry[6].body, NULL, FALSE);  \
1574                 g_free(hentry[6].body);                         \
1575                 hentry[6].body = tmp;                           \
1576         }                                                       \
1577         if (hentry[7].body != NULL) {                           \
1578                 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);  \
1579                 g_free(hentry[7].body);                         \
1580                 hentry[7].body = tmp;                           \
1581         }                                                       \
1582 }
1583
1584 static void procmime_parse_multipart(MimeInfo *mimeinfo, gboolean short_scan)
1585 {
1586         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1587                                 {"Content-Transfer-Encoding:",
1588                                                    NULL, FALSE},
1589                                 {"Content-Description:",
1590                                                    NULL, TRUE},
1591                                 {"Content-ID:",
1592                                                    NULL, TRUE},
1593                                 {"Content-Disposition:",
1594                                                    NULL, TRUE},
1595                                 {"Content-Location:",
1596                                                    NULL, TRUE},
1597                                 {"Original-Message-ID:",
1598                                                    NULL, TRUE},
1599                                 {"Disposition:",
1600                                                    NULL, TRUE},
1601                                 {NULL,             NULL, FALSE}};
1602         gchar *tmp;
1603         gchar *boundary;
1604         gint boundary_len = 0, lastoffset = -1, i;
1605         gchar buf[BUFFSIZE];
1606         FILE *fp;
1607         int result = 0;
1608         gboolean start_found = FALSE;
1609         gboolean end_found = FALSE;
1610
1611         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1612         if (!boundary)
1613                 return;
1614         boundary_len = strlen(boundary);
1615
1616         procmime_decode_content(mimeinfo);
1617
1618         fp = claws_fopen(mimeinfo->data.filename, "rb");
1619         if (fp == NULL) {
1620                 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1621                 return;
1622         }
1623
1624         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1625                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1626                 claws_fclose(fp);
1627                 return;
1628         }
1629
1630         while (claws_fgets(buf, sizeof(buf), fp) != NULL && result == 0) {
1631                 if (ftell(fp) - 1 > (mimeinfo->offset + mimeinfo->length))
1632                         break;
1633
1634                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1635                         start_found = TRUE;
1636
1637                         if (lastoffset != -1) {
1638                                 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1639                                 if (len < 0)
1640                                         len = 0;
1641                                 result = procmime_parse_mimepart(mimeinfo,
1642                                                         hentry[0].body, hentry[1].body,
1643                                                         hentry[2].body, hentry[3].body, 
1644                                                         hentry[4].body, hentry[5].body,
1645                                                         hentry[6].body, hentry[7].body,
1646                                                         mimeinfo->data.filename, lastoffset,
1647                                                         len, short_scan);
1648                                 if (result == 1 && short_scan)
1649                                         break;
1650                                 
1651                         } 
1652                         
1653                         if (buf[2 + boundary_len]     == '-' &&
1654                             buf[2 + boundary_len + 1] == '-') {
1655                                 end_found = TRUE;
1656                                 break;
1657                         }
1658                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1659                                 g_free(hentry[i].body);
1660                                 hentry[i].body = NULL;
1661                         }
1662                         GET_HEADERS();
1663                         lastoffset = ftell(fp);
1664                 }
1665         }
1666         
1667         if (start_found && !end_found && lastoffset != -1) {
1668                 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1669
1670                 if (len >= 0) {
1671                         result = procmime_parse_mimepart(mimeinfo,
1672                                         hentry[0].body, hentry[1].body,
1673                                         hentry[2].body, hentry[3].body, 
1674                                         hentry[4].body, hentry[5].body,
1675                                         hentry[6].body, hentry[7].body,
1676                                         mimeinfo->data.filename, lastoffset,
1677                                         len, short_scan);
1678                 }
1679                 mimeinfo->broken = TRUE;
1680         }
1681         
1682         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1683                 g_free(hentry[i].body);
1684                 hentry[i].body = NULL;
1685         }
1686         claws_fclose(fp);
1687 }
1688
1689 static void parse_parameters(const gchar *parameters, GHashTable *table)
1690 {
1691         gchar *params, *param, *next;
1692         GSList *convlist = NULL, *concatlist = NULL, *cur;
1693
1694         params = g_strdup(parameters);
1695         param = params;
1696         next = params;
1697         for (; next != NULL; param = next) {
1698                 gchar *attribute, *value, *tmp, *down_attr, *orig_down_attr;
1699                 gint len;
1700                 gboolean convert = FALSE;
1701
1702                 next = strchr_with_skip_quote(param, '"', ';');
1703                 if (next != NULL) {
1704                         next[0] = '\0';
1705                         next++;
1706                 }
1707
1708                 g_strstrip(param);
1709
1710                 attribute = param;
1711                 value = strchr(attribute, '=');
1712                 if (value == NULL)
1713                         continue;
1714
1715                 value[0] = '\0';
1716                 value++;
1717                 while (value[0] != '\0' && value[0] == ' ')
1718                         value++;
1719
1720                 down_attr = g_utf8_strdown(attribute, -1);
1721                 orig_down_attr = down_attr;
1722         
1723                 len = down_attr ? strlen(down_attr):0;
1724                 if (len > 0 && down_attr[len - 1] == '*') {
1725                         gchar *srcpos, *dstpos, *endpos;
1726
1727                         convert = TRUE;
1728                         down_attr[len - 1] = '\0';
1729
1730                         srcpos = value;
1731                         dstpos = value;
1732                         endpos = value + strlen(value);
1733                         while (srcpos < endpos) {
1734                                 if (*srcpos != '%')
1735                                         *dstpos = *srcpos;
1736                                 else {
1737                                         guchar dstvalue;
1738
1739                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1740                                                 *dstpos = '?';
1741                                         else
1742                                                 *dstpos = dstvalue;
1743                                         srcpos += 2;
1744                                 }
1745                                 srcpos++;
1746                                 dstpos++;
1747                         }
1748                         *dstpos = '\0';
1749                         if (value[0] == '"')
1750                                 extract_quote(value, '"');
1751                 } else {
1752                         if (value[0] == '"')
1753                                 extract_quote(value, '"');
1754                         else if ((tmp = strchr(value, ' ')) != NULL)
1755                                 *tmp = '\0';
1756                 }
1757
1758                 if (down_attr) {
1759                         while (down_attr[0] == ' ')
1760                                 down_attr++;
1761                         while (down_attr[strlen(down_attr)-1] == ' ') 
1762                                 down_attr[strlen(down_attr)-1] = '\0';
1763                 } 
1764
1765                 while (value[0] != '\0' && value[0] == ' ')
1766                         value++;
1767                 while (value[strlen(value)-1] == ' ') 
1768                         value[strlen(value)-1] = '\0';
1769
1770                 if (down_attr && strrchr(down_attr, '*') != NULL) {
1771                         gchar *tmpattr;
1772
1773                         tmpattr = g_strdup(down_attr);
1774                         tmp = strrchr(tmpattr, '*');
1775                         tmp[0] = '\0';
1776
1777                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1778                             (g_slist_find_custom(concatlist, down_attr, (GCompareFunc)g_strcmp0) == NULL))
1779                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1780
1781                         if (convert && (g_slist_find_custom(convlist, tmpattr, (GCompareFunc)g_strcmp0) == NULL))
1782                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1783
1784                         g_free(tmpattr);
1785                 } else if (convert) {
1786                         if (g_slist_find_custom(convlist, down_attr, (GCompareFunc)g_strcmp0) == NULL)
1787                                 convlist = g_slist_prepend(convlist, g_strdup(down_attr));
1788                 }
1789
1790                 if (g_hash_table_lookup(table, down_attr) == NULL)
1791                         g_hash_table_insert(table, g_strdup(down_attr), g_strdup(value));
1792                 g_free(orig_down_attr);
1793         }
1794
1795         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1796                 gchar *attribute, *attrwnum, *partvalue;
1797                 gint n = 0;
1798                 GString *value;
1799
1800                 attribute = (gchar *) cur->data;
1801                 value = g_string_sized_new(64);
1802
1803                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1804                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1805                         g_string_append(value, partvalue);
1806
1807                         g_hash_table_remove(table, attrwnum);
1808                         g_free(attrwnum);
1809                         n++;
1810                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1811                 }
1812                 g_free(attrwnum);
1813
1814                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1815                 g_string_free(value, TRUE);
1816         }
1817         slist_free_strings_full(concatlist);
1818
1819         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1820                 gchar *attribute, *key, *value;
1821                 gchar *charset, *lang, *oldvalue, *newvalue;
1822
1823                 attribute = (gchar *) cur->data;
1824                 if (!g_hash_table_lookup_extended(
1825                         table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
1826                         continue;
1827
1828                 charset = value;
1829                 if (charset == NULL)
1830                         continue;
1831                 lang = strchr(charset, '\'');
1832                 if (lang == NULL)
1833                         continue;
1834                 lang[0] = '\0';
1835                 lang++;
1836                 oldvalue = strchr(lang, '\'');
1837                 if (oldvalue == NULL)
1838                         continue;
1839                 oldvalue[0] = '\0';
1840                 oldvalue++;
1841
1842                 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1843
1844                 g_hash_table_remove(table, attribute);
1845                 g_free(key);
1846                 g_free(value);
1847
1848                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1849         }
1850         slist_free_strings_full(convlist);
1851
1852         g_free(params);
1853 }       
1854
1855 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1856 {
1857         cm_return_if_fail(content_type != NULL);
1858         cm_return_if_fail(mimeinfo != NULL);
1859
1860         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1861          * if it's not available we use the default Content-Type */
1862         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1863                 mimeinfo->type = MIMETYPE_TEXT;
1864                 mimeinfo->subtype = g_strdup("plain");
1865                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1866                                        "charset") == NULL) {
1867                         g_hash_table_insert(mimeinfo->typeparameters,
1868                                     g_strdup("charset"),
1869                                     g_strdup(
1870                                         conv_get_locale_charset_str_no_utf8()));
1871                 }
1872         } else {
1873                 gchar *type, *subtype, *params;
1874
1875                 type = g_strdup(content_type);
1876                 subtype = strchr(type, '/') + 1;
1877                 *(subtype - 1) = '\0';
1878                 if ((params = strchr(subtype, ';')) != NULL) {
1879                         params[0] = '\0';
1880                         params++;
1881                 }
1882
1883                 mimeinfo->type = procmime_get_media_type(type);
1884                 mimeinfo->subtype = g_strstrip(g_strdup(subtype));
1885
1886                 /* Get mimeinfo->typeparameters */
1887                 if (params != NULL)
1888                         parse_parameters(params, mimeinfo->typeparameters);
1889
1890                 g_free(type);
1891         }
1892 }
1893
1894 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1895 {
1896         gchar *tmp, *params;
1897
1898         cm_return_if_fail(content_disposition != NULL);
1899         cm_return_if_fail(mimeinfo != NULL);
1900
1901         tmp = g_strdup(content_disposition);
1902         if ((params = strchr(tmp, ';')) != NULL) {
1903                 params[0] = '\0';
1904                 params++;
1905         }       
1906         g_strstrip(tmp);
1907
1908         if (!g_ascii_strcasecmp(tmp, "inline")) 
1909                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1910         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1911                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1912         else
1913                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1914         
1915         if (params != NULL)
1916                 parse_parameters(params, mimeinfo->dispositionparameters);
1917
1918         g_free(tmp);
1919 }
1920
1921
1922 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1923 {
1924         struct EncodingTable *enc_table;
1925         
1926         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1927                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1928                         mimeinfo->encoding_type = enc_table->enc_type;
1929                         return;
1930                 }
1931         }
1932         mimeinfo->encoding_type = ENC_UNKNOWN;
1933         return;
1934 }
1935
1936 static GSList *registered_parsers = NULL;
1937
1938 static MimeParser *procmime_get_mimeparser_for_type(MimeMediaType type, const gchar *sub_type)
1939 {
1940         GSList *cur;
1941         for (cur = registered_parsers; cur; cur = cur->next) {
1942                 MimeParser *parser = (MimeParser *)cur->data;
1943                 if (parser->type == type && !g_strcmp0(parser->sub_type, sub_type))
1944                         return parser;
1945         }
1946         return NULL;
1947 }
1948
1949 void procmime_mimeparser_register(MimeParser *parser)
1950 {
1951         if (!procmime_get_mimeparser_for_type(parser->type, parser->sub_type))
1952                 registered_parsers = g_slist_append(registered_parsers, parser);
1953 }
1954
1955
1956 void procmime_mimeparser_unregister(MimeParser *parser) 
1957 {
1958         registered_parsers = g_slist_remove(registered_parsers, parser);
1959 }
1960
1961 static gboolean procmime_mimeparser_parse(MimeParser *parser, MimeInfo *mimeinfo)
1962 {
1963         cm_return_val_if_fail(parser->parse != NULL, FALSE);
1964         return parser->parse(parser, mimeinfo); 
1965 }
1966
1967 static int procmime_parse_mimepart(MimeInfo *parent,
1968                              gchar *content_type,
1969                              gchar *content_encoding,
1970                              gchar *content_description,
1971                              gchar *content_id,
1972                              gchar *content_disposition,
1973                              gchar *content_location,
1974                              const gchar *original_msgid,
1975                              const gchar *disposition_notification_hdr,
1976                              const gchar *filename,
1977                              guint offset,
1978                              guint length,
1979                              gboolean short_scan)
1980 {
1981         MimeInfo *mimeinfo;
1982         MimeParser *parser = NULL;
1983         gboolean parsed = FALSE;
1984         int result = 0;
1985
1986         /* Create MimeInfo */
1987         mimeinfo = procmime_mimeinfo_new();
1988         mimeinfo->content = MIMECONTENT_FILE;
1989
1990         if (parent != NULL) {
1991                 if (g_node_depth(parent->node) > 32) {
1992                         /* 32 is an arbitrary value
1993                          * this avoids DOSsing ourselves 
1994                          * with enormous messages
1995                          */
1996                         procmime_mimeinfo_free_all(&mimeinfo);
1997                         return -1;                      
1998                 }
1999                 g_node_append(parent->node, mimeinfo->node);
2000         }
2001         mimeinfo->data.filename = g_strdup(filename);
2002         mimeinfo->offset = offset;
2003         mimeinfo->length = length;
2004
2005         if (content_type != NULL) {
2006                 g_strchomp(content_type);
2007                 procmime_parse_content_type(content_type, mimeinfo);
2008         } else {
2009                 mimeinfo->type = MIMETYPE_TEXT;
2010                 mimeinfo->subtype = g_strdup("plain");
2011                 if (g_hash_table_lookup(mimeinfo->typeparameters,
2012                                        "charset") == NULL) {
2013                         g_hash_table_insert(mimeinfo->typeparameters,
2014                                     g_strdup("charset"),
2015                                     g_strdup(
2016                                         conv_get_locale_charset_str_no_utf8()));
2017                 }
2018         }
2019
2020         if (content_encoding != NULL) {
2021                 g_strchomp(content_encoding);
2022                 procmime_parse_content_encoding(content_encoding, mimeinfo);
2023         } else {
2024                 mimeinfo->encoding_type = ENC_UNKNOWN;
2025         }
2026
2027         if (content_description != NULL)
2028                 mimeinfo->description = g_strdup(content_description);
2029         else
2030                 mimeinfo->description = NULL;
2031
2032         if (content_id != NULL)
2033                 mimeinfo->id = g_strdup(content_id);
2034         else
2035                 mimeinfo->id = NULL;
2036
2037         if (content_location != NULL)
2038                 mimeinfo->location = g_strdup(content_location);
2039         else
2040                 mimeinfo->location = NULL;
2041
2042         if (content_disposition != NULL) {
2043                 g_strchomp(content_disposition);
2044                 procmime_parse_content_disposition(content_disposition, mimeinfo);
2045         } else
2046                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
2047
2048         /* Call parser for mime type */
2049         if ((parser = procmime_get_mimeparser_for_type(mimeinfo->type, mimeinfo->subtype)) != NULL) {
2050                 parsed = procmime_mimeparser_parse(parser, mimeinfo);
2051         } 
2052         if (!parsed) {
2053                 switch (mimeinfo->type) {
2054                 case MIMETYPE_TEXT:
2055                         if (g_ascii_strcasecmp(mimeinfo->subtype, "plain") == 0 && short_scan) {
2056                                 return 1;
2057                         }
2058                         break;
2059
2060                 case MIMETYPE_MESSAGE:
2061                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2062                                 procmime_parse_message_rfc822(mimeinfo, short_scan);
2063                         }
2064                         if (g_ascii_strcasecmp(mimeinfo->subtype, "disposition-notification") == 0) {
2065                                 procmime_parse_disposition_notification(mimeinfo, 
2066                                         original_msgid, disposition_notification_hdr, short_scan);
2067                         }
2068                         break;
2069                         
2070                 case MIMETYPE_MULTIPART:
2071                         procmime_parse_multipart(mimeinfo, short_scan);
2072                         break;
2073                 
2074                 case MIMETYPE_APPLICATION:
2075                         if (g_ascii_strcasecmp(mimeinfo->subtype, "octet-stream") == 0
2076                         && original_msgid && *original_msgid 
2077                         && disposition_notification_hdr && *disposition_notification_hdr) {
2078                                 procmime_parse_disposition_notification(mimeinfo, 
2079                                         original_msgid, disposition_notification_hdr, short_scan);
2080                         }
2081                         break;
2082                 default:
2083                         break;
2084                 }
2085         }
2086
2087         return result;
2088 }
2089
2090 static gchar *typenames[] = {
2091     "text",
2092     "image",
2093     "audio",
2094     "video",
2095     "application",
2096     "message",
2097     "multipart",
2098     "unknown",
2099 };
2100
2101 static gboolean output_func(GNode *node, gpointer data)
2102 {
2103         guint i, depth;
2104         MimeInfo *mimeinfo = (MimeInfo *) node->data;
2105
2106         depth = g_node_depth(node);
2107         for (i = 0; i < depth; i++)
2108                 g_print("    ");
2109         g_print("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
2110
2111         return FALSE;
2112 }
2113
2114 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
2115 {
2116         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
2117 }
2118
2119 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset, gboolean short_scan)
2120 {
2121         MimeInfo *mimeinfo;
2122         GStatBuf buf;
2123
2124         if (g_stat(filename, &buf) < 0) {
2125                 FILE_OP_ERROR(filename, "stat");
2126                 return NULL;
2127         }
2128
2129         mimeinfo = procmime_mimeinfo_new();
2130         mimeinfo->content = MIMECONTENT_FILE;
2131         mimeinfo->encoding_type = ENC_UNKNOWN;
2132         mimeinfo->type = MIMETYPE_MESSAGE;
2133         mimeinfo->subtype = g_strdup("rfc822");
2134         mimeinfo->data.filename = g_strdup(filename);
2135         mimeinfo->offset = offset;
2136         mimeinfo->length = buf.st_size - offset;
2137
2138         procmime_parse_message_rfc822(mimeinfo, short_scan);
2139         if (debug_get_mode())
2140                 output_mime_structure(mimeinfo, 0);
2141
2142         return mimeinfo;
2143 }
2144
2145 static MimeInfo *procmime_scan_file_full(const gchar *filename, gboolean short_scan)
2146 {
2147         MimeInfo *mimeinfo;
2148
2149         cm_return_val_if_fail(filename != NULL, NULL);
2150
2151         mimeinfo = procmime_scan_file_with_offset(filename, 0, short_scan);
2152
2153         return mimeinfo;
2154 }
2155
2156 MimeInfo *procmime_scan_file(const gchar *filename)
2157 {
2158         return procmime_scan_file_full(filename, FALSE);
2159 }
2160
2161 static MimeInfo *procmime_scan_file_short(const gchar *filename)
2162 {
2163         return procmime_scan_file_full(filename, TRUE);
2164 }
2165
2166 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan)
2167 {
2168         FILE *fp;
2169         MimeInfo *mimeinfo;
2170         gchar buf[BUFFSIZE];
2171         gint offset = 0;
2172
2173         cm_return_val_if_fail(filename != NULL, NULL);
2174
2175         /* Open file */
2176         if ((fp = claws_fopen(filename, "rb")) == NULL)
2177                 return NULL;
2178         /* Skip queue header */
2179         while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2180                 /* new way */
2181                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
2182                         strlen("X-Claws-End-Special-Headers:"))) ||
2183                    (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
2184                         strlen("X-Sylpheed-End-Special-Headers:"))))
2185                         break;
2186                 /* old way */
2187                 if (buf[0] == '\r' || buf[0] == '\n') break;
2188                 /* from other mailers */
2189                 if (!strncmp(buf, "Date: ", 6)
2190                 ||  !strncmp(buf, "To: ", 4)
2191                 ||  !strncmp(buf, "From: ", 6)
2192                 ||  !strncmp(buf, "Subject: ", 9)) {
2193                         rewind(fp);
2194                         break;
2195                 }
2196         }
2197         offset = ftell(fp);
2198         claws_fclose(fp);
2199
2200         mimeinfo = procmime_scan_file_with_offset(filename, offset, short_scan);
2201
2202         return mimeinfo;
2203 }
2204
2205 MimeInfo *procmime_scan_queue_file(const gchar *filename)
2206 {
2207         return procmime_scan_queue_file_full(filename, FALSE);
2208 }
2209
2210 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename)
2211 {
2212         return procmime_scan_queue_file_full(filename, TRUE);
2213 }
2214
2215 typedef enum {
2216     ENC_AS_TOKEN,
2217     ENC_AS_QUOTED_STRING,
2218     ENC_AS_EXTENDED,
2219     ENC_AS_ENCWORD
2220 } EncodeAs;
2221
2222 typedef struct _ParametersData {
2223         FILE *fp;
2224         guint len;
2225         gint error;
2226 } ParametersData;
2227
2228 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
2229 {
2230         gchar *param = key;
2231         gchar *val = value, *valpos, *tmp;
2232         ParametersData *pdata = (ParametersData *)user_data;
2233         GString *buf = g_string_new("");
2234         gint len;
2235
2236         EncodeAs encas = ENC_AS_TOKEN;
2237
2238         for (valpos = val; *valpos != 0; valpos++) {
2239                 if (!IS_ASCII(*valpos)) {
2240                         encas = ENC_AS_ENCWORD;
2241                         break;
2242                 }
2243             
2244                 /* CTLs */
2245                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
2246                         encas = ENC_AS_QUOTED_STRING;
2247                         continue;
2248                 }
2249
2250                 /* tspecials + SPACE */
2251                 switch (*valpos) {
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                 case '=':
2268                         encas = ENC_AS_QUOTED_STRING;
2269                         continue;
2270                 }
2271         }
2272         
2273         switch (encas) {
2274         case ENC_AS_TOKEN:
2275                 g_string_append_printf(buf, "%s=%s", param, val);
2276                 break;
2277
2278         case ENC_AS_QUOTED_STRING:
2279                 g_string_append_printf(buf, "%s=\"%s\"", param, val);
2280                 break;
2281
2282 #if 0 /* we don't use that for now */
2283         case ENC_AS_EXTENDED:
2284                 if (!g_utf8_validate(val, -1, NULL))
2285                         g_string_append_printf(buf, "%s*=%s''", param,
2286                                 conv_get_locale_charset_str());
2287                 else
2288                         g_string_append_printf(buf, "%s*=%s''", param,
2289                                 CS_INTERNAL);
2290                 for (valpos = val; *valpos != '\0'; valpos++) {
2291                         if (IS_ASCII(*valpos) && isalnum(*valpos)) {
2292                                 g_string_append_printf(buf, "%c", *valpos);
2293                         } else {
2294                                 gchar hexstr[3] = "XX";
2295                                 get_hex_str(hexstr, *valpos);
2296                                 g_string_append_printf(buf, "%%%s", hexstr);
2297                         }
2298                 }
2299                 break;
2300 #else
2301         case ENC_AS_EXTENDED:
2302                 debug_print("Unhandled ENC_AS_EXTENDED.\n");
2303                 break;
2304 #endif
2305         case ENC_AS_ENCWORD:
2306                 len = MAX(strlen(val)*6, 512);
2307                 tmp = g_malloc(len+1);
2308                 codeconv_set_strict(TRUE);
2309                 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2310                         prefs_common.outgoing_charset);
2311                 codeconv_set_strict(FALSE);
2312                 if (!tmp || !*tmp) {
2313                         codeconv_set_strict(TRUE);
2314                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2315                                 conv_get_outgoing_charset_str());
2316                         codeconv_set_strict(FALSE);
2317                 }
2318                 if (!tmp || !*tmp) {
2319                         codeconv_set_strict(TRUE);
2320                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2321                                 CS_UTF_8);
2322                         codeconv_set_strict(FALSE);
2323                 }
2324                 if (!tmp || !*tmp) {
2325                         conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2326                                 CS_UTF_8);
2327                 }
2328                 g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
2329                 g_free(tmp);
2330                 break;
2331
2332         }
2333         
2334         if (buf->str && strlen(buf->str)) {
2335                 tmp = strstr(buf->str, "\n");
2336                 if (tmp)
2337                         len = (tmp - buf->str);
2338                 else
2339                         len = strlen(buf->str);
2340                 if (pdata->len + len > 76) {
2341                         if (fprintf(pdata->fp, ";\n %s", buf->str) < 0)
2342                                 pdata->error = TRUE;
2343                         pdata->len = strlen(buf->str) + 1;
2344                 } else {
2345                         if (fprintf(pdata->fp, "; %s", buf->str) < 0)
2346                                 pdata->error = TRUE;
2347                         pdata->len += strlen(buf->str) + 2;
2348                 }
2349         }
2350         g_string_free(buf, TRUE);
2351 }
2352
2353 #define TRY(func) { \
2354         if (!(func)) { \
2355                 return -1; \
2356         } \
2357 }
2358
2359 int procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
2360 {
2361         struct TypeTable *type_table;
2362         ParametersData *pdata = g_new0(ParametersData, 1);
2363         debug_print("procmime_write_mime_header\n");
2364         
2365         pdata->fp = fp;
2366         pdata->error = FALSE;
2367         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
2368                 if (mimeinfo->type == type_table->type) {
2369                         gchar *buf = g_strdup_printf(
2370                                 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
2371                         if (fprintf(fp, "%s", buf) < 0) {
2372                                 g_free(buf);
2373                                 g_free(pdata);
2374                                 return -1;
2375                         }
2376                         pdata->len = strlen(buf);
2377                         g_free(buf);
2378                         break;
2379                 }
2380         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
2381         if (pdata->error == TRUE) {
2382                 g_free(pdata);
2383                 return -1;
2384         }
2385         g_free(pdata);
2386
2387         TRY(fprintf(fp, "\n") >= 0);
2388
2389         if (mimeinfo->encoding_type != ENC_UNKNOWN)
2390                 TRY(fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type)) >= 0);
2391
2392         if (mimeinfo->description != NULL)
2393                 TRY(fprintf(fp, "Content-Description: %s\n", mimeinfo->description) >= 0);
2394
2395         if (mimeinfo->id != NULL)
2396                 TRY(fprintf(fp, "Content-ID: %s\n", mimeinfo->id) >= 0);
2397
2398         if (mimeinfo->location != NULL)
2399                 TRY(fprintf(fp, "Content-Location: %s\n", mimeinfo->location) >= 0);
2400
2401         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
2402                 ParametersData *pdata = g_new0(ParametersData, 1);
2403                 gchar *buf = NULL;
2404                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
2405                         buf = g_strdup("Content-Disposition: inline");
2406                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
2407                         buf = g_strdup("Content-Disposition: attachment");
2408                 else
2409                         buf = g_strdup("Content-Disposition: unknown");
2410
2411                 if (fprintf(fp, "%s", buf) < 0) {
2412                         g_free(buf);
2413                         g_free(pdata);
2414                         return -1;
2415                 }
2416                 pdata->len = strlen(buf);
2417                 g_free(buf);
2418
2419                 pdata->fp = fp;
2420                 pdata->error = FALSE;
2421                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
2422                 if (pdata->error == TRUE) {
2423                         g_free(pdata);
2424                         return -1;
2425                 }
2426                 g_free(pdata);
2427                 TRY(fprintf(fp, "\n") >= 0);
2428         }
2429
2430         TRY(fprintf(fp, "\n") >= 0);
2431         
2432         return 0;
2433 }
2434
2435 static gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
2436 {
2437         FILE *infp;
2438         GNode *childnode;
2439         MimeInfo *child;
2440         gchar buf[BUFFSIZE];
2441         gboolean skip = FALSE;;
2442         size_t len;
2443
2444         debug_print("procmime_write_message_rfc822\n");
2445
2446         /* write header */
2447         switch (mimeinfo->content) {
2448         case MIMECONTENT_FILE:
2449                 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2450                         FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2451                         return -1;
2452                 }
2453                 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2454                         FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2455                         claws_fclose(infp);
2456                         return -1;
2457                 }
2458                 while (claws_fgets(buf, sizeof(buf), infp) == buf) {
2459                         strcrchomp(buf);
2460                         if (buf[0] == '\n' && buf[1] == '\0')
2461                                 break;
2462                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
2463                                 continue;
2464                         if (g_ascii_strncasecmp(buf, "MIME-Version:", 13) == 0 ||
2465                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
2466                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
2467                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
2468                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
2469                             g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 ||
2470                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
2471                                 skip = TRUE;
2472                                 continue;
2473                         }
2474                         len = strlen(buf);
2475                         if (claws_fwrite(buf, sizeof(gchar), len, fp) < len) {
2476                                 g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from file", len);
2477                                 claws_fclose(infp);
2478                                 return -1;
2479                         }
2480                         skip = FALSE;
2481                 }
2482                 claws_fclose(infp);
2483                 break;
2484
2485         case MIMECONTENT_MEM:
2486                 len = strlen(mimeinfo->data.mem);
2487                 if (claws_fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) {
2488                         g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from mem", len);
2489                         return -1;
2490                 }
2491                 break;
2492
2493         default:
2494                 break;
2495         }
2496
2497         childnode = mimeinfo->node->children;
2498         if (childnode == NULL)
2499                 return -1;
2500
2501         child = (MimeInfo *) childnode->data;
2502         if (fprintf(fp, "MIME-Version: 1.0\n") < 0) {
2503                 g_warning("failed to write mime version");
2504                 return -1;
2505         }
2506         if (procmime_write_mime_header(child, fp) < 0)
2507                 return -1;
2508         return procmime_write_mimeinfo(child, fp);
2509 }
2510
2511 static gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
2512 {
2513         FILE *infp;
2514         GNode *childnode;
2515         gchar *boundary, *str, *str2;
2516         gchar buf[BUFFSIZE];
2517         gboolean firstboundary;
2518         size_t len;
2519
2520         debug_print("procmime_write_multipart\n");
2521
2522         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
2523
2524         switch (mimeinfo->content) {
2525         case MIMECONTENT_FILE:
2526                 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2527                         FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2528                         return -1;
2529                 }
2530                 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2531                         FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2532                         claws_fclose(infp);
2533                         return -1;
2534                 }
2535                 while (claws_fgets(buf, sizeof(buf), infp) == buf) {
2536                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
2537                                 break;
2538                         len = strlen(buf);
2539                         if (claws_fwrite(buf, sizeof(gchar), len, fp) < len) {
2540                                 g_warning("failed to write %"G_GSIZE_FORMAT, len);
2541                                 claws_fclose(infp);
2542                                 return -1;
2543                         }
2544                 }
2545                 claws_fclose(infp);
2546                 break;
2547
2548         case MIMECONTENT_MEM:
2549                 str = g_strdup(mimeinfo->data.mem);
2550                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
2551                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
2552                         *(str2 - 2) = '\0';
2553                 len = strlen(str);
2554                 if (claws_fwrite(str, sizeof(gchar), len, fp) < len) {
2555                         g_warning("failed to write %"G_GSIZE_FORMAT" from mem", len);
2556                         g_free(str);
2557                         return -1;
2558                 }
2559                 g_free(str);
2560                 break;
2561
2562         default:
2563                 break;
2564         }
2565
2566         childnode = mimeinfo->node->children;
2567         firstboundary = TRUE;
2568         while (childnode != NULL) {
2569                 MimeInfo *child = childnode->data;
2570
2571                 if (firstboundary)
2572                         firstboundary = FALSE;
2573                 else
2574                         TRY(fprintf(fp, "\n") >= 0);
2575                         
2576                 TRY(fprintf(fp, "--%s\n", boundary) >= 0);
2577
2578                 if (procmime_write_mime_header(child, fp) < 0)
2579                         return -1;
2580                 if (procmime_write_mimeinfo(child, fp) < 0)
2581                         return -1;
2582
2583                 childnode = g_node_next_sibling(childnode);
2584         }       
2585         TRY(fprintf(fp, "\n--%s--\n", boundary) >= 0);
2586
2587         return 0;
2588 }
2589
2590 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
2591 {
2592         FILE *infp;
2593         size_t len;
2594         debug_print("procmime_write_mimeinfo\n");
2595
2596         if (G_NODE_IS_LEAF(mimeinfo->node)) {
2597                 switch (mimeinfo->content) {
2598                 case MIMECONTENT_FILE:
2599                         if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2600                                 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2601                                 return -1;
2602                         }
2603                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
2604                         claws_fclose(infp);
2605                         return 0;
2606
2607                 case MIMECONTENT_MEM:
2608                         len = strlen(mimeinfo->data.mem);
2609                         if (claws_fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len)
2610                                 return -1;
2611                         return 0;
2612
2613                 default:
2614                         return 0;
2615                 }
2616         } else {
2617                 /* Call writer for mime type */
2618                 switch (mimeinfo->type) {
2619                 case MIMETYPE_MESSAGE:
2620                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2621                                 return procmime_write_message_rfc822(mimeinfo, fp);
2622                         }
2623                         break;
2624                         
2625                 case MIMETYPE_MULTIPART:
2626                         return procmime_write_multipart(mimeinfo, fp);
2627                         
2628                 default:
2629                         break;
2630                 }
2631
2632                 return -1;
2633         }
2634
2635         return 0;
2636 }
2637
2638 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
2639 {
2640         gchar *base;
2641
2642         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
2643                 base = g_strdup("mimetmp.html");
2644         else {
2645                 const gchar *basetmp;
2646                 gchar *basename;
2647
2648                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
2649                 if (basetmp == NULL)
2650                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
2651                 if (basetmp == NULL)
2652                         basetmp = "mimetmp";
2653                 basename = g_path_get_basename(basetmp);
2654                 if (*basename == '\0') {
2655                         g_free(basename);
2656                         basename = g_strdup("mimetmp");
2657                 }
2658                 base = conv_filename_from_utf8(basename);
2659                 g_free(basename);
2660                 subst_for_shellsafe_filename(base);
2661         }
2662         
2663         return base;
2664 }
2665
2666 void *procmime_get_part_as_string(MimeInfo *mimeinfo,
2667                 gboolean null_terminate)
2668 {
2669         FILE *infp;
2670         gchar *data;
2671         gint length, readlength;
2672
2673         cm_return_val_if_fail(mimeinfo != NULL, NULL);
2674
2675         if (mimeinfo->encoding_type != ENC_BINARY &&
2676                         !procmime_decode_content(mimeinfo))
2677                 return NULL;
2678
2679         if (mimeinfo->content == MIMECONTENT_MEM)
2680                 return g_strdup(mimeinfo->data.mem);
2681
2682         if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2683                 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2684                 return NULL;
2685         }
2686
2687         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2688                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2689                 claws_fclose(infp);
2690                 return NULL;
2691         }
2692
2693         length = mimeinfo->length;
2694
2695         data = g_malloc(null_terminate ? length + 1 : length);
2696         if (data == NULL) {
2697                 g_warning("Could not allocate %d bytes for procmime_get_part_as_string.\n",
2698                                 (null_terminate ? length + 1 : length));
2699                 claws_fclose(infp);
2700                 return NULL;
2701         }
2702
2703         readlength = claws_fread(data, length, 1, infp);
2704         if (readlength <= 0) {
2705                 FILE_OP_ERROR(mimeinfo->data.filename, "fread");
2706                 g_free(data);
2707                 claws_fclose(infp);
2708                 return NULL;
2709         }
2710
2711         claws_fclose(infp);
2712
2713         if (null_terminate)
2714                 data[length] = '\0';
2715
2716         return data;
2717 }
2718
2719 /* Returns an open GInputStream. The caller should just
2720  * read mimeinfo->length bytes from it and then release it. */
2721 GInputStream *procmime_get_part_as_inputstream(MimeInfo *mimeinfo,
2722                 GError **error)
2723 {
2724         cm_return_val_if_fail(mimeinfo != NULL, NULL);
2725
2726         if (mimeinfo->encoding_type != ENC_BINARY &&
2727                         !procmime_decode_content(mimeinfo))
2728                 return NULL;
2729
2730         if (mimeinfo->content == MIMECONTENT_MEM) {
2731                 /* NULL for destroy func, since we're not copying
2732                  * the data for the stream. */
2733                 return g_memory_input_stream_new_from_data(
2734                                 mimeinfo->data.mem,
2735                                 mimeinfo->length, NULL);
2736         } else {
2737                 return g_memory_input_stream_new_from_data(
2738                                 procmime_get_part_as_string(mimeinfo, FALSE),
2739                                 mimeinfo->length, g_free);
2740         }
2741 }