numpack.c \
ostream.c \
ostream-buffer.c \
+ ostream-escaped.c \
ostream-failure-at.c \
ostream-file.c \
ostream-hash.c \
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 \
}
}
+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++)
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
+/* 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
+#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
test_mempool_alloconly,
test_net,
test_numpack,
+ test_ostream_escaped,
test_ostream_failure_at,
test_ostream_file,
test_primes,
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
+/* 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();
+}