bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen#include "lib.h"
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen#include "buffer.h"
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen#include "base64.h"
1c6f6f5bef70f16546b3bc8f4cd5f93f373e82a2Timo Sirainen#include "hex-binary.h"
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen#include "istream-private.h"
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen#include "istream-base64.h"
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstruct base64_decoder_istream {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen struct istream_private istream;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen};
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstatic int i_stream_read_parent(struct istream_private *stream)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen{
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen size_t size;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen ssize_t ret;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen size = i_stream_get_data_size(stream->parent);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (size >= 4)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return 1;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen /* we have less than one base64 block.
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen see if there is more data available. */
bcd286622779a93f809b11993db0550f8c7cc9b5Timo Sirainen ret = i_stream_read_memarea(stream->parent);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (ret <= 0) {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->istream.stream_errno = stream->parent->stream_errno;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->istream.eof = stream->parent->eof;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return ret;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen }
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen size = i_stream_get_data_size(stream->parent);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_assert(size != 0);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return 1;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen}
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstatic int
3700eab7818bcf8a68424c418485ae4e8770865dTimo Siraineni_stream_base64_try_decode_block(struct base64_decoder_istream *bstream)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen{
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen struct istream_private *stream = &bstream->istream;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen const unsigned char *data;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen size_t size, avail, buffer_avail, pos;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen buffer_t buf;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen data = i_stream_get_data(stream->parent, &size);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (size == 0)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return 0;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_stream_try_alloc(stream, (size+3)/4*3, &avail);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen buffer_avail = stream->buffer_size - stream->pos;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if ((size + 3) / 4 * 3 > buffer_avail) {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen /* can't fit everything to destination buffer.
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen write as much as we can. */
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen size = (buffer_avail / 3) * 4;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (size == 0)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return -2;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen }
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3281669db44d09a087a203201248abbc81b3cc1aTimo Sirainen buffer_create_from_data(&buf, stream->w_buffer + stream->pos,
3281669db44d09a087a203201248abbc81b3cc1aTimo Sirainen buffer_avail);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (base64_decode(data, size, &pos, &buf) < 0) {
1c6f6f5bef70f16546b3bc8f4cd5f93f373e82a2Timo Sirainen io_stream_set_error(&stream->iostream,
1c6f6f5bef70f16546b3bc8f4cd5f93f373e82a2Timo Sirainen "Invalid base64 data: 0x%s",
098319673eec04a1721b00f491992a784088abb8Timo Sirainen binary_to_hex(data+pos, I_MIN(size-pos, 8)));
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->istream.stream_errno = EINVAL;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return -1;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen }
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->pos += buf.used;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_stream_skip(stream->parent, pos);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return pos > 0 ? 1 : 0;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen}
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainenstatic void i_stream_base64_last_partial_block(struct istream_private *stream)
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen{
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen const unsigned char *data;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen size_t i, size;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen /* base64 input with a partial block */
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen data = i_stream_get_data(stream->parent, &size);
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen for (i = 0; i < size; i++) {
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen if (!base64_is_valid_char(data[i]))
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen break;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen }
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen if (i == size) {
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen io_stream_set_error(&stream->iostream,
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen "base64 input ends with a partial block: 0x%s",
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen binary_to_hex(data, size));
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen stream->istream.stream_errno = EPIPE;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen } else {
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen io_stream_set_error(&stream->iostream,
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen "Invalid base64 data: 0x%s",
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen binary_to_hex(data, size));
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen stream->istream.stream_errno = EINVAL;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen }
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen}
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstatic ssize_t i_stream_base64_decoder_read(struct istream_private *stream)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen{
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen struct base64_decoder_istream *bstream =
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen (struct base64_decoder_istream *)stream;
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen size_t pre_count, post_count;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen int ret;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen do {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen ret = i_stream_read_parent(stream);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (ret <= 0) {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (ret < 0 && stream->istream.stream_errno == 0 &&
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen i_stream_get_data_size(stream->parent) > 0)
e0bd5e662367e8c53c65ee3ad1252dd9469784cfTimo Sirainen i_stream_base64_last_partial_block(stream);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return ret;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen }
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen /* encode as many blocks as fits into destination buffer */
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen pre_count = stream->pos - stream->skip;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen while ((ret = i_stream_base64_try_decode_block(bstream)) > 0) ;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen post_count = stream->pos - stream->skip;
36a50bc13192383ee374be3c68c18842aa597f62Timo Sirainen } while (ret == 0 && pre_count == post_count);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
13984f1ae4258317236228de92adc1481ae4fc40Timo Sirainen if (ret < 0 && pre_count == post_count)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return ret;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_assert(post_count > pre_count);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return post_count - pre_count;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen}
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstatic void
3700eab7818bcf8a68424c418485ae4e8770865dTimo Siraineni_stream_base64_decoder_seek(struct istream_private *stream,
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen uoff_t v_offset, bool mark)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen{
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen if (v_offset < stream->istream.v_offset) {
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen /* seeking backwards - go back to beginning and seek
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen forward from there. */
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->parent_expected_offset = stream->parent_start_offset;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->skip = stream->pos = 0;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen stream->istream.v_offset = 0;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_stream_seek(stream->parent, 0);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen }
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen i_stream_default_seek_nonseekable(stream, v_offset, mark);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen}
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainenstruct istream *
3700eab7818bcf8a68424c418485ae4e8770865dTimo Siraineni_stream_create_base64_decoder(struct istream *input)
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen{
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen struct base64_decoder_istream *bstream;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream = i_new(struct base64_decoder_istream, 1);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.read = i_stream_base64_decoder_read;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.seek = i_stream_base64_decoder_seek;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.istream.readable_fd = FALSE;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.istream.blocking = input->blocking;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen bstream->istream.istream.seekable = input->seekable;
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen return i_stream_create(&bstream->istream, input,
2974dca6be5120e49279f06c8aa952e5fac56048Timo Sirainen i_stream_get_fd(input), 0);
3700eab7818bcf8a68424c418485ae4e8770865dTimo Sirainen}