1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Michael Zucchi <notzed@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <string.h>
25 
26 #include <glib/gi18n-lib.h>
27 
28 #include "camel-stream-filter.h"
29 
30 #define d(x)
31 
32 /* use my malloc debugger? */
33 /*extern void g_check(gpointer mp);*/
34 #define g_check(x)
35 
36 struct _filter {
37 	struct _filter *next;
38 	gint id;
39 	CamelMimeFilter *filter;
40 };
41 
42 struct _CamelStreamFilterPrivate {
43 
44 	CamelStream *source;
45 
46 	struct _filter *filters;
47 	gint filterid;		/* next filter id */
48 
49 	gchar *realbuffer;	/* buffer - READ_PAD */
50 	gchar *buffer;		/* READ_SIZE bytes */
51 
52 	gchar *filtered;		/* the filtered data */
53 	gsize filteredlen;
54 
55 	guint last_was_read:1;	/* was the last op read or write? */
56 	guint flushed:1;        /* were the filters flushed? */
57 };
58 
59 #define READ_PAD (128)		/* bytes padded before buffer */
60 #define READ_SIZE (4096)
61 
62 static void camel_stream_filter_seekable_init (GSeekableIface *iface);
63 
G_DEFINE_TYPE_WITH_CODE(CamelStreamFilter,camel_stream_filter,CAMEL_TYPE_STREAM,G_ADD_PRIVATE (CamelStreamFilter)G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,camel_stream_filter_seekable_init))64 G_DEFINE_TYPE_WITH_CODE (CamelStreamFilter, camel_stream_filter, CAMEL_TYPE_STREAM,
65 	G_ADD_PRIVATE (CamelStreamFilter)
66 	G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_filter_seekable_init))
67 
68 static void
69 stream_filter_finalize (GObject *object)
70 {
71 	CamelStreamFilter *stream = CAMEL_STREAM_FILTER (object);
72 	struct _filter *fn, *f;
73 
74 	f = stream->priv->filters;
75 	while (f) {
76 		fn = f->next;
77 		g_object_unref (f->filter);
78 		g_free (f);
79 		f = fn;
80 	}
81 
82 	g_free (stream->priv->realbuffer);
83 	g_object_unref (stream->priv->source);
84 
85 	/* Chain up to parent's finalize() method. */
86 	G_OBJECT_CLASS (camel_stream_filter_parent_class)->finalize (object);
87 }
88 
89 static gssize
stream_filter_read(CamelStream * stream,gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)90 stream_filter_read (CamelStream *stream,
91                     gchar *buffer,
92                     gsize n,
93                     GCancellable *cancellable,
94                     GError **error)
95 {
96 	CamelStreamFilterPrivate *priv;
97 	gssize size;
98 	struct _filter *f;
99 
100 	priv = CAMEL_STREAM_FILTER (stream)->priv;
101 
102 	priv->last_was_read = TRUE;
103 
104 	g_check (priv->realbuffer);
105 
106 	if (priv->filteredlen <= 0) {
107 		gsize presize = READ_PAD;
108 
109 		size = camel_stream_read (
110 			priv->source, priv->buffer,
111 			READ_SIZE, cancellable, error);
112 		if (size <= 0) {
113 			/* this is somewhat untested */
114 			if (camel_stream_eos (priv->source)) {
115 				f = priv->filters;
116 				priv->filtered = priv->buffer;
117 				priv->filteredlen = 0;
118 				while (f) {
119 					camel_mime_filter_complete (
120 						f->filter, priv->filtered, priv->filteredlen,
121 						presize, &priv->filtered, &priv->filteredlen, &presize);
122 					g_check (priv->realbuffer);
123 					f = f->next;
124 				}
125 				size = priv->filteredlen;
126 				priv->flushed = TRUE;
127 			}
128 			if (size <= 0)
129 				return size;
130 		} else {
131 			f = priv->filters;
132 			priv->filtered = priv->buffer;
133 			priv->filteredlen = size;
134 
135 			d (printf (
136 				"\n\nOriginal content (%s): '",
137 				G_OBJECT_TYPE_NAME (priv->source)));
138 			d (fwrite (priv->filtered, sizeof (gchar), priv->filteredlen, stdout));
139 			d (printf ("'\n"));
140 
141 			while (f) {
142 				camel_mime_filter_filter (
143 					f->filter, priv->filtered, priv->filteredlen, presize,
144 					&priv->filtered, &priv->filteredlen, &presize);
145 				g_check (priv->realbuffer);
146 
147 				d (printf (
148 					"Filtered content (%s): '",
149 					G_OBJECT_TYPE_NAME (f->filter)));
150 				d (fwrite (priv->filtered, sizeof (gchar), priv->filteredlen, stdout));
151 				d (printf ("'\n"));
152 
153 				f = f->next;
154 			}
155 		}
156 	}
157 
158 	size = MIN (n, priv->filteredlen);
159 	memcpy (buffer, priv->filtered, size);
160 	priv->filteredlen -= size;
161 	priv->filtered += size;
162 
163 	g_check (priv->realbuffer);
164 
165 	return size;
166 }
167 
168 /* Note: Since the caller expects to write out as much as they asked us to
169  * write (for 'success'), we return what they asked us to write (for 'success')
170  * rather than the true number of written bytes */
171 static gssize
stream_filter_write(CamelStream * stream,const gchar * buf,gsize n,GCancellable * cancellable,GError ** error)172 stream_filter_write (CamelStream *stream,
173                      const gchar *buf,
174                      gsize n,
175                      GCancellable *cancellable,
176                      GError **error)
177 {
178 	CamelStreamFilterPrivate *priv;
179 	struct _filter *f;
180 	gsize presize, len, left = n;
181 	gchar *buffer, realbuffer[READ_SIZE + READ_PAD];
182 
183 	priv = CAMEL_STREAM_FILTER (stream)->priv;
184 
185 	priv->last_was_read = FALSE;
186 
187 	d (printf (
188 		"\n\nWriting: Original content (%s): '",
189 		G_OBJECT_TYPE_NAME (priv->source)));
190 	d (fwrite (buf, sizeof (gchar), n, stdout));
191 	d (printf ("'\n"));
192 
193 	g_check (priv->realbuffer);
194 
195 	while (left) {
196 		/* Sigh, since filters expect non const args, copy the input first, we do this in handy sized chunks */
197 		len = MIN (READ_SIZE, left);
198 		buffer = realbuffer + READ_PAD;
199 		memcpy (buffer, buf, len);
200 		buf += len;
201 		left -= len;
202 
203 		f = priv->filters;
204 		presize = READ_PAD;
205 		while (f) {
206 			camel_mime_filter_filter (f->filter, buffer, len, presize, &buffer, &len, &presize);
207 
208 			g_check (priv->realbuffer);
209 
210 			d (printf (
211 				"Filtered content (%s): '",
212 				G_OBJECT_TYPE_NAME (f->filter)));
213 			d (fwrite (buffer, sizeof (gchar), len, stdout));
214 			d (printf ("'\n"));
215 
216 			f = f->next;
217 		}
218 
219 		if (camel_stream_write (priv->source, buffer, len, cancellable, error) != len)
220 			return -1;
221 	}
222 
223 	g_check (priv->realbuffer);
224 
225 	return n;
226 }
227 
228 static gint
stream_filter_flush(CamelStream * stream,GCancellable * cancellable,GError ** error)229 stream_filter_flush (CamelStream *stream,
230                      GCancellable *cancellable,
231                      GError **error)
232 {
233 	CamelStreamFilterPrivate *priv;
234 	struct _filter *f;
235 	gchar *buffer;
236 	gsize presize;
237 	gsize len;
238 
239 	priv = CAMEL_STREAM_FILTER (stream)->priv;
240 
241 	if (priv->last_was_read)
242 		return 0;
243 
244 	buffer = (gchar *) "";
245 	len = 0;
246 	presize = 0;
247 	f = priv->filters;
248 
249 	d (printf (
250 		"\n\nFlushing: Original content (%s): '",
251 		G_OBJECT_TYPE_NAME (priv->source)));
252 	d (fwrite (buffer, sizeof (gchar), len, stdout));
253 	d (printf ("'\n"));
254 
255 	while (f) {
256 		camel_mime_filter_complete (f->filter, buffer, len, presize, &buffer, &len, &presize);
257 
258 		d (printf (
259 			"Filtered content (%s): '",
260 			G_OBJECT_TYPE_NAME (f->filter)));
261 		d (fwrite (buffer, sizeof (gchar), len, stdout));
262 		d (printf ("'\n"));
263 
264 		f = f->next;
265 	}
266 
267 	if (len > 0 && camel_stream_write (priv->source, buffer, len, cancellable, error) == -1)
268 		return -1;
269 
270 	return camel_stream_flush (priv->source, cancellable, error);
271 }
272 
273 static gint
stream_filter_close(CamelStream * stream,GCancellable * cancellable,GError ** error)274 stream_filter_close (CamelStream *stream,
275                      GCancellable *cancellable,
276                      GError **error)
277 {
278 	CamelStreamFilterPrivate *priv;
279 
280 	priv = CAMEL_STREAM_FILTER (stream)->priv;
281 
282 	/* Ignore errors while flushing. */
283 	if (!priv->last_was_read)
284 		stream_filter_flush (stream, cancellable, NULL);
285 
286 	return camel_stream_close (priv->source, cancellable, error);
287 }
288 
289 static gboolean
stream_filter_eos(CamelStream * stream)290 stream_filter_eos (CamelStream *stream)
291 {
292 	CamelStreamFilterPrivate *priv;
293 
294 	priv = CAMEL_STREAM_FILTER (stream)->priv;
295 
296 	if (priv->filteredlen > 0)
297 		return FALSE;
298 
299 	if (!priv->flushed)
300 		return FALSE;
301 
302 	return camel_stream_eos (priv->source);
303 }
304 
305 static goffset
stream_filter_tell(GSeekable * seekable)306 stream_filter_tell (GSeekable *seekable)
307 {
308 	CamelStreamFilterPrivate *priv;
309 
310 	priv = CAMEL_STREAM_FILTER (seekable)->priv;
311 
312 	if (!G_IS_SEEKABLE (priv->source))
313 		return 0;
314 
315 	return g_seekable_tell (G_SEEKABLE (priv->source));
316 }
317 
318 static gboolean
stream_filter_can_seek(GSeekable * seekable)319 stream_filter_can_seek (GSeekable *seekable)
320 {
321 	return TRUE;
322 }
323 
324 static gboolean
stream_filter_seek(GSeekable * seekable,goffset offset,GSeekType type,GCancellable * cancellable,GError ** error)325 stream_filter_seek (GSeekable *seekable,
326                     goffset offset,
327                     GSeekType type,
328                     GCancellable *cancellable,
329                     GError **error)
330 {
331 	CamelStreamFilterPrivate *priv;
332 	struct _filter *f;
333 
334 	priv = CAMEL_STREAM_FILTER (seekable)->priv;
335 
336 	if (type != G_SEEK_SET || offset != 0) {
337 		g_set_error_literal (
338 			error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
339 			_("Only reset to beginning is supported with CamelStreamFilter"));
340 		return FALSE;
341 	}
342 
343 	priv->filteredlen = 0;
344 	priv->flushed = FALSE;
345 
346 	f = priv->filters;
347 	while (f) {
348 		if (G_IS_SEEKABLE (f->filter) && !g_seekable_seek (G_SEEKABLE (f->filter), offset, type, cancellable, error))
349 			return FALSE;
350 
351 		f = f->next;
352 	}
353 
354 	return priv->source && G_IS_SEEKABLE (priv->source) ? g_seekable_seek (G_SEEKABLE (priv->source), offset, type, cancellable, error) : TRUE;
355 }
356 
357 static gboolean
stream_filter_can_truncate(GSeekable * seekable)358 stream_filter_can_truncate (GSeekable *seekable)
359 {
360 	return FALSE;
361 }
362 
363 static gboolean
stream_filter_truncate_fn(GSeekable * seekable,goffset offset,GCancellable * cancellable,GError ** error)364 stream_filter_truncate_fn (GSeekable *seekable,
365                            goffset offset,
366                            GCancellable *cancellable,
367                            GError **error)
368 {
369 	/* XXX Don't bother translating this.  Camel never calls it. */
370 	g_set_error_literal (
371 		error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
372 		"Truncation is not supported");
373 
374 	return FALSE;
375 }
376 
377 static void
camel_stream_filter_class_init(CamelStreamFilterClass * class)378 camel_stream_filter_class_init (CamelStreamFilterClass *class)
379 {
380 	GObjectClass *object_class;
381 	CamelStreamClass *stream_class;
382 
383 	object_class = G_OBJECT_CLASS (class);
384 	object_class->finalize = stream_filter_finalize;
385 
386 	stream_class = CAMEL_STREAM_CLASS (class);
387 	stream_class->read = stream_filter_read;
388 	stream_class->write = stream_filter_write;
389 	stream_class->flush = stream_filter_flush;
390 	stream_class->close = stream_filter_close;
391 	stream_class->eos = stream_filter_eos;
392 }
393 
394 static void
camel_stream_filter_seekable_init(GSeekableIface * iface)395 camel_stream_filter_seekable_init (GSeekableIface *iface)
396 {
397 	iface->tell = stream_filter_tell;
398 	iface->can_seek = stream_filter_can_seek;
399 	iface->seek = stream_filter_seek;
400 	iface->can_truncate = stream_filter_can_truncate;
401 	iface->truncate_fn = stream_filter_truncate_fn;
402 }
403 
404 static void
camel_stream_filter_init(CamelStreamFilter * stream)405 camel_stream_filter_init (CamelStreamFilter *stream)
406 {
407 	stream->priv = camel_stream_filter_get_instance_private (stream);
408 	stream->priv->realbuffer = g_malloc (READ_SIZE + READ_PAD);
409 	stream->priv->buffer = stream->priv->realbuffer + READ_PAD;
410 	stream->priv->last_was_read = TRUE;
411 	stream->priv->flushed = FALSE;
412 }
413 
414 /**
415  * camel_stream_filter_new:
416  * @source: a #CamelStream to filter
417  *
418  * Create a new #CamelStreamFilter object. The @source stream
419  * is referenced, thus the caller can unref it, if not needed.
420  *
421  * Returns: (transfer full): a new #CamelStreamFilter object.
422  *
423  * Since: 2.32
424  **/
425 CamelStream *
camel_stream_filter_new(CamelStream * source)426 camel_stream_filter_new (CamelStream *source)
427 {
428 	CamelStream *stream;
429 	CamelStreamFilterPrivate *priv;
430 
431 	g_return_val_if_fail (CAMEL_IS_STREAM (source), NULL);
432 
433 	stream = g_object_new (CAMEL_TYPE_STREAM_FILTER, NULL);
434 	priv = CAMEL_STREAM_FILTER (stream)->priv;
435 
436 	priv->source = g_object_ref (source);
437 
438 	return stream;
439 }
440 
441 /**
442  * camel_stream_filter_get_source:
443  * @stream: a #CamelStreamFilter
444  *
445  * Returns: (transfer none):
446  *
447  * Since: 2.32
448  **/
449 CamelStream *
camel_stream_filter_get_source(CamelStreamFilter * stream)450 camel_stream_filter_get_source (CamelStreamFilter *stream)
451 {
452 	g_return_val_if_fail (CAMEL_IS_STREAM_FILTER (stream), NULL);
453 
454 	return stream->priv->source;
455 }
456 
457 /**
458  * camel_stream_filter_add:
459  * @stream: a #CamelStreamFilter object
460  * @filter: a #CamelMimeFilter object
461  *
462  * Add a new #CamelMimeFilter to execute during the processing of this
463  * stream.  Each filter added is processed after the previous one.
464  *
465  * Note that a filter should only be added to a single stream
466  * at a time, otherwise unpredictable results may occur.
467  *
468  * Returns: a filter id for the added @filter.
469  **/
470 gint
camel_stream_filter_add(CamelStreamFilter * stream,CamelMimeFilter * filter)471 camel_stream_filter_add (CamelStreamFilter *stream,
472                          CamelMimeFilter *filter)
473 {
474 	CamelStreamFilterPrivate *priv;
475 	struct _filter *fn, *f;
476 
477 	g_return_val_if_fail (CAMEL_IS_STREAM_FILTER (stream), -1);
478 	g_return_val_if_fail (CAMEL_IS_MIME_FILTER (filter), -1);
479 
480 	priv = CAMEL_STREAM_FILTER (stream)->priv;
481 
482 	fn = g_malloc (sizeof (*fn));
483 	fn->id = priv->filterid++;
484 	fn->filter = g_object_ref (filter);
485 
486 	/* sure, we could use a GList, but we wouldn't save much */
487 	f = (struct _filter *) &priv->filters;
488 	while (f->next)
489 		f = f->next;
490 	f->next = fn;
491 	fn->next = NULL;
492 	return fn->id;
493 }
494 
495 /**
496  * camel_stream_filter_remove:
497  * @stream: a #CamelStreamFilter object
498  * @id: Filter id, as returned from camel_stream_filter_add()
499  *
500  * Remove a processing filter from the stream by id.
501  **/
502 void
camel_stream_filter_remove(CamelStreamFilter * stream,gint id)503 camel_stream_filter_remove (CamelStreamFilter *stream,
504                             gint id)
505 {
506 	CamelStreamFilterPrivate *priv;
507 	struct _filter *fn, *f;
508 
509 	g_return_if_fail (CAMEL_IS_STREAM_FILTER (stream));
510 
511 	priv = CAMEL_STREAM_FILTER (stream)->priv;
512 
513 	f = (struct _filter *) &priv->filters;
514 	while (f && f->next) {
515 		fn = f->next;
516 		if (fn->id == id) {
517 			f->next = fn->next;
518 			g_object_unref (fn->filter);
519 			g_free (fn);
520 		}
521 		f = f->next;
522 	}
523 }
524