82a8f9e81ab5aaff737a9db3c9c975828e3bd4b5
[claws.git] / src / plugins / tnef_parse / tnef_parse.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Colin Leroy <colin@colino.net>
4  * and the Claws Mail Team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25
26 #include <unistd.h>
27 #include <stdio.h>
28
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32
33 #ifdef YTNEF_H_SUBDIR
34 #include <libytnef/tnef-types.h>
35 #include <libytnef/ytnef.h>
36 #include <libytnef/mapi.h>
37 #include <libytnef/mapidefs.h>
38 #else
39 #include <tnef-types.h>
40 #include <ytnef.h>
41 #include <mapi.h>
42 #include <mapidefs.h>
43 #endif
44
45 #include "common/claws.h"
46 #include "common/version.h"
47 #include "main.h"
48 #include "plugin.h"
49 #include "procmime.h"
50 #include "utils.h"
51 #include "file-utils.h"
52
53 #include "tnef_dump.h"
54
55 static MimeParser *tnef_parser = NULL;
56
57 static MimeInfo *tnef_broken_mimeinfo(const gchar *reason)
58 {
59         MimeInfo *sub_info = NULL;
60         gchar *tmpfilename = NULL;
61         FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
62         GStatBuf statbuf;
63
64         if (!fp) {
65                 g_free(tmpfilename);
66                 return NULL;
67         }
68         sub_info = procmime_mimeinfo_new();
69         sub_info->content = MIMECONTENT_FILE;
70         sub_info->data.filename = tmpfilename;
71         sub_info->type = MIMETYPE_TEXT;
72         sub_info->subtype = g_strdup("plain");
73
74         fprintf(fp, _("\n"
75                         "Claws Mail TNEF parser:\n\n"
76                         "%s\n"), reason?reason:_("Unknown error"));
77
78         claws_fclose(fp);
79         if (g_stat(tmpfilename, &statbuf) < 0) {
80                 claws_unlink(tmpfilename);
81                 procmime_mimeinfo_free_all(&sub_info);
82                 return NULL;
83
84         }
85
86         sub_info->tmp = TRUE;
87         sub_info->length = statbuf.st_size;
88         sub_info->encoding_type = ENC_BINARY;
89
90         return sub_info;
91
92 }
93
94 static MimeInfo *tnef_dump_file(const gchar *filename, char *data, size_t size)
95 {
96         MimeInfo *sub_info = NULL;
97         gchar *tmpfilename = NULL;
98         FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
99         GStatBuf statbuf;
100         gchar *content_type = NULL;
101         if (!fp) {
102                 g_free(tmpfilename);
103                 return NULL;
104         }
105         sub_info = procmime_mimeinfo_new();
106         sub_info->content = MIMECONTENT_FILE;
107         sub_info->data.filename = tmpfilename;
108         sub_info->type = MIMETYPE_APPLICATION;
109         sub_info->subtype = g_strdup("octet-stream");
110         
111         if (filename) {
112                 g_hash_table_insert(sub_info->typeparameters,
113                                     g_strdup("filename"),
114                                     g_strdup(filename));
115         
116                 content_type = procmime_get_mime_type(filename);
117                 if (content_type && strchr(content_type, '/')) {
118                         g_free(sub_info->subtype);
119                         sub_info->subtype = g_strdup(strchr(content_type, '/')+1);
120                         *(strchr(content_type, '/')) = '\0';
121                         sub_info->type = procmime_get_media_type(content_type);
122                         g_free(content_type);
123                 }
124         } 
125
126         if (claws_fwrite(data, 1, size, fp) < size) {
127                 FILE_OP_ERROR(tmpfilename, "claws_fwrite");
128                 claws_fclose(fp);
129                 claws_unlink(tmpfilename);
130                 procmime_mimeinfo_free_all(&sub_info);
131                 return tnef_broken_mimeinfo(_("Failed to write the part data."));
132         }
133         claws_fclose(fp);
134
135         if (g_stat(tmpfilename, &statbuf) < 0) {
136                 claws_unlink(tmpfilename);
137                 procmime_mimeinfo_free_all(&sub_info);
138                 return tnef_broken_mimeinfo(_("Failed to write the part data."));
139         } else {
140                 sub_info->tmp = TRUE;
141                 sub_info->length = statbuf.st_size;
142                 sub_info->encoding_type = ENC_BINARY;
143         }
144
145         return sub_info;
146 }
147
148 MimeInfo *tnef_parse_vcal(TNEFStruct *tnef)
149 {
150         MimeInfo *sub_info = NULL;
151         gchar *tmpfilename = NULL;
152         FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
153         GStatBuf statbuf;
154         gboolean result = FALSE;
155         if (!fp) {
156                 g_free(tmpfilename);
157                 return NULL;
158         }
159         sub_info = procmime_mimeinfo_new();
160         sub_info->content = MIMECONTENT_FILE;
161         sub_info->data.filename = tmpfilename;
162         sub_info->type = MIMETYPE_TEXT;
163         sub_info->subtype = g_strdup("calendar");
164         g_hash_table_insert(sub_info->typeparameters,
165                             g_strdup("filename"),
166                             g_strdup("calendar.ics"));
167
168         result = SaveVCalendar(fp, tnef);
169
170         claws_fclose(fp);
171
172         if (g_stat(tmpfilename, &statbuf) < 0) {
173                 result = FALSE;
174         } else {
175                 sub_info->tmp = TRUE;
176                 sub_info->length = statbuf.st_size;
177                 sub_info->encoding_type = ENC_BINARY;
178         }
179
180         if (!result) {
181                 claws_unlink(tmpfilename);
182                 procmime_mimeinfo_free_all(&sub_info);
183                 return tnef_broken_mimeinfo(_("Failed to parse VCalendar data."));
184         }
185         return sub_info;
186 }
187
188 MimeInfo *tnef_parse_vtask(TNEFStruct *tnef)
189 {
190         MimeInfo *sub_info = NULL;
191         gchar *tmpfilename = NULL;
192         FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
193         GStatBuf statbuf;
194         gboolean result = FALSE;
195         if (!fp) {
196                 g_free(tmpfilename);
197                 return NULL;
198         }
199         sub_info = procmime_mimeinfo_new();
200         sub_info->content = MIMECONTENT_FILE;
201         sub_info->data.filename = tmpfilename;
202         sub_info->type = MIMETYPE_TEXT;
203         sub_info->subtype = g_strdup("calendar");
204         g_hash_table_insert(sub_info->typeparameters,
205                             g_strdup("filename"),
206                             g_strdup("task.ics"));
207
208         result = SaveVTask(fp, tnef);
209
210         claws_fclose(fp);
211
212         if (g_stat(tmpfilename, &statbuf) < 0) {
213                 result = FALSE;
214         } else {
215                 sub_info->tmp = TRUE;
216                 sub_info->length = statbuf.st_size;
217                 sub_info->encoding_type = ENC_BINARY;
218         }
219         if (!result) {
220                 claws_unlink(tmpfilename);
221                 procmime_mimeinfo_free_all(&sub_info);
222                 return tnef_broken_mimeinfo(_("Failed to parse VTask data."));
223         }
224         return sub_info;
225 }
226
227 MimeInfo *tnef_parse_rtf(TNEFStruct *tnef, variableLength *tmp_var)
228 {
229         variableLength buf;
230         MimeInfo *info = NULL;
231         buf.data = DecompressRTF(tmp_var, &(buf.size));
232         if (buf.data) {
233                 info = tnef_dump_file("message.rtf", buf.data, buf.size);
234                 free(buf.data);
235                 return info;
236         } else {
237                 return NULL;
238         }
239 }
240
241 MimeInfo *tnef_parse_vcard(TNEFStruct *tnef)
242 {
243         MimeInfo *sub_info = NULL;
244         gchar *tmpfilename = NULL;
245         FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
246         GStatBuf statbuf;
247         gboolean result = FALSE;
248         gint ret;
249         if (!fp) {
250                 g_free(tmpfilename);
251                 return NULL;
252         }
253         sub_info = procmime_mimeinfo_new();
254         sub_info->content = MIMECONTENT_FILE;
255         sub_info->data.filename = tmpfilename;
256         sub_info->type = MIMETYPE_TEXT;
257         sub_info->subtype = g_strdup("x-vcard");
258         g_hash_table_insert(sub_info->typeparameters,
259                             g_strdup("filename"),
260                             g_strdup("contact.vcf"));
261         
262         result = SaveVCard(fp, tnef);
263         
264         claws_fclose(fp);
265
266         ret = g_stat(tmpfilename, &statbuf);
267         if (ret == -1) {
268                 debug_print("couldn't stat tmpfilename '%s'\n", tmpfilename);
269         }
270
271         if ((ret == -1) || !result) {
272                 claws_unlink(tmpfilename);
273                 procmime_mimeinfo_free_all(&sub_info);
274                 return tnef_broken_mimeinfo(_("Failed to parse VCard data."));
275         }
276
277         sub_info->tmp = TRUE;
278         sub_info->length = statbuf.st_size;
279         sub_info->encoding_type = ENC_BINARY;
280         return sub_info;
281 }
282
283 static gboolean tnef_parse (MimeParser *parser, MimeInfo *mimeinfo)
284 {
285         TNEFStruct *tnef;
286         MimeInfo *sub_info = NULL;
287         variableLength *tmp_var;
288         Attachment *att;
289         int parse_result = 0;
290         gboolean cal_done = FALSE;
291
292         if (!procmime_decode_content(mimeinfo)) {
293                 debug_print("error decoding\n");
294                 return FALSE;
295         }
296         debug_print("Tnef parser parsing part (%d).\n", mimeinfo->length);
297         if (mimeinfo->content == MIMECONTENT_FILE)
298                 debug_print("content: %s\n", mimeinfo->data.filename);
299         else 
300                 debug_print("contents in memory (len %"G_GSIZE_FORMAT")\n", 
301                         strlen(mimeinfo->data.mem));
302         
303         tnef = g_new0(TNEFStruct, 1);
304         TNEFInitialize(tnef);
305
306         tnef->Debug = debug_get_mode();
307
308         if (mimeinfo->content == MIMECONTENT_MEM)
309                 parse_result = TNEFParseMemory(mimeinfo->data.mem, mimeinfo->length, tnef);
310         else
311                 parse_result = TNEFParseFile(mimeinfo->data.filename, tnef);
312         
313         mimeinfo->type = MIMETYPE_MULTIPART;
314         mimeinfo->subtype = g_strdup("mixed");
315         g_hash_table_insert(mimeinfo->typeparameters,
316                             g_strdup("description"),
317                             g_strdup("Parsed from MS-TNEF"));
318
319         if (parse_result != 0) {
320                 g_warning("Failed to parse TNEF data.");
321                 TNEFFree(tnef);
322                 return FALSE;
323         }
324         
325         sub_info = NULL;
326         if (tnef->messageClass[0] != '\0') {
327                 if (strcmp(tnef->messageClass, "IPM.Contact") == 0)
328                         sub_info = tnef_parse_vcard(tnef);
329                 else if (strcmp(tnef->messageClass, "IPM.Task") == 0)
330                         sub_info = tnef_parse_vtask(tnef);
331                 else if (strcmp(tnef->messageClass, "IPM.Appointment") == 0) {
332                         sub_info = tnef_parse_vcal(tnef);
333                         cal_done = TRUE;
334                 }
335         }
336
337         if (sub_info)
338                 g_node_append(mimeinfo->node, sub_info->node);
339         sub_info = NULL;
340
341         if (tnef->MapiProperties.count > 0) {
342                 tmp_var = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG(PT_BINARY,PR_RTF_COMPRESSED));
343                 if (tmp_var != MAPI_UNDEFINED) {
344                         sub_info = tnef_parse_rtf(tnef, tmp_var);
345                 }
346         }
347
348         if (sub_info)
349                 g_node_append(mimeinfo->node, sub_info->node);
350         sub_info = NULL;
351
352         tmp_var = MAPIFindUserProp(&(tnef->MapiProperties), PROP_TAG(PT_STRING8,0x24));
353         if (tmp_var != MAPI_UNDEFINED) {
354                 if (!cal_done && strcmp(tmp_var->data, "IPM.Appointment") == 0) {
355                         sub_info = tnef_parse_vcal(tnef);
356                 }
357         }
358         
359         if (sub_info)
360                 g_node_append(mimeinfo->node, sub_info->node);
361         sub_info = NULL;
362
363         att = tnef->starting_attach.next;
364         while (att) {
365                 gchar *filename = NULL;
366                 gboolean is_object = TRUE;
367                 DWORD signature;
368
369                 tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(30,0x3707));
370                 if (tmp_var == MAPI_UNDEFINED)
371                         tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(30,0x3001));
372                 if (tmp_var == MAPI_UNDEFINED)
373                         tmp_var = &(att->Title);
374
375                 if (tmp_var->data)
376                         filename = g_strdup(tmp_var->data);
377                 
378                 tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(PT_OBJECT, PR_ATTACH_DATA_OBJ));
379                 if (tmp_var == MAPI_UNDEFINED)
380                         tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(PT_BINARY, PR_ATTACH_DATA_OBJ));
381                 if (tmp_var == MAPI_UNDEFINED) {
382                         tmp_var = &(att->FileData);
383                         is_object = FALSE;
384                 }
385                 
386                 sub_info = tnef_dump_file(filename, 
387                         tmp_var->data + (is_object ? 16:0), 
388                         tmp_var->size - (is_object ? 16:0));
389                 
390                 if (sub_info)
391                         g_node_append(mimeinfo->node, sub_info->node);
392         
393                 memcpy(&signature, tmp_var->data+(is_object ? 16:0), sizeof(DWORD));
394
395                 if (TNEFCheckForSignature(signature) == 0) {
396                         debug_print("that's TNEF stuff, process it\n");
397                         tnef_parse(parser, sub_info);
398                 }
399
400                 sub_info = NULL;
401                 
402                 att = att->next;
403
404                 g_free(filename);
405         }
406         
407         TNEFFree(tnef);
408         return TRUE;
409 }
410
411 gint plugin_init(gchar **error)
412 {
413         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
414                                 VERSION_NUMERIC, _("TNEF Parser"), error))
415                 return -1;
416
417         tnef_parser = g_new0(MimeParser, 1);
418         tnef_parser->type = MIMETYPE_APPLICATION;
419         tnef_parser->sub_type = "ms-tnef";
420         tnef_parser->parse = tnef_parse;
421         
422         procmime_mimeparser_register(tnef_parser);
423
424         return 0;
425 }
426
427 gboolean plugin_done(void)
428 {
429         procmime_mimeparser_unregister(tnef_parser);
430         g_free(tnef_parser);
431         tnef_parser = NULL;
432
433         return TRUE;
434 }
435
436 const gchar *plugin_name(void)
437 {
438         return _("TNEF Parser");
439 }
440
441 const gchar *plugin_desc(void)
442 {
443         return _("This Claws Mail plugin allows you to read application/ms-tnef attachments.\n\n"
444                  "The plugin uses the Ytnef library, which is copyright 2002-2007 by "
445                  "Randall Hand <yerase@yerot.com>");
446 }
447
448 const gchar *plugin_type(void)
449 {
450         return "GTK2";
451 }
452
453 const gchar *plugin_licence(void)
454 {
455         return "GPL3+";
456 }
457
458 const gchar *plugin_version(void)
459 {
460         return VERSION;
461 }
462
463 struct PluginFeature *plugin_provides(void)
464 {
465         static struct PluginFeature features[] = 
466                 { {PLUGIN_MIMEPARSER, "application/ms-tnef"},
467                   {PLUGIN_NOTHING, NULL}};
468         return features;
469 }