diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | configure.ac | 38 | ||||
-rw-r--r-- | imap/command.c | 1 | ||||
-rw-r--r-- | imap/imap.c | 14 | ||||
-rw-r--r-- | imap/imap_private.h | 1 | ||||
-rw-r--r-- | init.h | 11 | ||||
-rw-r--r-- | mutt.h | 6 | ||||
-rw-r--r-- | mutt_zstrm.c | 287 | ||||
-rw-r--r-- | mutt_zstrm.h | 28 |
9 files changed, 388 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 25507629..cede1adb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -63,7 +63,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c compress.c crypt-gpgme.c crypt-mod-pgp-c mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \ - sidebar.c smime.c smtp.c utf8.c wcwidth.c \ + sidebar.c smime.c smtp.c utf8.c wcwidth.c mutt_zstrm.c \ bcache.h browser.h hcache.h mbyte.h monitor.h mutt_idna.h remailer.h url.h EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ @@ -77,7 +77,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \ _mutt_regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \ mbyte.h lib.h extlib.c pgpewrap.c smime_keys.pl pgplib.h \ - README.SSL smime.h group.h \ + README.SSL smime.h group.h mutt_zstrm.h \ muttbug pgppacket.h depcomp ascii.h BEWARE PATCHES patchlist.sh \ ChangeLog mkchangelog.sh mkreldate.sh mutt_idna.h sidebar.h OPS.SIDEBAR \ snprintf.c regex.c crypt-gpgme.h hcachever.sh.in \ diff --git a/configure.ac b/configure.ac index 8c1c27ee..4e9cf2b2 100644 --- a/configure.ac +++ b/configure.ac @@ -756,6 +756,44 @@ then fi AM_CONDITIONAL(USE_GSS, test x$need_gss = xyes) +# if zlib +AC_ARG_WITH(zlib, AS_HELP_STRING([--with-zlib@<:@=PFX@:>@],[Enable DEFLATE support for IMAP using libz]), + zlib_prefix="$withval", zlib_prefix="auto") +if test "$zlib_prefix" != "no" +then + if test "$need_imap" = "yes" + then + have_zlib= + saved_LDFLAGS="$LDFLAGS" + saved_CPPFLAGS="$CPPFLAGS" + if test "$zlib_prefix" != "yes" + then + LDFLAGS="$LDFLAGS -L$zlib_prefix/lib" + CPPFLAGS="$CPPFLAGS -I$zlib_prefix/include" + fi + AC_CHECK_HEADERS([zlib.h], [AC_CHECK_LIB([z], [deflate], + [have_zlib=yes])]) + if test "x$have_zlib" = "x" + then + if test "x$zlib_prefix" != "xauto" + then + AC_MSG_ERROR([ZLIB requested, but library or headers not found]) + fi + zlib_prefix=no + else + MUTTLIBS="$MUTTLIBS -lz" + AC_DEFINE(USE_ZLIB, 1, [Define if you have libz available]) + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS mutt_zstrm.o" + zlib_prefix=yes + fi + LDFLAGS="$saved_LDFLAGS" + CPPFLAGS="$saved_CPPFLAGS" + else + AC_MSG_WARN([ZLIB was requested but IMAP is not enabled]) + fi +fi +AM_CONDITIONAL(USE_ZLIB, test x$zlib_prefix = xyes) + dnl -- end imap dependencies -- AC_ARG_WITH(ssl, AS_HELP_STRING([--with-ssl@<:@=PFX@:>@],[Enable TLS support using OpenSSL]), diff --git a/imap/command.c b/imap/command.c index 69ac0b02..cb3f9c0c 100644 --- a/imap/command.c +++ b/imap/command.c @@ -74,6 +74,7 @@ static const char * const Capabilities[] = { "CONDSTORE", "QRESYNC", "LIST-EXTENDED", + "COMPRESS=DEFLATE", NULL }; diff --git a/imap/imap.c b/imap/imap.c index 4bbe65c7..8e8684f8 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -34,6 +34,9 @@ #if defined(USE_SSL) # include "mutt_ssl.h" #endif +#if defined(USE_ZLIB) +# include "mutt_zstrm.h" +#endif #include "buffy.h" #if USE_HCACHE #include "hcache.h" @@ -426,6 +429,17 @@ IMAP_DATA* imap_conn_find (const ACCOUNT* account, int flags) /* capabilities may have changed */ imap_exec (idata, "CAPABILITY", IMAP_CMD_QUEUE); +#if defined(USE_ZLIB) + /* RFC 4978 */ + if (mutt_bit_isset (idata->capabilities, COMPRESS_DEFLATE)) + { + if (query_quadoption (OPT_IMAPDEFLATE, + _("Use deflate compression on connection?")) == MUTT_YES && + imap_exec (idata, "COMPRESS DEFLATE", IMAP_CMD_FAIL_OK) != -2) + mutt_zstrm_wrap_conn (idata->conn); + } +#endif + /* enable RFC6855, if the server supports that */ if (mutt_bit_isset (idata->capabilities, ENABLE)) imap_exec (idata, "ENABLE UTF8=ACCEPT", IMAP_CMD_QUEUE); diff --git a/imap/imap_private.h b/imap/imap_private.h index 88af5a48..3d1d9993 100644 --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -121,6 +121,7 @@ enum CONDSTORE, /* RFC 7162 */ QRESYNC, /* RFC 7162 */ LIST_EXTENDED, /* RFC 5258: IMAP4 - LIST Command Extensions */ + COMPRESS_DEFLATE, /* RFC 4978: COMPRESS=DEFLATE */ CAPMAX }; @@ -1437,6 +1437,17 @@ struct option_t MuttVars[] = { ** those, and displays worse performance when enabled. Your ** mileage may vary. */ +#ifdef USE_ZLIB + { "imap_deflate", DT_QUAD, R_NONE, {.l=OPT_IMAPDEFLATE}, {.l=MUTT_YES} }, + /* + ** .pp + ** When \fIset\fP, mutt will use the COMPRESS=DEFLATE extension (RFC + ** 4978) if advertised by the server. + ** .pp + ** In general a good compression efficiency can be achieved, which + ** speeds up reading large mailboxes also on fairly good connections. + */ +#endif { "imap_delim_chars", DT_STR, R_NONE, {.p=&ImapDelimChars}, {.p="/."} }, /* ** .pp @@ -318,6 +318,9 @@ enum OPT_FORWEDIT, OPT_FCCATTACH, OPT_INCLUDE, +#if defined(USE_IMAP) && defined(USE_ZLIB) + OPT_IMAPDEFLATE, +#endif OPT_MFUPTO, OPT_MIMEFWD, OPT_MIMEFWDREST, @@ -444,6 +447,9 @@ enum OPTIMAPPEEK, OPTIMAPQRESYNC, OPTIMAPSERVERNOISE, +#ifdef USE_ZLIB + OPTIMAPDEFLATE, +#endif #endif #if defined(USE_SSL) # ifndef USE_SSL_GNUTLS diff --git a/mutt_zstrm.c b/mutt_zstrm.c new file mode 100644 index 00000000..fefdf995 --- /dev/null +++ b/mutt_zstrm.c @@ -0,0 +1,287 @@ +/* + * Copyright 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 char has_pending:1; + unsigned char is_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) +{ + (void) op; + + return safe_calloc (sze, v); +} + +static void mutt_zstrm_free (void* op, void* ptr) +{ + (void) op; + + 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)); + + 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; + int inflatemode = Z_SYNC_FLUSH; + + /* shortcut end of stream call */ + if (zctx->read.has_pending == 1 && zctx->read.is_eof == 1) + { + /* next read will yield an error */ + zctx->read.has_pending = 0; + zctx->read.is_eof = 0; + 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.has_pending == 0 && zctx->read.pos == 0) + { + 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)); + /* error or end of stream? ensure zlib flushes whatever it can */ + if (rc <= 0) + inflatemode = Z_FINISH; + 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, inflatemode); + 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")); + /* re-call ourselves to read more bytes */ + zctx->read.has_pending = 0; /* trigger a read */ + return mutt_zstrm_read (conn, buf, len); + } + break; + case Z_STREAM_END: /* everything flushed, nothing remaining */ + zrc = len - zctx->read.z.avail_out; /* "returned" bytes */ + zctx->read.has_pending = 1; + zctx->read.is_eof = 1; + break; + case Z_DATA_ERROR: /* corrupt input */ + /* zlib can inflateSync here, but do we really want to skip bytes + * at this point? it may horribly mess up a protocol flow, so + * throw an error instead */ + return -1; + case Z_MEM_ERROR: /* out of memory -- shouldn't happen with safe_malloc */ + errno = ENOMEM; + return -1; + case Z_BUF_ERROR: /* output buffer full or nothing to read */ + /* since every call has an empty output buffer, this scenario + * means we read nothing, so retry reading */ + zctx->read.has_pending = 0; /* trigger a read */ + return mutt_zstrm_read (conn, buf, len); + } + + 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); +} diff --git a/mutt_zstrm.h b/mutt_zstrm.h new file mode 100644 index 00000000..0aad0ae0 --- /dev/null +++ b/mutt_zstrm.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + * + * 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. + */ + +#ifndef _MUTT_ZSTRM_H_ +#define _MUTT_ZSTRM_H_ 1 + +#include "mutt_socket.h" + +#if defined(USE_ZLIB) +void mutt_zstrm_wrap_conn (CONNECTION* conn); +#endif + +#endif /* _MUTT_ZSTRM_H_ */ |