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