# HG changeset patch # User Aki Tuomi # Date 1454941354 -7200 # Node ID a3872143befddf4ae675d9be2160a4284694dcd3 # Parent b5fcfa9ab945e1752f5cf552ef818ebf0549d79d lib: Add ostream-escaped filter diff -r b5fcfa9ab945 -r a3872143befd src/lib/Makefile.am --- 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 \ diff -r b5fcfa9ab945 -r a3872143befd src/lib/json-parser.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++) diff -r b5fcfa9ab945 -r a3872143befd src/lib/json-parser.h --- 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 diff -r b5fcfa9ab945 -r a3872143befd src/lib/ostream-escaped.c --- /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)); +} diff -r b5fcfa9ab945 -r a3872143befd src/lib/ostream-escaped.h --- /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 diff -r b5fcfa9ab945 -r a3872143befd src/lib/test-lib.c --- 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, diff -r b5fcfa9ab945 -r a3872143befd src/lib/test-lib.h --- 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); diff -r b5fcfa9ab945 -r a3872143befd src/lib/test-ostream-escaped.c --- /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(); +}