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