remove typo semi-colons
[claws.git] / src / procmime.c
blobb2c33c453c6017209a10d30f71a974126a155e14
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2021 the Claws Mail Team and Hiroyuki Yamamoto
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.
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.
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <stdio.h>
26 #include "defs.h"
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>
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"
57 #ifdef G_OS_WIN32
58 #include "w32_reg.h"
59 #define REG_MIME_TYPE_VALUE "Content Type"
60 #endif
62 static GHashTable *procmime_get_mime_type_table (void);
63 static MimeInfo *procmime_scan_file_short(const gchar *filename);
64 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename);
65 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan);
67 MimeInfo *procmime_mimeinfo_new(void)
69 MimeInfo *mimeinfo;
71 mimeinfo = g_new0(MimeInfo, 1);
73 mimeinfo->content = MIMECONTENT_EMPTY;
74 mimeinfo->data.filename = NULL;
76 mimeinfo->type = MIMETYPE_UNKNOWN;
77 mimeinfo->encoding_type = ENC_UNKNOWN;
78 mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
80 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
81 mimeinfo->dispositionparameters
82 = g_hash_table_new(g_str_hash, g_str_equal);
84 mimeinfo->node = g_node_new(mimeinfo);
86 return mimeinfo;
89 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
91 g_free(key);
92 g_free(value);
94 return TRUE;
97 static gchar *forced_charset = NULL;
99 void procmime_force_charset(const gchar *str)
101 g_free(forced_charset);
102 forced_charset = NULL;
103 if (str)
104 forced_charset = g_strdup(str);
107 static EncodingType forced_encoding = 0;
109 void procmime_force_encoding(EncodingType encoding)
111 forced_encoding = encoding;
114 static gboolean free_func(GNode *node, gpointer data)
116 MimeInfo *mimeinfo = (MimeInfo *) node->data;
118 switch (mimeinfo->content) {
119 case MIMECONTENT_FILE:
120 if (mimeinfo->tmp)
121 claws_unlink(mimeinfo->data.filename);
122 g_free(mimeinfo->data.filename);
123 break;
125 case MIMECONTENT_MEM:
126 if (mimeinfo->tmp)
127 g_free(mimeinfo->data.mem);
128 default:
129 break;
132 g_free(mimeinfo->subtype);
133 g_free(mimeinfo->description);
134 g_free(mimeinfo->id);
135 g_free(mimeinfo->location);
137 g_hash_table_foreach_remove(mimeinfo->typeparameters,
138 procmime_mimeinfo_parameters_destroy, NULL);
139 g_hash_table_destroy(mimeinfo->typeparameters);
140 g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
141 procmime_mimeinfo_parameters_destroy, NULL);
142 g_hash_table_destroy(mimeinfo->dispositionparameters);
144 if (mimeinfo->privacy)
145 privacy_free_privacydata(mimeinfo->privacy);
147 g_free(mimeinfo);
149 return FALSE;
152 void procmime_mimeinfo_free_all(MimeInfo **mimeinfo_ptr)
154 MimeInfo *mimeinfo = *mimeinfo_ptr;
155 GNode *node;
157 if (!mimeinfo)
158 return;
160 node = mimeinfo->node;
161 g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
163 g_node_destroy(node);
165 *mimeinfo_ptr = NULL;
168 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
170 cm_return_val_if_fail(mimeinfo != NULL, NULL);
171 cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
173 if (mimeinfo->node->parent == NULL)
174 return NULL;
175 return (MimeInfo *) mimeinfo->node->parent->data;
178 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
180 cm_return_val_if_fail(mimeinfo != NULL, NULL);
181 cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
183 if (mimeinfo->node->children)
184 return (MimeInfo *) mimeinfo->node->children->data;
185 if (mimeinfo->node->next)
186 return (MimeInfo *) mimeinfo->node->next->data;
188 if (mimeinfo->node->parent == NULL)
189 return NULL;
191 while (mimeinfo->node->parent != NULL) {
192 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
193 if (mimeinfo->node->next)
194 return (MimeInfo *) mimeinfo->node->next->data;
197 return NULL;
200 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
202 gchar *filename;
203 MimeInfo *mimeinfo;
205 filename = procmsg_get_message_file_path(msginfo);
206 if (!filename || !is_file_exist(filename)) {
207 g_free(filename);
208 return NULL;
211 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
212 !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
213 mimeinfo = procmime_scan_file(filename);
214 else
215 mimeinfo = procmime_scan_queue_file(filename);
216 g_free(filename);
218 return mimeinfo;
221 MimeInfo *procmime_scan_message_short(MsgInfo *msginfo)
223 gchar *filename;
224 MimeInfo *mimeinfo;
226 filename = procmsg_get_message_file_path(msginfo);
227 if (!filename || !is_file_exist(filename)) {
228 g_free(filename);
229 return NULL;
232 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
233 !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
234 mimeinfo = procmime_scan_file_short(filename);
235 else
236 mimeinfo = procmime_scan_queue_file_short(filename);
237 g_free(filename);
239 return mimeinfo;
242 enum
244 H_CONTENT_TRANSFER_ENCODING = 0,
245 H_CONTENT_TYPE = 1,
246 H_CONTENT_DISPOSITION = 2,
247 H_CONTENT_DESCRIPTION = 3,
248 H_SUBJECT = 4
251 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
253 const gchar *value;
255 cm_return_val_if_fail(mimeinfo != NULL, NULL);
256 cm_return_val_if_fail(name != NULL, NULL);
258 value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
259 if (value == NULL)
260 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
262 return value;
265 #define FLUSH_LASTLINE() { \
266 if (*lastline != '\0') { \
267 gint llen = 0; \
268 strretchomp(lastline); \
269 llen = strlen(lastline); \
270 if (lastline[llen-1] == ' ' && !account_sigsep_matchlist_str_found(lastline, "%s") && \
271 !(llen == 2 && lastline[1] == ' ' && strchr(prefs_common.quote_chars, lastline[0]))) { \
272 /* this is flowed */ \
273 if (delsp) \
274 lastline[llen-1] = '\0'; \
275 if (claws_fputs(lastline, outfp) == EOF) \
276 err = TRUE; \
277 } else { \
278 if (claws_fputs(lastline, outfp) == EOF) \
279 err = TRUE; \
280 if (claws_fputs("\n", outfp) == EOF) \
281 err = TRUE; \
284 strcpy(lastline, buf); \
287 gboolean procmime_decode_content(MimeInfo *mimeinfo)
289 gchar buf[BUFFSIZE];
290 gint readend;
291 gchar *tmpfilename;
292 FILE *outfp, *infp;
293 GStatBuf statbuf;
294 gboolean tmp_file = FALSE;
295 gboolean flowed = FALSE;
296 gboolean delsp = FALSE;
297 gboolean err = FALSE;
298 gint state = 0;
299 guint save = 0;
301 cm_return_val_if_fail(mimeinfo != NULL, FALSE);
303 EncodingType encoding = forced_encoding
304 ? forced_encoding
305 : mimeinfo->encoding_type;
306 gchar lastline[BUFFSIZE];
307 memset(lastline, 0, BUFFSIZE);
309 if (prefs_common.respect_flowed_format &&
310 mimeinfo->type == MIMETYPE_TEXT &&
311 !strcasecmp(mimeinfo->subtype, "plain")) {
312 if (procmime_mimeinfo_get_parameter(mimeinfo, "format") != NULL &&
313 !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "format"),"flowed"))
314 flowed = TRUE;
315 if (flowed &&
316 procmime_mimeinfo_get_parameter(mimeinfo, "delsp") != NULL &&
317 !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "delsp"),"yes"))
318 delsp = TRUE;
321 if (!flowed && (
322 encoding == ENC_UNKNOWN ||
323 encoding == ENC_BINARY ||
324 encoding == ENC_7BIT ||
325 encoding == ENC_8BIT
327 return TRUE;
329 if (mimeinfo->type == MIMETYPE_MULTIPART || mimeinfo->type == MIMETYPE_MESSAGE)
330 return TRUE;
332 if (mimeinfo->data.filename == NULL)
333 return FALSE;
335 infp = claws_fopen(mimeinfo->data.filename, "rb");
336 if (!infp) {
337 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
338 return FALSE;
340 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
341 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
342 claws_fclose(infp);
343 return FALSE;
346 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
347 if (!outfp) {
348 perror("tmpfile");
349 claws_fclose(infp);
350 return FALSE;
353 tmp_file = TRUE;
354 readend = mimeinfo->offset + mimeinfo->length;
356 account_sigsep_matchlist_create(); /* FLUSH_LASTLINE will use it */
358 *buf = '\0';
359 if (encoding == ENC_QUOTED_PRINTABLE) {
360 while ((ftell(infp) < readend) && (claws_fgets(buf, sizeof(buf), infp) != NULL)) {
361 gint len;
362 len = qp_decode_line(buf);
363 buf[len] = '\0';
364 if (!flowed) {
365 if (claws_fwrite(buf, 1, len, outfp) < len)
366 err = TRUE;
367 } else {
368 FLUSH_LASTLINE();
371 if (flowed)
372 FLUSH_LASTLINE();
373 } else if (encoding == ENC_BASE64) {
374 gchar outbuf[BUFFSIZE + 1];
375 gint len, inlen, inread;
376 gboolean got_error = FALSE;
377 gboolean uncanonicalize = FALSE;
378 FILE *tmpfp = NULL;
379 gboolean null_bytes = FALSE;
380 gboolean starting = TRUE;
382 if (mimeinfo->type == MIMETYPE_TEXT ||
383 mimeinfo->type == MIMETYPE_MESSAGE) {
384 uncanonicalize = TRUE;
385 tmpfp = my_tmpfile();
386 if (!tmpfp) {
387 perror("my_tmpfile");
388 if (tmp_file)
389 claws_fclose(outfp);
390 claws_fclose(infp);
391 return FALSE;
393 } else
394 tmpfp = outfp;
396 while ((inlen = MIN(readend - ftell(infp), sizeof(buf))) > 0 && !err) {
397 inread = claws_fread(buf, 1, inlen, infp);
398 memset(outbuf, 0, sizeof(buf));
399 len = g_base64_decode_step(buf, inlen, outbuf, &state, &save);
400 if (uncanonicalize == TRUE && strlen(outbuf) < len && starting) {
401 uncanonicalize = FALSE;
402 null_bytes = TRUE;
404 starting = FALSE;
405 if (((inread != inlen) || len < 0) && !got_error) {
406 g_warning("bad BASE64 content");
407 if (claws_fwrite(_("[Error decoding BASE64]\n"),
408 sizeof(gchar),
409 strlen(_("[Error decoding BASE64]\n")),
410 tmpfp) < strlen(_("[Error decoding BASE64]\n")))
411 g_warning("error decoding BASE64");
412 got_error = TRUE;
413 continue;
414 } else if (len >= 0) {
415 /* print out the error message only once
416 * per block */
417 if (null_bytes) {
418 /* we won't uncanonicalize, output to outfp directly */
419 if (claws_fwrite(outbuf, sizeof(gchar), len, outfp) < len)
420 err = TRUE;
421 } else {
422 if (claws_fwrite(outbuf, sizeof(gchar), len, tmpfp) < len)
423 err = TRUE;
425 got_error = FALSE;
429 if (uncanonicalize) {
430 rewind(tmpfp);
431 while (claws_fgets(buf, sizeof(buf), tmpfp) != NULL) {
432 strcrchomp(buf);
433 if (claws_fputs(buf, outfp) == EOF)
434 err = TRUE;
437 if (tmpfp != outfp) {
438 claws_fclose(tmpfp);
440 } else if (encoding == ENC_X_UUENCODE) {
441 gchar outbuf[BUFFSIZE];
442 gint len;
443 gboolean flag = FALSE;
445 while ((ftell(infp) < readend) && (claws_fgets(buf, sizeof(buf), infp) != NULL)) {
446 if (!flag && strncmp(buf,"begin ", 6)) continue;
448 if (flag) {
449 len = fromuutobits(outbuf, buf);
450 if (len <= 0) {
451 if (len < 0)
452 g_warning("bad UUENCODE content (%d)", len);
453 break;
455 if (claws_fwrite(outbuf, sizeof(gchar), len, outfp) < len)
456 err = TRUE;
457 } else
458 flag = TRUE;
460 } else {
461 while ((ftell(infp) < readend) && (claws_fgets(buf, sizeof(buf), infp) != NULL)) {
462 if (!flowed) {
463 if (claws_fputs(buf, outfp) == EOF)
464 err = TRUE;
465 } else {
466 FLUSH_LASTLINE();
469 if (flowed)
470 FLUSH_LASTLINE();
471 if (err == TRUE)
472 g_warning("write error");
475 claws_fclose(outfp);
476 claws_fclose(infp);
478 account_sigsep_matchlist_delete();
480 if (err == TRUE) {
481 return FALSE;
484 if (g_stat(tmpfilename, &statbuf) < 0) {
485 FILE_OP_ERROR(tmpfilename, "stat");
486 return FALSE;
489 if (mimeinfo->tmp)
490 claws_unlink(mimeinfo->data.filename);
491 g_free(mimeinfo->data.filename);
492 mimeinfo->data.filename = tmpfilename;
493 mimeinfo->tmp = TRUE;
494 mimeinfo->offset = 0;
495 mimeinfo->length = statbuf.st_size;
496 mimeinfo->encoding_type = ENC_BINARY;
498 return TRUE;
501 #define B64_LINE_SIZE 57
502 #define B64_BUFFSIZE 77
504 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
506 FILE *infp = NULL, *outfp;
507 gint len;
508 gchar *tmpfilename;
509 GStatBuf statbuf;
510 gboolean err = FALSE;
512 if (mimeinfo->content == MIMECONTENT_EMPTY)
513 return TRUE;
515 if (mimeinfo->encoding_type != ENC_UNKNOWN &&
516 mimeinfo->encoding_type != ENC_BINARY &&
517 mimeinfo->encoding_type != ENC_7BIT &&
518 mimeinfo->encoding_type != ENC_8BIT)
519 if(!procmime_decode_content(mimeinfo))
520 return FALSE;
522 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
523 if (!outfp) {
524 perror("tmpfile");
525 return FALSE;
528 if (mimeinfo->content == MIMECONTENT_FILE && mimeinfo->data.filename) {
529 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
530 g_warning("can't open file %s", mimeinfo->data.filename);
531 claws_fclose(outfp);
532 return FALSE;
534 } else if (mimeinfo->content == MIMECONTENT_MEM) {
535 infp = str_open_as_stream(mimeinfo->data.mem);
536 if (infp == NULL) {
537 claws_fclose(outfp);
538 return FALSE;
540 } else {
541 claws_fclose(outfp);
542 g_warning("unknown mimeinfo");
543 return FALSE;
546 if (encoding == ENC_BASE64) {
547 gchar inbuf[B64_LINE_SIZE], *out;
548 FILE *tmp_fp = infp;
549 gchar *tmp_file = NULL;
551 if (mimeinfo->type == MIMETYPE_TEXT ||
552 mimeinfo->type == MIMETYPE_MESSAGE) {
553 if (mimeinfo->content == MIMECONTENT_FILE) {
554 tmp_file = get_tmp_file();
555 if (canonicalize_file(mimeinfo->data.filename, tmp_file) < 0) {
556 g_free(tmp_file);
557 claws_fclose(infp);
558 claws_fclose(outfp);
559 return FALSE;
561 if ((tmp_fp = claws_fopen(tmp_file, "rb")) == NULL) {
562 FILE_OP_ERROR(tmp_file, "claws_fopen");
563 claws_unlink(tmp_file);
564 g_free(tmp_file);
565 claws_fclose(infp);
566 claws_fclose(outfp);
567 return FALSE;
569 } else {
570 gchar *out = canonicalize_str(mimeinfo->data.mem);
571 claws_fclose(infp);
572 infp = str_open_as_stream(out);
573 tmp_fp = infp;
574 g_free(out);
575 if (infp == NULL) {
576 claws_fclose(outfp);
577 return FALSE;
582 while ((len = claws_fread(inbuf, sizeof(gchar),
583 B64_LINE_SIZE, tmp_fp))
584 == B64_LINE_SIZE) {
585 out = g_base64_encode(inbuf, B64_LINE_SIZE);
586 if (claws_fputs(out, outfp) == EOF)
587 err = TRUE;
588 g_free(out);
589 if (claws_fputc('\n', outfp) == EOF)
590 err = TRUE;
592 if (len > 0 && claws_feof(tmp_fp)) {
593 out = g_base64_encode(inbuf, len);
594 if (claws_fputs(out, outfp) == EOF)
595 err = TRUE;
596 g_free(out);
597 if (claws_fputc('\n', outfp) == EOF)
598 err = TRUE;
601 if (tmp_file) {
602 claws_fclose(tmp_fp);
603 claws_unlink(tmp_file);
604 g_free(tmp_file);
606 } else if (encoding == ENC_QUOTED_PRINTABLE) {
607 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
609 while (claws_fgets(inbuf, sizeof(inbuf), infp) != NULL) {
610 qp_encode_line(outbuf, inbuf);
612 if (!strncmp("From ", outbuf, sizeof("From ")-1)) {
613 gchar *tmpbuf = outbuf;
615 tmpbuf += sizeof("From ")-1;
617 if (claws_fputs("=46rom ", outfp) == EOF)
618 err = TRUE;
619 if (claws_fputs(tmpbuf, outfp) == EOF)
620 err = TRUE;
621 } else {
622 if (claws_fputs(outbuf, outfp) == EOF)
623 err = TRUE;
626 } else {
627 gchar buf[BUFFSIZE];
629 while (claws_fgets(buf, sizeof(buf), infp) != NULL) {
630 strcrchomp(buf);
631 if (claws_fputs(buf, outfp) == EOF)
632 err = TRUE;
636 claws_fclose(outfp);
637 claws_fclose(infp);
639 if (err == TRUE)
640 return FALSE;
642 if (mimeinfo->content == MIMECONTENT_FILE) {
643 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
644 claws_unlink(mimeinfo->data.filename);
645 g_free(mimeinfo->data.filename);
646 } else if (mimeinfo->content == MIMECONTENT_MEM) {
647 if (mimeinfo->tmp && (mimeinfo->data.mem != NULL))
648 g_free(mimeinfo->data.mem);
651 if (g_stat(tmpfilename, &statbuf) < 0) {
652 FILE_OP_ERROR(tmpfilename, "stat");
653 return FALSE;
655 mimeinfo->content = MIMECONTENT_FILE;
656 mimeinfo->data.filename = tmpfilename;
657 mimeinfo->tmp = TRUE;
658 mimeinfo->offset = 0;
659 mimeinfo->length = statbuf.st_size;
660 mimeinfo->encoding_type = encoding;
662 return TRUE;
665 static gint procmime_get_part_to_stream(FILE *outfp, MimeInfo *mimeinfo)
667 FILE *infp;
668 gchar buf[BUFFSIZE];
669 gint restlength, readlength;
670 gint saved_errno = 0;
672 cm_return_val_if_fail(outfp != NULL, -1);
673 cm_return_val_if_fail(mimeinfo != NULL, -1);
675 if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
676 return -EINVAL;
678 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
679 saved_errno = errno;
680 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
681 return -(saved_errno);
683 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
684 saved_errno = errno;
685 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
686 claws_fclose(infp);
687 return -(saved_errno);
690 restlength = mimeinfo->length;
692 while ((restlength > 0) && ((readlength = claws_fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
693 if (claws_fwrite(buf, 1, readlength, outfp) != readlength) {
694 saved_errno = errno;
695 claws_fclose(infp);
696 return -(saved_errno);
698 restlength -= readlength;
701 claws_fclose(infp);
702 rewind(outfp);
704 return 0;
707 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
709 FILE *outfp;
710 gint result;
711 gint saved_errno = 0;
713 cm_return_val_if_fail(outfile != NULL, -1);
715 if ((outfp = claws_fopen(outfile, "wb")) == NULL) {
716 saved_errno = errno;
717 FILE_OP_ERROR(outfile, "claws_fopen");
718 return -(saved_errno);
721 if (change_file_mode_rw(outfp, outfile) < 0) {
722 FILE_OP_ERROR(outfile, "chmod");
723 g_warning("can't change file mode: %s", outfile);
726 result = procmime_get_part_to_stream(outfp, mimeinfo);
728 if (claws_fclose(outfp) == EOF) {
729 saved_errno = errno;
730 FILE_OP_ERROR(outfile, "claws_fclose");
731 if (claws_unlink(outfile) < 0)
732 FILE_OP_ERROR(outfile, "claws_unlink");
733 return -(saved_errno);
736 return result;
739 gboolean procmime_scan_text_content(MimeInfo *mimeinfo,
740 gboolean (*scan_callback)(const gchar *str, gpointer cb_data),
741 gpointer cb_data)
743 FILE *tmpfp;
744 const gchar *src_codeset;
745 gboolean conv_fail = FALSE;
746 gchar buf[BUFFSIZE];
747 gchar *str;
748 gboolean scan_ret = FALSE;
749 int r;
751 cm_return_val_if_fail(mimeinfo != NULL, TRUE);
752 cm_return_val_if_fail(scan_callback != NULL, TRUE);
754 if (!procmime_decode_content(mimeinfo))
755 return TRUE;
757 tmpfp = my_tmpfile();
759 if (tmpfp == NULL) {
760 FILE_OP_ERROR("tmpfile", "open");
761 return TRUE;
764 if ((r = procmime_get_part_to_stream(tmpfp, mimeinfo)) < 0) {
765 g_warning("procmime_get_part_to_stream error %d", r);
766 return TRUE;
769 src_codeset = forced_charset
770 ? forced_charset :
771 procmime_mimeinfo_get_parameter(mimeinfo, "charset");
773 /* use supersets transparently when possible */
774 if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_ISO_8859_1))
775 src_codeset = CS_WINDOWS_1252;
776 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_GBK))
777 src_codeset = CS_GB18030;
778 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GBK))
779 src_codeset = CS_GB18030;
780 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GB2312))
781 src_codeset = CS_GB18030;
782 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_VIET_VPS))
783 src_codeset = CS_WINDOWS_874;
785 if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
786 SC_HTMLParser *parser;
787 CodeConverter *conv;
789 conv = conv_code_converter_new(src_codeset);
790 parser = sc_html_parser_new(tmpfp, conv);
791 while ((str = sc_html_parse(parser)) != NULL) {
792 if ((scan_ret = scan_callback(str, cb_data)) == TRUE)
793 break;
795 sc_html_parser_destroy(parser);
796 conv_code_converter_destroy(conv);
797 } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
798 ERTFParser *parser;
799 CodeConverter *conv;
801 conv = conv_code_converter_new(src_codeset);
802 parser = ertf_parser_new(tmpfp, conv);
803 while ((str = ertf_parse(parser)) != NULL) {
804 if ((scan_ret = scan_callback(str, cb_data)) == TRUE)
805 break;
807 ertf_parser_destroy(parser);
808 conv_code_converter_destroy(conv);
809 } else if (mimeinfo->type == MIMETYPE_TEXT && mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
810 while (claws_fgets(buf, sizeof(buf), tmpfp) != NULL) {
811 str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
812 if (str) {
813 if ((scan_ret = scan_callback(str, cb_data)) == TRUE) {
814 g_free(str);
815 break;
817 g_free(str);
818 } else {
819 conv_fail = TRUE;
820 if ((scan_ret = scan_callback(buf, cb_data)) == TRUE)
821 break;
826 if (conv_fail)
827 g_warning("procmime_get_text_content(): code conversion failed");
829 claws_fclose(tmpfp);
831 return scan_ret;
834 static gboolean scan_fputs_cb(const gchar *str, gpointer fp)
836 if (claws_fputs(str, (FILE *)fp) == EOF)
837 return TRUE;
839 return FALSE;
842 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
844 FILE *outfp;
845 gboolean err;
847 if ((outfp = my_tmpfile()) == NULL) {
848 perror("my_tmpfile");
849 return NULL;
852 err = procmime_scan_text_content(mimeinfo, scan_fputs_cb, outfp);
854 rewind(outfp);
855 if (err == TRUE) {
856 claws_fclose(outfp);
857 return NULL;
859 return outfp;
863 FILE *procmime_get_binary_content(MimeInfo *mimeinfo)
865 FILE *outfp;
867 cm_return_val_if_fail(mimeinfo != NULL, NULL);
869 if (!procmime_decode_content(mimeinfo))
870 return NULL;
872 outfp = my_tmpfile();
874 if (procmime_get_part_to_stream(outfp, mimeinfo) < 0) {
875 return NULL;
878 return outfp;
881 /* search the first text part of (multipart) MIME message,
882 decode, convert it and output to outfp. */
883 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
885 FILE *outfp = NULL;
886 MimeInfo *mimeinfo, *partinfo;
887 gboolean empty_ok = FALSE, short_scan = TRUE;
889 cm_return_val_if_fail(msginfo != NULL, NULL);
891 /* first we try to short-scan (for speed), refusing empty parts */
892 scan_again:
893 if (short_scan)
894 mimeinfo = procmime_scan_message_short(msginfo);
895 else
896 mimeinfo = procmime_scan_message(msginfo);
897 if (!mimeinfo) return NULL;
899 partinfo = mimeinfo;
900 while (partinfo && (partinfo->type != MIMETYPE_TEXT ||
901 (partinfo->length == 0 && !empty_ok))) {
902 partinfo = procmime_mimeinfo_next(partinfo);
904 if (partinfo)
905 outfp = procmime_get_text_content(partinfo);
906 else if (!empty_ok && short_scan) {
907 /* if short scan didn't find a non-empty part, rescan
908 * fully for non-empty parts
910 short_scan = FALSE;
911 procmime_mimeinfo_free_all(&mimeinfo);
912 goto scan_again;
913 } else if (!empty_ok && !short_scan) {
914 /* if full scan didn't find a non-empty part, rescan
915 * accepting empty parts
917 empty_ok = TRUE;
918 procmime_mimeinfo_free_all(&mimeinfo);
919 goto scan_again;
921 procmime_mimeinfo_free_all(&mimeinfo);
923 return outfp;
927 static gboolean find_encrypted_func(GNode *node, gpointer data)
929 MimeInfo *mimeinfo = (MimeInfo *) node->data;
930 MimeInfo **encinfo = (MimeInfo **) data;
932 if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
933 *encinfo = mimeinfo;
934 return TRUE;
937 return FALSE;
940 static MimeInfo *find_encrypted_part(MimeInfo *rootinfo)
942 MimeInfo *encinfo = NULL;
944 g_node_traverse(rootinfo->node, G_IN_ORDER, G_TRAVERSE_ALL, -1,
945 find_encrypted_func, &encinfo);
947 return encinfo;
950 /* search the first encrypted text part of (multipart) MIME message,
951 decode, convert it and output to outfp. */
952 FILE *procmime_get_first_encrypted_text_content(MsgInfo *msginfo)
954 FILE *outfp = NULL;
955 MimeInfo *mimeinfo, *partinfo, *encinfo;
957 cm_return_val_if_fail(msginfo != NULL, NULL);
959 mimeinfo = procmime_scan_message(msginfo);
960 if (!mimeinfo) {
961 return NULL;
964 partinfo = mimeinfo;
965 if ((encinfo = find_encrypted_part(partinfo)) != NULL) {
966 debug_print("decrypting message part\n");
967 if (privacy_mimeinfo_decrypt(encinfo) < 0) {
968 alertpanel_error(_("Couldn't decrypt: %s"),
969 privacy_get_error());
970 return NULL;
973 partinfo = mimeinfo;
974 while (partinfo && partinfo->type != MIMETYPE_TEXT) {
975 partinfo = procmime_mimeinfo_next(partinfo);
976 if (privacy_mimeinfo_is_signed(partinfo))
977 procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
980 if (partinfo)
981 outfp = procmime_get_text_content(partinfo);
983 procmime_mimeinfo_free_all(&mimeinfo);
985 return outfp;
988 gboolean procmime_msginfo_is_encrypted(MsgInfo *msginfo)
990 MimeInfo *mimeinfo, *partinfo;
991 gboolean result = FALSE;
993 cm_return_val_if_fail(msginfo != NULL, FALSE);
995 mimeinfo = procmime_scan_message(msginfo);
996 if (!mimeinfo) {
997 return FALSE;
1000 partinfo = mimeinfo;
1001 result = (find_encrypted_part(partinfo) != NULL);
1002 procmime_mimeinfo_free_all(&mimeinfo);
1004 return result;
1007 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
1009 static guint32 id = 0;
1010 gchar *base;
1011 gchar *filename;
1012 gchar f_prefix[10];
1014 cm_return_val_if_fail(mimeinfo != NULL, NULL);
1016 g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
1018 if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
1019 base = g_strdup("mimetmp.html");
1020 else {
1021 const gchar *basetmp;
1023 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
1024 if (basetmp == NULL)
1025 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
1026 if (basetmp == NULL)
1027 basetmp = "mimetmp";
1028 basetmp = g_path_get_basename(basetmp);
1029 if (*basetmp == '\0')
1030 basetmp = g_strdup("mimetmp");
1031 base = conv_filename_from_utf8(basetmp);
1032 g_free((gchar*)basetmp);
1033 subst_for_shellsafe_filename(base);
1036 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1037 f_prefix, base, NULL);
1039 g_free(base);
1041 return filename;
1044 static GList *mime_type_list = NULL;
1046 gchar *procmime_get_mime_type(const gchar *filename)
1048 const gchar *p;
1049 gchar *ext = NULL;
1050 gchar *base;
1051 gchar *str;
1052 #ifndef G_OS_WIN32
1053 static GHashTable *mime_type_table = NULL;
1054 MimeType *mime_type;
1056 if (!mime_type_table) {
1057 mime_type_table = procmime_get_mime_type_table();
1058 if (!mime_type_table) return NULL;
1060 #endif
1062 if (filename == NULL)
1063 return NULL;
1065 base = g_path_get_basename(filename);
1066 if ((p = strrchr(base, '.')) != NULL)
1067 #ifndef G_OS_WIN32
1068 ext = g_utf8_strdown(p + 1, -1);
1069 #else
1070 ext = g_utf8_strdown(p, -1);
1071 #endif
1072 else
1073 ext = g_utf8_strdown(base, -1);
1074 g_free(base);
1076 #ifndef G_OS_WIN32
1077 mime_type = g_hash_table_lookup(mime_type_table, ext);
1079 if (mime_type) {
1080 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
1081 NULL);
1082 debug_print("got type %s for %s\n", str, ext);
1083 g_free(ext);
1084 return str;
1086 g_free(ext);
1087 return NULL;
1088 #else
1089 str = read_w32_registry_string(HKEY_CLASSES_ROOT, ext, REG_MIME_TYPE_VALUE);
1090 debug_print("got type %s for %s\n", str, ext);
1091 g_free(ext);
1092 return str;
1093 #endif
1096 static guint procmime_str_hash(gconstpointer gptr)
1098 guint hash_result = 0;
1099 const char *str;
1101 for (str = gptr; str && *str; str++) {
1102 if (isupper(*str)) hash_result += (*str + ' ');
1103 else hash_result += *str;
1106 return hash_result;
1109 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
1111 const char *str1 = gptr1;
1112 const char *str2 = gptr2;
1114 return !g_utf8_collate(str1, str2);
1117 static GHashTable *procmime_get_mime_type_table(void)
1119 GHashTable *table = NULL;
1120 GList *cur;
1121 MimeType *mime_type;
1122 gchar **exts;
1124 if (!mime_type_list) {
1125 mime_type_list = procmime_get_mime_type_list();
1126 if (!mime_type_list) return NULL;
1129 table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
1131 for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1132 gint i;
1133 gchar *key;
1135 mime_type = (MimeType *)cur->data;
1137 if (!mime_type->extension) continue;
1139 exts = g_strsplit(mime_type->extension, " ", 16);
1140 for (i = 0; exts[i] != NULL; i++) {
1141 /* Don't overwrite previously inserted extension */
1142 if (!g_hash_table_lookup(table, exts[i])) {
1143 key = g_strdup(exts[i]);
1144 g_hash_table_insert(table, key, mime_type);
1147 g_strfreev(exts);
1150 return table;
1153 GList *procmime_get_mime_type_list(void)
1155 GList *list = NULL;
1156 FILE *fp;
1157 gchar buf[BUFFSIZE];
1158 gchar *p;
1159 gchar *delim;
1160 MimeType *mime_type;
1161 gboolean fp_is_glob_file = TRUE;
1163 if (mime_type_list)
1164 return mime_type_list;
1166 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
1167 if ((fp = claws_fopen(DATAROOTDIR "/mime/globs", "rb")) == NULL)
1168 #else
1169 if ((fp = claws_fopen("/usr/share/mime/globs", "rb")) == NULL)
1170 #endif
1172 fp_is_glob_file = FALSE;
1173 if ((fp = claws_fopen("/etc/mime.types", "rb")) == NULL) {
1174 if ((fp = claws_fopen(SYSCONFDIR "/mime.types", "rb"))
1175 == NULL) {
1176 FILE_OP_ERROR(SYSCONFDIR "/mime.types",
1177 "claws_fopen");
1178 return NULL;
1183 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
1184 p = strchr(buf, '#');
1185 if (p) *p = '\0';
1186 g_strstrip(buf);
1188 p = buf;
1190 if (fp_is_glob_file) {
1191 while (*p && !g_ascii_isspace(*p) && (*p!=':')) p++;
1192 } else {
1193 while (*p && !g_ascii_isspace(*p)) p++;
1196 if (*p) {
1197 *p = '\0';
1198 p++;
1200 delim = strchr(buf, '/');
1201 if (delim == NULL) continue;
1202 *delim = '\0';
1204 mime_type = g_new(MimeType, 1);
1205 mime_type->type = g_strdup(buf);
1206 mime_type->sub_type = g_strdup(delim + 1);
1208 if (fp_is_glob_file) {
1209 while (*p && (g_ascii_isspace(*p)||(*p=='*')||(*p=='.'))) p++;
1210 } else {
1211 while (*p && g_ascii_isspace(*p)) p++;
1214 if (*p)
1215 mime_type->extension = g_utf8_strdown(p, -1);
1216 else
1217 mime_type->extension = NULL;
1219 list = g_list_append(list, mime_type);
1222 claws_fclose(fp);
1224 if (!list)
1225 g_warning("can't read mime.types");
1227 return list;
1230 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1232 if (!charset)
1233 return ENC_8BIT;
1234 else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
1235 !g_ascii_strcasecmp(charset, "US-ASCII"))
1236 return ENC_7BIT;
1237 else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
1238 !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
1239 !g_ascii_strcasecmp(charset, "X-MAC-CYRILLIC") ||
1240 !g_ascii_strcasecmp(charset, "MAC-CYRILLIC") ||
1241 !g_ascii_strcasecmp(charset, "Windows-1251"))
1242 return ENC_8BIT;
1243 else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
1244 return ENC_QUOTED_PRINTABLE;
1245 else if (!g_ascii_strncasecmp(charset, "UTF-8", 5))
1246 return ENC_QUOTED_PRINTABLE;
1247 else
1248 return ENC_8BIT;
1251 EncodingType procmime_get_encoding_for_text_file(const gchar *file, gboolean *has_binary)
1253 FILE *fp;
1254 guchar buf[BUFFSIZE];
1255 size_t len;
1256 size_t octet_chars = 0;
1257 size_t total_len = 0;
1258 gfloat octet_percentage;
1259 gboolean force_b64 = FALSE;
1261 if ((fp = claws_fopen(file, "rb")) == NULL) {
1262 FILE_OP_ERROR(file, "claws_fopen");
1263 return ENC_UNKNOWN;
1266 while ((len = claws_fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
1267 guchar *p;
1268 gint i;
1270 for (p = buf, i = 0; i < len; ++p, ++i) {
1271 if (*p & 0x80)
1272 ++octet_chars;
1273 if (*p == '\0') {
1274 force_b64 = TRUE;
1275 *has_binary = TRUE;
1278 total_len += len;
1281 claws_fclose(fp);
1283 if (total_len > 0)
1284 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1285 else
1286 octet_percentage = 0.0;
1288 debug_print("procmime_get_encoding_for_text_file(): "
1289 "8bit chars: %"G_GSIZE_FORMAT" / %"G_GSIZE_FORMAT" (%f%%)\n", octet_chars, total_len,
1290 100.0 * octet_percentage);
1292 if (octet_percentage > 0.20 || force_b64) {
1293 debug_print("using BASE64\n");
1294 return ENC_BASE64;
1295 } else if (octet_chars > 0) {
1296 debug_print("using quoted-printable\n");
1297 return ENC_QUOTED_PRINTABLE;
1298 } else {
1299 debug_print("using 7bit\n");
1300 return ENC_7BIT;
1304 struct EncodingTable
1306 gchar *str;
1307 EncodingType enc_type;
1310 struct EncodingTable encoding_table[] = {
1311 {"7bit", ENC_7BIT},
1312 {"8bit", ENC_8BIT},
1313 {"binary", ENC_BINARY},
1314 {"quoted-printable", ENC_QUOTED_PRINTABLE},
1315 {"base64", ENC_BASE64},
1316 {"x-uuencode", ENC_UNKNOWN},
1317 {NULL, ENC_UNKNOWN},
1320 const gchar *procmime_get_encoding_str(EncodingType encoding)
1322 struct EncodingTable *enc_table;
1324 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1325 if (enc_table->enc_type == encoding)
1326 return enc_table->str;
1328 return NULL;
1331 /* --- NEW MIME STUFF --- */
1332 struct TypeTable
1334 gchar *str;
1335 MimeMediaType type;
1338 static struct TypeTable mime_type_table[] = {
1339 {"text", MIMETYPE_TEXT},
1340 {"image", MIMETYPE_IMAGE},
1341 {"audio", MIMETYPE_AUDIO},
1342 {"video", MIMETYPE_VIDEO},
1343 {"model", MIMETYPE_MODEL},
1344 {"application", MIMETYPE_APPLICATION},
1345 {"message", MIMETYPE_MESSAGE},
1346 {"multipart", MIMETYPE_MULTIPART},
1347 {NULL, 0},
1350 const gchar *procmime_get_media_type_str(MimeMediaType type)
1352 struct TypeTable *type_table;
1354 for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1355 if (type_table->type == type)
1356 return type_table->str;
1358 return NULL;
1361 MimeMediaType procmime_get_media_type(const gchar *str)
1363 struct TypeTable *typetablearray;
1365 for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1366 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1367 return typetablearray->type;
1369 return MIMETYPE_UNKNOWN;
1373 *\brief Safe wrapper for content type string.
1375 *\return const gchar * Pointer to content type string.
1377 gchar *procmime_get_content_type_str(MimeMediaType type,
1378 const char *subtype)
1380 const gchar *type_str = NULL;
1382 if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1383 return g_strdup("unknown");
1384 return g_strdup_printf("%s/%s", type_str, subtype);
1387 static int procmime_parse_mimepart(MimeInfo *parent,
1388 gchar *content_type,
1389 gchar *content_encoding,
1390 gchar *content_description,
1391 gchar *content_id,
1392 gchar *content_disposition,
1393 gchar *content_location,
1394 const gchar *original_msgid,
1395 const gchar *disposition_notification_hdr,
1396 const gchar *filename,
1397 guint offset,
1398 guint length,
1399 gboolean short_scan);
1401 static void procmime_parse_message_rfc822(MimeInfo *mimeinfo, gboolean short_scan)
1403 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE},
1404 {"Content-Transfer-Encoding:",
1405 NULL, FALSE},
1406 {"Content-Description:",
1407 NULL, TRUE},
1408 {"Content-ID:",
1409 NULL, TRUE},
1410 {"Content-Disposition:",
1411 NULL, TRUE},
1412 {"Content-Location:",
1413 NULL, TRUE},
1414 {"MIME-Version:",
1415 NULL, TRUE},
1416 {"Original-Message-ID:",
1417 NULL, TRUE},
1418 {"Disposition:",
1419 NULL, TRUE},
1420 {NULL, NULL, FALSE}};
1421 guint content_start, i;
1422 FILE *fp;
1423 gchar *tmp;
1424 gint len = 0;
1426 procmime_decode_content(mimeinfo);
1428 fp = claws_fopen(mimeinfo->data.filename, "rb");
1429 if (fp == NULL) {
1430 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1431 return;
1433 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1434 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1435 claws_fclose(fp);
1436 return;
1438 procheader_get_header_fields(fp, hentry);
1439 if (hentry[0].body != NULL) {
1440 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);
1441 g_free(hentry[0].body);
1442 hentry[0].body = tmp;
1444 if (hentry[2].body != NULL) {
1445 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);
1446 g_free(hentry[2].body);
1447 hentry[2].body = tmp;
1449 if (hentry[4].body != NULL) {
1450 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);
1451 g_free(hentry[4].body);
1452 hentry[4].body = tmp;
1454 if (hentry[5].body != NULL) {
1455 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);
1456 g_free(hentry[5].body);
1457 hentry[5].body = tmp;
1459 if (hentry[7].body != NULL) {
1460 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);
1461 g_free(hentry[7].body);
1462 hentry[7].body = tmp;
1464 if (hentry[8].body != NULL) {
1465 tmp = conv_unmime_header(hentry[8].body, NULL, FALSE);
1466 g_free(hentry[8].body);
1467 hentry[8].body = tmp;
1470 content_start = ftell(fp);
1471 claws_fclose(fp);
1473 len = mimeinfo->length - (content_start - mimeinfo->offset);
1474 if (len < 0)
1475 len = 0;
1476 procmime_parse_mimepart(mimeinfo,
1477 hentry[0].body, hentry[1].body,
1478 hentry[2].body, hentry[3].body,
1479 hentry[4].body, hentry[5].body,
1480 hentry[7].body, hentry[8].body,
1481 mimeinfo->data.filename, content_start,
1482 len, short_scan);
1484 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1485 g_free(hentry[i].body);
1486 hentry[i].body = NULL;
1490 static void procmime_parse_disposition_notification(MimeInfo *mimeinfo,
1491 const gchar *original_msgid, const gchar *disposition_notification_hdr,
1492 gboolean short_scan)
1494 HeaderEntry hentry[] = {{"Original-Message-ID:", NULL, TRUE},
1495 {"Disposition:", NULL, TRUE},
1496 {NULL, NULL, FALSE}};
1497 guint i;
1498 FILE *fp;
1499 gchar *orig_msg_id = NULL;
1500 gchar *disp = NULL;
1502 procmime_decode_content(mimeinfo);
1504 debug_print("parse disposition notification\n");
1505 fp = claws_fopen(mimeinfo->data.filename, "rb");
1506 if (fp == NULL) {
1507 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1508 return;
1510 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1511 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1512 claws_fclose(fp);
1513 return;
1516 if (original_msgid && disposition_notification_hdr) {
1517 hentry[0].body = g_strdup(original_msgid);
1518 hentry[1].body = g_strdup(disposition_notification_hdr);
1519 } else {
1520 procheader_get_header_fields(fp, hentry);
1523 claws_fclose(fp);
1525 if (!hentry[0].body || !hentry[1].body) {
1526 debug_print("MsgId %s, Disp %s\n",
1527 hentry[0].body ? hentry[0].body:"(nil)",
1528 hentry[1].body ? hentry[1].body:"(nil)");
1529 goto bail;
1532 orig_msg_id = g_strdup(hentry[0].body);
1533 disp = g_strdup(hentry[1].body);
1535 extract_parenthesis(orig_msg_id, '<', '>');
1536 remove_space(orig_msg_id);
1538 if (strstr(disp, "displayed")) {
1539 /* find sent message, if possible */
1540 MsgInfo *info = NULL;
1541 GList *flist;
1542 debug_print("%s has been displayed.\n", orig_msg_id);
1543 for (flist = folder_get_list(); flist != NULL; flist = g_list_next(flist)) {
1544 FolderItem *outbox = ((Folder *)(flist->data))->outbox;
1545 if (!outbox) {
1546 debug_print("skipping folder with no outbox...\n");
1547 continue;
1549 info = folder_item_get_msginfo_by_msgid(outbox, orig_msg_id);
1550 debug_print("%s %s in %s\n", info?"found":"didn't find", orig_msg_id, outbox->path);
1551 if (info) {
1552 procmsg_msginfo_set_flags(info, MSG_RETRCPT_GOT, 0);
1553 procmsg_msginfo_free(&info);
1557 g_free(orig_msg_id);
1558 g_free(disp);
1559 bail:
1560 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1561 g_free(hentry[i].body);
1562 hentry[i].body = NULL;
1566 #define GET_HEADERS() { \
1567 procheader_get_header_fields(fp, hentry); \
1568 if (hentry[0].body != NULL) { \
1569 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE); \
1570 g_free(hentry[0].body); \
1571 hentry[0].body = tmp; \
1573 if (hentry[2].body != NULL) { \
1574 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE); \
1575 g_free(hentry[2].body); \
1576 hentry[2].body = tmp; \
1578 if (hentry[4].body != NULL) { \
1579 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE); \
1580 g_free(hentry[4].body); \
1581 hentry[4].body = tmp; \
1583 if (hentry[5].body != NULL) { \
1584 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE); \
1585 g_free(hentry[5].body); \
1586 hentry[5].body = tmp; \
1588 if (hentry[6].body != NULL) { \
1589 tmp = conv_unmime_header(hentry[6].body, NULL, FALSE); \
1590 g_free(hentry[6].body); \
1591 hentry[6].body = tmp; \
1593 if (hentry[7].body != NULL) { \
1594 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE); \
1595 g_free(hentry[7].body); \
1596 hentry[7].body = tmp; \
1600 static void procmime_parse_multipart(MimeInfo *mimeinfo, gboolean short_scan)
1602 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE},
1603 {"Content-Transfer-Encoding:",
1604 NULL, FALSE},
1605 {"Content-Description:",
1606 NULL, TRUE},
1607 {"Content-ID:",
1608 NULL, TRUE},
1609 {"Content-Disposition:",
1610 NULL, TRUE},
1611 {"Content-Location:",
1612 NULL, TRUE},
1613 {"Original-Message-ID:",
1614 NULL, TRUE},
1615 {"Disposition:",
1616 NULL, TRUE},
1617 {NULL, NULL, FALSE}};
1618 gchar *tmp;
1619 gchar *boundary;
1620 gint boundary_len = 0, lastoffset = -1, i;
1621 gchar buf[BUFFSIZE];
1622 FILE *fp;
1623 int result = 0;
1624 gboolean start_found = FALSE;
1625 gboolean end_found = FALSE;
1627 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1628 if (!boundary)
1629 return;
1630 boundary_len = strlen(boundary);
1632 procmime_decode_content(mimeinfo);
1634 fp = claws_fopen(mimeinfo->data.filename, "rb");
1635 if (fp == NULL) {
1636 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1637 return;
1640 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
1641 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1642 claws_fclose(fp);
1643 return;
1646 while (claws_fgets(buf, sizeof(buf), fp) != NULL && result == 0) {
1647 if (ftell(fp) - 1 > (mimeinfo->offset + mimeinfo->length))
1648 break;
1650 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1651 start_found = TRUE;
1653 if (lastoffset != -1) {
1654 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1655 if (len < 0)
1656 len = 0;
1657 result = procmime_parse_mimepart(mimeinfo,
1658 hentry[0].body, hentry[1].body,
1659 hentry[2].body, hentry[3].body,
1660 hentry[4].body, hentry[5].body,
1661 hentry[6].body, hentry[7].body,
1662 mimeinfo->data.filename, lastoffset,
1663 len, short_scan);
1664 if (result == 1 && short_scan)
1665 break;
1669 if (buf[2 + boundary_len] == '-' &&
1670 buf[2 + boundary_len + 1] == '-') {
1671 end_found = TRUE;
1672 break;
1674 for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1675 g_free(hentry[i].body);
1676 hentry[i].body = NULL;
1678 GET_HEADERS();
1679 lastoffset = ftell(fp);
1683 if (start_found && !end_found && lastoffset != -1) {
1684 gint len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
1686 if (len >= 0) {
1687 result = procmime_parse_mimepart(mimeinfo,
1688 hentry[0].body, hentry[1].body,
1689 hentry[2].body, hentry[3].body,
1690 hentry[4].body, hentry[5].body,
1691 hentry[6].body, hentry[7].body,
1692 mimeinfo->data.filename, lastoffset,
1693 len, short_scan);
1695 mimeinfo->broken = TRUE;
1698 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1699 g_free(hentry[i].body);
1700 hentry[i].body = NULL;
1702 claws_fclose(fp);
1705 static void parse_parameters(const gchar *parameters, GHashTable *table)
1707 gchar *params, *param, *next;
1708 GSList *convlist = NULL, *concatlist = NULL, *cur;
1710 params = g_strdup(parameters);
1711 param = params;
1712 next = params;
1713 for (; next != NULL; param = next) {
1714 gchar *attribute, *value, *tmp, *down_attr, *orig_down_attr;
1715 gint len;
1716 gboolean convert = FALSE;
1718 next = strchr_with_skip_quote(param, '"', ';');
1719 if (next != NULL) {
1720 next[0] = '\0';
1721 next++;
1724 g_strstrip(param);
1726 attribute = param;
1727 value = strchr(attribute, '=');
1728 if (value == NULL)
1729 continue;
1731 value[0] = '\0';
1732 value++;
1733 while (value[0] != '\0' && value[0] == ' ')
1734 value++;
1736 down_attr = g_utf8_strdown(attribute, -1);
1737 orig_down_attr = down_attr;
1739 len = down_attr ? strlen(down_attr):0;
1740 if (len > 0 && down_attr[len - 1] == '*') {
1741 gchar *srcpos, *dstpos, *endpos;
1743 convert = TRUE;
1744 down_attr[len - 1] = '\0';
1746 srcpos = value;
1747 dstpos = value;
1748 endpos = value + strlen(value);
1749 while (srcpos < endpos) {
1750 if (*srcpos != '%')
1751 *dstpos = *srcpos;
1752 else {
1753 guchar dstvalue;
1755 if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1756 *dstpos = '?';
1757 else
1758 *dstpos = dstvalue;
1759 srcpos += 2;
1761 srcpos++;
1762 dstpos++;
1764 *dstpos = '\0';
1765 if (value[0] == '"')
1766 extract_quote(value, '"');
1767 } else {
1768 if (value[0] == '"')
1769 extract_quote(value, '"');
1770 else if ((tmp = strchr(value, ' ')) != NULL)
1771 *tmp = '\0';
1774 if (down_attr) {
1775 while (down_attr[0] == ' ')
1776 down_attr++;
1777 while (down_attr[strlen(down_attr)-1] == ' ')
1778 down_attr[strlen(down_attr)-1] = '\0';
1781 while (value[0] != '\0' && value[0] == ' ')
1782 value++;
1783 while (value[strlen(value)-1] == ' ')
1784 value[strlen(value)-1] = '\0';
1786 if (down_attr && strrchr(down_attr, '*') != NULL) {
1787 gchar *tmpattr;
1789 tmpattr = g_strdup(down_attr);
1790 tmp = strrchr(tmpattr, '*');
1791 tmp[0] = '\0';
1793 if ((tmp[1] == '0') && (tmp[2] == '\0') &&
1794 (g_slist_find_custom(concatlist, down_attr, (GCompareFunc)g_strcmp0) == NULL))
1795 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1797 if (convert && (g_slist_find_custom(convlist, tmpattr, (GCompareFunc)g_strcmp0) == NULL))
1798 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1800 g_free(tmpattr);
1801 } else if (convert) {
1802 if (g_slist_find_custom(convlist, down_attr, (GCompareFunc)g_strcmp0) == NULL)
1803 convlist = g_slist_prepend(convlist, g_strdup(down_attr));
1806 if (g_hash_table_lookup(table, down_attr) == NULL)
1807 g_hash_table_insert(table, g_strdup(down_attr), g_strdup(value));
1808 g_free(orig_down_attr);
1811 for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1812 gchar *attribute, *attrwnum, *partvalue;
1813 gint n = 0;
1814 GString *value;
1816 attribute = (gchar *) cur->data;
1817 value = g_string_sized_new(64);
1819 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1820 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1821 g_string_append(value, partvalue);
1823 g_hash_table_remove(table, attrwnum);
1824 g_free(attrwnum);
1825 n++;
1826 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1828 g_free(attrwnum);
1830 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1831 g_string_free(value, TRUE);
1833 slist_free_strings_full(concatlist);
1835 for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1836 gchar *attribute, *key, *value;
1837 gchar *charset, *lang, *oldvalue, *newvalue;
1839 attribute = (gchar *) cur->data;
1840 if (!g_hash_table_lookup_extended(
1841 table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
1842 continue;
1844 charset = value;
1845 if (charset == NULL)
1846 continue;
1847 lang = strchr(charset, '\'');
1848 if (lang == NULL)
1849 continue;
1850 lang[0] = '\0';
1851 lang++;
1852 oldvalue = strchr(lang, '\'');
1853 if (oldvalue == NULL)
1854 continue;
1855 oldvalue[0] = '\0';
1856 oldvalue++;
1858 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1860 g_hash_table_remove(table, attribute);
1861 g_free(key);
1862 g_free(value);
1864 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1866 slist_free_strings_full(convlist);
1868 g_free(params);
1871 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1873 cm_return_if_fail(content_type != NULL);
1874 cm_return_if_fail(mimeinfo != NULL);
1876 /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1877 * if it's not available we use the default Content-Type */
1878 if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1879 mimeinfo->type = MIMETYPE_TEXT;
1880 mimeinfo->subtype = g_strdup("plain");
1881 if (g_hash_table_lookup(mimeinfo->typeparameters,
1882 "charset") == NULL) {
1883 g_hash_table_insert(mimeinfo->typeparameters,
1884 g_strdup("charset"),
1885 g_strdup(
1886 conv_get_locale_charset_str_no_utf8()));
1888 } else {
1889 gchar *type, *subtype, *params;
1891 type = g_strdup(content_type);
1892 subtype = strchr(type, '/') + 1;
1893 *(subtype - 1) = '\0';
1894 if ((params = strchr(subtype, ';')) != NULL) {
1895 params[0] = '\0';
1896 params++;
1899 mimeinfo->type = procmime_get_media_type(type);
1900 mimeinfo->subtype = g_strstrip(g_strdup(subtype));
1902 /* Get mimeinfo->typeparameters */
1903 if (params != NULL)
1904 parse_parameters(params, mimeinfo->typeparameters);
1906 g_free(type);
1910 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1912 gchar *tmp, *params;
1914 cm_return_if_fail(content_disposition != NULL);
1915 cm_return_if_fail(mimeinfo != NULL);
1917 tmp = g_strdup(content_disposition);
1918 if ((params = strchr(tmp, ';')) != NULL) {
1919 params[0] = '\0';
1920 params++;
1922 g_strstrip(tmp);
1924 if (!g_ascii_strcasecmp(tmp, "inline"))
1925 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1926 else if (!g_ascii_strcasecmp(tmp, "attachment"))
1927 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1928 else
1929 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1931 if (params != NULL)
1932 parse_parameters(params, mimeinfo->dispositionparameters);
1934 g_free(tmp);
1938 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1940 struct EncodingTable *enc_table;
1942 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1943 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1944 mimeinfo->encoding_type = enc_table->enc_type;
1945 return;
1948 mimeinfo->encoding_type = ENC_UNKNOWN;
1949 return;
1952 static GSList *registered_parsers = NULL;
1954 static MimeParser *procmime_get_mimeparser_for_type(MimeMediaType type, const gchar *sub_type)
1956 GSList *cur;
1957 for (cur = registered_parsers; cur; cur = cur->next) {
1958 MimeParser *parser = (MimeParser *)cur->data;
1959 if (parser->type == type && !g_strcmp0(parser->sub_type, sub_type))
1960 return parser;
1962 return NULL;
1965 void procmime_mimeparser_register(MimeParser *parser)
1967 if (!procmime_get_mimeparser_for_type(parser->type, parser->sub_type))
1968 registered_parsers = g_slist_append(registered_parsers, parser);
1972 void procmime_mimeparser_unregister(MimeParser *parser)
1974 registered_parsers = g_slist_remove(registered_parsers, parser);
1977 static gboolean procmime_mimeparser_parse(MimeParser *parser, MimeInfo *mimeinfo)
1979 cm_return_val_if_fail(parser->parse != NULL, FALSE);
1980 return parser->parse(parser, mimeinfo);
1983 static int procmime_parse_mimepart(MimeInfo *parent,
1984 gchar *content_type,
1985 gchar *content_encoding,
1986 gchar *content_description,
1987 gchar *content_id,
1988 gchar *content_disposition,
1989 gchar *content_location,
1990 const gchar *original_msgid,
1991 const gchar *disposition_notification_hdr,
1992 const gchar *filename,
1993 guint offset,
1994 guint length,
1995 gboolean short_scan)
1997 MimeInfo *mimeinfo;
1998 MimeParser *parser = NULL;
1999 gboolean parsed = FALSE;
2000 int result = 0;
2002 /* Create MimeInfo */
2003 mimeinfo = procmime_mimeinfo_new();
2004 mimeinfo->content = MIMECONTENT_FILE;
2006 if (parent != NULL) {
2007 if (g_node_depth(parent->node) > 32) {
2008 /* 32 is an arbitrary value
2009 * this avoids DOSsing ourselves
2010 * with enormous messages
2012 procmime_mimeinfo_free_all(&mimeinfo);
2013 return -1;
2015 g_node_append(parent->node, mimeinfo->node);
2017 mimeinfo->data.filename = g_strdup(filename);
2018 mimeinfo->offset = offset;
2019 mimeinfo->length = length;
2021 if (content_type != NULL) {
2022 g_strchomp(content_type);
2023 procmime_parse_content_type(content_type, mimeinfo);
2024 } else {
2025 mimeinfo->type = MIMETYPE_TEXT;
2026 mimeinfo->subtype = g_strdup("plain");
2027 if (g_hash_table_lookup(mimeinfo->typeparameters,
2028 "charset") == NULL) {
2029 g_hash_table_insert(mimeinfo->typeparameters,
2030 g_strdup("charset"),
2031 g_strdup(
2032 conv_get_locale_charset_str_no_utf8()));
2036 if (content_encoding != NULL) {
2037 g_strchomp(content_encoding);
2038 procmime_parse_content_encoding(content_encoding, mimeinfo);
2039 } else {
2040 mimeinfo->encoding_type = ENC_UNKNOWN;
2043 if (content_description != NULL)
2044 mimeinfo->description = g_strdup(content_description);
2045 else
2046 mimeinfo->description = NULL;
2048 if (content_id != NULL)
2049 mimeinfo->id = g_strdup(content_id);
2050 else
2051 mimeinfo->id = NULL;
2053 if (content_location != NULL)
2054 mimeinfo->location = g_strdup(content_location);
2055 else
2056 mimeinfo->location = NULL;
2058 if (content_disposition != NULL) {
2059 g_strchomp(content_disposition);
2060 procmime_parse_content_disposition(content_disposition, mimeinfo);
2061 } else
2062 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
2064 /* Call parser for mime type */
2065 if ((parser = procmime_get_mimeparser_for_type(mimeinfo->type, mimeinfo->subtype)) != NULL) {
2066 parsed = procmime_mimeparser_parse(parser, mimeinfo);
2068 if (!parsed) {
2069 switch (mimeinfo->type) {
2070 case MIMETYPE_TEXT:
2071 if (g_ascii_strcasecmp(mimeinfo->subtype, "plain") == 0 && short_scan) {
2072 return 1;
2074 break;
2076 case MIMETYPE_MESSAGE:
2077 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2078 procmime_parse_message_rfc822(mimeinfo, short_scan);
2080 if (g_ascii_strcasecmp(mimeinfo->subtype, "disposition-notification") == 0) {
2081 procmime_parse_disposition_notification(mimeinfo,
2082 original_msgid, disposition_notification_hdr, short_scan);
2084 break;
2086 case MIMETYPE_MULTIPART:
2087 procmime_parse_multipart(mimeinfo, short_scan);
2088 break;
2090 case MIMETYPE_APPLICATION:
2091 if (g_ascii_strcasecmp(mimeinfo->subtype, "octet-stream") == 0
2092 && original_msgid && *original_msgid
2093 && disposition_notification_hdr && *disposition_notification_hdr) {
2094 procmime_parse_disposition_notification(mimeinfo,
2095 original_msgid, disposition_notification_hdr, short_scan);
2097 break;
2098 default:
2099 break;
2103 return result;
2106 static gchar *typenames[] = {
2107 "text",
2108 "image",
2109 "audio",
2110 "video",
2111 "application",
2112 "message",
2113 "multipart",
2114 "model",
2115 "unknown",
2118 static gboolean output_func(GNode *node, gpointer data)
2120 guint i, depth;
2121 MimeInfo *mimeinfo = (MimeInfo *) node->data;
2123 depth = g_node_depth(node);
2124 for (i = 0; i < depth; i++)
2125 g_print(" ");
2126 g_print("%s/%s (offset:%d length:%d encoding: %d)\n",
2127 (mimeinfo->type <= MIMETYPE_UNKNOWN)? typenames[mimeinfo->type] : "unknown",
2128 mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
2130 return FALSE;
2133 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
2135 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
2138 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset, gboolean short_scan)
2140 MimeInfo *mimeinfo;
2141 GStatBuf buf;
2143 if (g_stat(filename, &buf) < 0) {
2144 FILE_OP_ERROR(filename, "stat");
2145 return NULL;
2148 mimeinfo = procmime_mimeinfo_new();
2149 mimeinfo->content = MIMECONTENT_FILE;
2150 mimeinfo->encoding_type = ENC_UNKNOWN;
2151 mimeinfo->type = MIMETYPE_MESSAGE;
2152 mimeinfo->subtype = g_strdup("rfc822");
2153 mimeinfo->data.filename = g_strdup(filename);
2154 mimeinfo->offset = offset;
2155 mimeinfo->length = buf.st_size - offset;
2157 procmime_parse_message_rfc822(mimeinfo, short_scan);
2158 if (debug_get_mode())
2159 output_mime_structure(mimeinfo, 0);
2161 return mimeinfo;
2164 static MimeInfo *procmime_scan_file_full(const gchar *filename, gboolean short_scan)
2166 MimeInfo *mimeinfo;
2168 cm_return_val_if_fail(filename != NULL, NULL);
2170 mimeinfo = procmime_scan_file_with_offset(filename, 0, short_scan);
2172 return mimeinfo;
2175 MimeInfo *procmime_scan_file(const gchar *filename)
2177 return procmime_scan_file_full(filename, FALSE);
2180 static MimeInfo *procmime_scan_file_short(const gchar *filename)
2182 return procmime_scan_file_full(filename, TRUE);
2185 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan)
2187 FILE *fp;
2188 MimeInfo *mimeinfo;
2189 gchar buf[BUFFSIZE];
2190 gint offset = 0;
2192 cm_return_val_if_fail(filename != NULL, NULL);
2194 /* Open file */
2195 if ((fp = claws_fopen(filename, "rb")) == NULL)
2196 return NULL;
2197 /* Skip queue header */
2198 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2199 /* new way */
2200 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
2201 strlen("X-Claws-End-Special-Headers:"))) ||
2202 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
2203 strlen("X-Sylpheed-End-Special-Headers:"))))
2204 break;
2205 /* old way */
2206 if (buf[0] == '\r' || buf[0] == '\n') break;
2207 /* from other mailers */
2208 if (!strncmp(buf, "Date: ", 6)
2209 || !strncmp(buf, "To: ", 4)
2210 || !strncmp(buf, "From: ", 6)
2211 || !strncmp(buf, "Subject: ", 9)) {
2212 rewind(fp);
2213 break;
2216 offset = ftell(fp);
2217 claws_fclose(fp);
2219 mimeinfo = procmime_scan_file_with_offset(filename, offset, short_scan);
2221 return mimeinfo;
2224 MimeInfo *procmime_scan_queue_file(const gchar *filename)
2226 return procmime_scan_queue_file_full(filename, FALSE);
2229 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename)
2231 return procmime_scan_queue_file_full(filename, TRUE);
2234 typedef enum {
2235 ENC_AS_TOKEN,
2236 ENC_AS_QUOTED_STRING,
2237 ENC_AS_EXTENDED,
2238 ENC_AS_ENCWORD
2239 } EncodeAs;
2241 typedef struct _ParametersData {
2242 FILE *fp;
2243 guint len;
2244 gint error;
2245 } ParametersData;
2247 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
2249 gchar *param = key;
2250 gchar *val = value, *valpos, *tmp;
2251 ParametersData *pdata = (ParametersData *)user_data;
2252 GString *buf = g_string_new("");
2253 gint len;
2255 EncodeAs encas = ENC_AS_TOKEN;
2257 for (valpos = val; *valpos != 0; valpos++) {
2258 if (!IS_ASCII(*valpos)) {
2259 encas = ENC_AS_ENCWORD;
2260 break;
2263 /* CTLs */
2264 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
2265 encas = ENC_AS_QUOTED_STRING;
2266 continue;
2269 /* tspecials + SPACE */
2270 switch (*valpos) {
2271 case ' ':
2272 case '(':
2273 case ')':
2274 case '<':
2275 case '>':
2276 case '@':
2277 case ',':
2278 case ';':
2279 case ':':
2280 case '\\':
2281 case '"':
2282 case '/':
2283 case '[':
2284 case ']':
2285 case '?':
2286 case '=':
2287 encas = ENC_AS_QUOTED_STRING;
2288 continue;
2292 switch (encas) {
2293 case ENC_AS_TOKEN:
2294 g_string_append_printf(buf, "%s=%s", param, val);
2295 break;
2297 case ENC_AS_QUOTED_STRING:
2298 g_string_append_printf(buf, "%s=\"%s\"", param, val);
2299 break;
2301 #if 0 /* we don't use that for now */
2302 case ENC_AS_EXTENDED:
2303 if (!g_utf8_validate(val, -1, NULL))
2304 g_string_append_printf(buf, "%s*=%s''", param,
2305 conv_get_locale_charset_str());
2306 else
2307 g_string_append_printf(buf, "%s*=%s''", param,
2308 CS_INTERNAL);
2309 for (valpos = val; *valpos != '\0'; valpos++) {
2310 if (IS_ASCII(*valpos) && isalnum(*valpos)) {
2311 g_string_append_printf(buf, "%c", *valpos);
2312 } else {
2313 gchar hexstr[3] = "XX";
2314 get_hex_str(hexstr, *valpos);
2315 g_string_append_printf(buf, "%%%s", hexstr);
2318 break;
2319 #else
2320 case ENC_AS_EXTENDED:
2321 debug_print("Unhandled ENC_AS_EXTENDED.\n");
2322 break;
2323 #endif
2324 case ENC_AS_ENCWORD:
2325 len = MAX(strlen(val)*6, 512);
2326 tmp = g_malloc(len+1);
2327 codeconv_set_strict(TRUE);
2328 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2329 prefs_common.outgoing_charset);
2330 codeconv_set_strict(FALSE);
2331 if (!tmp || !*tmp) {
2332 codeconv_set_strict(TRUE);
2333 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2334 conv_get_outgoing_charset_str());
2335 codeconv_set_strict(FALSE);
2337 if (!tmp || !*tmp) {
2338 codeconv_set_strict(TRUE);
2339 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2340 CS_UTF_8);
2341 codeconv_set_strict(FALSE);
2343 if (!tmp || !*tmp) {
2344 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
2345 CS_UTF_8);
2347 g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
2348 g_free(tmp);
2349 break;
2353 if (buf->str && strlen(buf->str)) {
2354 tmp = strstr(buf->str, "\n");
2355 if (tmp)
2356 len = (tmp - buf->str);
2357 else
2358 len = strlen(buf->str);
2359 if (pdata->len + len > 76) {
2360 if (fprintf(pdata->fp, ";\n %s", buf->str) < 0)
2361 pdata->error = TRUE;
2362 pdata->len = strlen(buf->str) + 1;
2363 } else {
2364 if (fprintf(pdata->fp, "; %s", buf->str) < 0)
2365 pdata->error = TRUE;
2366 pdata->len += strlen(buf->str) + 2;
2369 g_string_free(buf, TRUE);
2372 #define TRY(func) { \
2373 if (!(func)) { \
2374 return -1; \
2378 int procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
2380 struct TypeTable *type_table;
2381 ParametersData *pdata = g_new0(ParametersData, 1);
2382 debug_print("procmime_write_mime_header\n");
2384 pdata->fp = fp;
2385 pdata->error = FALSE;
2386 for (type_table = mime_type_table; type_table->str != NULL; type_table++)
2387 if (mimeinfo->type == type_table->type) {
2388 gchar *buf = g_strdup_printf(
2389 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
2390 if (fprintf(fp, "%s", buf) < 0) {
2391 g_free(buf);
2392 g_free(pdata);
2393 return -1;
2395 pdata->len = strlen(buf);
2396 g_free(buf);
2397 break;
2399 g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
2400 if (pdata->error == TRUE) {
2401 g_free(pdata);
2402 return -1;
2404 g_free(pdata);
2406 TRY(fprintf(fp, "\n") >= 0);
2408 if (mimeinfo->encoding_type != ENC_UNKNOWN)
2409 TRY(fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type)) >= 0);
2411 if (mimeinfo->description != NULL)
2412 TRY(fprintf(fp, "Content-Description: %s\n", mimeinfo->description) >= 0);
2414 if (mimeinfo->id != NULL)
2415 TRY(fprintf(fp, "Content-ID: %s\n", mimeinfo->id) >= 0);
2417 if (mimeinfo->location != NULL)
2418 TRY(fprintf(fp, "Content-Location: %s\n", mimeinfo->location) >= 0);
2420 if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
2421 ParametersData *pdata = g_new0(ParametersData, 1);
2422 gchar *buf = NULL;
2423 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
2424 buf = g_strdup("Content-Disposition: inline");
2425 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
2426 buf = g_strdup("Content-Disposition: attachment");
2427 else
2428 buf = g_strdup("Content-Disposition: unknown");
2430 if (fprintf(fp, "%s", buf) < 0) {
2431 g_free(buf);
2432 g_free(pdata);
2433 return -1;
2435 pdata->len = strlen(buf);
2436 g_free(buf);
2438 pdata->fp = fp;
2439 pdata->error = FALSE;
2440 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
2441 if (pdata->error == TRUE) {
2442 g_free(pdata);
2443 return -1;
2445 g_free(pdata);
2446 TRY(fprintf(fp, "\n") >= 0);
2449 TRY(fprintf(fp, "\n") >= 0);
2451 return 0;
2454 static gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
2456 FILE *infp;
2457 GNode *childnode;
2458 MimeInfo *child;
2459 gchar buf[BUFFSIZE];
2460 gboolean skip = FALSE;
2461 size_t len;
2463 debug_print("procmime_write_message_rfc822\n");
2465 /* write header */
2466 switch (mimeinfo->content) {
2467 case MIMECONTENT_FILE:
2468 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2469 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2470 return -1;
2472 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2473 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2474 claws_fclose(infp);
2475 return -1;
2477 while (claws_fgets(buf, sizeof(buf), infp) == buf) {
2478 strcrchomp(buf);
2479 if (buf[0] == '\n' && buf[1] == '\0')
2480 break;
2481 if (skip && (buf[0] == ' ' || buf[0] == '\t'))
2482 continue;
2483 if (g_ascii_strncasecmp(buf, "MIME-Version:", 13) == 0 ||
2484 g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
2485 g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
2486 g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
2487 g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
2488 g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 ||
2489 g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
2490 skip = TRUE;
2491 continue;
2493 len = strlen(buf);
2494 if (claws_fwrite(buf, sizeof(gchar), len, fp) < len) {
2495 g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from file", len);
2496 claws_fclose(infp);
2497 return -1;
2499 skip = FALSE;
2501 claws_fclose(infp);
2502 break;
2504 case MIMECONTENT_MEM:
2505 len = strlen(mimeinfo->data.mem);
2506 if (claws_fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) {
2507 g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from mem", len);
2508 return -1;
2510 break;
2512 default:
2513 break;
2516 childnode = mimeinfo->node->children;
2517 if (childnode == NULL)
2518 return -1;
2520 child = (MimeInfo *) childnode->data;
2521 if (fprintf(fp, "MIME-Version: 1.0\n") < 0) {
2522 g_warning("failed to write mime version");
2523 return -1;
2525 if (procmime_write_mime_header(child, fp) < 0)
2526 return -1;
2527 return procmime_write_mimeinfo(child, fp);
2530 static gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
2532 FILE *infp;
2533 GNode *childnode;
2534 gchar *boundary, *str, *str2;
2535 gchar buf[BUFFSIZE];
2536 gboolean firstboundary;
2537 size_t len;
2539 debug_print("procmime_write_multipart\n");
2541 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
2543 switch (mimeinfo->content) {
2544 case MIMECONTENT_FILE:
2545 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2546 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2547 return -1;
2549 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2550 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2551 claws_fclose(infp);
2552 return -1;
2554 while (claws_fgets(buf, sizeof(buf), infp) == buf) {
2555 if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
2556 break;
2557 len = strlen(buf);
2558 if (claws_fwrite(buf, sizeof(gchar), len, fp) < len) {
2559 g_warning("failed to write %"G_GSIZE_FORMAT, len);
2560 claws_fclose(infp);
2561 return -1;
2564 claws_fclose(infp);
2565 break;
2567 case MIMECONTENT_MEM:
2568 str = g_strdup(mimeinfo->data.mem);
2569 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
2570 (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
2571 *(str2 - 2) = '\0';
2572 len = strlen(str);
2573 if (claws_fwrite(str, sizeof(gchar), len, fp) < len) {
2574 g_warning("failed to write %"G_GSIZE_FORMAT" from mem", len);
2575 g_free(str);
2576 return -1;
2578 g_free(str);
2579 break;
2581 default:
2582 break;
2585 childnode = mimeinfo->node->children;
2586 firstboundary = TRUE;
2587 while (childnode != NULL) {
2588 MimeInfo *child = childnode->data;
2590 if (firstboundary)
2591 firstboundary = FALSE;
2592 else
2593 TRY(fprintf(fp, "\n") >= 0);
2595 TRY(fprintf(fp, "--%s\n", boundary) >= 0);
2597 if (procmime_write_mime_header(child, fp) < 0)
2598 return -1;
2599 if (procmime_write_mimeinfo(child, fp) < 0)
2600 return -1;
2602 childnode = g_node_next_sibling(childnode);
2604 TRY(fprintf(fp, "\n--%s--\n", boundary) >= 0);
2606 return 0;
2609 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
2611 FILE *infp;
2612 size_t len;
2613 debug_print("procmime_write_mimeinfo\n");
2615 if (G_NODE_IS_LEAF(mimeinfo->node)) {
2616 switch (mimeinfo->content) {
2617 case MIMECONTENT_FILE:
2618 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2619 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2620 return -1;
2622 copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
2623 claws_fclose(infp);
2624 return 0;
2626 case MIMECONTENT_MEM:
2627 len = strlen(mimeinfo->data.mem);
2628 if (claws_fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len)
2629 return -1;
2630 return 0;
2632 default:
2633 return 0;
2635 } else {
2636 /* Call writer for mime type */
2637 switch (mimeinfo->type) {
2638 case MIMETYPE_MESSAGE:
2639 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
2640 return procmime_write_message_rfc822(mimeinfo, fp);
2642 break;
2644 case MIMETYPE_MULTIPART:
2645 return procmime_write_multipart(mimeinfo, fp);
2647 default:
2648 break;
2651 return -1;
2654 return 0;
2657 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
2659 gchar *base;
2661 if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
2662 base = g_strdup("mimetmp.html");
2663 else {
2664 const gchar *basetmp;
2665 gchar *basename;
2667 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
2668 if (basetmp == NULL)
2669 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
2670 if (basetmp == NULL)
2671 basetmp = "mimetmp";
2672 basename = g_path_get_basename(basetmp);
2673 if (*basename == '\0') {
2674 g_free(basename);
2675 basename = g_strdup("mimetmp");
2677 base = conv_filename_from_utf8(basename);
2678 g_free(basename);
2679 subst_for_shellsafe_filename(base);
2682 return base;
2685 void *procmime_get_part_as_string(MimeInfo *mimeinfo,
2686 gboolean null_terminate)
2688 FILE *infp;
2689 gchar *data;
2690 gint length, readlength;
2692 cm_return_val_if_fail(mimeinfo != NULL, NULL);
2694 if (mimeinfo->encoding_type != ENC_BINARY &&
2695 !procmime_decode_content(mimeinfo))
2696 return NULL;
2698 if (mimeinfo->content == MIMECONTENT_MEM)
2699 return g_strdup(mimeinfo->data.mem);
2701 if ((infp = claws_fopen(mimeinfo->data.filename, "rb")) == NULL) {
2702 FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
2703 return NULL;
2706 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
2707 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
2708 claws_fclose(infp);
2709 return NULL;
2712 length = mimeinfo->length;
2714 data = g_malloc(null_terminate ? length + 1 : length);
2715 if (data == NULL) {
2716 g_warning("could not allocate %d bytes for procmime_get_part_as_string",
2717 (null_terminate ? length + 1 : length));
2718 claws_fclose(infp);
2719 return NULL;
2722 readlength = claws_fread(data, length, 1, infp);
2723 if (readlength <= 0) {
2724 FILE_OP_ERROR(mimeinfo->data.filename, "fread");
2725 g_free(data);
2726 claws_fclose(infp);
2727 return NULL;
2730 claws_fclose(infp);
2732 if (null_terminate)
2733 data[length] = '\0';
2735 return data;
2738 /* Returns an open GInputStream. The caller should just
2739 * read mimeinfo->length bytes from it and then release it. */
2740 GInputStream *procmime_get_part_as_inputstream(MimeInfo *mimeinfo)
2742 cm_return_val_if_fail(mimeinfo != NULL, NULL);
2744 if (mimeinfo->encoding_type != ENC_BINARY &&
2745 !procmime_decode_content(mimeinfo)) {
2746 g_warning("could not decode part");
2747 return NULL;
2749 if (mimeinfo->content == MIMECONTENT_MEM) {
2750 /* NULL for destroy func, since we're not copying
2751 * the data for the stream. */
2752 return g_memory_input_stream_new_from_data(
2753 mimeinfo->data.mem,
2754 mimeinfo->length, NULL);
2755 } else {
2756 return g_memory_input_stream_new_from_data(
2757 procmime_get_part_as_string(mimeinfo, FALSE),
2758 mimeinfo->length, g_free);
2762 GdkPixbuf *procmime_get_part_as_pixbuf(MimeInfo *mimeinfo, GError **error)
2764 GdkPixbuf *pixbuf;
2765 GInputStream *stream;
2767 if (error)
2768 *error = NULL;
2770 stream = procmime_get_part_as_inputstream(mimeinfo);
2771 if (stream == NULL) {
2772 if (error)
2773 *error = g_error_new_literal(G_FILE_ERROR, -1, _("Could not decode part"));
2774 return NULL;
2777 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, error);
2778 g_object_unref(stream);
2780 if (error && *error != NULL)
2781 return NULL;
2783 return pixbuf;