cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi#include "lib.h"
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi#include "istream-private.h"
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi#include "istream-metawrap.h"
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi#define METAWRAP_MAX_METADATA_LINE_LEN 8192
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomistruct metawrap_istream {
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi struct istream_private istream;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi metawrap_callback_t *callback;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi void *context;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi uoff_t start_offset, pending_seek;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi bool in_metadata;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi};
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomistatic int metadata_header_read(struct metawrap_istream *mstream)
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi{
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi char *line, *p;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi while ((line = i_stream_read_next_line(mstream->istream.parent)) != NULL) {
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi if (*line == '\0') {
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi mstream->callback(NULL, NULL, mstream->context);
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi return 1;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi }
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi p = strchr(line, ':');
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi if (p == NULL) {
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi io_stream_set_error(&mstream->istream.iostream,
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi "Metadata header line is missing ':' at offset %"PRIuUOFF_T,
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi mstream->istream.istream.v_offset);
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi mstream->istream.istream.stream_errno = EINVAL;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi return -1;
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi }
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi *p++ = '\0';
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi mstream->callback(line, p, mstream->context);
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi }
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi if (mstream->istream.parent->eof) {
cca227d2929386ee11e4aa23464681ef35eb9679Aki Tuomi if (mstream->istream.parent->stream_errno != 0) {
mstream->istream.istream.stream_errno =
mstream->istream.parent->stream_errno;
} else {
io_stream_set_error(&mstream->istream.iostream,
"Metadata header is missing ending line at offset %"PRIuUOFF_T,
mstream->istream.istream.v_offset);
mstream->istream.istream.stream_errno = EPIPE;
return -1;
}
mstream->istream.istream.eof = TRUE;
return -1;
}
i_assert(!mstream->istream.parent->blocking);
return 0;
}
static ssize_t i_stream_metawrap_read(struct istream_private *stream)
{
struct metawrap_istream *mstream = (struct metawrap_istream *)stream;
int ret;
i_stream_seek(stream->parent, mstream->start_offset +
stream->istream.v_offset);
if (mstream->in_metadata) {
size_t prev_max_size = i_stream_get_max_buffer_size(stream->parent);
i_stream_set_max_buffer_size(stream->parent, METAWRAP_MAX_METADATA_LINE_LEN);
ret = metadata_header_read(mstream);
i_stream_set_max_buffer_size(stream->parent, prev_max_size);
i_assert(stream->istream.v_offset == 0);
mstream->start_offset = stream->parent->v_offset;
if (ret <= 0)
return ret;
/* this stream is kind of silently skipping over the metadata */
stream->start_offset += mstream->start_offset;
mstream->in_metadata = FALSE;
if (mstream->pending_seek != 0) {
i_stream_seek(&stream->istream, mstream->pending_seek);
return i_stream_read_memarea(&stream->istream);
}
}
/* after metadata header it's all just passthrough */
return i_stream_read_copy_from_parent(&stream->istream);
}
static void
i_stream_metawrap_seek(struct istream_private *stream,
uoff_t v_offset, bool mark ATTR_UNUSED)
{
struct metawrap_istream *mstream = (struct metawrap_istream *)stream;
if (!mstream->in_metadata) {
/* already read through metadata. we can skip directly. */
stream->istream.v_offset = v_offset;
mstream->pending_seek = 0;
} else {
/* we need to read through the metadata first */
mstream->pending_seek = v_offset;
stream->istream.v_offset = 0;
}
stream->skip = stream->pos = 0;
}
static int i_stream_metawrap_stat(struct istream_private *stream, bool exact)
{
struct metawrap_istream *mstream = (struct metawrap_istream *)stream;
const struct stat *st;
int ret;
if (i_stream_stat(stream->parent, exact, &st) < 0) {
stream->istream.stream_errno = stream->parent->stream_errno;
return -1;
}
stream->statbuf = *st;
if (mstream->in_metadata) {
ret = i_stream_read_memarea(&stream->istream);
if (ret < 0 && stream->istream.stream_errno != 0)
return -1;
if (ret == 0) {
stream->statbuf.st_size = -1;
return 0;
}
}
i_assert((uoff_t)stream->statbuf.st_size >= mstream->start_offset);
stream->statbuf.st_size -= mstream->start_offset;
return 0;
}
struct istream *
i_stream_create_metawrap(struct istream *input,
metawrap_callback_t *callback, void *context)
{
struct metawrap_istream *mstream;
mstream = i_new(struct metawrap_istream, 1);
mstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
mstream->istream.read = i_stream_metawrap_read;
mstream->istream.seek = i_stream_metawrap_seek;
mstream->istream.stat = input->seekable ? i_stream_metawrap_stat : NULL;
mstream->istream.istream.readable_fd = input->readable_fd;
mstream->istream.istream.blocking = input->blocking;
mstream->istream.istream.seekable = input->seekable;
mstream->in_metadata = TRUE;
mstream->callback = callback;
mstream->context = context;
return i_stream_create(&mstream->istream, input,
i_stream_get_fd(input), 0);
}