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