1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2020 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20 
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 
31 #include "gmime-stream-gio.h"
32 
33 
34 /**
35  * SECTION: gmime-stream-gio
36  * @title: GMimeStreamGIO
37  * @short_description: A wrapper for GLib's GIO streams
38  * @see_also: #GMimeStream
39  *
40  * A simple #GMimeStream implementation that sits on top of GLib's GIO
41  * input and output streams.
42  **/
43 
44 
45 static void g_mime_stream_gio_class_init (GMimeStreamGIOClass *klass);
46 static void g_mime_stream_gio_init (GMimeStreamGIO *stream, GMimeStreamGIOClass *klass);
47 static void g_mime_stream_gio_finalize (GObject *object);
48 
49 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
50 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
51 static int stream_flush (GMimeStream *stream);
52 static int stream_close (GMimeStream *stream);
53 static gboolean stream_eos (GMimeStream *stream);
54 static int stream_reset (GMimeStream *stream);
55 static gint64 stream_seek (GMimeStream *stream, gint64 ofgioet, GMimeSeekWhence whence);
56 static gint64 stream_tell (GMimeStream *stream);
57 static gint64 stream_length (GMimeStream *stream);
58 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
59 
60 
61 static GMimeStreamClass *parent_class = NULL;
62 
63 
64 GType
g_mime_stream_gio_get_type(void)65 g_mime_stream_gio_get_type (void)
66 {
67 	static GType type = 0;
68 
69 	if (!type) {
70 		static const GTypeInfo info = {
71 			sizeof (GMimeStreamGIOClass),
72 			NULL, /* base_class_init */
73 			NULL, /* base_class_finalize */
74 			(GClassInitFunc) g_mime_stream_gio_class_init,
75 			NULL, /* class_finalize */
76 			NULL, /* class_data */
77 			sizeof (GMimeStreamGIO),
78 			0,    /* n_preallocs */
79 			(GInstanceInitFunc) g_mime_stream_gio_init,
80 		};
81 
82 		type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamGIO", &info, 0);
83 	}
84 
85 	return type;
86 }
87 
88 
89 static void
g_mime_stream_gio_class_init(GMimeStreamGIOClass * klass)90 g_mime_stream_gio_class_init (GMimeStreamGIOClass *klass)
91 {
92 	GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
93 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
94 
95 	parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
96 
97 	object_class->finalize = g_mime_stream_gio_finalize;
98 
99 	stream_class->read = stream_read;
100 	stream_class->write = stream_write;
101 	stream_class->flush = stream_flush;
102 	stream_class->close = stream_close;
103 	stream_class->eos = stream_eos;
104 	stream_class->reset = stream_reset;
105 	stream_class->seek = stream_seek;
106 	stream_class->tell = stream_tell;
107 	stream_class->length = stream_length;
108 	stream_class->substream = stream_substream;
109 }
110 
111 static void
g_mime_stream_gio_init(GMimeStreamGIO * stream,GMimeStreamGIOClass * klass)112 g_mime_stream_gio_init (GMimeStreamGIO *stream, GMimeStreamGIOClass *klass)
113 {
114 	stream->ostream = NULL;
115 	stream->istream = NULL;
116 	stream->file = NULL;
117 
118 	stream->owner = TRUE;
119 	stream->eos = FALSE;
120 }
121 
122 static void
g_mime_stream_gio_finalize(GObject * object)123 g_mime_stream_gio_finalize (GObject *object)
124 {
125 	GMimeStream *stream = (GMimeStream *) object;
126 
127 	stream_close (stream);
128 
129 	G_OBJECT_CLASS (parent_class)->finalize (object);
130 }
131 
132 static void
set_errno(GError * err)133 set_errno (GError *err)
134 {
135 	if (!err) {
136 		errno = 0;
137 		return;
138 	}
139 
140 	switch (err->code) {
141 	case G_IO_ERROR_NOT_FOUND: errno = ENOENT; break;
142 	case G_IO_ERROR_EXISTS: errno = EEXIST; break;
143 	case G_IO_ERROR_IS_DIRECTORY: errno = EISDIR; break;
144 	case G_IO_ERROR_NOT_DIRECTORY: errno = ENOTDIR; break;
145 	case G_IO_ERROR_NOT_EMPTY: errno = ENOTEMPTY; break;
146 	case G_IO_ERROR_FILENAME_TOO_LONG: errno = ENAMETOOLONG; break;
147 	case G_IO_ERROR_TOO_MANY_LINKS: errno = EMLINK; break;
148 	case G_IO_ERROR_NO_SPACE: errno = ENOSPC; break; // or ENOMEM
149 	case G_IO_ERROR_INVALID_ARGUMENT: errno = EINVAL; break;
150 	case G_IO_ERROR_PERMISSION_DENIED: errno = EACCES; break; // or EPERM
151 #ifdef ENOTSUP
152 	case G_IO_ERROR_NOT_SUPPORTED: errno = ENOTSUP; break;
153 #endif
154 #ifdef ECANCELED
155 	case G_IO_ERROR_CANCELLED: errno = ECANCELED; break;
156 #endif
157 	case G_IO_ERROR_READ_ONLY: errno = EROFS; break;
158 #ifdef ETIMEDOUT
159 	case G_IO_ERROR_TIMED_OUT: errno = ETIMEDOUT; break;
160 #endif
161 	case G_IO_ERROR_BUSY: errno = EBUSY; break;
162 	case G_IO_ERROR_WOULD_BLOCK: errno = EAGAIN; break;
163 	case G_IO_ERROR_FAILED:
164 	default:
165 		errno = EIO;
166 		break;
167 	}
168 
169 	g_error_free (err);
170 }
171 
172 static ssize_t
stream_read(GMimeStream * stream,char * buf,size_t len)173 stream_read (GMimeStream *stream, char *buf, size_t len)
174 {
175 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
176 	GError *err = NULL;
177 	ssize_t nread;
178 
179 	if (gio->file == NULL) {
180 		errno = EBADF;
181 		return -1;
182 	}
183 
184 	if (gio->istream == NULL) {
185 		/* try opening an input stream */
186 		if (!(gio->istream = (GInputStream *) g_file_read (gio->file, NULL, &err))) {
187 			set_errno (err);
188 			return -1;
189 		}
190 	}
191 
192 	if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
193 		errno = EINVAL;
194 		return -1;
195 	}
196 
197 	if (stream->bound_end != -1)
198 		len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
199 
200 	/* make sure we are at the right position */
201 	if (G_IS_SEEKABLE (gio->istream)) {
202 		GSeekable *seekable = (GSeekable *) gio->istream;
203 
204 		if (!g_seekable_seek (seekable, stream->position, G_SEEK_SET, NULL, &err)) {
205 			set_errno (err);
206 			return -1;
207 		}
208 	}
209 
210 	if ((nread = g_input_stream_read (gio->istream, buf, len, NULL, &err)) < 0) {
211 		set_errno (err);
212 		return -1;
213 	}
214 
215 	if (nread > 0)
216 		stream->position += nread;
217 	else if (nread == 0)
218 		gio->eos = TRUE;
219 
220 	return nread;
221 }
222 
223 static ssize_t
stream_write(GMimeStream * stream,const char * buf,size_t len)224 stream_write (GMimeStream *stream, const char *buf, size_t len)
225 {
226 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
227 	size_t nwritten = 0;
228 	GError *err = NULL;
229 
230 	if (gio->file == NULL) {
231 		errno = EBADF;
232 		return -1;
233 	}
234 
235 	if (gio->ostream == NULL) {
236 		/* try opening an output stream */
237 		if (!(gio->ostream = (GOutputStream *) g_file_append_to (gio->file, G_FILE_CREATE_NONE, NULL, &err))) {
238 			set_errno (err);
239 			return -1;
240 		}
241 	}
242 
243 	if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
244 		errno = EINVAL;
245 		return -1;
246 	}
247 
248 	if (stream->bound_end != -1)
249 		len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
250 
251 	/* make sure we are at the right position */
252 	if (G_IS_SEEKABLE (gio->ostream)) {
253 		GSeekable *seekable = (GSeekable *) gio->ostream;
254 
255 		if (!g_seekable_seek (seekable, stream->position, G_SEEK_SET, NULL, &err)) {
256 			set_errno (err);
257 			return -1;
258 		}
259 	}
260 
261 	if (!g_output_stream_write_all (gio->ostream, buf, len, &nwritten, NULL, &err)) {
262 		set_errno (err);
263 		gio->eos = TRUE;
264 
265 		if (nwritten == 0) {
266 			/* nothing was written, return error */
267 			return -1;
268 		}
269 
270 		errno = 0;
271 	}
272 
273 	if (nwritten > 0)
274 		stream->position += nwritten;
275 
276 	return nwritten;
277 }
278 
279 static int
stream_flush(GMimeStream * stream)280 stream_flush (GMimeStream *stream)
281 {
282 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
283 	GError *err = NULL;
284 
285 	if (gio->file == NULL) {
286 		errno = EBADF;
287 		return -1;
288 	}
289 
290 	if (gio->ostream && !g_output_stream_flush (gio->ostream, NULL, &err)) {
291 		set_errno (err);
292 		return -1;
293 	}
294 
295 	return 0;
296 }
297 
298 static int
stream_close(GMimeStream * stream)299 stream_close (GMimeStream *stream)
300 {
301 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
302 
303 	if (gio->istream) {
304 		g_input_stream_close (gio->istream, NULL, NULL);
305 		g_object_unref (gio->istream);
306 		gio->istream = NULL;
307 	}
308 
309 	if (gio->ostream) {
310 		g_output_stream_close (gio->ostream, NULL, NULL);
311 		g_object_unref (gio->ostream);
312 		gio->ostream = NULL;
313 	}
314 
315 	if (gio->owner && gio->file)
316 		g_object_unref (gio->file);
317 
318 	gio->file = NULL;
319 
320 	return 0;
321 }
322 
323 static gboolean
stream_eos(GMimeStream * stream)324 stream_eos (GMimeStream *stream)
325 {
326 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
327 
328 	if (gio->file == NULL)
329 		return TRUE;
330 
331 	return gio->eos;
332 }
333 
334 static int
stream_reset(GMimeStream * stream)335 stream_reset (GMimeStream *stream)
336 {
337 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
338 	GError *err = NULL;
339 
340 	if (gio->file == NULL) {
341 		errno = EBADF;
342 		return -1;
343 	}
344 
345 	if (stream->position == stream->bound_start) {
346 		gio->eos = FALSE;
347 		return 0;
348 	}
349 
350 	if (gio->istream != NULL) {
351 		/* reset the input stream */
352 		if (!G_IS_SEEKABLE (gio->istream)) {
353 			errno = EINVAL;
354 			return -1;
355 		}
356 
357 		if (!g_seekable_seek ((GSeekable *) gio->istream, stream->bound_start, G_SEEK_SET, NULL, &err)) {
358 			set_errno (err);
359 			return -1;
360 		}
361 	}
362 
363 	if (gio->ostream != NULL) {
364 		/* reset the output stream */
365 		if (!G_IS_SEEKABLE (gio->ostream)) {
366 			errno = EINVAL;
367 			return -1;
368 		}
369 
370 		if (!g_seekable_seek ((GSeekable *) gio->ostream, stream->bound_start, G_SEEK_SET, NULL, &err)) {
371 			set_errno (err);
372 			return -1;
373 		}
374 	}
375 
376 	gio->eos = FALSE;
377 
378 	return 0;
379 }
380 
381 static gint64
gio_seekable_seek(GMimeStream * stream,GSeekable * seekable,gint64 offset,GMimeSeekWhence whence)382 gio_seekable_seek (GMimeStream *stream, GSeekable *seekable, gint64 offset, GMimeSeekWhence whence)
383 {
384 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
385 	gboolean need_seek = TRUE;
386 	GError *err = NULL;
387 	gint64 real;
388 
389 	switch (whence) {
390 	case GMIME_STREAM_SEEK_SET:
391 		real = offset;
392 		break;
393 	case GMIME_STREAM_SEEK_CUR:
394 		real = stream->position + offset;
395 		break;
396 	case GMIME_STREAM_SEEK_END:
397 		if (offset > 0 || (stream->bound_end == -1 && !gio->eos)) {
398 			/* need to do an actual lseek() here because
399 			 * we either don't know the offset of the end
400 			 * of the stream and/or don't know if we can
401 			 * seek past the end */
402 			if (!g_seekable_seek (seekable, offset, G_SEEK_END, NULL, &err)) {
403 				set_errno (err);
404 				return -1;
405 			}
406 
407 			need_seek = FALSE;
408 			real = offset;
409 		} else if (gio->eos && stream->bound_end == -1) {
410 			/* seeking backwards from eos (which happens
411 			 * to be our current position) */
412 			real = stream->position + offset;
413 		} else {
414 			/* seeking backwards from a known position */
415 			real = stream->bound_end + offset;
416 		}
417 
418 		break;
419 	default:
420 		g_assert_not_reached ();
421 		return -1;
422 	}
423 
424 	/* sanity check the resultant offset */
425 	if (real < stream->bound_start) {
426 		errno = EINVAL;
427 		return -1;
428 	}
429 
430 	/* short-cut if we are seeking to our current position */
431 	if (real == stream->position)
432 		return real;
433 
434 	if (stream->bound_end != -1 && real > stream->bound_end) {
435 		errno = EINVAL;
436 		return -1;
437 	}
438 
439 	if (need_seek && !g_seekable_seek (seekable, real, G_SEEK_SET, NULL, &err)) {
440 		set_errno (err);
441 		return -1;
442 	}
443 
444 	return real;
445 }
446 
447 static gint64
stream_seek(GMimeStream * stream,gint64 offset,GMimeSeekWhence whence)448 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
449 {
450 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
451 	gint64 real;
452 
453 	if (gio->file == NULL) {
454 		errno = EBADF;
455 		return -1;
456 	}
457 
458 	/* if either of our streams are unseekable, fail */
459 	if ((gio->istream != NULL && !G_IS_SEEKABLE (gio->istream)) ||
460 	    (gio->ostream != NULL && !G_IS_SEEKABLE (gio->ostream))) {
461 		errno = EINVAL;
462 		return -1;
463 	}
464 
465 	if (gio->istream || gio->ostream) {
466 		gint64 outreal = -1;
467 		gint64 inreal = -1;
468 
469 		if (gio->istream) {
470 			/* seek on our input stream */
471 			if ((inreal = gio_seekable_seek (stream, (GSeekable *) gio->istream, offset, whence)) == -1)
472 				return -1;
473 
474 			if (gio->ostream == NULL)
475 				outreal = inreal;
476 		}
477 
478 		if (gio->ostream) {
479 			/* seek on our output stream */
480 			if ((outreal = gio_seekable_seek (stream, (GSeekable *) gio->istream, offset, whence)) == -1)
481 				return -1;
482 
483 			if (gio->istream == NULL)
484 				inreal = outreal;
485 		}
486 
487 		if (outreal != inreal) {
488 		}
489 
490 		real = outreal;
491 	} else {
492 		/* no streams yet opened... */
493 		switch (whence) {
494 		case GMIME_STREAM_SEEK_SET:
495 			real = offset;
496 			break;
497 		case GMIME_STREAM_SEEK_CUR:
498 			real = stream->position + offset;
499 			break;
500 		case GMIME_STREAM_SEEK_END:
501 			real = stream->bound_end + offset;
502 			break;
503 		default:
504 			g_assert_not_reached ();
505 			return -1;
506 		}
507 
508 		/* check that we haven't seekend beyond bound_end */
509 		if (stream->bound_end != -1 && real > stream->bound_end) {
510 			errno = EINVAL;
511 			return -1;
512 		}
513 
514 		/* check that we are within the starting bounds */
515 		if (real < stream->bound_start) {
516 			errno = EINVAL;
517 			return -1;
518 		}
519 	}
520 
521 	/* reset eos if appropriate */
522 	if ((stream->bound_end != -1 && real < stream->bound_end) ||
523 	    (gio->eos && real < stream->position))
524 		gio->eos = FALSE;
525 
526 	stream->position = real;
527 
528 	return real;
529 }
530 
531 static gint64
stream_tell(GMimeStream * stream)532 stream_tell (GMimeStream *stream)
533 {
534 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
535 
536 	if (gio->file == NULL) {
537 		errno = EBADF;
538 		return -1;
539 	}
540 
541 	return stream->position;
542 }
543 
544 static gint64
gio_seekable_bound_end(GMimeStream * stream,GSeekable * seekable)545 gio_seekable_bound_end (GMimeStream *stream, GSeekable *seekable)
546 {
547 	GError *err = NULL;
548 	gint64 bound_end;
549 
550 	if (!g_seekable_seek (seekable, 0, G_SEEK_END, NULL, &err)) {
551 		set_errno (err);
552 		return -1;
553 	}
554 
555 	bound_end = g_seekable_tell (seekable);
556 	if (bound_end < stream->bound_start) {
557 		errno = EINVAL;
558 		return -1;
559 	}
560 
561 	if (!g_seekable_seek (seekable, stream->position, G_SEEK_SET, NULL, &err)) {
562 		set_errno (err);
563 		return -1;
564 	}
565 
566 	return bound_end;
567 }
568 
569 static gint64
stream_length(GMimeStream * stream)570 stream_length (GMimeStream *stream)
571 {
572 	GMimeStreamGIO *gio = (GMimeStreamGIO *) stream;
573 	gint64 bound_end;
574 
575 	if (gio->file == NULL) {
576 		errno = EBADF;
577 		return -1;
578 	}
579 
580 	if (stream->bound_end != -1)
581 		return stream->bound_end - stream->bound_start;
582 
583 	if (gio->istream && G_IS_SEEKABLE (gio->istream)) {
584 		if ((bound_end = gio_seekable_bound_end (stream, (GSeekable *) gio->istream)) == -1)
585 			return -1;
586 	} else if (gio->ostream && G_IS_SEEKABLE (gio->ostream)) {
587 		if ((bound_end = gio_seekable_bound_end (stream, (GSeekable *) gio->ostream)) == -1)
588 			return -1;
589 	} else if (!gio->istream && !gio->ostream) {
590 		/* try opening an input stream to get the length */
591 		if (!(gio->istream = (GInputStream *) g_file_read (gio->file, NULL, NULL))) {
592 			errno = EINVAL;
593 			return -1;
594 		}
595 
596 		if ((bound_end = gio_seekable_bound_end (stream, (GSeekable *) gio->istream)) == -1)
597 			return -1;
598 	} else {
599 		/* neither of our streams is seekable, can't get the length */
600 		errno = EINVAL;
601 		return -1;
602 	}
603 
604 	return bound_end - stream->bound_start;
605 }
606 
607 static GMimeStream *
stream_substream(GMimeStream * stream,gint64 start,gint64 end)608 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
609 {
610 	GMimeStreamGIO *gio;
611 
612 	gio = g_object_new (GMIME_TYPE_STREAM_GIO, NULL);
613 	g_mime_stream_construct ((GMimeStream *) gio, start, end);
614 	gio->file = ((GMimeStreamGIO *) stream)->file;
615 	gio->owner = FALSE;
616 	gio->eos = FALSE;
617 
618 	return (GMimeStream *) gio;
619 }
620 
621 
622 /**
623  * g_mime_stream_gio_new:
624  * @file: a #GFile
625  *
626  * Creates a new #GMimeStreamGIO wrapper around a #GFile object.
627  *
628  * Returns: (transfer full): a stream using @file.
629  **/
630 GMimeStream *
g_mime_stream_gio_new(GFile * file)631 g_mime_stream_gio_new (GFile *file)
632 {
633 	return g_mime_stream_gio_new_with_bounds (file, 0, -1);
634 }
635 
636 
637 /**
638  * g_mime_stream_gio_new_with_bounds:
639  * @file: a #GFile
640  * @start: start boundary
641  * @end: end boundary
642  *
643  * Creates a new #GMimeStreamGIO stream around a #GFile with bounds
644  * @start and @end.
645  *
646  * Returns: (transfer full): a stream using @file with bounds @start
647  * and @end.
648  **/
649 GMimeStream *
g_mime_stream_gio_new_with_bounds(GFile * file,gint64 start,gint64 end)650 g_mime_stream_gio_new_with_bounds (GFile *file, gint64 start, gint64 end)
651 {
652 	GMimeStreamGIO *gio;
653 
654 	g_return_val_if_fail (G_IS_FILE (file), NULL);
655 
656 	gio = g_object_new (GMIME_TYPE_STREAM_GIO, NULL);
657 	g_mime_stream_construct ((GMimeStream *) gio, start, end);
658 	gio->file = file;
659 	gio->owner = TRUE;
660 	gio->eos = FALSE;
661 
662 	return (GMimeStream *) gio;
663 }
664 
665 
666 /**
667  * g_mime_stream_gio_get_owner:
668  * @stream: a #GMimeStreamGIO stream
669  *
670  * Gets whether or not @stream owns the backend #GFile.
671  *
672  * Returns: %TRUE if @stream owns the backend #GFile or %FALSE
673  * otherwise.
674  **/
675 gboolean
g_mime_stream_gio_get_owner(GMimeStreamGIO * stream)676 g_mime_stream_gio_get_owner (GMimeStreamGIO *stream)
677 {
678 	g_return_val_if_fail (GMIME_IS_STREAM_GIO (stream), FALSE);
679 
680 	return stream->owner;
681 }
682 
683 
684 /**
685  * g_mime_stream_gio_set_owner:
686  * @stream: a #GMimeStreamGIO stream
687  * @owner: %TRUE if this stream should own the #GFile or %FALSE otherwise
688  *
689  * Sets whether or not @stream owns the backend GIO pointer.
690  *
691  * Note: @owner should be %TRUE if the stream should close() the
692  * backend file descriptor when destroyed or %FALSE otherwise.
693  **/
694 void
g_mime_stream_gio_set_owner(GMimeStreamGIO * stream,gboolean owner)695 g_mime_stream_gio_set_owner (GMimeStreamGIO *stream, gboolean owner)
696 {
697 	g_return_if_fail (GMIME_IS_STREAM_GIO (stream));
698 
699 	stream->owner = owner;
700 }
701