1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 *
15 * Authors:
16 * Michael Zucchi <notzed@ximian.com>
17 *
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 *
20 */
21
22 #include "evolution-config.h"
23
24 #include <string.h>
25
26 #include "e-mail-inline-filter.h"
27 #include "e-mail-part-utils.h"
28
29 #define d(x)
30
31 G_DEFINE_TYPE (EMailInlineFilter, e_mail_inline_filter, CAMEL_TYPE_MIME_FILTER)
32
33 enum {
34 EMIF_PLAIN,
35 EMIF_BINHEX,
36 EMIF_POSTSCRIPT,
37 EMIF_PGPSIGNED,
38 EMIF_PGPENCRYPTED
39 };
40
41 static const struct {
42 const gchar *type;
43 const gchar *subtype;
44 CamelTransferEncoding encoding;
45 guint plain : 1;
46 } emif_types[] = {
47 { "text", "plain",
48 CAMEL_TRANSFER_ENCODING_DEFAULT, 1 },
49
50 { "application", "mac-binhex40",
51 CAMEL_TRANSFER_ENCODING_7BIT, 0 },
52
53 { "application", "postscript",
54 CAMEL_TRANSFER_ENCODING_7BIT, 0 },
55
56 { "application", "x-inlinepgp-signed",
57 CAMEL_TRANSFER_ENCODING_DEFAULT, 0 },
58
59 { "application", "x-inlinepgp-encrypted",
60 CAMEL_TRANSFER_ENCODING_DEFAULT, 0 }
61 };
62
63 static CamelMimePart *
construct_part_from_stream(CamelStream * mem,const GByteArray * data)64 construct_part_from_stream (CamelStream *mem,
65 const GByteArray *data)
66 {
67 CamelMimePart *part = NULL;
68 CamelMimeParser *parser;
69
70 g_return_val_if_fail (mem != NULL, NULL);
71 g_return_val_if_fail (data != NULL, NULL);
72
73 if (data->len <= 13 || g_ascii_strncasecmp ((const gchar *) data->data, "Content-Type:", 13) != 0)
74 return NULL;
75
76 parser = camel_mime_parser_new ();
77 camel_mime_parser_scan_from (parser, FALSE);
78 camel_mime_parser_scan_pre_from (parser, FALSE);
79
80 if (camel_mime_parser_init_with_stream (parser, mem, NULL) != -1) {
81 part = camel_mime_part_new ();
82 if (!camel_mime_part_construct_from_parser_sync (part, parser, NULL, NULL)) {
83 g_object_unref (part);
84 part = NULL;
85 }
86 }
87
88 g_object_unref (parser);
89
90 return part;
91 }
92
93 static void
inline_filter_add_part(EMailInlineFilter * emif,const gchar * data,gint len)94 inline_filter_add_part (EMailInlineFilter *emif,
95 const gchar *data,
96 gint len)
97 {
98 CamelTransferEncoding encoding;
99 CamelContentType *content_type;
100 CamelDataWrapper *dw;
101 const gchar *mimetype;
102 CamelMimePart *part;
103 CamelStream *mem;
104 gchar *type;
105
106 if (emif->state == EMIF_PLAIN || emif->state == EMIF_PGPSIGNED || emif->state == EMIF_PGPENCRYPTED)
107 encoding = emif->base_encoding;
108 else
109 encoding = emif_types[emif->state].encoding;
110
111 g_byte_array_append (emif->data, (guchar *) data, len);
112 /* check the part will actually have content */
113 if (emif->data->len <= 0) {
114 return;
115 }
116
117 mem = camel_stream_mem_new_with_byte_array (emif->data);
118 part = construct_part_from_stream (mem, emif->data);
119 if (part) {
120 g_object_unref (mem);
121 emif->data = g_byte_array_new ();
122 g_free (emif->filename);
123 emif->filename = NULL;
124
125 emif->parts = g_slist_append (emif->parts, part);
126 emif->found_any = TRUE;
127
128 return;
129 }
130
131 emif->data = g_byte_array_new ();
132 g_seekable_seek (G_SEEKABLE (mem), 0, G_SEEK_SET, NULL, NULL);
133
134 dw = camel_data_wrapper_new ();
135 if (encoding == emif->base_encoding && (encoding == CAMEL_TRANSFER_ENCODING_BASE64 || encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)) {
136 CamelMimeFilter *enc_filter = camel_mime_filter_basic_new (encoding == CAMEL_TRANSFER_ENCODING_BASE64 ? CAMEL_MIME_FILTER_BASIC_BASE64_ENC : CAMEL_MIME_FILTER_BASIC_QP_ENC);
137 CamelStream *filter_stream;
138
139 filter_stream = camel_stream_filter_new (mem);
140 camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream), enc_filter);
141
142 /* properly encode content */
143 camel_data_wrapper_construct_from_stream_sync (
144 dw, filter_stream, NULL, NULL);
145
146 g_object_unref (enc_filter);
147 g_object_unref (filter_stream);
148 } else {
149 camel_data_wrapper_construct_from_stream_sync (
150 dw, mem, NULL, NULL);
151 }
152 g_object_unref (mem);
153
154 if (emif_types[emif->state].plain && emif->base_type) {
155 /* create a copy */
156 type = camel_content_type_format (emif->base_type);
157 content_type = camel_content_type_decode (type);
158 g_free (type);
159 } else {
160 /* we want to preserve all params */
161 type = camel_content_type_format (emif->base_type);
162 content_type = camel_content_type_decode (type);
163 g_free (type);
164
165 g_free (content_type->type);
166 g_free (content_type->subtype);
167 content_type->type = g_strdup (emif_types[emif->state].type);
168 content_type->subtype = g_strdup (emif_types[emif->state].subtype);
169 }
170
171 camel_data_wrapper_take_mime_type_field (dw, content_type);
172 camel_data_wrapper_set_encoding (dw, encoding);
173
174 part = camel_mime_part_new ();
175 camel_medium_set_content ((CamelMedium *) part, dw);
176 camel_mime_part_set_encoding (part, encoding);
177 g_object_unref (dw);
178
179 if (emif->filename)
180 camel_mime_part_set_filename (part, emif->filename);
181
182 /* pre-snoop the mime type of unknown objects, and poke and hack it into place */
183 if (camel_content_type_is (camel_data_wrapper_get_mime_type_field (dw), "application", "octet-stream")
184 && (mimetype = e_mail_part_snoop_type (part))
185 && strcmp (mimetype, "application/octet-stream") != 0) {
186 camel_data_wrapper_set_mime_type (dw, mimetype);
187 camel_mime_part_set_content_type (part, mimetype);
188 if (emif->filename)
189 camel_mime_part_set_filename (part, emif->filename);
190 }
191
192 g_free (emif->filename);
193 emif->filename = NULL;
194
195 emif->parts = g_slist_append (emif->parts, part);
196 }
197
198 static gboolean
newline_or_whitespace_follows(const gchar * str,guint len,guint skip_first)199 newline_or_whitespace_follows (const gchar *str,
200 guint len,
201 guint skip_first)
202 {
203 if (len <= skip_first)
204 return len == skip_first;
205
206 str += skip_first;
207 len -= skip_first;
208
209 while (len > 0 && *str != '\n') {
210 if (!*str)
211 return TRUE;
212
213 if (!camel_mime_is_lwsp (*str))
214 return FALSE;
215
216 len--;
217 str++;
218 }
219
220 return len == 0 || *str == '\n';
221 }
222
223 static gint
inline_filter_scan(CamelMimeFilter * f,gchar * in,gsize len,gint final)224 inline_filter_scan (CamelMimeFilter *f,
225 gchar *in,
226 gsize len,
227 gint final)
228 {
229 EMailInlineFilter *emif = (EMailInlineFilter *) f;
230 gchar *inptr = in, *inend = in + len;
231 gchar *data_start = in;
232 gchar *start = in;
233
234 while (inptr < inend) {
235 gint rest_len;
236 gboolean set_null_byte = FALSE;
237
238 start = inptr;
239
240 while (inptr < inend && *inptr != '\n')
241 inptr++;
242
243 if (inptr == inend && start == inptr) {
244 if (!final) {
245 camel_mime_filter_backup (f, start, inend - start);
246 inend = start;
247 }
248 break;
249 }
250
251 rest_len = inend - start;
252 if (inptr < inend) {
253 *inptr++ = 0;
254 set_null_byte = TRUE;
255 }
256
257 #define restore_inptr() G_STMT_START { if (set_null_byte) inptr[-1] = '\n'; } G_STMT_END
258
259 switch (emif->state) {
260 case EMIF_PLAIN:
261 if (rest_len >= 45 && strncmp (start, "(This file must be converted with BinHex 4.0)", 45) == 0) {
262 restore_inptr ();
263 inline_filter_add_part (emif, data_start, start - data_start);
264 data_start = start;
265 emif->state = EMIF_BINHEX;
266 } else if (rest_len >= 11 && strncmp (start, "%!PS-Adobe-", 11) == 0) {
267 restore_inptr ();
268 inline_filter_add_part (emif, data_start, start - data_start);
269 data_start = start;
270 emif->state = EMIF_POSTSCRIPT;
271 } else if (rest_len >= 34 && strncmp (start, "-----BEGIN PGP SIGNED MESSAGE-----", 34) == 0 &&
272 newline_or_whitespace_follows (start, rest_len, 34)) {
273 restore_inptr ();
274 inline_filter_add_part (emif, data_start, start - data_start);
275 data_start = start;
276 emif->state = EMIF_PGPSIGNED;
277 } else if (rest_len >= 27 && strncmp (start, "-----BEGIN PGP MESSAGE-----", 27) == 0 &&
278 newline_or_whitespace_follows (start, rest_len, 27)) {
279 restore_inptr ();
280 inline_filter_add_part (emif, data_start, start - data_start);
281 data_start = start;
282 emif->state = EMIF_PGPENCRYPTED;
283 }
284
285 break;
286 case EMIF_BINHEX:
287 if (inptr > (start + 1) && inptr[-2] == ':') {
288 restore_inptr ();
289 inline_filter_add_part (emif, data_start, inptr - data_start);
290 data_start = inptr;
291 emif->state = EMIF_PLAIN;
292 emif->found_any = TRUE;
293 }
294 break;
295 case EMIF_POSTSCRIPT:
296 if (rest_len >= 5 && strncmp (start, "%%EOF", 5) == 0) {
297 restore_inptr ();
298 inline_filter_add_part (emif, data_start, inptr - data_start);
299 data_start = inptr;
300 emif->state = EMIF_PLAIN;
301 emif->found_any = TRUE;
302 }
303 break;
304 case EMIF_PGPSIGNED:
305 if (rest_len >= 27 && strncmp (start, "-----END PGP SIGNATURE-----", 27) == 0 &&
306 newline_or_whitespace_follows (start, rest_len, 27)) {
307 restore_inptr ();
308 inline_filter_add_part (emif, data_start, inptr - data_start);
309 data_start = inptr;
310 emif->state = EMIF_PLAIN;
311 emif->found_any = TRUE;
312 }
313 break;
314 case EMIF_PGPENCRYPTED:
315 if (rest_len >= 25 && strncmp (start, "-----END PGP MESSAGE-----", 25) == 0 &&
316 newline_or_whitespace_follows (start, rest_len, 25)) {
317 restore_inptr ();
318 inline_filter_add_part (emif, data_start, inptr - data_start);
319 data_start = inptr;
320 emif->state = EMIF_PLAIN;
321 emif->found_any = TRUE;
322 }
323 break;
324 }
325
326 restore_inptr ();
327
328 #undef restore_inptr
329 }
330
331 if (final) {
332 /* always stop as plain, especially when not read those tags fully */
333 emif->state = EMIF_PLAIN;
334
335 inline_filter_add_part (emif, data_start, inend - data_start);
336 } else if (start > data_start) {
337 /* backup the last line, in case the tag is divided within buffers */
338 camel_mime_filter_backup (f, start, inend - start);
339 g_byte_array_append (emif->data, (guchar *) data_start, start - data_start);
340 } else {
341 g_byte_array_append (emif->data, (guchar *) data_start, inend - data_start);
342 }
343
344 return 0;
345 }
346
347 static void
inline_filter_finalize(GObject * object)348 inline_filter_finalize (GObject *object)
349 {
350 EMailInlineFilter *emif = E_MAIL_INLINE_FILTER (object);
351
352 if (emif->base_type)
353 camel_content_type_unref (emif->base_type);
354
355 camel_mime_filter_reset (CAMEL_MIME_FILTER (object));
356 g_byte_array_free (emif->data, TRUE);
357 g_free (emif->filename);
358
359 /* Chain up to parent's finalize() method. */
360 G_OBJECT_CLASS (e_mail_inline_filter_parent_class)->finalize (object);
361 }
362
363 static void
inline_filter_filter(CamelMimeFilter * filter,const gchar * in,gsize len,gsize prespace,gchar ** out,gsize * outlen,gsize * outprespace)364 inline_filter_filter (CamelMimeFilter *filter,
365 const gchar *in,
366 gsize len,
367 gsize prespace,
368 gchar **out,
369 gsize *outlen,
370 gsize *outprespace)
371 {
372 inline_filter_scan (filter, (gchar *) in, len, FALSE);
373
374 *out = (gchar *)in;
375 *outlen = len;
376 *outprespace = prespace;
377 }
378
379 static void
inline_filter_complete(CamelMimeFilter * filter,const gchar * in,gsize len,gsize prespace,gchar ** out,gsize * outlen,gsize * outprespace)380 inline_filter_complete (CamelMimeFilter *filter,
381 const gchar *in,
382 gsize len,
383 gsize prespace,
384 gchar **out,
385 gsize *outlen,
386 gsize *outprespace)
387 {
388 inline_filter_scan (filter, (gchar *) in, len, TRUE);
389
390 *out = (gchar *)in;
391 *outlen = len;
392 *outprespace = prespace;
393 }
394
395 static void
inline_filter_reset(CamelMimeFilter * filter)396 inline_filter_reset (CamelMimeFilter *filter)
397 {
398 EMailInlineFilter *emif = E_MAIL_INLINE_FILTER (filter);
399 GSList *l;
400
401 l = emif->parts;
402 while (l) {
403 GSList *n = l->next;
404
405 g_object_unref (l->data);
406 g_slist_free_1 (l);
407
408 l = n;
409 }
410 emif->parts = NULL;
411 g_byte_array_set_size (emif->data, 0);
412 emif->found_any = FALSE;
413 }
414
415 static void
e_mail_inline_filter_class_init(EMailInlineFilterClass * class)416 e_mail_inline_filter_class_init (EMailInlineFilterClass *class)
417 {
418 GObjectClass *object_class;
419 CamelMimeFilterClass *mime_filter_class;
420
421 object_class = G_OBJECT_CLASS (class);
422 object_class->finalize = inline_filter_finalize;
423
424 mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
425 mime_filter_class->filter = inline_filter_filter;
426 mime_filter_class->complete = inline_filter_complete;
427 mime_filter_class->reset = inline_filter_reset;
428 }
429
430 static void
e_mail_inline_filter_init(EMailInlineFilter * emif)431 e_mail_inline_filter_init (EMailInlineFilter *emif)
432 {
433 emif->data = g_byte_array_new ();
434 emif->found_any = FALSE;
435 }
436
437 /**
438 * em_inline_filter_new:
439 * @base_encoding: The base transfer-encoding of the
440 * raw data being processed.
441 * @base_type: The base content-type of the raw data, should always be
442 * text/plain.
443 * @filename: Filename of the part, or NULL
444 *
445 * Create a filter which will scan a (text) stream for
446 * embedded parts. You can then retrieve the contents
447 * as a CamelMultipart object.
448 *
449 * Return value:
450 **/
451 EMailInlineFilter *
e_mail_inline_filter_new(CamelTransferEncoding base_encoding,CamelContentType * base_type,const gchar * filename)452 e_mail_inline_filter_new (CamelTransferEncoding base_encoding,
453 CamelContentType *base_type,
454 const gchar *filename)
455 {
456 EMailInlineFilter *emif;
457
458 emif = g_object_new (E_TYPE_MAIL_INLINE_FILTER, NULL);
459 emif->base_encoding = base_encoding;
460 if (base_type) {
461 emif->base_type = base_type;
462 camel_content_type_ref (emif->base_type);
463 }
464
465 if (filename && *filename)
466 emif->filename = g_strdup (filename);
467
468 return emif;
469 }
470
471 CamelMultipart *
e_mail_inline_filter_get_multipart(EMailInlineFilter * emif)472 e_mail_inline_filter_get_multipart (EMailInlineFilter *emif)
473 {
474 GSList *l = emif->parts;
475 CamelMultipart *mp;
476
477 mp = camel_multipart_new ();
478 while (l) {
479 camel_multipart_add_part (mp, l->data);
480 l = l->next;
481 }
482
483 return mp;
484 }
485
486 gboolean
e_mail_inline_filter_found_any(EMailInlineFilter * emif)487 e_mail_inline_filter_found_any (EMailInlineFilter *emif)
488 {
489 g_return_val_if_fail (emif != NULL, FALSE);
490
491 return emif->found_any;
492 }
493