changeset 19735:a3872143befd

lib: Add ostream-escaped filter
author Aki Tuomi <aki.tuomi@dovecot.fi>
date Mon, 08 Feb 2016 16:22:34 +0200
parents b5fcfa9ab945
children 14e4c1a9c0a6
files src/lib/Makefile.am src/lib/json-parser.c src/lib/json-parser.h src/lib/ostream-escaped.c src/lib/ostream-escaped.h src/lib/test-lib.c src/lib/test-lib.h src/lib/test-ostream-escaped.c
diffstat 8 files changed, 260 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib/Makefile.am	Tue Feb 09 10:03:56 2016 +0200
+++ b/src/lib/Makefile.am	Mon Feb 08 16:22:34 2016 +0200
@@ -108,6 +108,7 @@
 	numpack.c \
 	ostream.c \
 	ostream-buffer.c \
+	ostream-escaped.c \
 	ostream-failure-at.c \
 	ostream-file.c \
 	ostream-hash.c \
@@ -325,6 +326,7 @@
 	test-mempool-alloconly.c \
 	test-net.c \
 	test-numpack.c \
+	test-ostream-escaped.c \
 	test-ostream-failure-at.c \
 	test-ostream-file.c \
 	test-primes.c \
--- a/src/lib/json-parser.c	Tue Feb 09 10:03:56 2016 +0200
+++ b/src/lib/json-parser.c	Mon Feb 08 16:22:34 2016 +0200
@@ -702,6 +702,11 @@
 	}
 }
 
