[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: RFC4978 (COMPRESS=DEFLATE feature)



At Sun, 14 Feb 2010 12:55:51 +0100, Herbert J. Skuhra wrote:

> > zlib.el is not ready yet, so all deflate/inflate work is done by
> > external utility. If you (or anybody else) want to value, how good
> > compress feature is, I can post sources.
> 
> Vitaly, please post the sources. I've already checked out gnutls from
> git.

Disclaimer: this is not production quality code ;)

ssl-program-name "/path/to/deflater"
ssl-program-arguments '("/path/to/gnutls-cli" "-p" service host)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <zlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>

#define MAX(a,b) (a > b ? a : b)

int stdin_fd[2], stdout_fd[2], stderr_fd[2];
int do_deflate = 0;
int fd_log = 0;
int pid;

#define CHUNK 4096
#define DEBUG 0

z_stream strm_def;
z_stream strm_inf;

unsigned char in[CHUNK], out[CHUNK], inf_buf[CHUNK * 16];
int bytes, ret, inf_sz;
char *ptr;
unsigned char* inf_ptr = inf_buf;

void make_pipes()
{
	assert(pipe2(stdin_fd, O_NONBLOCK) == 0);
	assert(pipe2(stdout_fd, O_NONBLOCK) == 0);
	assert(pipe2(stderr_fd, O_NONBLOCK) == 0);
}

void prepare()
{
	close(0);
	dup(stdin_fd[0]);
	close(1);
	dup(stdout_fd[1]);
	close(2);
	dup(stderr_fd[1]);
	close(stdin_fd[1]);
	close(stdout_fd[0]);
	close(stderr_fd[0]);
}

void z_init()
{
	int ret;

	strm_def.zalloc = Z_NULL;
	strm_def.zfree = Z_NULL;
	strm_def.opaque = Z_NULL;
	ret = deflateInit2(&strm_def,
			   Z_DEFAULT_COMPRESSION,
			   Z_DEFLATED,
			   -MAX_WBITS,
			   MAX_MEM_LEVEL,
			   Z_DEFAULT_STRATEGY);
	assert(ret == Z_OK);

	strm_inf.zalloc = Z_NULL;
	strm_inf.zfree = Z_NULL;
	strm_inf.opaque = Z_NULL;
	ret = inflateInit2(&strm_inf, -MAX_WBITS);
	assert(ret == Z_OK);
}

void write_all(int fd, char* buf, int count)
{
	int ret;
	fd_set set;
	struct timeval tv;

	do {
		FD_ZERO(&set);
		FD_SET(fd, &set);
		tv.tv_sec = 0;
		tv.tv_usec = 250;
		ret = select (fd + 1, NULL, &set, NULL, &tv);

		if (ret > 0 && FD_ISSET (fd, &set)) {
			ret = write(fd, buf, count);
			if (ret > 0) {
				buf += ret;
				count -= ret;
			} else if (ret < 0 && errno != EAGAIN)
				exit(1);
		}
	} while (count);
}

void logger(int io, char* str, int bytes)
{
	if (fd_log) {
		if (io)
			write_all(fd_log, "ans: ", 5);
		else
			write_all(fd_log, "req: ", 5);
		write_all(fd_log, str, bytes);
		fsync(fd_log);
	}
}

void process_stdin()
{
	char *b;
	if (do_deflate != 1) {	/* do_deflate == 1 means we're waiting
				 * for response to COMPRESS*/
		memset(in, 0, CHUNK);
		memset(out, 0, CHUNK);
		bytes = read(0, in, CHUNK);
		if (bytes > 0) {
			char *b = strchr (in, '\n');
			if (b != NULL && *(b - 1) != '\r') {
				strcpy (b, "\r\n");
				bytes++;
			}
			logger(0, in, bytes);

			ptr = (char*)(long)strcasestr(in, "compress deflate");
			if (ptr)
				do_deflate = 1;
			if (do_deflate == 2) {
				int have;
				strm_def.avail_in = bytes;
				strm_def.next_in = in;

				if (DEBUG) {
					int i;
					printf("\nA (%d) > ", bytes);
					for (i = 0; i < bytes; i++)
						printf("%02x ", in[i]);
					printf("\n");
				}

				do {
					strm_def.avail_out = CHUNK;
					strm_def.next_out = out;
					ret = deflate(&strm_def, Z_SYNC_FLUSH);
					assert(ret != Z_STREAM_ERROR);
					have = CHUNK - strm_def.avail_out;
					if (DEBUG) {
						int i;
						printf("Z-in (%d) > ", have);
						for (i = 0; i < have; i++)
							printf("%02x ", out[i]);
						printf("\n");
					}
					write_all(stdin_fd[1], out, have);
				} while (strm_def.avail_out == 0);
				assert(strm_def.avail_in == 0);
			} else {
				write_all(stdin_fd[1], in, bytes);
			}
		}
	}
}

