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