+void ostream_escaped_json_format(string_t *dest, unsigned char src)
+{
+	json_append_escaped_char(dest, src);
+}
+
 void json_append_escaped(string_t *dest, const char *src)
 {
 	for (; *src != '\0'; src++)
--- a/src/lib/json-parser.h	Tue Feb 09 10:03:56 2016 +0200
+++ b/src/lib/json-parser.h	Mon Feb 08 16:22:34 2016 +0200
@@ -50,5 +50,6 @@
 void json_append_escaped(string_t *dest, const char *src);
 /* Same as json_append_escaped(), but append non-\0 terminated input. */
 void json_append_escaped_data(string_t *dest, const unsigned char *src, size_t size);
+void ostream_escaped_json_format(string_t *dest, unsigned char src);
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ostream-escaped.c	Mon Feb 08 16:22:34 2016 +0200
@@ -0,0 +1,138 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+#include "ostream-private.h"
+#include "ostream-escaped.h"
+
+struct escaped_ostream {
+	struct ostream_private ostream;
+	ostream_escaped_escape_formatter_t format;
+
+	string_t *buf;
+	bool flushed;
+};
+
+static ssize_t
+o_stream_escaped_send_outbuf(struct escaped_ostream *estream)
+{
+	ssize_t ret;
+
+	if (estream->flushed)
+		return 1; /* nothing to send */
+	ret = o_stream_send(estream->ostream.parent, str_data(estream->buf), str_len(estream->buf));
+	if (ret < 0) {
+		o_stream_copy_error_from_parent(&estream->ostream);
+		return -1;
+	}
+	if ((size_t)ret != str_len(estream->buf)) {
+		/* move data */
+		str_delete(estream->buf, 0, ret);
+		return 0;
+	}
+	str_truncate(estream->buf, 0);
+	estream->flushed = TRUE;
+	return 1;
+}
+
+static ssize_t
+o_stream_escaped_send_chunk(struct escaped_ostream *estream,
+			    const unsigned char *data, size_t len)
+{
+	size_t i, max_buffer_size, flush_pos;
+	ssize_t ret;
+
+	max_buffer_size = I_MIN(o_stream_get_max_buffer_size(estream->ostream.parent),
+				estream->ostream.max_buffer_size);
+	if (max_buffer_size > IO_BLOCK_SIZE) {
+		/* avoid using up too much memory in case of large buffers */
+		max_buffer_size = IO_BLOCK_SIZE;
+	}
+
+	flush_pos = str_len(estream->buf);
+	for (i = 0; i < len; i++) {
+		if (str_len(estream->buf) + 2 > max_buffer_size) { /* escaping takes at least two bytes */
+			estream->ostream.ostream.offset +=
+				str_len(estream->buf) - flush_pos;
+			ret = o_stream_escaped_send_outbuf(estream);
+			if (ret < 0)
+				return ret;
+			flush_pos = str_len(estream->buf);
+			if (ret == 0)
+				break;
+		}
+		estream->format(estream->buf, data[i]);
+		estream->flushed = FALSE;
+	}
+	/* we'll return how many bytes of input we consumed, but ostream offset
+	   contains how many bytes we actually wrote */
+	estream->ostream.ostream.offset += str_len(estream->buf) - flush_pos;
+	return i;
+}
+
+static ssize_t
+o_stream_escaped_sendv(struct ostream_private *stream,
+		       const struct const_iovec *iov, unsigned int iov_count)
+{
+	struct escaped_ostream *estream = (struct escaped_ostream *)stream;
+	unsigned int iov_cur;
+	ssize_t ret, bytes = 0;
+
+	for (iov_cur = 0; iov_cur < iov_count; iov_cur++) {
+		ret = o_stream_escaped_send_chunk(estream,
+				iov[iov_cur].iov_base, iov[iov_cur].iov_len);
+		if (ret < 0)
+			return ret;
+		bytes += ret;
+		if ((size_t)ret != iov[iov_cur].iov_len)
+			break;
+	}
+	if (o_stream_escaped_send_outbuf(estream) < 0)
+		return -1;
+	return bytes;
+}
+
+static int
+o_stream_escaped_flush(struct ostream_private *stream)
+{
+	struct escaped_ostream *estream = (struct escaped_ostream *)stream;
+	int ret;
+
+	if ((ret = o_stream_escaped_send_outbuf(estream)) <= 0)
+		return ret;
+	if ((ret = o_stream_flush(stream->parent)) < 0)
+		o_stream_copy_error_from_parent(stream);
+	return ret;
+}
+
+static void o_stream_escaped_destroy(struct iostream_private *stream)
+{
+	struct escaped_ostream *estream = (struct escaped_ostream *)stream;
+
+	str_free(&estream->buf);
+	o_stream_unref(&estream->ostream.parent);
+}
+
+void ostream_escaped_hex_format(string_t *dest, unsigned char chr)
+{
+	str_printfa(dest, "%02x", chr);
+}
+
+struct ostream *
+o_stream_create_escaped(struct ostream *output,
+			ostream_escaped_escape_formatter_t format)
+{
+	struct escaped_ostream *estream;
+
+	estream = i_new(struct escaped_ostream, 1);
+	estream->ostream.sendv = o_stream_escaped_sendv;
+	estream->ostream.flush = o_stream_escaped_flush;
+	estream->ostream.max_buffer_size = o_stream_get_max_buffer_size(output);
+	estream->ostream.iostream.destroy = o_stream_escaped_destroy;
+	estream->buf = str_new(default_pool, 512);
+	estream->format = format;
+	estream->flushed = FALSE;
+
+	return o_stream_create(&estream->ostream, output, o_stream_get_fd(output));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ostream-escaped.h	Mon Feb 08 16:22:34 2016 +0200
@@ -0,0 +1,27 @@
+#ifndef OSTREAM_ESCAPED_H
+#define OSTREAM_ESCAPED_H
+
+/**
+  * Provides escape filter for ostream
+  * This is intended to be used when certain (or all)
+  * characters need to be escaped before sending.
+
+  * Such usecases are f.ex.
+  *  - JSON, ostream_escaped_json_format
+  *  - hex,  ostream_escaped_hex_format
+
+  * To implement your own filter, create function
+  * that matches ostream_escaped_escape_formatter_t
+  * and use it as parameter
+  */
+
+typedef void (*ostream_escaped_escape_formatter_t)
+	(string_t *dest, unsigned char chr);
+
+void ostream_escaped_hex_format(string_t *dest, unsigned char chr);
+
+struct ostream *
+o_stream_create_escaped(struct ostream *output,
+			ostream_escaped_escape_formatter_t formatter);
+
+#endif
--- a/src/lib/test-lib.c	Tue Feb 09 10:03:56 2016 +0200
+++ b/src/lib/test-lib.c	Mon Feb 08 16:22:34 2016 +0200
@@ -38,6 +38,7 @@
 		test_mempool_alloconly,
 		test_net,
 		test_numpack,
+		test_ostream_escaped,
 		test_ostream_failure_at,
 		test_ostream_file,
 		test_primes,
--- a/src/lib/test-lib.h	Tue Feb 09 10:03:56 2016 +0200
+++ b/src/lib/test-lib.h	Mon Feb 08 16:22:34 2016 +0200
@@ -40,6 +40,7 @@
 enum fatal_test_state fatal_mempool(int);
 void test_net(void);
 void test_numpack(void);
+void test_ostream_escaped(void);
 void test_ostream_failure_at(void);
 void test_ostream_file(void);
 void test_primes(void);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/test-ostream-escaped.c	Mon Feb 08 16:22:34 2016 +0200
@@ -0,0 +1,85 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+#include "ostream.h"
+#include "ostream-escaped.h"
+#include "json-parser.h"
+
+static void test_ostream_escaped_json(void)
+{
+	struct ostream *os_sink;
+	struct ostream *os_encode;
+	struct const_iovec iov[2];
+	string_t *str = t_str_new(64);
+
+	test_begin("test_ostream_escaped_json()");
+	os_sink = o_stream_create_buffer(str);
+	os_encode = o_stream_create_escaped(os_sink, ostream_escaped_json_format);
+
+	/* test sending iovec */
+	iov[0].iov_base = "hello";
+	iov[0].iov_len = 5;
+	iov[1].iov_base = ", world";
+	iov[1].iov_len = 7;
+	test_assert(o_stream_sendv(os_encode, iov, 2) == 12);
+	test_assert(os_encode->offset == 12);
+	test_assert(strcmp(str_c(str), "hello, world") == 0);
+
+	/* reset buffer */
+	str_truncate(str, 0); os_sink->offset = 0; os_encode->offset = 0;
+
+	/* test shrinking ostream-escaped's max buffer size */
+	o_stream_set_max_buffer_size(os_encode, 10);
+	o_stream_set_max_buffer_size(os_sink, 100);
+	test_assert(o_stream_send(os_encode, "\x15\x00!\x00\x15\x11" "123456", 12) == 12);
+	test_assert(os_encode->offset == 2*6 + 1 + 3*6 + 6);
+	test_assert(strcmp(str_c(str), "\\u0015\\u0000!\\u0000\\u0015\\u0011123456") == 0);
+
+	/* reset buffer */
+	str_truncate(str, 0); os_sink->offset = 0; os_encode->offset = 0;
+
+	/* test shrinking sink's max buffer size */
+	o_stream_set_max_buffer_size(os_encode, 100);
+	o_stream_set_max_buffer_size(os_sink, 10);
+	const char *partial_input = "\x15!\x01?#&";
+	ssize_t ret = o_stream_send_str(os_encode, partial_input);
+	test_assert(ret < 6);
+	/* send the rest */
+	o_stream_set_max_buffer_size(os_sink, 100);
+	ret += o_stream_send_str(os_encode, partial_input + ret);
+	test_assert(ret == (ssize_t)strlen(partial_input));
+	test_assert(os_encode->offset == str_len(str));
+	test_assert(strcmp(str_c(str), "\\u0015!\\u0001?#&") == 0);
+
+	o_stream_unref(&os_encode);
+	o_stream_unref(&os_sink);
+
+	test_end();
+}
+
+static void test_ostream_escaped_hex(void)
+{
+	struct ostream *os_sink;
+	struct ostream *os_encode;
+	string_t *str = t_str_new(64);
+
+	os_sink = o_stream_create_buffer(str);
+	os_encode = o_stream_create_escaped(os_sink, ostream_escaped_hex_format);
+
+	test_begin("test_ostream_escaped_hex()");
+	o_stream_send_str(os_encode, "hello, world");
+	o_stream_flush(os_encode);
+
+	test_assert(strcmp(str_c(str), "68656c6c6f2c20776f726c64") == 0);
+
+	o_stream_unref(&os_encode);
+	o_stream_unref(&os_sink);
+
+	test_end();
+}
+
+void test_ostream_escaped(void) {
+	test_ostream_escaped_json();
+	test_ostream_escaped_hex();
+}