1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-stream.c : abstract class for a stream
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
19  */
20 
21 #include "evolution-data-server-config.h"
22 
23 #include <glib/gi18n-lib.h>
24 
25 #include <camel/camel-debug.h>
26 
27 #include "camel-stream.h"
28 
29 struct _CamelStreamPrivate {
30 	GIOStream *base_stream;
31 	GMutex base_stream_lock;
32 	gboolean eos;
33 };
34 
35 enum {
36 	PROP_0,
37 	PROP_BASE_STREAM
38 };
39 
40 /* Forward Declarations */
41 static void	camel_stream_seekable_init	(GSeekableIface *iface);
42 
G_DEFINE_TYPE_WITH_CODE(CamelStream,camel_stream,G_TYPE_OBJECT,G_ADD_PRIVATE (CamelStream)G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,camel_stream_seekable_init))43 G_DEFINE_TYPE_WITH_CODE (
44 	CamelStream,
45 	camel_stream,
46 	G_TYPE_OBJECT,
47 	G_ADD_PRIVATE (CamelStream)
48 	G_IMPLEMENT_INTERFACE (
49 		G_TYPE_SEEKABLE,
50 		camel_stream_seekable_init))
51 
52 static void
53 stream_set_property (GObject *object,
54                      guint property_id,
55                      const GValue *value,
56                      GParamSpec *pspec)
57 {
58 	switch (property_id) {
59 		case PROP_BASE_STREAM:
60 			camel_stream_set_base_stream (
61 				CAMEL_STREAM (object),
62 				g_value_get_object (value));
63 			return;
64 	}
65 
66 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
67 }
68 
69 static void
stream_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)70 stream_get_property (GObject *object,
71                      guint property_id,
72                      GValue *value,
73                      GParamSpec *pspec)
74 {
75 	switch (property_id) {
76 		case PROP_BASE_STREAM:
77 			g_value_take_object (
78 				value,
79 				camel_stream_ref_base_stream (
80 				CAMEL_STREAM (object)));
81 			return;
82 	}
83 
84 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
85 }
86 
87 static void
stream_dispose(GObject * object)88 stream_dispose (GObject *object)
89 {
90 	CamelStreamPrivate *priv;
91 
92 	priv = CAMEL_STREAM (object)->priv;
93 
94 	g_clear_object (&priv->base_stream);
95 
96 	/* Chain up to parent's dispose() method. */
97 	G_OBJECT_CLASS (camel_stream_parent_class)->dispose (object);
98 }
99 
100 static void
stream_finalize(GObject * object)101 stream_finalize (GObject *object)
102 {
103 	CamelStreamPrivate *priv;
104 
105 	priv = CAMEL_STREAM (object)->priv;
106 
107 	g_mutex_clear (&priv->base_stream_lock);
108 
109 	/* Chain up to parent's finalize() method. */
110 	G_OBJECT_CLASS (camel_stream_parent_class)->finalize (object);
111 }
112 
113 static gssize
stream_read(CamelStream * stream,gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)114 stream_read (CamelStream *stream,
115              gchar *buffer,
116              gsize n,
117              GCancellable *cancellable,
118              GError **error)
119 {
120 	GIOStream *base_stream;
121 	gssize n_bytes_read = 0;
122 
123 	base_stream = camel_stream_ref_base_stream (stream);
124 
125 	if (base_stream != NULL) {
126 		GInputStream *input_stream;
127 
128 		input_stream = g_io_stream_get_input_stream (base_stream);
129 
130 		n_bytes_read = g_input_stream_read (
131 			input_stream, buffer, n, cancellable, error);
132 
133 		g_object_unref (base_stream);
134 	}
135 
136 	stream->priv->eos = n_bytes_read <= 0;
137 
138 	return n_bytes_read;
139 }
140 
141 static gssize
stream_write(CamelStream * stream,const gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)142 stream_write (CamelStream *stream,
143               const gchar *buffer,
144               gsize n,
145               GCancellable *cancellable,
146               GError **error)
147 {
148 	GIOStream *base_stream;
149 	gssize n_bytes_written = -1;
150 
151 	base_stream = camel_stream_ref_base_stream (stream);
152 
153 	if (base_stream != NULL) {
154 		GOutputStream *output_stream;
155 		gsize n_written = 0;
156 
157 		output_stream = g_io_stream_get_output_stream (base_stream);
158 		stream->priv->eos = FALSE;
159 
160 		if (g_output_stream_write_all (output_stream, buffer, n, &n_written, cancellable, error))
161 			n_bytes_written = (gssize) n_written;
162 		else
163 			n_bytes_written = -1;
164 
165 		g_object_unref (base_stream);
166 	} else {
167 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot write with no base stream"));
168 	}
169 
170 	return n_bytes_written;
171 }
172 
173 static gint
stream_close(CamelStream * stream,GCancellable * cancellable,GError ** error)174 stream_close (CamelStream *stream,
175               GCancellable *cancellable,
176               GError **error)
177 {
178 	GIOStream *base_stream;
179 	gboolean success = TRUE;
180 
181 	base_stream = camel_stream_ref_base_stream (stream);
182 
183 	if (base_stream != NULL) {
184 		success = g_io_stream_close (
185 			base_stream, cancellable, error);
186 
187 		g_object_unref (base_stream);
188 	}
189 
190 	return success ? 0 : -1;
191 }
192 
193 static gint
stream_flush(CamelStream * stream,GCancellable * cancellable,GError ** error)194 stream_flush (CamelStream *stream,
195               GCancellable *cancellable,
196               GError **error)
197 {
198 	GIOStream *base_stream;
199 	gboolean success = TRUE;
200 
201 	base_stream = camel_stream_ref_base_stream (stream);
202 
203 	if (base_stream != NULL) {
204 		GOutputStream *output_stream;
205 
206 		output_stream = g_io_stream_get_output_stream (base_stream);
207 
208 		success = g_output_stream_flush (
209 			output_stream, cancellable, error);
210 
211 		g_object_unref (base_stream);
212 	}
213 
214 	return success ? 0 : -1;
215 }
216 
217 static gboolean
stream_eos(CamelStream * stream)218 stream_eos (CamelStream *stream)
219 {
220 	return stream->priv->eos;
221 }
222 
223 static goffset
stream_tell(GSeekable * seekable)224 stream_tell (GSeekable *seekable)
225 {
226 	CamelStream *stream;
227 	GIOStream *base_stream;
228 	goffset position = 0;
229 
230 	stream = CAMEL_STREAM (seekable);
231 	base_stream = camel_stream_ref_base_stream (stream);
232 
233 	if (G_IS_SEEKABLE (base_stream)) {
234 		position = g_seekable_tell (G_SEEKABLE (base_stream));
235 	} else if (base_stream != NULL) {
236 		g_critical (
237 			"Stream type '%s' is not seekable",
238 			G_OBJECT_TYPE_NAME (base_stream));
239 	}
240 
241 	g_clear_object (&base_stream);
242 
243 	return position;
244 }
245 
246 static gboolean
stream_can_seek(GSeekable * seekable)247 stream_can_seek (GSeekable *seekable)
248 {
249 	CamelStream *stream;
250 	GIOStream *base_stream;
251 	gboolean can_seek = FALSE;
252 
253 	stream = CAMEL_STREAM (seekable);
254 	base_stream = camel_stream_ref_base_stream (stream);
255 
256 	if (G_IS_SEEKABLE (base_stream))
257 		can_seek = g_seekable_can_seek (G_SEEKABLE (base_stream));
258 
259 	g_clear_object (&base_stream);
260 
261 	return can_seek;
262 }
263 
264 static gboolean
stream_seek(GSeekable * seekable,goffset offset,GSeekType type,GCancellable * cancellable,GError ** error)265 stream_seek (GSeekable *seekable,
266              goffset offset,
267              GSeekType type,
268              GCancellable *cancellable,
269              GError **error)
270 {
271 	CamelStream *stream;
272 	GIOStream  *base_stream;
273 	gboolean success = FALSE;
274 
275 	stream = CAMEL_STREAM (seekable);
276 	base_stream = camel_stream_ref_base_stream (stream);
277 
278 	if (G_IS_SEEKABLE (base_stream)) {
279 		stream->priv->eos = FALSE;
280 		success = g_seekable_seek (
281 			G_SEEKABLE (base_stream),
282 			offset, type, cancellable, error);
283 	} else if (base_stream != NULL) {
284 		g_set_error (
285 			error, G_IO_ERROR,
286 			G_IO_ERROR_NOT_SUPPORTED,
287 			_("Stream type “%s” is not seekable"),
288 			G_OBJECT_TYPE_NAME (base_stream));
289 	} else {
290 		g_warn_if_reached ();
291 	}
292 
293 	g_clear_object (&base_stream);
294 
295 	return success;
296 }
297 
298 static gboolean
stream_can_truncate(GSeekable * seekable)299 stream_can_truncate (GSeekable *seekable)
300 {
301 	CamelStream *stream;
302 	GIOStream *base_stream;
303 	gboolean can_truncate = FALSE;
304 
305 	stream = CAMEL_STREAM (seekable);
306 	base_stream = camel_stream_ref_base_stream (stream);
307 
308 	if (G_IS_SEEKABLE (base_stream))
309 		can_truncate = g_seekable_can_truncate (
310 			G_SEEKABLE (base_stream));
311 
312 	g_clear_object (&base_stream);
313 
314 	return can_truncate;
315 }
316 
317 static gboolean
stream_truncate(GSeekable * seekable,goffset offset,GCancellable * cancellable,GError ** error)318 stream_truncate (GSeekable *seekable,
319                  goffset offset,
320                  GCancellable *cancellable,
321                  GError **error)
322 {
323 	CamelStream *stream;
324 	GIOStream *base_stream;
325 	gboolean success = FALSE;
326 
327 	stream = CAMEL_STREAM (seekable);
328 	base_stream = camel_stream_ref_base_stream (stream);
329 
330 	if (G_IS_SEEKABLE (base_stream)) {
331 		success = g_seekable_truncate (
332 			G_SEEKABLE (base_stream),
333 			offset, cancellable, error);
334 	} else if (base_stream != NULL) {
335 		g_set_error (
336 			error, G_IO_ERROR,
337 			G_IO_ERROR_NOT_SUPPORTED,
338 			_("Stream type “%s” is not seekable"),
339 			G_OBJECT_TYPE_NAME (base_stream));
340 	} else {
341 		g_warn_if_reached ();
342 	}
343 
344 	g_clear_object (&base_stream);
345 
346 	return success;
347 }
348 
349 static void
camel_stream_class_init(CamelStreamClass * class)350 camel_stream_class_init (CamelStreamClass *class)
351 {
352 	GObjectClass *object_class;
353 
354 	object_class = G_OBJECT_CLASS (class);
355 	object_class->set_property = stream_set_property;
356 	object_class->get_property = stream_get_property;
357 	object_class->dispose = stream_dispose;
358 	object_class->finalize = stream_finalize;
359 
360 	class->read = stream_read;
361 	class->write = stream_write;
362 	class->close = stream_close;
363 	class->flush = stream_flush;
364 	class->eos = stream_eos;
365 
366 	g_object_class_install_property (
367 		object_class,
368 		PROP_BASE_STREAM,
369 		g_param_spec_object (
370 			"base-stream",
371 			"Base Stream",
372 			"The base GIOStream",
373 			G_TYPE_IO_STREAM,
374 			G_PARAM_READWRITE |
375 			G_PARAM_EXPLICIT_NOTIFY |
376 			G_PARAM_STATIC_STRINGS));
377 }
378 
379 static void
camel_stream_seekable_init(GSeekableIface * iface)380 camel_stream_seekable_init (GSeekableIface *iface)
381 {
382 	iface->tell = stream_tell;
383 	iface->can_seek = stream_can_seek;
384 	iface->seek = stream_seek;
385 	iface->can_truncate = stream_can_truncate;
386 	iface->truncate_fn = stream_truncate;
387 }
388 
389 static void
camel_stream_init(CamelStream * stream)390 camel_stream_init (CamelStream *stream)
391 {
392 	stream->priv = camel_stream_get_instance_private (stream);
393 
394 	g_mutex_init (&stream->priv->base_stream_lock);
395 }
396 
397 /**
398  * camel_stream_new:
399  * @base_stream: a #GIOStream
400  *
401  * Creates a #CamelStream as a thin wrapper for @base_stream.
402  *
403  * Returns: a #CamelStream
404  *
405  * Since: 3.12
406  **/
407 CamelStream *
camel_stream_new(GIOStream * base_stream)408 camel_stream_new (GIOStream *base_stream)
409 {
410 	g_return_val_if_fail (G_IS_IO_STREAM (base_stream), NULL);
411 
412 	return g_object_new (
413 		CAMEL_TYPE_STREAM, "base-stream", base_stream, NULL);
414 }
415 
416 /**
417  * camel_stream_ref_base_stream:
418  * @stream: a #CamelStream
419  *
420  * Returns the #GIOStream for @stream.  This is only valid if @stream was
421  * created with camel_stream_new().  For all other #CamelStream subclasses
422  * this function returns %NULL.
423  *
424  * The returned #GIOStream is referenced for thread-safety and should be
425  * unreferenced with g_object_unref() when finished with it.
426  *
427  * Returns: (transfer full) (nullable): a #GIOStream, or %NULL
428  *
429  * Since: 3.12
430  **/
431 GIOStream *
camel_stream_ref_base_stream(CamelStream * stream)432 camel_stream_ref_base_stream (CamelStream *stream)
433 {
434 	GIOStream *base_stream = NULL;
435 
436 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
437 
438 	g_mutex_lock (&stream->priv->base_stream_lock);
439 
440 	if (stream->priv->base_stream != NULL)
441 		base_stream = g_object_ref (stream->priv->base_stream);
442 
443 	g_mutex_unlock (&stream->priv->base_stream_lock);
444 
445 	return base_stream;
446 }
447 
448 /**
449  * camel_stream_set_base_stream:
450  * @stream: a #CamelStream
451  * @base_stream: a #GIOStream
452  *
453  * Replaces the #GIOStream passed to camel_stream_new() with @base_stream.
454  * The new @base_stream should wrap the original #GIOStream, such as when
455  * adding Transport Layer Security after issuing a STARTTLS command.
456  *
457  * Since: 3.12
458  **/
459 void
camel_stream_set_base_stream(CamelStream * stream,GIOStream * base_stream)460 camel_stream_set_base_stream (CamelStream *stream,
461                               GIOStream *base_stream)
462 {
463 	g_return_if_fail (CAMEL_IS_STREAM (stream));
464 	g_return_if_fail (G_IS_IO_STREAM (base_stream));
465 
466 	g_mutex_lock (&stream->priv->base_stream_lock);
467 
468 	g_clear_object (&stream->priv->base_stream);
469 	stream->priv->base_stream = g_object_ref (base_stream);
470 
471 	g_mutex_unlock (&stream->priv->base_stream_lock);
472 
473 	g_object_notify (G_OBJECT (stream), "base-stream");
474 }
475 
476 /**
477  * camel_stream_read:
478  * @stream: a #CamelStream object.
479  * @buffer: (array length=n) (type gchar): output buffer
480  * @n: max number of bytes to read.
481  * @cancellable: optional #GCancellable object, or %NULL
482  * @error: return location for a #GError, or %NULL
483  *
484  * Attempts to read up to @n bytes from @stream into @buffer.
485  *
486  * Returns: the number of bytes actually read, or -1 on error and set
487  * errno.
488  **/
489 gssize
camel_stream_read(CamelStream * stream,gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)490 camel_stream_read (CamelStream *stream,
491                    gchar *buffer,
492                    gsize n,
493                    GCancellable *cancellable,
494                    GError **error)
495 {
496 	CamelStreamClass *class;
497 	gssize n_bytes;
498 
499 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
500 	g_return_val_if_fail (n == 0 || buffer, -1);
501 
502 	class = CAMEL_STREAM_GET_CLASS (stream);
503 	g_return_val_if_fail (class != NULL, -1);
504 	g_return_val_if_fail (class->read != NULL, -1);
505 
506 	n_bytes = class->read (stream, buffer, n, cancellable, error);
507 	CAMEL_CHECK_GERROR (stream, read, n_bytes >= 0, error);
508 
509 	return n_bytes;
510 }
511 
512 /**
513  * camel_stream_write:
514  * @stream: a #CamelStream object
515  * @buffer: (array length=n) (type gchar): buffer to write.
516  * @n: number of bytes to write
517  * @cancellable: optional #GCancellable object, or %NULL
518  * @error: return location for a #GError, or %NULL
519  *
520  * Attempts to write up to @n bytes of @buffer into @stream.
521  *
522  * Returns: the number of bytes written to the stream, or -1 on error
523  * along with setting errno.
524  **/
525 gssize
camel_stream_write(CamelStream * stream,const gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)526 camel_stream_write (CamelStream *stream,
527                     const gchar *buffer,
528                     gsize n,
529                     GCancellable *cancellable,
530                     GError **error)
531 {
532 	CamelStreamClass *class;
533 	gssize n_bytes;
534 
535 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
536 	g_return_val_if_fail (n == 0 || buffer, -1);
537 
538 	class = CAMEL_STREAM_GET_CLASS (stream);
539 	g_return_val_if_fail (class != NULL, -1);
540 	g_return_val_if_fail (class->write != NULL, -1);
541 
542 	n_bytes = class->write (stream, buffer, n, cancellable, error);
543 	CAMEL_CHECK_GERROR (stream, write, n_bytes >= 0, error);
544 
545 	return n_bytes;
546 }
547 
548 /**
549  * camel_stream_flush:
550  * @stream: a #CamelStream object
551  * @cancellable: optional #GCancellable object, or %NULL
552  * @error: return location for a #GError, or %NULL
553  *
554  * Flushes any buffered data to the stream's backing store.  Only
555  * meaningful for writable streams.
556  *
557  * Returns: 0 on success or -1 on fail along with setting @error
558  **/
559 gint
camel_stream_flush(CamelStream * stream,GCancellable * cancellable,GError ** error)560 camel_stream_flush (CamelStream *stream,
561                     GCancellable *cancellable,
562                     GError **error)
563 {
564 	CamelStreamClass *class;
565 	gint retval;
566 
567 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
568 
569 	class = CAMEL_STREAM_GET_CLASS (stream);
570 	g_return_val_if_fail (class != NULL, -1);
571 	g_return_val_if_fail (class->flush != NULL, -1);
572 
573 	retval = class->flush (stream, cancellable, error);
574 	CAMEL_CHECK_GERROR (stream, flush, retval == 0, error);
575 
576 	return retval;
577 }
578 
579 /**
580  * camel_stream_close:
581  * @stream: a #CamelStream object
582  * @cancellable: optional #GCancellable object, or %NULL
583  * @error: return location for a #GError, or %NULL
584  *
585  * Closes the stream.
586  *
587  * Returns: 0 on success or -1 on error.
588  **/
589 gint
camel_stream_close(CamelStream * stream,GCancellable * cancellable,GError ** error)590 camel_stream_close (CamelStream *stream,
591                     GCancellable *cancellable,
592                     GError **error)
593 {
594 	CamelStreamClass *class;
595 	gint retval;
596 
597 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
598 
599 	class = CAMEL_STREAM_GET_CLASS (stream);
600 	g_return_val_if_fail (class != NULL, -1);
601 	g_return_val_if_fail (class->close != NULL, -1);
602 
603 	retval = class->close (stream, cancellable, error);
604 	CAMEL_CHECK_GERROR (stream, close, retval == 0, error);
605 
606 	return retval;
607 }
608 
609 /**
610  * camel_stream_eos:
611  * @stream: a #CamelStream object
612  *
613  * Tests if there are bytes left to read on the @stream object.
614  *
615  * Returns: %TRUE on EOS or %FALSE otherwise.
616  **/
617 gboolean
camel_stream_eos(CamelStream * stream)618 camel_stream_eos (CamelStream *stream)
619 {
620 	CamelStreamClass *class;
621 
622 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), TRUE);
623 
624 	class = CAMEL_STREAM_GET_CLASS (stream);
625 	g_return_val_if_fail (class != NULL, TRUE);
626 	g_return_val_if_fail (class->eos != NULL, TRUE);
627 
628 	return class->eos (stream);
629 }
630 
631 /***************** Utility functions ********************/
632 
633 /**
634  * camel_stream_write_string:
635  * @stream: a #CamelStream object
636  * @string: a string
637  * @cancellable: optional #GCancellable object, or %NULL
638  * @error: return location for a #GError, or %NULL
639  *
640  * Writes the string to the stream.
641  *
642  * Returns: the number of characters written or -1 on error.
643  **/
644 gssize
camel_stream_write_string(CamelStream * stream,const gchar * string,GCancellable * cancellable,GError ** error)645 camel_stream_write_string (CamelStream *stream,
646                            const gchar *string,
647                            GCancellable *cancellable,
648                            GError **error)
649 {
650 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
651 	g_return_val_if_fail (string != NULL, -1);
652 
653 	return camel_stream_write (
654 		stream, string, strlen (string), cancellable, error);
655 }
656 
657 /**
658  * camel_stream_write_to_stream:
659  * @stream: source #CamelStream object
660  * @output_stream: destination #CamelStream object
661  * @cancellable: optional #GCancellable object, or %NULL
662  * @error: return location for a #GError, or %NULL
663  *
664  * Write all of a stream (until eos) into another stream, in a
665  * blocking fashion.
666  *
667  * Returns: -1 on error, or the number of bytes succesfully
668  * copied across streams.
669  **/
670 gssize
camel_stream_write_to_stream(CamelStream * stream,CamelStream * output_stream,GCancellable * cancellable,GError ** error)671 camel_stream_write_to_stream (CamelStream *stream,
672                               CamelStream *output_stream,
673                               GCancellable *cancellable,
674                               GError **error)
675 {
676 	gchar tmp_buf[4096];
677 	gssize total = 0;
678 	gssize nb_read;
679 	gssize nb_written;
680 
681 	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
682 	g_return_val_if_fail (CAMEL_IS_STREAM (output_stream), -1);
683 
684 	while (!camel_stream_eos (stream)) {
685 		nb_read = camel_stream_read (
686 			stream, tmp_buf, sizeof (tmp_buf),
687 			cancellable, error);
688 		if (nb_read < 0)
689 			return -1;
690 		else if (nb_read > 0) {
691 			nb_written = 0;
692 
693 			while (nb_written < nb_read) {
694 				gssize len = camel_stream_write (
695 					output_stream,
696 					tmp_buf + nb_written,
697 					nb_read - nb_written,
698 					cancellable, error);
699 				if (len < 0)
700 					return -1;
701 				nb_written += len;
702 			}
703 			total += nb_written;
704 		}
705 	}
706 	return total;
707 }
708