/*
* Copyright (C) 2019 Fabian Groffen <grobian@gentoo.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include <zlib.h>
#include <errno.h>
#include "mutt.h"
#include "mutt_socket.h"
#include "mutt_zstrm.h"
typedef struct
{
struct zstrm_direction {
z_stream z;
char *buf;
unsigned int len;
unsigned int pos;
unsigned int conn_eof : 1;
unsigned int stream_eof : 1;
} read, write;
/* underlying stream */
CONNECTION next_conn;
}
zstrmctx;
/* simple wrapper functions to match zlib interface for calling
* malloc/free */
static void *mutt_zstrm_malloc (void* op, unsigned int sze, unsigned int v)
{
return safe_calloc (sze, v);
}
static void mutt_zstrm_free (void* op, void* ptr)
{
FREE (&ptr);
}
static int mutt_zstrm_open (CONNECTION* conn)
{
/* cannot open a zlib connection, must wrap an existing one */
return -1;
}
static int mutt_zstrm_close (CONNECTION* conn)
{
zstrmctx* zctx = conn->sockdata;
int rc = zctx->next_conn.conn_close (&zctx->next_conn);
dprint (4, (debugfile, "zstrm_close: read %llu->%llu (%.1fx) "
"wrote %llu<-%llu (%.1fx)\n",
zctx->read.z.total_in, zctx->read.z.total_out,
(float)zctx->read.z.total_out / (float)zctx->read.z.total_in,
zctx->write.z.total_in, zctx->write.z.total_out,
(float)zctx->write.z.total_in / (float)zctx->write.z.total_out));
conn->sockdata = zctx->next_conn.sockdata;
conn->conn_open = zctx->next_conn.conn_open;
conn->conn_close = zctx->next_conn.conn_close;
conn->conn_read = zctx->next_conn.conn_read;
conn->conn_write = zctx->next_conn.conn_write;
conn->conn_poll = zctx->next_conn.conn_poll;
inflateEnd (&zctx->read.z);
deflateEnd (&zctx->write.z);
FREE (&zctx->read.buf);
FREE (&zctx->write.buf);
FREE (&zctx);
return rc;
}
static int mutt_zstrm_read (CONNECTION* conn, char* buf, size_t len)
{
zstrmctx* zctx = conn->sockdata;
int rc = 0;
int zrc;
retry:
if (zctx->read.stream_eof)
return 0;
/* when avail_out was 0 on last call, we need to call inflate again,
* because more data might be available using the current input, so
* avoid callling read on the underlying stream in that case (for it
* might block) */
if (zctx->read.pos == 0 && !zctx->read.conn_eof)
{
rc = zctx->next_conn.conn_read (&zctx->next_conn,
zctx->read.buf, zctx->read.len);
dprint (4, (debugfile, "zstrm_read: consuming data from next "
"stream: %d bytes\n", rc));
if (rc < 0)
return rc;
else if (rc == 0)
zctx->read.conn_eof = 1;
else
zctx->read.pos += rc;
}
zctx->read.z.avail_in = (uInt) zctx->read.pos;
zctx->read.z.next_in = (Bytef*) zctx->read.buf;
zctx->read.z.avail_out = (uInt) len;
zctx->read.z.next_out = (Bytef*) buf;
zrc = inflate (&zctx->read.z, Z_SYNC_FLUSH);
dprint (4, (debugfile, "zstrm_read: rc=%d, "
"consumed %u/%u bytes, produced %u/%u bytes\n", zrc,
zctx->read.pos - zctx->read.z.avail_in, zctx->read.pos,
len - zctx->read.z.avail_out, len));
/* shift any remaining input data to the front of the buffer */
if ((Bytef*) zctx->read.buf != zctx->read.z.next_in)
{
memmove(zctx->read.buf, zctx->read.z.next_in, zctx->read.z.avail_in);
zctx->read.pos = zctx->read.z.avail_in;
}
switch (zrc)
{
case Z_OK: /* progress has been made */
zrc = len - zctx->read.z.avail_out; /* "returned" bytes */
if (zrc == 0)
{
/* there was progress, so must have been reading input */
dprint (4, (debugfile, "zstrm_read: inflate just consumed\n"));
goto retry;
}
break;
case Z_STREAM_END: /* everything flushed, nothing remaining */
dprint (4, (debugfile, "zstrm_read: inflate returned Z_STREAM_END.\n"));
zrc = len - zctx->read.z.avail_out; /* "returned" bytes */
zctx->read.stream_eof = 1;
break;
case Z_BUF_ERROR: /* no progress was possible */
if (!zctx->read.conn_eof)
{
dprint (5, (debugfile, "zstrm_read: inflate returned Z_BUF_ERROR. retrying.\n"));
goto retry;
}
zrc = 0;
break;
default:
/* bail on other rcs, such as Z_DATA_ERROR, or Z_MEM_ERROR */
dprint (4, (debugfile, "zstrm_read: inflate returned %d. aborting.\n", zrc));
zrc = -1;
break;
}
return zrc;
}
static int mutt_zstrm_poll (CONNECTION* conn, time_t wait_secs)
{
zstrmctx* zctx = conn->sockdata;
dprint (4, (debugfile, "zstrm_poll: %s\n",
zctx->read.z.avail_out == 0 || zctx->read.pos > 0 ?
"last read wrote full buffer" : "falling back on next stream"));
if (zctx->read.z.avail_out == 0 || zctx->read.pos > 0)
return 1;
else
return zctx->next_conn.conn_poll (&zctx->next_conn, wait_secs);
}
static int mutt_zstrm_write (CONNECTION* conn, const char* buf, size_t count)
{
zstrmctx* zctx = conn->sockdata;
int rc;
int zrc;
char *wbufp;
zctx->write.z.avail_in = (uInt) count;
zctx->write.z.next_in = (Bytef*) buf;
zctx->write.z.avail_out = (uInt) zctx->write.len;
zctx->write.z.next_out = (Bytef*) zctx->write.buf;
do
{
zrc = deflate (&zctx->write.z, Z_PARTIAL_FLUSH);
if (zrc == Z_OK)
{
/* push out produced data to the underlying stream */
zctx->write.pos = zctx->write.len - zctx->write.z.avail_out;
wbufp = zctx->write.buf;
dprint (4, (debugfile, "zstrm_write: deflate consumed %d/%d bytes\n",
count - zctx->write.z.avail_in, count));
while (zctx->write.pos > 0)
{
rc = zctx->next_conn.conn_write (&zctx->next_conn,
wbufp, zctx->write.pos);
dprint (4, (debugfile, "zstrm_write: next stream wrote: %d bytes\n",
rc));
if (rc < 0)
return -1; /* we can't recover from write failure */
wbufp += rc;
zctx->write.pos -= rc;
}
/* see if there's more for us to do, retry if the output buffer
* was full (there may be something in zlib buffers), and retry
* when there is still available input data */
if (zctx->write.z.avail_out != 0 && zctx->write.z.avail_in == 0)
break;
zctx->write.z.avail_out = (uInt) zctx->write.len;
zctx->write.z.next_out = (Bytef*) zctx->write.buf;
}
else
{
/* compression went wrong, but this is basically impossible
* according to the docs */
return -1;
}
} while (1);
rc = (int) count;
return rc <= 0 ? 1 : rc; /* avoid wrong behaviour due to overflow */
}
void mutt_zstrm_wrap_conn (CONNECTION* conn)
{
zstrmctx* zctx;
zctx = (zstrmctx*) safe_calloc (1, sizeof (zstrmctx));
/* store wrapped stream as next stream */
zctx->next_conn.fd = conn->fd;
zctx->next_conn.sockdata = conn->sockdata;
zctx->next_conn.conn_open = conn->conn_open;
zctx->next_conn.conn_close = conn->conn_close;
zctx->next_conn.conn_read = conn->conn_read;
zctx->next_conn.conn_write = conn->conn_write;
zctx->next_conn.conn_poll = conn->conn_poll;
/* replace connection with our wrappers, where appropriate */
conn->sockdata = (void*) zctx;
conn->conn_open = mutt_zstrm_open;
conn->conn_read = mutt_zstrm_read;
conn->conn_write = mutt_zstrm_write;
conn->conn_close = mutt_zstrm_close;
conn->conn_poll = mutt_zstrm_poll;
/* allocate/setup (de)compression buffers */
zctx->read.len = HUGE_STRING;
zctx->read.buf = safe_malloc (zctx->read.len);
zctx->read.pos = 0;
zctx->write.len = HUGE_STRING;
zctx->write.buf = safe_malloc (zctx->write.len);
zctx->write.pos = 0;
/* initialise zlib for inflate and deflate for RFC-4978 */
zctx->read.z.zalloc = mutt_zstrm_malloc;
zctx->read.z.zfree = mutt_zstrm_free;
zctx->read.z.opaque = NULL;
zctx->read.z.avail_out = zctx->read.len;
inflateInit2 (&zctx->read.z, -15);
zctx->write.z.zalloc = mutt_zstrm_malloc;
zctx->write.z.zfree = mutt_zstrm_free;
zctx->write.z.opaque = NULL;
zctx->write.z.avail_out = zctx->write.len;
deflateInit2 (&zctx->write.z, Z_DEFAULT_COMPRESSION,
Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
}