1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2014 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 <errno.h>
27 
28 #include "gmime-stream-cat.h"
29 
30 #define d(x)
31 
32 
33 /**
34  * SECTION: gmime-stream-cat
35  * @title: GMimeStreamCat
36  * @short_description: A concatenated stream
37  * @see_also: #GMimeStream
38  *
39  * A #GMimeStream which chains together any number of other streams.
40  **/
41 
42 
43 static void g_mime_stream_cat_class_init (GMimeStreamCatClass *klass);
44 static void g_mime_stream_cat_init (GMimeStreamCat *stream, GMimeStreamCatClass *klass);
45 static void g_mime_stream_cat_finalize (GObject *object);
46 
47 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
48 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
49 static int stream_flush (GMimeStream *stream);
50 static int stream_close (GMimeStream *stream);
51 static gboolean stream_eos (GMimeStream *stream);
52 static int stream_reset (GMimeStream *stream);
53 static gint64 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence);
54 static gint64 stream_tell (GMimeStream *stream);
55 static gint64 stream_length (GMimeStream *stream);
56 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
57 
58 
59 static GMimeStreamClass *parent_class = NULL;
60 
61 
62 struct _cat_node {
63 	struct _cat_node *next;
64 	GMimeStream *stream;
65 	gint64 position;
66 	int id; /* for debugging */
67 };
68 
69 GType
g_mime_stream_cat_get_type(void)70 g_mime_stream_cat_get_type (void)
71 {
72 	static GType type = 0;
73 
74 	if (!type) {
75 		static const GTypeInfo info = {
76 			sizeof (GMimeStreamCatClass),
77 			NULL, /* base_class_init */
78 			NULL, /* base_class_finalize */
79 			(GClassInitFunc) g_mime_stream_cat_class_init,
80 			NULL, /* class_finalize */
81 			NULL, /* class_data */
82 			sizeof (GMimeStreamCat),
83 			0,    /* n_preallocs */
84 			(GInstanceInitFunc) g_mime_stream_cat_init,
85 		};
86 
87 		type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamCat", &info, 0);
88 	}
89 
90 	return type;
91 }
92 
93 
94 static void
g_mime_stream_cat_class_init(GMimeStreamCatClass * klass)95 g_mime_stream_cat_class_init (GMimeStreamCatClass *klass)
96 {
97 	GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
98 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
99 
100 	parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
101 
102 	object_class->finalize = g_mime_stream_cat_finalize;
103 
104 	stream_class->read = stream_read;
105 	stream_class->write = stream_write;
106 	stream_class->flush = stream_flush;
107 	stream_class->close = stream_close;
108 	stream_class->eos = stream_eos;
109 	stream_class->reset = stream_reset;
110 	stream_class->seek = stream_seek;
111 	stream_class->tell = stream_tell;
112 	stream_class->length = stream_length;
113 	stream_class->substream = stream_substream;
114 }
115 
116 static void
g_mime_stream_cat_init(GMimeStreamCat * stream,GMimeStreamCatClass * klass)117 g_mime_stream_cat_init (GMimeStreamCat *stream, GMimeStreamCatClass *klass)
118 {
119 	stream->sources = NULL;
120 	stream->current = NULL;
121 }
122 
123 static void
g_mime_stream_cat_finalize(GObject * object)124 g_mime_stream_cat_finalize (GObject *object)
125 {
126 	GMimeStreamCat *cat = (GMimeStreamCat *) object;
127 	struct _cat_node *n, *nn;
128 
129 	n = cat->sources;
130 	while (n != NULL) {
131 		nn = n->next;
132 		g_object_unref (n->stream);
133 		g_free (n);
134 		n = nn;
135 	}
136 
137 	G_OBJECT_CLASS (parent_class)->finalize (object);
138 }
139 
140 static ssize_t
stream_read(GMimeStream * stream,char * buf,size_t len)141 stream_read (GMimeStream *stream, char *buf, size_t len)
142 {
143 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
144 	struct _cat_node *current;
145 	ssize_t nread = 0;
146 	gint64 offset;
147 
148 	/* check for end-of-stream */
149 	if (stream->bound_end != -1 && stream->position >= stream->bound_end)
150 		return -1;
151 
152 	/* don't allow our caller to read past the end of the stream */
153 	if (stream->bound_end != -1)
154 		len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
155 
156 	if (!(current = cat->current))
157 		return -1;
158 
159 	/* make sure our stream position is where it should be */
160 	offset = current->stream->bound_start + current->position;
161 	if (g_mime_stream_seek (current->stream, offset, GMIME_STREAM_SEEK_SET) == -1)
162 		return -1;
163 
164 	do {
165 		if ((nread = g_mime_stream_read (current->stream, buf, len)) <= 0) {
166 			cat->current = current = current->next;
167 			if (current != NULL) {
168 				if (g_mime_stream_reset (current->stream) == -1)
169 					return -1;
170 				current->position = 0;
171 			}
172 			nread = 0;
173 		} else if (nread > 0) {
174 			current->position += nread;
175 		}
176 	} while (nread == 0 && current != NULL);
177 
178 	if (nread > 0)
179 		stream->position += nread;
180 
181 	return nread;
182 }
183 
184 static ssize_t
stream_write(GMimeStream * stream,const char * buf,size_t len)185 stream_write (GMimeStream *stream, const char *buf, size_t len)
186 {
187 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
188 	struct _cat_node *current;
189 	size_t nwritten = 0;
190 	ssize_t n = -1;
191 	gint64 offset;
192 
193 	/* check for end-of-stream */
194 	if (stream->bound_end != -1 && stream->position >= stream->bound_end)
195 		return -1;
196 
197 	/* don't allow our caller to write past the end of the stream */
198 	if (stream->bound_end != -1)
199 		len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
200 
201 	if (!(current = cat->current))
202 		return -1;
203 
204 	/* make sure our stream position is where it should be */
205 	offset = current->stream->bound_start + current->position;
206 	if (g_mime_stream_seek (current->stream, offset, GMIME_STREAM_SEEK_SET) == -1)
207 		return -1;
208 
209 	do {
210 		n = -1;
211 		while (!g_mime_stream_eos (current->stream) && nwritten < len) {
212 			if ((n = g_mime_stream_write (current->stream, buf + nwritten, len - nwritten)) <= 0)
213 				break;
214 
215 			current->position += n;
216 
217 			nwritten += n;
218 		}
219 
220 		if (nwritten < len) {
221 			/* try spilling over into the next stream */
222 			current = current->next;
223 			if (current) {
224 				current->position = 0;
225 				if (g_mime_stream_reset (current->stream) == -1)
226 					break;
227 			} else {
228 				break;
229 			}
230 		}
231 	} while (nwritten < len);
232 
233 	stream->position += nwritten;
234 
235 	cat->current = current;
236 
237 	if (n == -1 && nwritten == 0)
238 		return -1;
239 
240 	return nwritten;
241 }
242 
243 static int
stream_flush(GMimeStream * stream)244 stream_flush (GMimeStream *stream)
245 {
246 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
247 	struct _cat_node *node;
248 	int errnosav = 0;
249 	int rv = 0;
250 
251 	/* flush all streams up to and including the current stream */
252 
253 	node = cat->sources;
254 	while (node) {
255 		if (g_mime_stream_flush (node->stream) == -1) {
256 			if (errnosav == 0)
257 				errnosav = errno;
258 			rv = -1;
259 		}
260 
261 		if (node == cat->current)
262 			break;
263 
264 		node = node->next;
265 	}
266 
267 	return rv;
268 }
269 
270 static int
stream_close(GMimeStream * stream)271 stream_close (GMimeStream *stream)
272 {
273 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
274 	struct _cat_node *n, *nn;
275 
276 	cat->current = NULL;
277 	n = cat->sources;
278 	while (n != NULL) {
279 		nn = n->next;
280 		g_object_unref (n->stream);
281 		g_free (n);
282 		n = nn;
283 	}
284 
285 	cat->sources = NULL;
286 
287 	return 0;
288 }
289 
290 static gboolean
stream_eos(GMimeStream * stream)291 stream_eos (GMimeStream *stream)
292 {
293 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
294 
295 	if (cat->current == NULL)
296 		return TRUE;
297 
298 	if (stream->bound_end != -1 && stream->position >= stream->bound_end)
299 		return TRUE;
300 
301 	return FALSE;
302 }
303 
304 static int
stream_reset(GMimeStream * stream)305 stream_reset (GMimeStream *stream)
306 {
307 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
308 	struct _cat_node *n;
309 
310 	if (stream->position == stream->bound_start)
311 		return 0;
312 
313 	n = cat->sources;
314 	while (n != NULL) {
315 		if (g_mime_stream_reset (n->stream) == -1)
316 			return -1;
317 
318 		n->position = 0;
319 		n = n->next;
320 	}
321 
322 	cat->current = cat->sources;
323 
324 	return 0;
325 }
326 
327 static gint64
stream_seek(GMimeStream * stream,gint64 offset,GMimeSeekWhence whence)328 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
329 {
330 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
331 	struct _cat_node *current, *n;
332 	gint64 real, off, len;
333 
334 	d(fprintf (stderr, "GMimeStreamCat::stream_seek (%p, %ld, %d)\n",
335 		   stream, offset, whence));
336 
337 	if (cat->sources == NULL)
338 		return -1;
339 
340 	switch (whence) {
341 	case GMIME_STREAM_SEEK_SET:
342 	seek_set:
343 		/* sanity check our seek - make sure we don't under/over-seek our bounds */
344 		if (offset < 0) {
345 			d(fprintf (stderr, "offset %ld < 0, fail\n", offset));
346 			return -1;
347 		}
348 
349 		/* sanity check our seek */
350 		if (stream->bound_end != -1 && offset > stream->bound_end) {
351 			d(fprintf (stderr, "offset %ld > bound_end %ld, fail\n",
352 				   offset, stream->bound_end));
353 			return -1;
354 		}
355 
356 		/* short-cut if we are seeking to our current position */
357 		if (offset == stream->position) {
358 			d(fprintf (stderr, "offset %ld == stream->position %ld, no need to seek\n",
359 				   offset, stream->position));
360 			return offset;
361 		}
362 
363 		real = 0;
364 		n = cat->sources;
365 		current = cat->current;
366 
367 		while (n != current) {
368 			if (real + n->position > offset)
369 				break;
370 			real += n->position;
371 			n = n->next;
372 		}
373 
374 		if (n == NULL) {
375 			/* offset not within our grasp... */
376 			return -1;
377 		}
378 
379 		if (n != current) {
380 			/* seeking to a previous stream (n->stream) */
381 			if ((offset - real) != n->position) {
382 				/* FIXME: could probably skip these seek checks... */
383 				off = n->stream->bound_start + (offset - real);
384 				if (g_mime_stream_seek (n->stream, off, GMIME_STREAM_SEEK_SET) == -1)
385 					return -1;
386 			}
387 
388 			d(fprintf (stderr, "setting current stream to %i and updating cur->position to %ld\n",
389 				   n->id, offset - real));
390 
391 			current = n;
392 			current->position = offset - real;
393 
394 			break;
395 		} else {
396 			/* seeking to someplace in our current (or next) stream */
397 			d(fprintf (stderr, "seek offset %ld in current stream[%d] or after\n",
398 				   offset, current->id));
399 			if ((offset - real) == current->position) {
400 				/* exactly at our current position */
401 				d(fprintf (stderr, "seek offset at cur position of stream[%d]\n",
402 					   current->id));
403 				stream->position = offset;
404 				return offset;
405 			}
406 
407 			if ((offset - real) < current->position) {
408 				/* in current stream, but before current position */
409 				d(fprintf (stderr, "seeking backwards in cur stream[%d]\n",
410 					   current->id));
411 				/* FIXME: again, could probably skip seek checks... */
412 				off = current->stream->bound_start + (offset - real);
413 				if (g_mime_stream_seek (current->stream, off, GMIME_STREAM_SEEK_SET) == -1)
414 					return -1;
415 
416 				d(fprintf (stderr, "setting cur stream[%d] position to %ld\n",
417 					   current->id, offset - real));
418 				current->position = offset - real;
419 
420 				break;
421 			}
422 
423 			/* after our current position */
424 			d(fprintf (stderr, "after cur position in stream[%d] or in a later stream\n",
425 				   current->id));
426 			do {
427 				if (current->stream->bound_end != -1) {
428 					len = current->stream->bound_end - current->stream->bound_start;
429 				} else {
430 					if ((len = g_mime_stream_length (current->stream)) == -1)
431 						return -1;
432 				}
433 
434 				d(fprintf (stderr, "real = %lld, stream[%d] len = %lld\n",
435 					   real, current->id, len));
436 
437 				if ((real + len) > offset) {
438 					/* within the bounds of the current stream */
439 					d(fprintf (stderr, "offset within bounds of stream[%d]\n",
440 						   current->id));
441 					break;
442 				} else {
443 					d(fprintf (stderr, "not within bounds of stream[%d]\n",
444 						   current->id));
445 					current->position = len;
446 					real += len;
447 
448 					current = current->next;
449 					if (current == NULL) {
450 						d(fprintf (stderr, "ran out of streams, failed\n"));
451 						return -1;
452 					}
453 
454 					d(fprintf (stderr, "advanced to stream[%d]...\n", current->id));
455 
456 					if (g_mime_stream_reset (current->stream) == -1)
457 						return -1;
458 
459 					current->position = 0;
460 				}
461 			} while (1);
462 
463 			/* FIXME: another seek check... probably can skip this */
464 			off = current->stream->bound_start + (offset - real);
465 			if (g_mime_stream_seek (current->stream, off, GMIME_STREAM_SEEK_SET) == -1)
466 				return -1;
467 
468 			d(fprintf (stderr, "setting cur position of stream[%d] to %ld\n",
469 				   current->id, offset - real));
470 			current->position = offset - real;
471 		}
472 
473 		break;
474 	case GMIME_STREAM_SEEK_CUR:
475 		if (offset == 0)
476 			return stream->position;
477 
478 		/* calculate offset relative to the beginning of the stream */
479 		offset = stream->position + offset;
480 		goto seek_set;
481 		break;
482 	case GMIME_STREAM_SEEK_END:
483 		if (offset > 0)
484 			return -1;
485 
486 		/* calculate the offset of the end of the stream */
487 		n = cat->sources;
488 		real = stream->bound_start;
489 		while (n != NULL) {
490 			if ((len = g_mime_stream_length (n->stream)) == -1)
491 				return -1;
492 
493 			real += len;
494 			n = n->next;
495 		}
496 
497 		/* calculate offset relative to the beginning of the stream */
498 		offset = real + offset;
499 		goto seek_set;
500 		break;
501 	default:
502 		g_assert_not_reached ();
503 		return -1;
504 	}
505 
506 	d(fprintf (stderr, "setting stream->offset to %ld and current stream to %d\n",
507 		   offset, current->id));
508 
509 	stream->position = offset;
510 	cat->current = current;
511 
512 	/* reset all following streams */
513 	n = current->next;
514 	while (n != NULL) {
515 		if (g_mime_stream_reset (n->stream) == -1)
516 			return -1;
517 		n->position = 0;
518 		n = n->next;
519 	}
520 
521 	return offset;
522 }
523 
524 static gint64
stream_tell(GMimeStream * stream)525 stream_tell (GMimeStream *stream)
526 {
527 	return stream->position;
528 }
529 
530 static gint64
stream_length(GMimeStream * stream)531 stream_length (GMimeStream *stream)
532 {
533 	GMimeStreamCat *cat = GMIME_STREAM_CAT (stream);
534 	gint64 len, total = 0;
535 	struct _cat_node *n;
536 
537 	if (stream->bound_end != -1)
538 		return stream->bound_end - stream->bound_start;
539 
540 	n = cat->sources;
541 	while (n != NULL) {
542 		if ((len = g_mime_stream_length (n->stream)) == -1)
543 			return -1;
544 
545 		total += len;
546 		n = n->next;
547 	}
548 
549 	return total;
550 }
551 
552 struct _sub_node {
553 	struct _sub_node *next;
554 	GMimeStream *stream;
555 	gint64 start, end;
556 };
557 
558 static GMimeStream *
stream_substream(GMimeStream * stream,gint64 start,gint64 end)559 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
560 {
561 	GMimeStreamCat *cat = (GMimeStreamCat *) stream;
562 	struct _sub_node *streams, *tail, *s;
563 	gint64 offset = 0, subend = 0;
564 	GMimeStream *substream;
565 	struct _cat_node *n;
566 	gint64 len;
567 
568 	d(fprintf (stderr, "GMimeStreamCat::substream (%p, %ld, %ld)\n", stream, start, end));
569 
570 	/* find the first source stream that contains data we're interested in... */
571 	n = cat->sources;
572 	while (offset < start && n != NULL) {
573 		if (n->stream->bound_end == -1) {
574 			if ((len = g_mime_stream_length (n->stream)) == -1)
575 				return NULL;
576 		} else {
577 			len = n->stream->bound_end - n->stream->bound_start;
578 		}
579 
580 		if ((offset + len) > start)
581 			break;
582 
583 		if (end != -1 && (offset + len) >= end)
584 			break;
585 
586 		offset += len;
587 
588 		n = n->next;
589 	}
590 
591 	if (n == NULL)
592 		return NULL;
593 
594 	d(fprintf (stderr, "stream[%d] is the first stream containing data we want\n", n->id));
595 
596 	streams = NULL;
597 	tail = (struct _sub_node *) &streams;
598 
599 	do {
600 		s = g_new (struct _sub_node, 1);
601 		s->next = NULL;
602 		s->stream = n->stream;
603 		tail->next = s;
604 		tail = s;
605 
606 		s->start = n->stream->bound_start;
607 		if (n == cat->sources)
608 			s->start += start;
609 		else if (offset < start)
610 			s->start += (start - offset);
611 
612 		d(fprintf (stderr, "added stream[%d] to our list\n", n->id));
613 
614 		if (n->stream->bound_end == -1) {
615 			if ((len = g_mime_stream_length (n->stream)) == -1)
616 				goto error;
617 		} else {
618 			len = n->stream->bound_end - n->stream->bound_start;
619 		}
620 
621 		d(fprintf (stderr, "stream[%d]: len = %ld, offset of beginning of stream is %ld\n",
622 			   n->id, len, offset));
623 
624 		if (end != -1 && (end <= (offset + len))) {
625 			d(fprintf (stderr, "stream[%d]: requested end <= offset + len\n", n->id));
626 			s->end = n->stream->bound_start + (end - offset);
627 			d(fprintf (stderr, "stream[%d]: s->start = %ld, s->end = %ld; break\n",
628 				   n->id, s->start, s->end));
629 			subend += (end - offset);
630 			break;
631 		} else {
632 			s->end = n->stream->bound_start + len;
633 			d(fprintf (stderr, "stream[%d]: s->start = %ld, s->end = %ld\n",
634 				   n->id, s->start, s->end));
635 		}
636 
637 		subend += (s->end - s->start);
638 		offset += len;
639 
640 		n = n->next;
641 	} while (n != NULL);
642 
643 	d(fprintf (stderr, "returning a substream containing multiple source streams\n"));
644 	cat = g_object_newv (GMIME_TYPE_STREAM_CAT, 0, NULL);
645 	/* Note: we could pass -1 as bound_end, it should Just
646 	 * Work(tm) but setting absolute bounds is kinda
647 	 * nice... */
648 	g_mime_stream_construct (GMIME_STREAM (cat), 0, subend);
649 
650 	while (streams != NULL) {
651 		s = streams->next;
652 		substream = g_mime_stream_substream (streams->stream, streams->start, streams->end);
653 		g_mime_stream_cat_add_source (cat, substream);
654 		g_object_unref (substream);
655 		g_free (streams);
656 		streams = s;
657 	}
658 
659 	substream = (GMimeStream *) cat;
660 
661 	return substream;
662 
663  error:
664 
665 	while (streams != NULL) {
666 		s = streams->next;
667 		g_free (streams);
668 		streams = s;
669 	}
670 
671 	return NULL;
672 }
673 
674 
675 /**
676  * g_mime_stream_cat_new:
677  *
678  * Creates a new #GMimeStreamCat object.
679  *
680  * Returns: a new #GMimeStreamCat stream.
681  **/
682 GMimeStream *
g_mime_stream_cat_new(void)683 g_mime_stream_cat_new (void)
684 {
685 	GMimeStream *stream;
686 
687 	stream = g_object_newv (GMIME_TYPE_STREAM_CAT, 0, NULL);
688 	g_mime_stream_construct (stream, 0, -1);
689 
690 	return stream;
691 }
692 
693 
694 /**
695  * g_mime_stream_cat_add_source:
696  * @cat: a #GMimeStreamCat
697  * @source: a source stream
698  *
699  * Adds the @source stream to the @cat.
700  *
701  * Returns: %0 on success or %-1 on fail.
702  **/
703 int
g_mime_stream_cat_add_source(GMimeStreamCat * cat,GMimeStream * source)704 g_mime_stream_cat_add_source (GMimeStreamCat *cat, GMimeStream *source)
705 {
706 	struct _cat_node *node, *n;
707 
708 	g_return_val_if_fail (GMIME_IS_STREAM_CAT (cat), -1);
709 	g_return_val_if_fail (GMIME_IS_STREAM (source), -1);
710 
711 	node = g_new (struct _cat_node, 1);
712 	node->next = NULL;
713 	node->stream = source;
714 	g_object_ref (source);
715 	node->position = 0;
716 
717 	n = cat->sources;
718 	while (n && n->next)
719 		n = n->next;
720 
721 	if (n == NULL) {
722 		cat->sources = node;
723 		node->id = 0;
724 	} else {
725 		node->id = n->id + 1;
726 		n->next = node;
727 	}
728 
729 	if (!cat->current)
730 		cat->current = node;
731 
732 	return 0;
733 }
734