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