/* * Copyright (C) 2019 Fabian Groffen * * 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 #include #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); }