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