2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto & The Sylpheed-Claws Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
32 #include <sys/types.h>
38 #include "procheader.h"
40 #include "quoted-printable.h"
47 #include "prefs_common.h"
49 #include "prefs_gtk.h"
51 static GHashTable *procmime_get_mime_type_table (void);
53 MimeInfo *procmime_mimeinfo_new(void)
57 mimeinfo = g_new0(MimeInfo, 1);
58 mimeinfo->content = MIMECONTENT_EMPTY;
59 mimeinfo->data.filename = NULL;
61 mimeinfo->type = MIMETYPE_UNKNOWN;
62 mimeinfo->encoding_type = ENC_UNKNOWN;
63 mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
65 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
66 mimeinfo->dispositionparameters
67 = g_hash_table_new(g_str_hash, g_str_equal);
69 mimeinfo->node = g_node_new(mimeinfo);
74 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
82 static gchar *forced_charset = NULL;
84 void procmime_force_charset(const gchar *str)
86 g_free(forced_charset);
87 forced_charset = NULL;
89 forced_charset = g_strdup(str);
92 static EncodingType forced_encoding = 0;
94 void procmime_force_encoding(EncodingType encoding)
96 forced_encoding = encoding;
99 static gboolean free_func(GNode *node, gpointer data)
101 MimeInfo *mimeinfo = (MimeInfo *) node->data;
103 switch (mimeinfo->content) {
104 case MIMECONTENT_FILE:
106 unlink(mimeinfo->data.filename);
107 g_free(mimeinfo->data.filename);
110 case MIMECONTENT_MEM:
112 g_free(mimeinfo->data.mem);
117 g_free(mimeinfo->subtype);
118 g_free(mimeinfo->description);
119 g_free(mimeinfo->id);
121 g_hash_table_foreach_remove(mimeinfo->typeparameters,
122 procmime_mimeinfo_parameters_destroy, NULL);
123 g_hash_table_destroy(mimeinfo->typeparameters);
124 g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
125 procmime_mimeinfo_parameters_destroy, NULL);
126 g_hash_table_destroy(mimeinfo->dispositionparameters);
128 if (mimeinfo->privacy)
129 privacy_free_privacydata(mimeinfo->privacy);
136 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
143 node = mimeinfo->node;
144 g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
146 g_node_destroy(node);
150 MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
152 MimeInfo *child = parent->children;
155 parent->children = mimeinfo;
157 while (child->next != NULL)
160 child->next = mimeinfo;
163 mimeinfo->parent = parent;
164 mimeinfo->level = parent->level + 1;
169 void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
171 MimeInfo *parent = old->parent;
174 g_return_if_fail(parent != NULL);
175 g_return_if_fail(new->next == NULL);
177 for (child = parent->children; child && child != old;
181 g_warning("oops: parent can't find it's own child");
184 procmime_mimeinfo_free_all(old);
186 if (child == parent->children) {
187 new->next = parent->children->next;
188 parent->children = new;
190 new->next = child->next;
196 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
198 g_return_val_if_fail(mimeinfo != NULL, NULL);
199 g_return_val_if_fail(mimeinfo->node != NULL, NULL);
201 if (mimeinfo->node->parent == NULL)
203 return (MimeInfo *) mimeinfo->node->parent->data;
206 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
208 g_return_val_if_fail(mimeinfo != NULL, NULL);
209 g_return_val_if_fail(mimeinfo->node != NULL, NULL);
211 if (mimeinfo->node->children)
212 return (MimeInfo *) mimeinfo->node->children->data;
213 if (mimeinfo->node->next)
214 return (MimeInfo *) mimeinfo->node->next->data;
216 if (mimeinfo->node->parent == NULL)
219 while (mimeinfo->node->parent != NULL) {
220 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
221 if (mimeinfo->node->next)
222 return (MimeInfo *) mimeinfo->node->next->data;
228 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
233 filename = procmsg_get_message_file(msginfo);
236 if (msginfo->folder->stype != F_QUEUE &&
237 msginfo->folder->stype != F_DRAFT)
238 mimeinfo = procmime_scan_file(filename);
240 mimeinfo = procmime_scan_queue_file(filename);
248 H_CONTENT_TRANSFER_ENCODING = 0,
250 H_CONTENT_DISPOSITION = 2,
251 H_CONTENT_DESCRIPTION = 3,
255 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
259 g_return_val_if_fail(mimeinfo != NULL, NULL);
260 g_return_val_if_fail(name != NULL, NULL);
262 value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
264 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
269 gboolean procmime_decode_content(MimeInfo *mimeinfo)
277 EncodingType encoding = forced_encoding
279 : mimeinfo->encoding_type;
281 g_return_val_if_fail(mimeinfo != NULL, FALSE);
283 if (encoding == ENC_UNKNOWN ||
284 encoding == ENC_BINARY)
287 infp = fopen(mimeinfo->data.filename, "rb");
292 fseek(infp, mimeinfo->offset, SEEK_SET);
294 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
300 readend = mimeinfo->offset + mimeinfo->length;
302 if (encoding == ENC_QUOTED_PRINTABLE) {
303 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
305 len = qp_decode_line(buf);
306 fwrite(buf, len, 1, outfp);
308 } else if (encoding == ENC_BASE64) {
309 gchar outbuf[BUFFSIZE];
311 Base64Decoder *decoder;
313 decoder = base64_decoder_new();
314 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
315 len = base64_decoder_decode(decoder, buf, outbuf);
317 g_warning("Bad BASE64 content\n");
320 fwrite(outbuf, sizeof(gchar), len, outfp);
322 base64_decoder_free(decoder);
323 } else if (encoding == ENC_X_UUENCODE) {
324 gchar outbuf[BUFFSIZE];
326 gboolean flag = FALSE;
328 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
329 if (!flag && strncmp(buf,"begin ", 6)) continue;
332 len = fromuutobits(outbuf, buf);
335 g_warning("Bad UUENCODE content(%d)\n", len);
338 fwrite(outbuf, sizeof(gchar), len, outfp);
343 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
351 stat(tmpfilename, &statbuf);
352 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
353 unlink(mimeinfo->data.filename);
354 if (mimeinfo->data.filename != NULL)
355 g_free(mimeinfo->data.filename);
356 mimeinfo->data.filename = tmpfilename;
357 mimeinfo->tmp = TRUE;
358 mimeinfo->offset = 0;
359 mimeinfo->length = statbuf.st_size;
360 mimeinfo->encoding_type = ENC_BINARY;
365 #define B64_LINE_SIZE 57
366 #define B64_BUFFSIZE 77
368 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
372 gchar *tmpfilename, *tmpout;
375 if (mimeinfo->encoding_type != ENC_UNKNOWN &&
376 mimeinfo->encoding_type != ENC_BINARY &&
377 mimeinfo->encoding_type != ENC_7BIT &&
378 mimeinfo->encoding_type != ENC_8BIT)
379 if(!procmime_decode_content(mimeinfo))
382 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
388 if (mimeinfo->content == MIMECONTENT_MEM) {
389 infp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpout);
395 str_write_to_file(mimeinfo->data.mem, tmpout);
396 g_free(mimeinfo->data.mem);
397 mimeinfo->tmp = TRUE;
398 mimeinfo->data.filename = tmpout;
401 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
402 g_warning("Can't open file %s\n", mimeinfo->data.filename);
403 if (mimeinfo->content == MIMECONTENT_MEM) {
404 unlink(mimeinfo->data.filename);
405 g_free(mimeinfo->data.filename);
406 mimeinfo->data.filename = NULL;
411 if (encoding == ENC_BASE64) {
412 gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
414 while ((len = fread(inbuf, sizeof(gchar),
415 B64_LINE_SIZE, infp))
417 base64_encode(outbuf, inbuf, B64_LINE_SIZE);
418 fputs(outbuf, outfp);
421 if (len > 0 && feof(infp)) {
422 base64_encode(outbuf, inbuf, len);
423 fputs(outbuf, outfp);
426 } else if (encoding == ENC_QUOTED_PRINTABLE) {
427 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
429 while (fgets(inbuf, sizeof(inbuf), infp) != NULL) {
430 qp_encode_line(outbuf, inbuf);
431 fputs(outbuf, outfp);
436 while (fgets(buf, sizeof(buf), infp) != NULL) {
445 stat(tmpfilename, &statbuf);
446 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
447 unlink(mimeinfo->data.filename);
448 g_free(mimeinfo->data.filename);
449 mimeinfo->data.filename = tmpfilename;
450 mimeinfo->tmp = TRUE;
451 mimeinfo->offset = 0;
452 mimeinfo->length = statbuf.st_size;
453 mimeinfo->encoding_type = encoding;
454 mimeinfo->content = MIMECONTENT_FILE;
459 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
463 gint restlength, readlength;
465 g_return_val_if_fail(outfile != NULL, -1);
466 g_return_val_if_fail(mimeinfo != NULL, -1);
468 if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
471 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
472 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
475 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
476 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
480 if ((outfp = fopen(outfile, "wb")) == NULL) {
481 FILE_OP_ERROR(outfile, "fopen");
486 restlength = mimeinfo->length;
488 while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
489 fwrite(buf, 1, readlength, outfp);
490 restlength -= readlength;
494 if (fclose(outfp) == EOF) {
495 FILE_OP_ERROR(outfile, "fclose");
503 struct ContentRenderer {
508 static GList * renderer_list = NULL;
510 static struct ContentRenderer *
511 content_renderer_new(char * content_type, char * renderer)
513 struct ContentRenderer * cr;
515 cr = g_new(struct ContentRenderer, 1);
519 cr->content_type = g_strdup(content_type);
520 cr->renderer = g_strdup(renderer);
525 static void content_renderer_free(struct ContentRenderer * cr)
527 g_free(cr->content_type);
528 g_free(cr->renderer);
532 void renderer_read_config(void)
538 g_list_foreach(renderer_list, (GFunc) content_renderer_free, NULL);
539 renderer_list = NULL;
541 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
542 f = fopen(rcpath, "rb");
548 while (fgets(buf, BUFFSIZE, f)) {
550 struct ContentRenderer * cr;
553 p = strchr(buf, ' ');
558 cr = content_renderer_new(buf, p + 1);
562 renderer_list = g_list_append(renderer_list, cr);
568 void renderer_write_config(void)
574 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
576 if ((pfile = prefs_write_open(rcpath)) == NULL) {
577 g_warning("failed to write configuration to file\n");
584 for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
585 struct ContentRenderer * renderer;
586 renderer = cur->data;
587 fprintf(pfile->fp, "%s %s\n", renderer->content_type,
591 if (prefs_file_close(pfile) < 0) {
592 g_warning("failed to write configuration to file\n");
597 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
600 const gchar *src_codeset;
601 gboolean conv_fail = FALSE;
604 struct ContentRenderer * renderer;
606 gchar *tmpfile, *content_type;
608 g_return_val_if_fail(mimeinfo != NULL, NULL);
610 if (!procmime_decode_content(mimeinfo))
613 tmpfile = procmime_get_tmp_file_name(mimeinfo);
617 if (procmime_get_part(tmpfile, mimeinfo) < 0) {
622 tmpfp = fopen(tmpfile, "rb");
628 if ((outfp = my_tmpfile()) == NULL) {
635 src_codeset = forced_charset
637 procmime_mimeinfo_get_parameter(mimeinfo, "charset");
641 content_type = procmime_get_content_type_str(mimeinfo->type,
643 for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
644 struct ContentRenderer * cr;
647 if (g_ascii_strcasecmp(cr->content_type, content_type) == 0) {
652 g_free(content_type);
654 if (renderer != NULL) {
660 dup2(fileno(outfp), 1);
662 p = popen(renderer->renderer, "w");
667 fread(buf, sizeof(char), sizeof(buf),
669 fwrite(buf, sizeof(char), count, p);
674 #warning FIXME_GTK2 HTML/RTF not yet utf8
675 /* CodeConverter seems to have no effect here */
676 } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
680 conv = conv_code_converter_new(src_codeset);
681 parser = html_parser_new(tmpfp, conv);
682 while ((str = html_parse(parser)) != NULL) {
685 html_parser_destroy(parser);
686 conv_code_converter_destroy(conv);
687 } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
691 conv = conv_code_converter_new(src_codeset);
692 parser = ertf_parser_new(tmpfp, conv);
693 while ((str = ertf_parse(parser)) != NULL) {
696 ertf_parser_destroy(parser);
697 conv_code_converter_destroy(conv);
698 } else if (mimeinfo->type == MIMETYPE_TEXT) {
699 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
700 str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
712 g_warning("procmime_get_text_content(): Code conversion failed.\n");
722 /* search the first text part of (multipart) MIME message,
723 decode, convert it and output to outfp. */
724 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
727 MimeInfo *mimeinfo, *partinfo;
729 g_return_val_if_fail(msginfo != NULL, NULL);
731 mimeinfo = procmime_scan_message(msginfo);
732 if (!mimeinfo) return NULL;
735 while (partinfo && partinfo->type != MIMETYPE_TEXT)
736 partinfo = procmime_mimeinfo_next(partinfo);
739 outfp = procmime_get_text_content(partinfo);
741 procmime_mimeinfo_free_all(mimeinfo);
746 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
747 const gchar *str, gboolean case_sens)
751 gchar *(* StrFindFunc) (const gchar *haystack, const gchar *needle);
753 g_return_val_if_fail(mimeinfo != NULL, FALSE);
754 g_return_val_if_fail(mimeinfo->type == MIMETYPE_TEXT, FALSE);
755 g_return_val_if_fail(str != NULL, FALSE);
757 outfp = procmime_get_text_content(mimeinfo);
763 StrFindFunc = strstr;
765 StrFindFunc = strcasestr;
767 while (fgets(buf, sizeof(buf), outfp) != NULL) {
768 if (StrFindFunc(buf, str) != NULL) {
779 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
785 gboolean found = FALSE;
787 g_return_val_if_fail(msginfo != NULL, FALSE);
788 g_return_val_if_fail(str != NULL, FALSE);
790 filename = procmsg_get_message_file(msginfo);
791 if (!filename) return FALSE;
792 mimeinfo = procmime_scan_message(msginfo);
794 for (partinfo = mimeinfo; partinfo != NULL;
795 partinfo = procmime_mimeinfo_next(partinfo)) {
796 if (partinfo->type == MIMETYPE_TEXT) {
797 if (procmime_find_string_part
798 (partinfo, filename, str, case_sens) == TRUE) {
805 procmime_mimeinfo_free_all(mimeinfo);
811 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
813 static guint32 id = 0;
818 g_return_val_if_fail(mimeinfo != NULL, NULL);
820 g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
822 if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
823 base = "mimetmp.html";
825 const gchar *basetmp;
827 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
829 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
832 base = g_basename(basetmp);
833 if (*base == '\0') base = "mimetmp";
834 Xstrdup_a(base, base, return NULL);
835 subst_for_shellsafe_filename(base);
838 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
839 f_prefix, base, NULL);
844 static GList *mime_type_list = NULL;
846 gchar *procmime_get_mime_type(const gchar *filename)
848 static GHashTable *mime_type_table = NULL;
853 if (!mime_type_table) {
854 mime_type_table = procmime_get_mime_type_table();
855 if (!mime_type_table) return NULL;
858 filename = g_basename(filename);
859 p = strrchr(filename, '.');
862 Xstrdup_a(ext, p + 1, return NULL);
864 mime_type = g_hash_table_lookup(mime_type_table, ext);
868 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
876 static guint procmime_str_hash(gconstpointer gptr)
878 guint hash_result = 0;
881 for (str = gptr; str && *str; str++) {
882 if (isupper(*str)) hash_result += (*str + ' ');
883 else hash_result += *str;
889 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
891 const char *str1 = gptr1;
892 const char *str2 = gptr2;
894 return !g_utf8_collate(str1, str2);
897 static GHashTable *procmime_get_mime_type_table(void)
899 GHashTable *table = NULL;
904 if (!mime_type_list) {
905 mime_type_list = procmime_get_mime_type_list();
906 if (!mime_type_list) return NULL;
909 table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
911 for (cur = mime_type_list; cur != NULL; cur = cur->next) {
915 mime_type = (MimeType *)cur->data;
917 if (!mime_type->extension) continue;
919 exts = g_strsplit(mime_type->extension, " ", 16);
920 for (i = 0; exts[i] != NULL; i++) {
921 /* make the key case insensitive */
923 /* use previously dup'd key on overwriting */
924 if (g_hash_table_lookup(table, exts[i]))
927 key = g_strdup(exts[i]);
928 g_hash_table_insert(table, key, mime_type);
936 GList *procmime_get_mime_type_list(void)
946 return mime_type_list;
948 if ((fp = fopen("/etc/mime.types", "rb")) == NULL) {
949 if ((fp = fopen(SYSCONFDIR "/mime.types", "rb")) == NULL) {
950 FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
955 while (fgets(buf, sizeof(buf), fp) != NULL) {
956 p = strchr(buf, '#');
961 while (*p && !isspace(*p)) p++;
966 delim = strchr(buf, '/');
967 if (delim == NULL) continue;
970 mime_type = g_new(MimeType, 1);
971 mime_type->type = g_strdup(buf);
972 mime_type->sub_type = g_strdup(delim + 1);
974 while (*p && isspace(*p)) p++;
976 mime_type->extension = g_strdup(p);
978 mime_type->extension = NULL;
980 list = g_list_append(list, mime_type);
986 g_warning("Can't read mime.types\n");
991 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
995 else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
996 !g_ascii_strcasecmp(charset, "US-ASCII"))
998 else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
999 !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
1000 !g_ascii_strcasecmp(charset, "Windows-1251"))
1002 else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
1003 return ENC_QUOTED_PRINTABLE;
1008 EncodingType procmime_get_encoding_for_file(const gchar *file)
1014 if ((fp = fopen(file, "rb")) == NULL) {
1015 FILE_OP_ERROR(file, "fopen");
1019 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
1023 for (p = buf, i = 0; i < len; p++, i++) {
1035 struct EncodingTable
1038 EncodingType enc_type;
1041 struct EncodingTable encoding_table[] = {
1044 {"binary", ENC_BINARY},
1045 {"quoted-printable", ENC_QUOTED_PRINTABLE},
1046 {"base64", ENC_BASE64},
1047 {"x-uuencode", ENC_UNKNOWN},
1048 {NULL, ENC_UNKNOWN},
1051 const gchar *procmime_get_encoding_str(EncodingType encoding)
1053 struct EncodingTable *enc_table;
1055 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1056 if (enc_table->enc_type == encoding)
1057 return enc_table->str;
1062 /* --- NEW MIME STUFF --- */
1069 static struct TypeTable mime_type_table[] = {
1070 {"text", MIMETYPE_TEXT},
1071 {"image", MIMETYPE_IMAGE},
1072 {"audio", MIMETYPE_AUDIO},
1073 {"video", MIMETYPE_VIDEO},
1074 {"application", MIMETYPE_APPLICATION},
1075 {"message", MIMETYPE_MESSAGE},
1076 {"multipart", MIMETYPE_MULTIPART},
1080 const gchar *procmime_get_media_type_str(MimeMediaType type)
1082 struct TypeTable *type_table;
1084 for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1085 if (type_table->type == type)
1086 return type_table->str;
1091 MimeMediaType procmime_get_media_type(const gchar *str)
1093 struct TypeTable *typetablearray;
1095 for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1096 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1097 return typetablearray->type;
1099 return MIMETYPE_UNKNOWN;
1103 *\brief Safe wrapper for content type string.
1105 *\return const gchar * Pointer to content type string.
1107 gchar *procmime_get_content_type_str(MimeMediaType type,
1108 const char *subtype)
1110 const gchar *type_str = NULL;
1112 if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1113 return g_strdup("unknown");
1114 return g_strdup_printf("%s/%s", type_str, subtype);
1117 void procmime_parse_mimepart(MimeInfo *parent,
1118 gchar *content_type,
1119 gchar *content_encoding,
1120 gchar *content_description,
1122 gchar *content_disposition,
1123 const gchar *filename,
1127 void procmime_parse_message_rfc822(MimeInfo *mimeinfo)
1129 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE},
1130 {"Content-Transfer-Encoding:",
1132 {"Content-Description:",
1136 {"Content-Disposition:",
1140 {NULL, NULL, FALSE}};
1141 guint content_start, i;
1143 gint mime_major, mime_minor;
1145 procmime_decode_content(mimeinfo);
1147 fp = fopen(mimeinfo->data.filename, "rb");
1149 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1152 fseek(fp, mimeinfo->offset, SEEK_SET);
1153 procheader_get_header_fields(fp, hentry);
1154 if (hentry[0].body != NULL)
1155 conv_unmime_header_overwrite(hentry[0].body);
1156 if (hentry[2].body != NULL)
1157 conv_unmime_header_overwrite(hentry[2].body);
1158 if (hentry[4].body != NULL)
1159 conv_unmime_header_overwrite(hentry[4].body);
1160 content_start = ftell(fp);
1163 if ((hentry[5].body != NULL) &&
1164 (sscanf(hentry[5].body, "%d.%d", &mime_major, &mime_minor) == 2) &&
1165 (mime_major == 1) && (mime_minor == 0)) {
1166 procmime_parse_mimepart(mimeinfo,
1167 hentry[0].body, hentry[1].body,
1168 hentry[2].body, hentry[3].body,
1170 mimeinfo->data.filename, content_start,
1171 mimeinfo->length - (content_start - mimeinfo->offset));
1175 subinfo = procmime_mimeinfo_new();
1176 subinfo->encoding_type = ENC_UNKNOWN;
1177 subinfo->type = MIMETYPE_TEXT;
1178 subinfo->subtype = g_strdup("plain");
1179 subinfo->data.filename = g_strdup(mimeinfo->data.filename);
1180 subinfo->offset = content_start;
1181 subinfo->length = mimeinfo->length - (content_start - mimeinfo->offset);
1183 g_node_append(mimeinfo->node, subinfo->node);
1185 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1186 g_free(hentry[i].body);
1187 hentry[i].body = NULL;
1191 void procmime_parse_multipart(MimeInfo *mimeinfo)
1193 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE},
1194 {"Content-Transfer-Encoding:",
1196 {"Content-Description:",
1200 {"Content-Disposition:",
1202 {NULL, NULL, FALSE}};
1205 gint boundary_len = 0, lastoffset = -1, i;
1206 gchar buf[BUFFSIZE];
1209 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1212 boundary_len = strlen(boundary);
1214 procmime_decode_content(mimeinfo);
1216 fp = fopen(mimeinfo->data.filename, "rb");
1218 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1221 fseek(fp, mimeinfo->offset, SEEK_SET);
1222 while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
1223 if (ftell(fp) > (mimeinfo->offset + mimeinfo->length))
1226 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1227 if (lastoffset != -1) {
1228 procmime_parse_mimepart(mimeinfo,
1229 hentry[0].body, hentry[1].body,
1230 hentry[2].body, hentry[3].body,
1232 mimeinfo->data.filename, lastoffset,
1233 (ftell(fp) - strlen(buf)) - lastoffset - 1);
1236 if (buf[2 + boundary_len] == '-' &&
1237 buf[2 + boundary_len + 1] == '-')
1240 for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1241 g_free(hentry[i].body);
1242 hentry[i].body = NULL;
1244 procheader_get_header_fields(fp, hentry);
1245 if (hentry[0].body != NULL)
1246 conv_unmime_header_overwrite(hentry[0].body);
1247 if (hentry[2].body != NULL)
1248 conv_unmime_header_overwrite(hentry[2].body);
1249 if (hentry[4].body != NULL)
1250 conv_unmime_header_overwrite(hentry[4].body);
1251 lastoffset = ftell(fp);
1254 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1255 g_free(hentry[i].body);
1256 hentry[i].body = NULL;
1261 static void parse_parameters(const gchar *parameters, GHashTable *table)
1263 gchar *params, *param, *next;
1264 GSList *convlist = NULL, *concatlist = NULL, *cur;
1266 params = g_strdup(parameters);
1269 for (; next != NULL; param = next) {
1270 gchar *attribute, *value, *tmp;
1272 gboolean convert = FALSE;
1274 next = strchr_with_skip_quote(param, '"', ';');
1283 value = strchr(attribute, '=');
1290 g_strdown(attribute);
1292 len = strlen(attribute);
1293 if (attribute[len - 1] == '*') {
1294 gchar *srcpos, *dstpos, *endpos;
1297 attribute[len - 1] = '\0';
1301 endpos = value + strlen(value);
1302 while (srcpos < endpos) {
1308 if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1319 if (value[0] == '"')
1320 extract_quote(value, '"');
1321 else if ((tmp = strchr(value, ' ')) != NULL)
1325 if (strrchr(attribute, '*') != NULL) {
1328 tmpattr = g_strdup(attribute);
1329 tmp = strrchr(tmpattr, '*');
1332 if ((tmp[1] == '0') && (tmp[2] == '\0') &&
1333 (g_slist_find_custom(concatlist, attribute, g_str_equal) == NULL))
1334 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1336 if (convert && (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL))
1337 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1340 } else if (convert) {
1341 if (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL)
1342 convlist = g_slist_prepend(convlist, g_strdup(attribute));
1345 if (g_hash_table_lookup(table, attribute) == NULL)
1346 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value));
1349 for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1350 gchar *attribute, *attrwnum, *partvalue;
1354 attribute = (gchar *) cur->data;
1355 value = g_string_sized_new(64);
1357 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1358 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1359 g_string_append(value, partvalue);
1363 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1367 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1368 g_string_free(value, TRUE);
1370 slist_free_strings(concatlist);
1371 g_slist_free(concatlist);
1373 for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1374 gchar *attribute, *key, *value;
1375 gchar *charset, *lang, *oldvalue, *newvalue;
1377 attribute = (gchar *) cur->data;
1378 if (!g_hash_table_lookup_extended(table, attribute, (gpointer *) &key, (gpointer *) &value))
1382 lang = strchr(charset, '\'');
1387 oldvalue = strchr(lang, '\'');
1388 if (oldvalue == NULL)
1393 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1395 g_hash_table_remove(table, attribute);
1399 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1401 slist_free_strings(convlist);
1402 g_slist_free(convlist);
1407 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1409 g_return_if_fail(content_type != NULL);
1410 g_return_if_fail(mimeinfo != NULL);
1412 /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1413 * if it's not available we use the default Content-Type */
1414 if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1415 mimeinfo->type = MIMETYPE_TEXT;
1416 mimeinfo->subtype = g_strdup("plain");
1417 if (g_hash_table_lookup(mimeinfo->typeparameters,
1419 g_hash_table_insert(mimeinfo->typeparameters,
1420 g_strdup("charset"),
1421 g_strdup("us-ascii"));
1423 gchar *type, *subtype, *params;
1425 type = g_strdup(content_type);
1426 subtype = strchr(type, '/') + 1;
1427 *(subtype - 1) = '\0';
1428 if ((params = strchr(subtype, ';')) != NULL) {
1433 mimeinfo->type = procmime_get_media_type(type);
1434 mimeinfo->subtype = g_strdup(subtype);
1436 /* Get mimeinfo->typeparameters */
1438 parse_parameters(params, mimeinfo->typeparameters);
1444 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1446 gchar *tmp, *params;
1448 g_return_if_fail(content_disposition != NULL);
1449 g_return_if_fail(mimeinfo != NULL);
1451 tmp = g_strdup(content_disposition);
1452 if ((params = strchr(tmp, ';')) != NULL) {
1458 if (!g_ascii_strcasecmp(tmp, "inline"))
1459 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1460 else if (!g_ascii_strcasecmp(tmp, "attachment"))
1461 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1463 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1466 parse_parameters(params, mimeinfo->dispositionparameters);
1472 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1474 struct EncodingTable *enc_table;
1476 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1477 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1478 mimeinfo->encoding_type = enc_table->enc_type;
1482 mimeinfo->encoding_type = ENC_UNKNOWN;
1486 void procmime_parse_mimepart(MimeInfo *parent,
1487 gchar *content_type,
1488 gchar *content_encoding,
1489 gchar *content_description,
1491 gchar *content_disposition,
1492 const gchar *filename,
1498 /* Create MimeInfo */
1499 mimeinfo = procmime_mimeinfo_new();
1500 mimeinfo->content = MIMECONTENT_FILE;
1502 g_node_append(parent->node, mimeinfo->node);
1503 mimeinfo->data.filename = g_strdup(filename);
1504 mimeinfo->offset = offset;
1505 mimeinfo->length = length;
1507 if (content_type != NULL) {
1508 procmime_parse_content_type(content_type, mimeinfo);
1510 mimeinfo->type = MIMETYPE_TEXT;
1511 mimeinfo->subtype = g_strdup("plain");
1512 if (g_hash_table_lookup(mimeinfo->typeparameters,
1514 g_hash_table_insert(mimeinfo->typeparameters, g_strdup("charset"), g_strdup("us-ascii"));
1517 if (content_encoding != NULL) {
1518 procmime_parse_content_encoding(content_encoding, mimeinfo);
1520 mimeinfo->encoding_type = ENC_UNKNOWN;
1523 if (content_description != NULL)
1524 mimeinfo->description = g_strdup(content_description);
1526 mimeinfo->description = NULL;
1528 if (content_id != NULL)
1529 mimeinfo->id = g_strdup(content_id);
1531 mimeinfo->id = NULL;
1533 if (content_disposition != NULL)
1534 procmime_parse_content_disposition(content_disposition, mimeinfo);
1536 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1538 /* Call parser for mime type */
1539 switch (mimeinfo->type) {
1540 case MIMETYPE_MESSAGE:
1541 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1542 procmime_parse_message_rfc822(mimeinfo);
1546 case MIMETYPE_MULTIPART:
1547 procmime_parse_multipart(mimeinfo);
1555 static gchar *typenames[] = {
1566 static gboolean output_func(GNode *node, gpointer data)
1569 MimeInfo *mimeinfo = (MimeInfo *) node->data;
1571 depth = g_node_depth(node);
1572 for (i = 0; i < depth; i++)
1574 printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1579 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1581 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1584 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1589 stat(filename, &buf);
1591 mimeinfo = procmime_mimeinfo_new();
1592 mimeinfo->content = MIMECONTENT_FILE;
1593 mimeinfo->encoding_type = ENC_UNKNOWN;
1594 mimeinfo->type = MIMETYPE_MESSAGE;
1595 mimeinfo->subtype = g_strdup("rfc822");
1596 mimeinfo->data.filename = g_strdup(filename);
1597 mimeinfo->offset = offset;
1598 mimeinfo->length = buf.st_size - offset;
1600 procmime_parse_message_rfc822(mimeinfo);
1601 if (debug_get_mode())
1602 output_mime_structure(mimeinfo, 0);
1607 MimeInfo *procmime_scan_file(const gchar *filename)
1611 g_return_val_if_fail(filename != NULL, NULL);
1613 mimeinfo = procmime_scan_file_with_offset(filename, 0);
1618 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1622 gchar buf[BUFFSIZE];
1625 g_return_val_if_fail(filename != NULL, NULL);
1628 if ((fp = fopen(filename, "rb")) == NULL)
1630 /* Skip queue header */
1631 while (fgets(buf, sizeof(buf), fp) != NULL)
1632 if (buf[0] == '\r' || buf[0] == '\n') break;
1636 mimeinfo = procmime_scan_file_with_offset(filename, offset);
1643 ENC_AS_QUOTED_STRING,
1647 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1650 gchar *val = value, *valpos;
1651 FILE *fp = user_data;
1652 EncodeAs encas = ENC_AS_TOKEN;
1654 for (valpos = val; *valpos != 0; valpos++) {
1655 if (!IS_ASCII(*valpos)) {
1656 encas = ENC_AS_EXTENDED;
1661 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
1662 encas = ENC_AS_QUOTED_STRING;
1666 /* tspecials + SPACE */
1684 encas = ENC_AS_QUOTED_STRING;
1691 fprintf(fp, "; %s=", param);
1692 fprintf(fp, "%s", val);
1695 case ENC_AS_QUOTED_STRING:
1696 fprintf(fp, "; %s=", param);
1697 fprintf(fp, "\"%s\"", val);
1700 case ENC_AS_EXTENDED:
1701 fprintf(fp, "; %s*=", param);
1702 fprintf(fp, "%s''", conv_get_current_charset_str());
1703 for (valpos = val; *valpos != '\0'; valpos++) {
1704 if (IS_ASCII(*valpos) && isalnum(*valpos))
1705 fprintf(fp, "%c", *valpos);
1707 gchar hexstr[3] = "XX";
1708 get_hex_str(hexstr, *valpos);
1709 fprintf(fp, "%%%s", hexstr);
1716 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1718 struct TypeTable *type_table;
1720 debug_print("procmime_write_mime_header\n");
1722 for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1723 if (mimeinfo->type == type_table->type) {
1724 fprintf(fp, "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1727 g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, fp);
1730 if (mimeinfo->encoding_type != ENC_UNKNOWN)
1731 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1733 if (mimeinfo->description != NULL)
1734 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1736 if (mimeinfo->id != NULL)
1737 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1739 if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
1740 fprintf(fp, "Content-Disposition: ");
1741 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
1742 fprintf(fp, "inline");
1743 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
1744 fprintf(fp, "attachment");
1746 fprintf(fp, "unknown");
1748 /* FIXME: linebreaks after too many parameters */
1749 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, fp);
1756 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
1761 gchar buf[BUFFSIZE];
1762 gboolean skip = FALSE;;
1764 debug_print("procmime_write_message_rfc822\n");
1767 switch (mimeinfo->content) {
1768 case MIMECONTENT_FILE:
1769 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1770 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1773 fseek(infp, mimeinfo->offset, SEEK_SET);
1774 while (fgets(buf, sizeof(buf), infp) == buf) {
1775 if (buf[0] == '\n' && buf[1] == '\0')
1777 if (skip && (buf[0] == ' ' || buf[0] == '\t'))
1779 if (g_ascii_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
1780 g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
1781 g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
1782 g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
1783 g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
1784 g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
1788 fwrite(buf, sizeof(gchar), strlen(buf), fp);
1794 case MIMECONTENT_MEM:
1795 fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1802 childnode = mimeinfo->node->children;
1803 if (childnode == NULL)
1806 child = (MimeInfo *) childnode->data;
1807 fprintf(fp, "Mime-Version: 1.0\n");
1808 procmime_write_mime_header(child, fp);
1809 return procmime_write_mimeinfo(child, fp);
1812 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
1816 gchar *boundary, *str, *str2;
1817 gchar buf[BUFFSIZE];
1818 gboolean firstboundary;
1820 debug_print("procmime_write_multipart\n");
1822 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1824 switch (mimeinfo->content) {
1825 case MIMECONTENT_FILE:
1826 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1827 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1830 fseek(infp, mimeinfo->offset, SEEK_SET);
1831 while (fgets(buf, sizeof(buf), infp) == buf) {
1832 if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
1834 fwrite(buf, sizeof(gchar), strlen(buf), fp);
1839 case MIMECONTENT_MEM:
1840 str = g_strdup(mimeinfo->data.mem);
1841 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
1842 (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
1844 fwrite(str, strlen(str), sizeof(gchar), fp);
1852 childnode = mimeinfo->node->children;
1853 firstboundary = TRUE;
1854 while (childnode != NULL) {
1855 MimeInfo *child = childnode->data;
1858 firstboundary = FALSE;
1861 fprintf(fp, "--%s\n", boundary);
1863 procmime_write_mime_header(child, fp);
1864 if (procmime_write_mimeinfo(child, fp) < 0)
1867 childnode = g_node_next_sibling(childnode);
1869 fprintf(fp, "\n--%s--\n", boundary);
1874 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
1878 debug_print("procmime_write_mimeinfo\n");
1880 if (G_NODE_IS_LEAF(mimeinfo->node)) {
1881 switch (mimeinfo->content) {
1882 case MIMECONTENT_FILE:
1883 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1884 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1887 copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
1891 case MIMECONTENT_MEM:
1892 fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1899 /* Call writer for mime type */
1900 switch (mimeinfo->type) {
1901 case MIMETYPE_MESSAGE:
1902 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0)
1903 return procmime_write_message_rfc822(mimeinfo, fp);
1906 case MIMETYPE_MULTIPART:
1907 return procmime_write_multipart(mimeinfo, fp);