]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Add ostream-escaped filter
authorAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 8 Feb 2016 14:22:34 +0000 (16:22 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Wed, 10 Feb 2016 13:29:41 +0000 (15:29 +0200)
src/lib/Makefile.am
src/lib/json-parser.c
src/lib/json-parser.h
src/lib/ostream-escaped.c [new file with mode: 0644]
src/lib/ostream-escaped.h [new file with mode: 0644]
src/lib/test-lib.c
src/lib/test-lib.h
src/lib/test-ostream-escaped.c [new file with mode: 0644]

index 44df5b58d95c6854606eee1e523cf6df5262b599..4e53ea6541d5ad349999510b90ecfb937fa9ba17 100644 (file)
@@ -108,6 +108,7 @@ liblib_la_SOURCES = \
        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_lib_SOURCES = \
        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 \
index 14ade58a814ead64180be7e0ef579ff361ed7d9a..4a159bf41271949a48f81b72048d89dcf6f56dd7 100644 (file)
@@ -702,6 +702,11 @@ static void json_append_escaped_char(string_t *dest, unsigned char src)
        }
 }
 
+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++)
index f9b6e800d4fad9c975247524c0746f85ef1acf51..551422dcc3b9dd6f88f89b9a030451fc366797f0 100644 (file)
@@ -50,5 +50,6 @@ int json_parse_next_stream(struct json_parser *parser,
 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
diff --git a/src/lib/ostream-escaped.c b/src/lib/ostream-escaped.c
new file mode 100644 (file)
index 0000000..c875d70
--- /dev/null
@@ -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));
+}
diff --git a/src/lib/ostream-escaped.h b/src/lib/ostream-escaped.h
new file mode 100644 (file)
index 0000000..7930cd3
--- /dev/null
@@ -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
index daf2369e22cfedd24efc0f64638328c26ead3604..3430a856534d4627f3a3790a6dd5887f197b437e 100644 (file)
@@ -38,6 +38,7 @@ int main(void)
                test_mempool_alloconly,
                test_net,
                test_numpack,
+               test_ostream_escaped,
                test_ostream_failure_at,
                test_ostream_file,
                test_primes,
index 77c4339af7f815651e20cad0d407f732c11b7ca0..8e32c241abc962229e5cdd791d1c65b39a77a821 100644 (file)
@@ -40,6 +40,7 @@ void test_mempool_alloconly(void);
 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);
diff --git a/src/lib/test-ostream-escaped.c b/src/lib/test-ostream-escaped.c
new file mode 100644 (file)
index 0000000..f64813f
--- /dev/null
@@ -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();
+}