void process_stdout()
{
	memset(in, 0, CHUNK);
	bytes = read(stdout_fd[0], in, CHUNK);
	if (bytes > 0) {
		if (do_deflate == 1) {
			ptr = (char *)(long)strcasestr(in, "ok");
			if (ptr) {
				z_init();
				do_deflate = 2;
				write_all(1, in, bytes);
				logger(1, in, bytes);
			}
		} else if (do_deflate == 2) {
			int have;

			memcpy(inf_ptr, in, bytes);
			strm_inf.avail_in += bytes;
			strm_inf.next_in = inf_buf;

			if (DEBUG) {
				int i;
				printf("\nbytes = %d\n", bytes);
				printf("inf_ptr delta = %d\n",
				       inf_ptr - inf_buf);
				printf("avail_in = %d\n", strm_inf.avail_in);
				printf("Z-out (%d) > ", bytes);
				for (i = 0; i < bytes; i++)
					printf("%02x ", in[i]);
				printf("\n");
			}

			do {
				strm_inf.avail_out = CHUNK;
				strm_inf.next_out = out;
				ret = inflate(&strm_inf, Z_SYNC_FLUSH);
				assert(ret != Z_STREAM_ERROR);
				have = CHUNK - strm_inf.avail_out;
				if (DEBUG) {
					printf("\nhave = %d\n", have);
					printf("avail_in = %d\n",
					       strm_inf.avail_in);
					printf("next_in = %d\n",
					       strm_inf.next_in - inf_buf);
				}
				write_all(1, out, have);
				logger(1, out, have);
			} while (strm_inf.avail_out == 0);
			memmove(inf_buf, strm_inf.next_in, strm_inf.avail_in);
			inf_ptr = inf_buf + strm_inf.avail_in;
		} else {
			write_all(1, in, bytes);
			logger(1, in, bytes);
		}
	}
}

void process_stderr()
{
	memset(in, 0, CHUNK);
	bytes = read(stderr_fd[0], in, CHUNK);
	if (bytes > 0) {
		write(1, in, bytes);
	}
}

void do_job()
{
	fd_set set;
	struct timeval tv;

	FD_ZERO(&set);
	FD_SET(0, &set);
	FD_SET(stdout_fd[0], &set);
	FD_SET(stderr_fd[0], &set);

	tv.tv_sec = 0;
	tv.tv_usec = 250;
	ret = select (MAX(stdout_fd[0], stderr_fd[0]) + 1,
		      &set,
		      NULL,
		      NULL,
		      &tv);

	if (ret < 0)
		return;

	if (FD_ISSET(0, &set))
		process_stdin();

	if (FD_ISSET(stdout_fd[0], &set))
		process_stdout();

	if (FD_ISSET(stderr_fd[0], &set))
		process_stderr();
}

void sighandler(int sig, siginfo_t* si, void* re)
{
	if (pid > 0)
		kill(pid, SIGKILL);
}

int main(int argc, char* argv[])
{
	char *args[32];
	char *env[] = { NULL };
	struct sigaction sa;

	int i;
	for (i = 1; i <= argc; i++) {
		args[i - 1] = argv[i];
	}

	int ret;
	make_pipes();

	int flags;
	flags = fcntl(0, F_GETFL, 0);
	assert(flags != -1);
	fcntl(0, F_SETFL, flags | O_NONBLOCK);

	pid = fork();
	if (pid == 0) {
		prepare();
		ret = execve(args[0], args, env);
		printf("error = %d (%s)\n", ret, strerror(errno));
	} else if (pid > 0) {
		close(stdin_fd[0]);
		close(stdout_fd[1]);
		close(stderr_fd[1]);

		sa.sa_sigaction = sighandler;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = SA_RESTART | SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		sigaction(SIGINT, &sa, NULL);
		sigaction(SIGABRT, &sa, NULL);
		sigaction(SIGTERM, &sa, NULL);

		if (DEBUG) {
			fd_log = open("/tmp/log", O_WRONLY | O_CREAT | O_APPEND,
				      0666);
			assert(fd_log > 0);
		}
		for (;;) {
			do_job();
			if (waitpid(pid, 0, WNOHANG) > 0) {
				pid = 0;
				break;
			}
		}
		if (fd_log)
			close(fd_log);
	}
}
Index: elmo/elmo-imap4.el
===================================================================
RCS file: /cvs/root/wanderlust/elmo/elmo-imap4.el,v
retrieving revision 1.182
diff -p -u -w -r1.182 elmo-imap4.el
--- elmo/elmo-imap4.el	22 Jan 2010 15:42:07 -0000	1.182
+++ elmo/elmo-imap4.el	14 Feb 2010 10:29:56 -0000
@@ -90,6 +90,9 @@
 (defvar elmo-imap4-use-cache t
   "Use cache in imap4 folder.")
 
+(defvar elmo-imap4-use-compression nil
+  "Use compression in imap4 proto, when possible.")
+
 (defvar elmo-imap4-extra-namespace-alist
   '(("^\\({.*/nntp}\\).*$" . ".")) ; Default is for UW's remote nntp mailbox...
   "Extra namespace alist.
@@ -1145,7 +1148,7 @@ If CHOP-LENGTH is not specified, message
 		       (if (sasl-step-data step)
 			   (elmo-base64-encode-string (sasl-step-data step)
 						      'no-line-break)
-			 "")))))))
+			 ""))))))))
 ;; Some servers return reduced capabilities when client asks for them
 ;; before login. It might be a good idea to ask them again, otherwise
 ;; we can miss some useful feature.
@@ -1153,7 +1156,13 @@ If CHOP-LENGTH is not specified, message
 	  session
 	  (elmo-imap4-response-value
 	   (elmo-imap4-send-command-wait session "capability")
-	   'capability)))))))
+	  'capability))
+	(when (and (elmo-imap4-session-capable-p session 'compress=deflate)
+		   elmo-imap4-use-compression)
+	  (elmo-imap4-response-ok-p
+	   (elmo-imap4-send-command-wait
+	    session
+	    "compress deflate")))))))
 
 (luna-define-method elmo-network-setup-session ((session
 						 elmo-imap4-session))
-- 
wbr, Vitaly