bool i_stream_try_alloc(struct istream_private *stream, size_t wanted_size, size_t *size_r) { i_assert(wanted_size > 0); if (wanted_size > stream->buffer_size - stream->pos) { if (stream->skip > 0) { /* remove the unused bytes from beginning of buffer */ i_stream_compress(stream); } else if (stream->buffer_size < i_stream_get_max_buffer_size(&stream->istream)) { /* buffer is full - grow it */ i_stream_grow_buffer(stream, I_STREAM_MIN_SIZE); } } *size_r = stream->buffer_size - stream->pos; if (stream->try_alloc_limit > 0 && *size_r > stream->try_alloc_limit) *size_r = stream->try_alloc_limit; return *size_r > 0; }
static ssize_t i_stream_lzma_read(struct istream_private *stream) { struct lzma_istream *zstream = (struct lzma_istream *)stream; const unsigned char *data; uoff_t high_offset; size_t size, out_size; lzma_ret ret; high_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (zstream->eof_offset == high_offset) { i_assert(zstream->high_pos == 0 || zstream->high_pos == stream->pos); stream->istream.eof = TRUE; return -1; } if (stream->pos < zstream->high_pos) { /* we're here because we seeked back within the read buffer. */ ret = zstream->high_pos - stream->pos; stream->pos = zstream->high_pos; zstream->high_pos = 0; if (zstream->eof_offset != (uoff_t)-1) { high_offset = stream->istream.v_offset + (stream->pos - stream->skip); i_assert(zstream->eof_offset == high_offset); stream->istream.eof = TRUE; } return ret; } zstream->high_pos = 0; if (stream->pos + CHUNK_SIZE > stream->buffer_size) { /* try to keep at least CHUNK_SIZE available */ if (!zstream->marked && stream->skip > 0) { /* don't try to keep anything cached if we don't have a seek mark. */ i_stream_compress(stream); } if (stream->buffer_size < i_stream_get_max_buffer_size(&stream->istream)) i_stream_grow_buffer(stream, CHUNK_SIZE); if (stream->pos == stream->buffer_size) { if (stream->skip > 0) { /* lose our buffer cache */ i_stream_compress(stream); } if (stream->pos == stream->buffer_size) return -2; /* buffer full */ } } if (i_stream_read_more(stream->parent, &data, &size) < 0) { if (stream->parent->stream_errno != 0) { stream->istream.stream_errno = stream->parent->stream_errno; } else { i_assert(stream->parent->eof); lzma_stream_end(zstream); stream->istream.eof = TRUE; } return -1; } if (size == 0) { /* no more input */ i_assert(!stream->istream.blocking); return 0; } zstream->strm.next_in = data; zstream->strm.avail_in = size; out_size = stream->buffer_size - stream->pos; zstream->strm.next_out = stream->w_buffer + stream->pos; zstream->strm.avail_out = out_size; ret = lzma_code(&zstream->strm, LZMA_RUN); out_size -= zstream->strm.avail_out; stream->pos += out_size; i_stream_skip(stream->parent, size - zstream->strm.avail_in); switch (ret) { case LZMA_OK: break; case LZMA_DATA_ERROR: case LZMA_BUF_ERROR: lzma_read_error(zstream, "corrupted data"); stream->istream.stream_errno = EINVAL; return -1; case LZMA_FORMAT_ERROR: lzma_read_error(zstream, "wrong magic in header (not xz file?)"); stream->istream.stream_errno = EINVAL; return -1; case LZMA_OPTIONS_ERROR: lzma_read_error(zstream, "Unsupported xz options"); stream->istream.stream_errno = EINVAL; return -1; case LZMA_MEM_ERROR: i_fatal_status(FATAL_OUTOFMEM, "lzma.read(%s): Out of memory", i_stream_get_name(&stream->istream)); case LZMA_STREAM_END: lzma_stream_end(zstream); if (out_size == 0) { stream->istream.eof = TRUE; return -1; } break; default: lzma_read_error(zstream, t_strdup_printf( "lzma_code() failed with %d", ret)); stream->istream.stream_errno = EINVAL; return -1; } if (out_size == 0) { /* read more input */ return i_stream_lzma_read(stream); } return out_size; }
static ssize_t i_stream_zlib_read(struct istream_private *stream) { struct zlib_istream *zstream = (struct zlib_istream *)stream; const unsigned char *data; uoff_t high_offset; size_t size, out_size; int ret; high_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (zstream->eof_offset == high_offset) { i_assert(zstream->high_pos == 0 || zstream->high_pos == stream->pos); if (!zstream->trailer_read) { do { ret = i_stream_zlib_read_trailer(zstream); } while (ret == 0 && stream->istream.blocking); if (ret <= 0) return ret; } if (!zstream->gz || i_stream_is_eof(stream->parent)) { stream->istream.eof = TRUE; return -1; } /* gzip file with concatenated content */ zstream->eof_offset = (uoff_t)-1; zstream->stream_size = (uoff_t)-1; zstream->header_read = FALSE; zstream->trailer_read = FALSE; zstream->crc32 = 0; (void)inflateEnd(&zstream->zs); i_stream_zlib_init(zstream); } if (!zstream->header_read) { do { ret = i_stream_zlib_read_header(stream); } while (ret == 0 && stream->istream.blocking); if (ret <= 0) return ret; zstream->header_read = TRUE; } if (stream->pos < zstream->high_pos) { /* we're here because we seeked back within the read buffer. */ ret = zstream->high_pos - stream->pos; stream->pos = zstream->high_pos; zstream->high_pos = 0; if (zstream->trailer_read) { high_offset = stream->istream.v_offset + (stream->pos - stream->skip); i_assert(zstream->eof_offset == high_offset); stream->istream.eof = TRUE; } return ret; } zstream->high_pos = 0; if (stream->pos + CHUNK_SIZE > stream->buffer_size) { /* try to keep at least CHUNK_SIZE available */ if (!zstream->marked && stream->skip > 0) { /* don't try to keep anything cached if we don't have a seek mark. */ i_stream_compress(stream); } if (stream->buffer_size < i_stream_get_max_buffer_size(&stream->istream)) i_stream_grow_buffer(stream, CHUNK_SIZE); if (stream->pos == stream->buffer_size) { if (stream->skip > 0) { /* lose our buffer cache */ i_stream_compress(stream); } if (stream->pos == stream->buffer_size) return -2; /* buffer full */ } } if (i_stream_read_more(stream->parent, &data, &size) < 0) { if (stream->parent->stream_errno != 0) { stream->istream.stream_errno = stream->parent->stream_errno; } else { i_assert(stream->parent->eof); zlib_read_error(zstream, "unexpected EOF"); stream->istream.stream_errno = EPIPE; } return -1; } if (size == 0) { /* no more input */ i_assert(!stream->istream.blocking); return 0; } zstream->zs.next_in = (void *)data; zstream->zs.avail_in = size; out_size = stream->buffer_size - stream->pos; zstream->zs.next_out = stream->w_buffer + stream->pos; zstream->zs.avail_out = out_size; ret = inflate(&zstream->zs, Z_SYNC_FLUSH); out_size -= zstream->zs.avail_out; zstream->crc32 = crc32_data_more(zstream->crc32, stream->w_buffer + stream->pos, out_size); stream->pos += out_size; i_stream_skip(stream->parent, size - zstream->zs.avail_in); switch (ret) { case Z_OK: break; case Z_NEED_DICT: zlib_read_error(zstream, "can't read file without dict"); stream->istream.stream_errno = EINVAL; return -1; case Z_DATA_ERROR: zlib_read_error(zstream, "corrupted data"); stream->istream.stream_errno = EINVAL; return -1; case Z_MEM_ERROR: i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory", i_stream_get_name(&stream->istream)); case Z_STREAM_END: zstream->eof_offset = stream->istream.v_offset + (stream->pos - stream->skip); zstream->stream_size = zstream->eof_offset; zstream->zs.avail_in = 0; if (!zstream->trailer_read) { /* try to read and verify the trailer, we might not be called again. */ if (i_stream_zlib_read_trailer(zstream) < 0) return -1; } break; default: i_fatal("inflate() failed with %d", ret); } if (out_size == 0) { /* read more input */ return i_stream_zlib_read(stream); } return out_size; }
static ssize_t i_stream_lz4_read(struct istream_private *stream) { struct lz4_istream *zstream = (struct lz4_istream *)stream; const unsigned char *data; size_t size, max_size; int ret; if (!zstream->header_read) { if ((ret = i_stream_lz4_read_header(zstream)) <= 0) return ret; zstream->header_read = TRUE; } if (zstream->chunk_left == 0) { ret = i_stream_read_data(stream->parent, &data, &size, IOSTREAM_LZ4_CHUNK_PREFIX_LEN); if (ret < 0) { stream->istream.stream_errno = stream->parent->stream_errno; if (stream->istream.stream_errno == 0) { stream->istream.eof = TRUE; zstream->stream_size = stream->istream.v_offset + stream->pos - stream->skip; } return ret; } if (ret == 0 && !stream->istream.eof) return 0; zstream->chunk_size = zstream->chunk_left = ((uint32_t)data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; if (zstream->chunk_size == 0 || zstream->chunk_size > ISTREAM_LZ4_CHUNK_SIZE) { lz4_read_error(zstream, t_strdup_printf( "invalid lz4 chunk size: %u", zstream->chunk_size)); stream->istream.stream_errno = EINVAL; return -1; } i_stream_skip(stream->parent, IOSTREAM_LZ4_CHUNK_PREFIX_LEN); buffer_set_used_size(zstream->chunk_buf, 0); } /* read the whole compressed chunk into memory */ while (zstream->chunk_left > 0 && (ret = i_stream_read_data(zstream->istream.parent, &data, &size, 0)) > 0) { if (size > zstream->chunk_left) size = zstream->chunk_left; buffer_append(zstream->chunk_buf, data, size); i_stream_skip(zstream->istream.parent, size); zstream->chunk_left -= size; } if (zstream->chunk_left > 0) { if (ret == -1 && zstream->istream.parent->stream_errno == 0) { lz4_read_error(zstream, "truncated lz4 chunk"); stream->istream.stream_errno = EINVAL; return -1; } zstream->istream.istream.stream_errno = zstream->istream.parent->stream_errno; return ret; } /* if we already have max_buffer_size amount of data, fail here */ i_stream_compress(stream); if (stream->pos >= stream->max_buffer_size) return -2; /* allocate enough space for the old data and the new decompressed chunk. we don't know the original compressed size, so just allocate the max amount of memory. */ max_size = stream->pos + zstream->max_uncompressed_chunk_size; if (stream->buffer_size < max_size) { stream->w_buffer = i_realloc(stream->w_buffer, stream->buffer_size, max_size); stream->buffer_size = max_size; stream->buffer = stream->w_buffer; } ret = LZ4_decompress_safe(zstream->chunk_buf->data, (void *)(stream->w_buffer + stream->pos), zstream->chunk_buf->used, stream->buffer_size - stream->pos); i_assert(ret <= (int)zstream->max_uncompressed_chunk_size); if (ret < 0) { lz4_read_error(zstream, "corrupted lz4 chunk"); stream->istream.stream_errno = EINVAL; return -1; } i_assert(ret > 0); stream->pos += ret; i_assert(stream->pos <= stream->buffer_size); return ret; }
static ssize_t i_stream_bzlib_read(struct istream_private *stream) { struct bzlib_istream *zstream = (struct bzlib_istream *)stream; const unsigned char *data; uoff_t high_offset; size_t size; int ret; high_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (zstream->eof_offset == high_offset) { i_assert(zstream->high_pos == 0 || zstream->high_pos == stream->pos); stream->istream.eof = TRUE; return -1; } if (stream->pos < zstream->high_pos) { /* we're here because we seeked back within the read buffer. */ ret = zstream->high_pos - stream->pos; stream->pos = zstream->high_pos; zstream->high_pos = 0; if (zstream->eof_offset != (uoff_t)-1) { high_offset = stream->istream.v_offset + (stream->pos - stream->skip); i_assert(zstream->eof_offset == high_offset); stream->istream.eof = TRUE; } return ret; } zstream->high_pos = 0; if (stream->pos + CHUNK_SIZE > stream->buffer_size) { /* try to keep at least CHUNK_SIZE available */ if (!zstream->marked && stream->skip > 0) { /* don't try to keep anything cached if we don't have a seek mark. */ i_stream_compress(stream); } if (stream->max_buffer_size == 0 || stream->buffer_size < stream->max_buffer_size) i_stream_grow_buffer(stream, CHUNK_SIZE); if (stream->pos == stream->buffer_size) { if (stream->skip > 0) { /* lose our buffer cache */ i_stream_compress(stream); } if (stream->pos == stream->buffer_size) return -2; /* buffer full */ } } if (zstream->zs.avail_in == 0) { /* need to read more data. try to read a full CHUNK_SIZE */ i_stream_skip(stream->parent, zstream->prev_size); if (i_stream_read_data(stream->parent, &data, &size, CHUNK_SIZE-1) == -1 && size == 0) { if (stream->parent->stream_errno != 0) { stream->istream.stream_errno = stream->parent->stream_errno; } else { i_assert(stream->parent->eof); if (zstream->log_errors) { bzlib_read_error(zstream, "unexpected EOF"); } stream->istream.stream_errno = EINVAL; } return -1; } zstream->prev_size = size; if (size == 0) { /* no more input */ i_assert(!stream->istream.blocking); return 0; } zstream->zs.next_in = (char *)data; zstream->zs.avail_in = size; } size = stream->buffer_size - stream->pos; zstream->zs.next_out = (char *)stream->w_buffer + stream->pos; zstream->zs.avail_out = size; ret = BZ2_bzDecompress(&zstream->zs); size -= zstream->zs.avail_out; stream->pos += size; switch (ret) { case BZ_OK: break; case BZ_PARAM_ERROR: i_unreached(); case BZ_DATA_ERROR: if (zstream->log_errors) bzlib_read_error(zstream, "corrupted data"); stream->istream.stream_errno = EINVAL; return -1; case BZ_DATA_ERROR_MAGIC: if (zstream->log_errors) { bzlib_read_error(zstream, "wrong magic in header (not bz2 file?)"); } stream->istream.stream_errno = EINVAL; return -1; case BZ_MEM_ERROR: i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory", i_stream_get_name(&stream->istream)); case BZ_STREAM_END: zstream->eof_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (size == 0) { stream->istream.eof = TRUE; return -1; } break; default: i_fatal("BZ2_bzDecompress() failed with %d", ret); } if (size == 0) { /* read more input */ return i_stream_bzlib_read(stream); } return size; }
static ssize_t i_stream_ssl_read_real(struct istream_private *stream) { struct ssl_istream *sstream = (struct ssl_istream *)stream; struct ssl_iostream *ssl_io = sstream->ssl_io; unsigned char buffer[IO_BLOCK_SIZE]; size_t max_buffer_size = i_stream_get_max_buffer_size(&stream->istream); size_t orig_max_buffer_size = stream->max_buffer_size; size_t size; ssize_t ret, total_ret; if (sstream->seen_eof) { stream->istream.eof = TRUE; return -1; } if (stream->pos >= max_buffer_size) { i_stream_compress(stream); if (stream->pos >= max_buffer_size) return -2; } ret = openssl_iostream_more(ssl_io); if (ret <= 0) { if (ret < 0) { /* handshake failed */ i_assert(errno != 0); io_stream_set_error(&stream->iostream, "%s", ssl_io->last_error); stream->istream.stream_errno = errno; } return ret; } if (!i_stream_try_alloc(stream, 1, &size)) i_unreached(); if (stream->pos + size > max_buffer_size) { i_assert(max_buffer_size > stream->pos); size = max_buffer_size - stream->pos; } while ((ret = SSL_read(ssl_io->ssl, stream->w_buffer + stream->pos, size)) <= 0) { /* failed to read anything */ ret = openssl_iostream_handle_error(ssl_io, ret, "SSL_read"); if (ret <= 0) { if (ret == 0) return 0; if (ssl_io->last_error != NULL) { io_stream_set_error(&stream->iostream, "%s", ssl_io->last_error); } if (errno != EPIPE) stream->istream.stream_errno = errno; stream->istream.eof = TRUE; sstream->seen_eof = TRUE; return -1; } /* we did some BIO I/O, try reading again */ } stream->pos += ret; total_ret = ret; /* now make sure that we read everything already buffered in OpenSSL into the stream (without reading anything more). this makes I/O loop behave similary for ssl-istream as file-istream. */ sstream->ssl_io->input_handler = FALSE; stream->max_buffer_size = (size_t)-1; while ((ret = SSL_read(ssl_io->ssl, buffer, sizeof(buffer))) > 0) { memcpy(i_stream_alloc(stream, ret), buffer, ret); stream->pos += ret; total_ret += ret; } stream->max_buffer_size = orig_max_buffer_size; return total_ret; }