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