1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2000-2012 Jeffrey Stedfast
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.
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.
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
29 #include "gmime-stream-buffer.h"
32 * SECTION: gmime-stream-buffer
33 * @title: GMimeStreamBuffer
34 * @short_description: A buffered stream
35 * @see_also: #GMimeStream
37 * A #GMimeStreamBuffer can be used on top of any other type of stream
38 * and has 3 modes: block reads, block writes, and cached reads. Block
39 * reads are especially useful if you will be making a lot of small
40 * reads from a stream that accesses the file system. Block writes are
41 * useful for very much the same reason. The final mode, cached reads,
42 * can become memory intensive but can be very helpful when inheriting
43 * from a stream that does not support seeking (Note: this mode is the
44 * least tested so be careful using it).
47 #define BLOCK_BUFFER_LEN 4096
48 #define BUFFER_GROW_SIZE 1024 /* should this also be 4k? */
50 static void g_mime_stream_buffer_class_init (GMimeStreamBufferClass *klass);
51 static void g_mime_stream_buffer_init (GMimeStreamBuffer *stream, GMimeStreamBufferClass *klass);
52 static void g_mime_stream_buffer_finalize (GObject *object);
54 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
55 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
56 static int stream_flush (GMimeStream *stream);
57 static int stream_close (GMimeStream *stream);
58 static gboolean stream_eos (GMimeStream *stream);
59 static int stream_reset (GMimeStream *stream);
60 static gint64 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence);
61 static gint64 stream_tell (GMimeStream *stream);
62 static gint64 stream_length (GMimeStream *stream);
63 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
66 static GMimeStreamClass *parent_class = NULL;
70 g_mime_stream_buffer_get_type (void)
72 static GType type = 0;
75 static const GTypeInfo info = {
76 sizeof (GMimeStreamBufferClass),
77 NULL, /* base_class_init */
78 NULL, /* base_class_finalize */
79 (GClassInitFunc) g_mime_stream_buffer_class_init,
80 NULL, /* class_finalize */
81 NULL, /* class_data */
82 sizeof (GMimeStreamBuffer),
84 (GInstanceInitFunc) g_mime_stream_buffer_init,
87 type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamBuffer", &info, 0);
95 g_mime_stream_buffer_class_init (GMimeStreamBufferClass *klass)
97 GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
98 GObjectClass *object_class = G_OBJECT_CLASS (klass);
100 parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
102 object_class->finalize = g_mime_stream_buffer_finalize;
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;
117 g_mime_stream_buffer_init (GMimeStreamBuffer *stream, GMimeStreamBufferClass *klass)
119 stream->source = NULL;
120 stream->buffer = NULL;
121 stream->bufptr = NULL;
122 stream->bufend = NULL;
128 g_mime_stream_buffer_finalize (GObject *object)
130 GMimeStreamBuffer *stream = (GMimeStreamBuffer *) object;
133 g_object_unref (stream->source);
135 g_free (stream->buffer);
137 G_OBJECT_CLASS (parent_class)->finalize (object);
142 stream_read (GMimeStream *stream, char *buf, size_t len)
144 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
145 size_t buflen, offset;
146 ssize_t n, nread = 0;
148 if (buffer->source == NULL) {
153 switch (buffer->mode) {
154 case GMIME_STREAM_BUFFER_BLOCK_READ:
156 /* consume what we can from any pre-buffered data we have left */
157 if ((n = MIN (buffer->buflen, len)) > 0) {
158 memcpy (buf + nread, buffer->bufptr, n);
165 if (len >= BLOCK_BUFFER_LEN) {
166 /* bypass intermediate buffer, read straight from disk */
167 buffer->bufptr = buffer->buffer;
168 if ((n = g_mime_stream_read (buffer->source, buf + nread, len)) > 0) {
174 } else if (len > 0) {
175 /* buffer more data */
176 if ((n = g_mime_stream_read (buffer->source, buffer->buffer, BLOCK_BUFFER_LEN)) > 0)
179 buffer->bufptr = buffer->buffer;
190 case GMIME_STREAM_BUFFER_CACHE_READ:
192 buflen = (size_t) (buffer->bufend - buffer->bufptr);
193 if ((n = MIN (buflen, len)) > 0) {
194 memcpy (buf + nread, buffer->bufptr, n);
201 /* we need to read more data... */
202 offset = buffer->bufptr - buffer->buffer;
204 buffer->buflen = buffer->bufend - buffer->buffer + MAX (BUFFER_GROW_SIZE, len);
205 buffer->buffer = g_realloc (buffer->buffer, buffer->buflen);
206 buffer->bufend = buffer->buffer + buffer->buflen;
207 buffer->bufptr = buffer->buffer + offset;
209 n = g_mime_stream_read (buffer->source, buffer->bufptr,
210 buffer->bufend - buffer->bufptr);
212 buffer->bufend = n > 0 ? buffer->bufptr + n : buffer->bufptr;
224 if ((nread = g_mime_stream_read (buffer->source, buf, len)) == -1)
230 stream->position += nread;
236 stream_write (GMimeStream *stream, const char *buf, size_t len)
238 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
239 GMimeStream *source = buffer->source;
240 ssize_t n, nwritten = 0;
243 if (buffer->source == NULL) {
248 switch (buffer->mode) {
249 case GMIME_STREAM_BUFFER_BLOCK_WRITE:
251 n = MIN (BLOCK_BUFFER_LEN - buffer->buflen, left);
252 if (buffer->buflen > 0 || n < BLOCK_BUFFER_LEN) {
253 /* add the data to our pending write buffer */
254 memcpy (buffer->bufptr, buf + nwritten, n);
261 if (buffer->buflen == BLOCK_BUFFER_LEN) {
262 /* flush our buffer... */
263 n = g_mime_stream_write (source, buffer->buffer, BLOCK_BUFFER_LEN);
264 if (n == BLOCK_BUFFER_LEN) {
265 /* wrote everything... */
266 buffer->bufptr = buffer->buffer;
269 /* still have buffered data left... */
270 memmove (buffer->buffer, buffer->buffer + n, BLOCK_BUFFER_LEN - n);
273 } else if (n == -1) {
281 if (buffer->buflen == 0 && left >= BLOCK_BUFFER_LEN) {
282 while (left >= BLOCK_BUFFER_LEN) {
283 if ((n = g_mime_stream_write (source, buf + nwritten, BLOCK_BUFFER_LEN)) == -1) {
294 if (left >= BLOCK_BUFFER_LEN)
300 if ((nwritten = g_mime_stream_write (source, buf, len)) == -1)
306 stream->position += nwritten;
312 stream_flush (GMimeStream *stream)
314 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
316 if (buffer->mode == GMIME_STREAM_BUFFER_BLOCK_WRITE && buffer->buflen > 0) {
319 written = g_mime_stream_write (buffer->source, buffer->buffer, buffer->buflen);
321 memmove (buffer->buffer, buffer->buffer + written, buffer->buflen - written);
322 buffer->bufptr -= written;
323 buffer->buflen -= written;
326 if (buffer->buflen != 0)
330 return g_mime_stream_flush (buffer->source);
334 stream_close (GMimeStream *stream)
336 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
338 if (buffer->source == NULL)
341 g_mime_stream_close (buffer->source);
342 g_object_unref (buffer->source);
343 buffer->source = NULL;
345 g_free (buffer->buffer);
346 buffer->buffer = NULL;
347 buffer->bufptr = NULL;
348 buffer->bufend = NULL;
355 stream_eos (GMimeStream *stream)
357 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
359 if (buffer->source == NULL)
362 if (!g_mime_stream_eos (buffer->source))
365 switch (buffer->mode) {
366 case GMIME_STREAM_BUFFER_BLOCK_READ:
367 return buffer->buflen == 0;
368 case GMIME_STREAM_BUFFER_CACHE_READ:
369 return buffer->bufptr == buffer->bufend;
378 stream_reset (GMimeStream *stream)
380 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
382 if (buffer->source == NULL) {
387 switch (buffer->mode) {
388 case GMIME_STREAM_BUFFER_BLOCK_READ:
389 case GMIME_STREAM_BUFFER_BLOCK_WRITE:
390 if (g_mime_stream_reset (buffer->source) == -1)
393 buffer->bufptr = buffer->buffer;
396 case GMIME_STREAM_BUFFER_CACHE_READ:
397 buffer->bufptr = buffer->buffer;
400 if (g_mime_stream_reset (buffer->source) == -1)
410 stream_seek_block_read (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
412 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
415 /* convert all seeks into a relative seek (aka SEEK_CUR) */
417 case GMIME_STREAM_SEEK_CUR:
420 case GMIME_STREAM_SEEK_SET:
421 if (stream->position == offset)
422 return stream->position;
425 /* not allowed to seek to a negative position */
430 offset -= stream->position;
432 case GMIME_STREAM_SEEK_END:
433 if (stream->bound_end == -1) {
434 /* gotta do this the slow way */
435 if ((real = g_mime_stream_seek (buffer->source, offset, GMIME_STREAM_SEEK_END) == -1))
438 stream->position = real;
440 buffer->bufptr = buffer->buffer;
447 /* not allowed to seek past bound_end */
452 offset += stream->bound_end;
455 /* invalid whence argument */
460 /* now that offset is relative to our current position... */
463 return stream->position;
465 if ((offset < 0 && offset >= (buffer->buffer - buffer->bufptr))
466 || (offset > 0 && offset <= buffer->buflen)) {
467 /* the position is within our pre-buffered region */
468 stream->position += offset;
469 buffer->bufptr += (size_t) offset;
470 buffer->buflen -= (size_t) offset;
472 return stream->position;
475 /* we are now forced to do an actual seek */
476 offset += stream->position;
477 if ((real = g_mime_stream_seek (buffer->source, offset, GMIME_STREAM_SEEK_SET)) == -1)
480 stream->position = real;
482 buffer->bufptr = buffer->buffer;
489 stream_seek_cache_read (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
491 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
492 gint64 buflen, len, total = 0;
497 case GMIME_STREAM_SEEK_SET:
500 case GMIME_STREAM_SEEK_CUR:
501 real = stream->position + offset;
503 case GMIME_STREAM_SEEK_END:
504 if (stream->bound_end == -1) {
505 /* we have to do a real seek because the end boundary is unknown */
506 if ((real = g_mime_stream_seek (buffer->source, offset, whence)) == -1)
509 if (real < stream->bound_start) {
510 /* seek offset out of bounds */
515 real = stream->bound_end + offset;
516 if (real > stream->bound_end || real < stream->bound_start) {
517 /* seek offset out of bounds */
524 /* invalid whence argument */
529 if (real > stream->position) {
530 /* buffer any data between position and real */
531 len = real - (stream->bound_start + (buffer->bufend - buffer->bufptr));
533 if (buffer->bufptr + len <= buffer->bufend) {
534 buffer->bufptr += len;
535 stream->position = real;
539 pos = buffer->bufptr - buffer->buffer;
541 buflen = (buffer->bufend - buffer->buffer) + len;
542 if (buflen < G_MAXSIZE)
543 buffer->buflen = (size_t) buflen;
545 buffer->buflen = G_MAXSIZE;
547 buffer->buffer = g_realloc (buffer->buffer, buffer->buflen);
548 buffer->bufend = buffer->buffer + buffer->buflen;
549 buffer->bufptr = buffer->buffer + pos;
552 nread = g_mime_stream_read (buffer->source, buffer->bufptr,
553 buffer->bufend - buffer->bufptr);
556 buffer->bufptr += nread;
558 } while (nread != -1);
560 buffer->bufend = buffer->bufptr;
562 /* we failed to seek that far so reset our bufptr */
563 buffer->bufptr = buffer->buffer + pos;
567 } else if (real < stream->bound_start) {
568 /* seek offset out of bounds */
572 /* seek our cache pointer backwards */
573 buffer->bufptr = buffer->buffer + (real - stream->bound_start);
576 stream->position = real;
582 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
584 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
587 if (buffer->source == NULL) {
592 switch (buffer->mode) {
593 case GMIME_STREAM_BUFFER_BLOCK_WRITE:
594 if (stream_flush (stream) != 0)
597 if ((real = g_mime_stream_seek (buffer->source, offset, whence)) != -1) {
598 stream->position = real;
603 case GMIME_STREAM_BUFFER_BLOCK_READ:
604 return stream_seek_block_read (stream, offset, whence);
605 case GMIME_STREAM_BUFFER_CACHE_READ:
606 return stream_seek_cache_read (stream, offset, whence);
608 /* invalid whence argument */
615 stream_tell (GMimeStream *stream)
617 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
619 if (buffer->source == NULL) {
624 return stream->position;
628 stream_length (GMimeStream *stream)
630 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
632 if (buffer->source == NULL) {
637 return g_mime_stream_length (buffer->source);
641 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
643 GMimeStreamBuffer *buffer = (GMimeStreamBuffer *) stream;
645 /* FIXME: for cached reads we want to substream ourself rather
646 than substreaming our source because we have to assume that
647 the reason this stream is setup to do cached reads is
648 because the source stream is unseekable. */
650 return GMIME_STREAM_GET_CLASS (buffer->source)->substream (buffer->source, start, end);
655 * g_mime_stream_buffer_new:
656 * @source: source stream
657 * @mode: buffering mode
659 * Creates a new GMimeStreamBuffer object.
661 * Returns: a new buffer stream with source @source and mode @mode.
664 g_mime_stream_buffer_new (GMimeStream *source, GMimeStreamBufferMode mode)
666 GMimeStreamBuffer *buffer;
668 g_return_val_if_fail (GMIME_IS_STREAM (source), NULL);
670 buffer = g_object_newv (GMIME_TYPE_STREAM_BUFFER, 0, NULL);
672 buffer->source = source;
673 g_object_ref (source);
677 switch (buffer->mode) {
678 case GMIME_STREAM_BUFFER_BLOCK_READ:
679 case GMIME_STREAM_BUFFER_BLOCK_WRITE:
680 buffer->buffer = g_malloc (BLOCK_BUFFER_LEN);
681 buffer->bufend = buffer->buffer + BLOCK_BUFFER_LEN;
682 buffer->bufptr = buffer->buffer;
686 buffer->buffer = g_malloc (BUFFER_GROW_SIZE);
687 buffer->bufptr = buffer->buffer;
688 buffer->bufend = buffer->buffer;
689 buffer->buflen = BUFFER_GROW_SIZE;
693 g_mime_stream_construct (GMIME_STREAM (buffer),
697 return GMIME_STREAM (buffer);
702 * g_mime_stream_buffer_gets:
705 * @max: max length of a line
707 * Reads in at most one less than @max characters from @stream and
708 * stores them into the buffer pointed to by @buf. Reading stops after
709 * an EOS or newline ('\n'). If a newline is read, it is stored into
710 * the buffer. A '\0' is stored after the last character in the
713 * Returns: the number of characters read into @buf on success or %-1
717 g_mime_stream_buffer_gets (GMimeStream *stream, char *buf, size_t max)
719 register char *inptr, *outptr;
720 char *inend, *outend;
724 g_return_val_if_fail (GMIME_IS_STREAM (stream), -1);
727 outend = buf + max - 1;
729 if (GMIME_IS_STREAM_BUFFER (stream)) {
730 GMimeStreamBuffer *buffer = GMIME_STREAM_BUFFER (stream);
732 switch (buffer->mode) {
733 case GMIME_STREAM_BUFFER_BLOCK_READ:
734 while (outptr < outend) {
735 inptr = buffer->bufptr;
736 inend = inptr + buffer->buflen;
738 while (outptr < outend && inptr < inend && *inptr != '\n')
739 c = *outptr++ = *inptr++;
741 if (outptr < outend && inptr < inend && c != '\n')
742 c = *outptr++ = *inptr++;
744 buffer->buflen = inend - inptr;
745 buffer->bufptr = inptr;
750 if (buffer->buflen == 0) {
751 /* buffer more data */
752 buffer->bufptr = buffer->buffer;
753 n = g_mime_stream_read (buffer->source, buffer->buffer, BLOCK_BUFFER_LEN);
761 case GMIME_STREAM_BUFFER_CACHE_READ:
762 while (outptr < outend) {
763 inptr = buffer->bufptr;
764 inend = buffer->bufend;
765 while (outptr < outend && inptr < inend && *inptr != '\n')
766 c = *outptr++ = *inptr++;
768 if (outptr < outend && inptr < inend && c != '\n')
769 c = *outptr++ = *inptr++;
771 buffer->bufptr = inptr;
776 if (inptr == inend && outptr < outend) {
777 /* buffer more data */
778 size_t offset = (size_t) (buffer->bufptr - buffer->buffer);
780 buffer->buflen = buffer->bufend - buffer->buffer +
781 MAX (BUFFER_GROW_SIZE, outend - outptr + 1);
782 buffer->buffer = g_realloc (buffer->buffer, buffer->buflen);
783 buffer->bufend = buffer->buffer + buffer->buflen;
784 buffer->bufptr = buffer->buffer + offset;
785 nread = g_mime_stream_read (buffer->source, buffer->bufptr,
786 buffer->bufend - buffer->bufptr);
788 buffer->bufend = nread >= 0 ? buffer->bufptr + nread : buffer->bufptr;
796 goto slow_and_painful;
800 /* increment our stream position pointer */
801 stream->position += (outptr - buf);
803 /* ugh...do it the slow and painful way... */
805 while (outptr < outend && c != '\n' && (nread = g_mime_stream_read (stream, &c, 1)) == 1)
809 if (outptr <= outend) {
810 /* this should always be true unless @max == 0 */
814 return (ssize_t) (outptr - buf);
819 * g_mime_stream_buffer_readln:
821 * @buffer: output buffer
823 * Reads a single line into @buffer.
826 g_mime_stream_buffer_readln (GMimeStream *stream, GByteArray *buffer)
831 g_return_if_fail (GMIME_IS_STREAM (stream));
833 while (!g_mime_stream_eos (stream)) {
834 if ((len = g_mime_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) <= 0)
838 g_byte_array_append (buffer, (unsigned char *) linebuf, len);
840 if (linebuf[len - 1] == '\n')