From ee502bb6c2d4c58be8e1fc24722981d1887c4c5d Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Mon, 29 May 2017 13:39:18 +0300 Subject: [PATCH] lib-mail: Add qp encoder --- src/lib-mail/Makefile.am | 13 +++ src/lib-mail/istream-qp-encoder.c | 128 +++++++++++++++++++++ src/lib-mail/istream-qp.h | 6 + src/lib-mail/qp-encoder.c | 140 ++++++++++++++++++++++ src/lib-mail/qp-encoder.h | 25 ++++ src/lib-mail/test-istream-qp-encoder.c | 131 +++++++++++++++++++++ src/lib-mail/test-qp-encoder.c | 153 +++++++++++++++++++++++++ 7 files changed, 596 insertions(+) create mode 100644 src/lib-mail/istream-qp-encoder.c create mode 100644 src/lib-mail/qp-encoder.c create mode 100644 src/lib-mail/qp-encoder.h create mode 100644 src/lib-mail/test-istream-qp-encoder.c create mode 100644 src/lib-mail/test-qp-encoder.c diff --git a/src/lib-mail/Makefile.am b/src/lib-mail/Makefile.am index 38b95b9aef..86368a3e8b 100644 --- a/src/lib-mail/Makefile.am +++ b/src/lib-mail/Makefile.am @@ -13,6 +13,7 @@ libmail_la_SOURCES = \ istream-header-filter.c \ istream-nonuls.c \ istream-qp-decoder.c \ + istream-qp-encoder.c \ mail-html2text.c \ mail-user-hash.c \ mbox-from.c \ @@ -34,6 +35,7 @@ libmail_la_SOURCES = \ message-snippet.c \ ostream-dot.c \ qp-decoder.c \ + qp-encoder.c \ quoted-printable.c \ rfc2231-parser.c \ rfc822-parser.c @@ -71,6 +73,7 @@ headers = \ message-snippet.h \ ostream-dot.h \ qp-decoder.h \ + qp-encoder.h \ quoted-printable.h \ rfc2231-parser.h \ rfc822-parser.h @@ -84,6 +87,7 @@ test_programs = \ test-istream-binary-converter \ test-istream-header-filter \ test-istream-qp-decoder \ + test-istream-qp-encoder \ test-mail-html2text \ test-mbox-from \ test-message-address \ @@ -100,6 +104,7 @@ test_programs = \ test-message-snippet \ test-ostream-dot \ test-qp-decoder \ + test-qp-encoder \ test-quoted-printable \ test-rfc2231-parser \ test-rfc822-parser @@ -121,6 +126,10 @@ test_istream_qp_decoder_SOURCES = test-istream-qp-decoder.c test_istream_qp_decoder_LDADD = $(test_libs) test_istream_qp_decoder_DEPENDENCIES = $(test_deps) +test_istream_qp_encoder_SOURCES = test-istream-qp-encoder.c +test_istream_qp_encoder_LDADD = $(test_libs) +test_istream_qp_encoder_DEPENDENCIES = $(test_deps) + test_istream_binary_converter_SOURCES = test-istream-binary-converter.c test_istream_binary_converter_LDADD = $(test_libs) test_istream_binary_converter_DEPENDENCIES = $(test_deps) @@ -197,6 +206,10 @@ test_qp_decoder_SOURCES = test-qp-decoder.c test_qp_decoder_LDADD = $(test_libs) test_qp_decoder_DEPENDENCIES = $(test_deps) +test_qp_encoder_SOURCES = test-qp-encoder.c +test_qp_encoder_LDADD = $(test_libs) +test_qp_encoder_DEPENDENCIES = $(test_deps) + test_quoted_printable_SOURCES = test-quoted-printable.c test_quoted_printable_LDADD = $(test_libs) test_quoted_printable_DEPENDENCIES = $(test_deps) diff --git a/src/lib-mail/istream-qp-encoder.c b/src/lib-mail/istream-qp-encoder.c new file mode 100644 index 0000000000..d68ed12b44 --- /dev/null +++ b/src/lib-mail/istream-qp-encoder.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "hex-binary.h" +#include "qp-encoder.h" +#include "istream-private.h" +#include "istream-qp.h" + +struct qp_encoder_istream { + struct istream_private istream; + buffer_t *buf; + struct qp_encoder *qp; +}; + +static void i_stream_qp_encoder_close(struct iostream_private *stream, + bool close_parent) +{ + struct qp_encoder_istream *bstream = + (struct qp_encoder_istream *)stream; + + if (bstream->qp != NULL) + qp_encoder_deinit(&bstream->qp); + if (bstream->buf != NULL) + buffer_free(&bstream->buf); + if (close_parent) + i_stream_close(bstream->istream.parent); +} + +static ssize_t i_stream_qp_encoder_read(struct istream_private *stream) +{ + struct qp_encoder_istream *bstream = + (struct qp_encoder_istream *)stream; + const unsigned char *data; + size_t size; + int ret; + + for(;;) { + if (stream->skip > 0) { + i_assert(stream->skip <= bstream->buf->used); + buffer_delete(bstream->buf, 0, stream->skip); + stream->pos -= stream->skip; + stream->skip = 0; + } + + stream->buffer = bstream->buf->data; + i_assert(stream->pos <= bstream->buf->used); + + if (stream->pos >= bstream->istream.max_buffer_size) { + /* stream buffer still at maximum */ + return -2; + } + + /* if something is already interpolated, return as much of it as + we can */ + if (bstream->buf->used > 0) { + size_t new_pos, bytes; + + /* only return up to max_buffer_size bytes, even when buffer + actually has more, as not to confuse the caller */ + if (bstream->buf->used <= bstream->istream.max_buffer_size) { + new_pos = bstream->buf->used; + if (stream->parent->eof) + stream->istream.eof = TRUE; + } else { + new_pos = bstream->istream.max_buffer_size; + } + + bytes = new_pos - stream->pos; + stream->pos = new_pos; + return (ssize_t)bytes; + } + + /* need to read more input */ + ret = i_stream_read_more(stream->parent, &data, &size); + if (ret == 0) + return ret; + if (size == 0 && ret == -1) { + stream->istream.stream_errno = + stream->parent->stream_errno; + stream->istream.eof = stream->parent->eof; + return ret; + } + qp_encoder_more(bstream->qp, data, size); + i_stream_skip(stream->parent, size); + } +} + +static void +i_stream_qp_encoder_seek(struct istream_private *stream, + uoff_t v_offset, bool mark) +{ + struct qp_encoder_istream *bstream = + (struct qp_encoder_istream *)stream; + + if (v_offset < stream->istream.v_offset) { + /* seeking backwards - go back to beginning and seek + forward from there. */ + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + i_stream_seek(stream->parent, 0); + qp_encoder_finish(bstream->qp); + buffer_set_used_size(bstream->buf, 0); + } + i_stream_default_seek_nonseekable(stream, v_offset, mark); +} + +struct istream *i_stream_create_qp_encoder(struct istream *input, + enum qp_encoder_flag flags) +{ + struct qp_encoder_istream *bstream; + + bstream = i_new(struct qp_encoder_istream, 1); + bstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + bstream->buf = buffer_create_dynamic(default_pool, 128); + bstream->qp = qp_encoder_init(bstream->buf, ISTREAM_QP_ENCODER_MAX_LINE_LENGTH, flags); + + bstream->istream.iostream.close = i_stream_qp_encoder_close; + bstream->istream.read = i_stream_qp_encoder_read; + bstream->istream.seek = i_stream_qp_encoder_seek; + + bstream->istream.istream.readable_fd = FALSE; + bstream->istream.istream.blocking = input->blocking; + bstream->istream.istream.seekable = input->seekable; + return i_stream_create(&bstream->istream, input, + i_stream_get_fd(input)); +} diff --git a/src/lib-mail/istream-qp.h b/src/lib-mail/istream-qp.h index e1dc4cfc6b..464129bfdf 100644 --- a/src/lib-mail/istream-qp.h +++ b/src/lib-mail/istream-qp.h @@ -1,6 +1,12 @@ #ifndef ISTREAM_QP_H #define ISTREAM_QP_H +#include "qp-encoder.h" + +#define ISTREAM_QP_ENCODER_MAX_LINE_LENGTH 75 + struct istream *i_stream_create_qp_decoder(struct istream *input); +struct istream *i_stream_create_qp_encoder(struct istream *input, + enum qp_encoder_flag flags); #endif diff --git a/src/lib-mail/qp-encoder.c b/src/lib-mail/qp-encoder.c new file mode 100644 index 0000000000..994de878e7 --- /dev/null +++ b/src/lib-mail/qp-encoder.c @@ -0,0 +1,140 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "istream-private.h" +#include "qp-encoder.h" +#include + +struct qp_encoder { + const char *linebreak; + string_t *dest; + size_t line_len; + size_t max_len; + enum qp_encoder_flag flags; + bool add_header_preamble:1; + bool cr_last:1; +}; + +struct qp_encoder * +qp_encoder_init(string_t *dest, unsigned int max_len, enum qp_encoder_flag flags) +{ + i_assert(max_len > 0); + + if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 && + (flags & QP_ENCODER_FLAG_BINARY_DATA) != 0) + i_panic("qp encoder cannot do header format with binary data"); + + struct qp_encoder *qp = i_new(struct qp_encoder, 1); + qp->flags = flags; + qp->dest = dest; + qp->max_len = max_len; + + if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) { + qp->linebreak = "?=\r\n =?utf-8?Q?"; + qp->add_header_preamble = TRUE; + } else { + qp->linebreak = "=\r\n"; + } + return qp; +} + +void qp_encoder_deinit(struct qp_encoder **qp) +{ + i_free(*qp); +} + +static inline void +qp_encode_or_break(struct qp_encoder *qp, unsigned char c) +{ + bool encode = FALSE; + + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) { + if (c == ' ') + c = '_'; + else if (c != '\t' && + (c == '?' || c == '_' || c == '=' || c < 33 || c > 126)) + encode = TRUE; + } else if (c != ' ' && c != '\t' && + (c == '=' || c < 33 || c > 126)) { + encode = TRUE; + } + + /* Include terminating = as well */ + if ((c == ' ' || c == '\t') && qp->line_len + 4 >= qp->max_len) { + const char *ptr = strchr(qp->linebreak, '\n'); + str_printfa(qp->dest, "=%02X%s", c, qp->linebreak); + if (ptr != NULL) + qp->line_len = strlen(ptr+1); + else + qp->line_len = 0; + return; + } + + /* Include terminating = as well */ + if (qp->line_len + (encode?4:2) >= qp->max_len) { + str_append(qp->dest, qp->linebreak); + qp->line_len = 0; + } + + if (encode) { + str_printfa(qp->dest, "=%02X", c); + qp->line_len += 3; + } else { + str_append_c(qp->dest, c); + qp->line_len += 1; + } +} + +void qp_encoder_more(struct qp_encoder *qp, const void *_src, size_t src_size) +{ + const unsigned char *src = _src; + i_assert(_src != NULL || src_size == 0); + if (src_size == 0) + return; + if (qp->add_header_preamble) { + size_t used = qp->dest->used; + qp->add_header_preamble = FALSE; + str_append(qp->dest, "=?utf-8?Q?"); + qp->line_len = qp->dest->used - used; + } + for(unsigned int i = 0; i < src_size; i++) { + unsigned char c = src[i]; + /* if input is not binary data and we encounter newline + convert it as crlf, or if the last byte was CR, preserve + CRLF */ + if (c == '\n' && + ((qp->flags & (QP_ENCODER_FLAG_BINARY_DATA|QP_ENCODER_FLAG_HEADER_FORMAT)) == 0 || + qp->cr_last)) { + str_append_c(qp->dest, '\r'); + str_append_c(qp->dest, '\n'); + /* reset line length here */ + qp->line_len = 0; + qp->cr_last = FALSE; + continue; + } else if (qp->cr_last) { + qp_encode_or_break(qp, '\r'); + qp->cr_last = FALSE; + } + if (c == '\r') { + qp->cr_last = TRUE; + } else { + qp_encode_or_break(qp, c); + } + } +} + +void qp_encoder_finish(struct qp_encoder *qp) +{ + if (qp->cr_last) + qp_encode_or_break(qp, '\r'); + + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 && + !qp->add_header_preamble) + str_append(qp->dest, "?="); + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) + qp->add_header_preamble = TRUE; + qp->line_len = 0; + qp->cr_last = FALSE; +} diff --git a/src/lib-mail/qp-encoder.h b/src/lib-mail/qp-encoder.h new file mode 100644 index 0000000000..f4a614878d --- /dev/null +++ b/src/lib-mail/qp-encoder.h @@ -0,0 +1,25 @@ +#ifndef QP_ENCODER_H +#define QP_ENCODER_H 1 + +enum qp_encoder_flag { + /* encode spaces as underscores, encode crlfs, adds =?utf-8?q?..?= encapsulation */ + QP_ENCODER_FLAG_HEADER_FORMAT = 0x1, + /* treat input as true binary, no lf => crlf conversion, only CRLF is preserved */ + QP_ENCODER_FLAG_BINARY_DATA = 0x2, +}; + +/* Initialize quoted-printable encoder. Write all the encoded output to dest. */ +struct qp_encoder *qp_encoder_init(string_t *dest, unsigned int max_length, + enum qp_encoder_flag flags); +void qp_encoder_deinit(struct qp_encoder **qp); + +/* Translate more (binary) data into quoted printable. + If QP_ENCODER_FLAG_BINARY_DATA is not set, text is assumed to be in + UTF-8 (but not enforced). No other character sets are supported. */ +void qp_encoder_more(struct qp_encoder *qp, const void *src, size_t src_size); +/* Finish encoding any pending input. + This function also resets the entire encoder state, so the same encoder can + be used to encode more data if wanted. */ +void qp_encoder_finish(struct qp_encoder *qp); + +#endif diff --git a/src/lib-mail/test-istream-qp-encoder.c b/src/lib-mail/test-istream-qp-encoder.c new file mode 100644 index 0000000000..3400487e64 --- /dev/null +++ b/src/lib-mail/test-istream-qp-encoder.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "str.h" +#include "istream-private.h" +#include "istream-qp.h" + +static const struct { + const void *input; + const char *output; + int stream_errno; +} tests[] = { + { "", "", 0 }, + { "short test", "short test", 0 }, + { "C'est une cha\xc3\xaene de test simple", "C'est une cha=C3=AEne de test simple", 0 }, + { + "wrap after 76 characters wrap after 76 characters wrap after 76 characters wrap after 76 characters", + "wrap after 76 characters wrap after 76 characters wrap after 76 character=\r\ns wrap after 76 characters", + 0 + }, + { + /* the string is split up to avoid C compilers thinking \x99ed as escape */ + "P\xc5\x99" "edstavitel\xc3\xa9 francouzsk\xc3\xa9ho lidu, ustanoveni v " + "N\xc3\xa1rodn\xc3\xadm shrom\xc3\xa1\xc5\xbe" "d\xc4\x9bn\xc3\xad, domn" + "\xc3\xadvaj\xc3\xad" "ce se, \xc5\xbe" "e nev\xc4\x9b" "domost, zapomenut\xc3" + "\xad nebo pohrd\xc3\xa1n\xc3\xad lidsk\xc3\xbdmi pr\xc3\xa1vy jsou j" + "edin\xc3\xbdmi p\xc5\x99\xc3\xad\xc4\x8dinami ve\xc5\x99" "ejn\xc3\xb" + "dch ne\xc5\xa1t\xc4\x9bst\xc3\xad a zkorumpov\xc3\xa1n\xc3\xad vl" + "\xc3\xa1" "d, rozhodli se vylo\xc5\xbeit v slavnostn\xc3\xad Deklara" + "ci p\xc5\x99irozen\xc3\xa1, nezciziteln\xc3\xa1 a posv\xc3\xa1tn\xc3" + "\xa1 pr\xc3\xa1va \xc4\x8dlov\xc4\x9bka za t\xc3\xadm \xc3\xba\xc4" + "\x8d" "elem, aby tato Deklarace, neust\xc3\xa1le jsouc p\xc5\x99" "ed o" + "\xc4\x8" "dima v\xc5\xa1" "em \xc4\x8dlen\xc5\xafm lidsk\xc3\xa9 spo" + "le\xc4\x8dnosti, uv\xc3\xa1" "d\xc4\x9bla jim st\xc3\xa1le na pam\xc4" + "\x9b\xc5\xa5 jejich pr\xc3\xa1va a jejich povinnosti; aby \xc4\x8din" + "y z\xc3\xa1konod\xc3\xa1rn\xc3\xa9 moci a \xc4\x8diny v\xc3\xbdkonn" + "\xc3\xa9 moci mohly b\xc3\xbdt v ka\xc5\xbe" "d\xc3\xa9 chv\xc3\xadli p" + "orovn\xc3\xa1v\xc3\xa1ny s \xc3\xba\xc4\x8d" "elem ka\xc5\xbe" "d\xc3" + "\xa9 politick\xc3\xa9 instituce a byly v d\xc5\xafsledku toho chov" + "\xc3\xa1ny je\xc5\xa1t\xc4\x9b v\xc3\xad" "ce v \xc3\xba" "ct\xc4" + "\x9b; aby po\xc5\xbe" "adavky ob\xc4\x8d" "an\xc5\xaf, kdy\xc5\xbe s" + "e budou nap\xc5\x99\xc3\xad\xc5\xa1t\xc4\x9b zakl\xc3\xa1" "dat na j" + "ednoduch\xc3\xb" "dch a nepop\xc3\xadrateln\xc3\xbd" "ch z\xc3\xa1sa" + "d\xc3\xa1" "ch, sm\xc4\x9b\xc5\x99ovaly v\xc5\xb" "edy k zachov\xc3" + "\xa1n\xc3\xad \xc3\xbastavy a ku blahu v\xc5\xa1" "ech.", + "P=C5=99edstavitel=C3=A9 francouzsk=C3=A9ho lidu, ustanoveni v N=C3=A1rodn=\r\n" + "=C3=ADm shrom=C3=A1=C5=BEd=C4=9Bn=C3=AD, domn=C3=ADvaj=C3=ADce se, =C5=BE=\r\n" + "e nev=C4=9Bdomost, zapomenut=C3=AD nebo pohrd=C3=A1n=C3=AD lidsk=C3=BDmi=20=\r\n" + "pr=C3=A1vy jsou jedin=C3=BDmi p=C5=99=C3=AD=C4=8Dinami ve=C5=99ejn=C3=0Bd=\r\n" + "ch ne=C5=A1t=C4=9Bst=C3=AD a zkorumpov=C3=A1n=C3=AD vl=C3=A1d, rozhodli=20=\r\n" + "se vylo=C5=BEit v slavnostn=C3=AD Deklaraci p=C5=99irozen=C3=A1, nezcizit=\r\n" + "eln=C3=A1 a posv=C3=A1tn=C3=A1 pr=C3=A1va =C4=8Dlov=C4=9Bka za t=C3=ADm=20=\r\n" + "=C3=BA=C4=8Delem, aby tato Deklarace, neust=C3=A1le jsouc p=C5=99ed o=C4=\r\n" + "=08dima v=C5=A1em =C4=8Dlen=C5=AFm lidsk=C3=A9 spole=C4=8Dnosti, uv=C3=A1=\r\n" + "d=C4=9Bla jim st=C3=A1le na pam=C4=9B=C5=A5 jejich pr=C3=A1va a jejich po=\r\n" + "vinnosti; aby =C4=8Diny z=C3=A1konod=C3=A1rn=C3=A9 moci a =C4=8Diny v=C3=\r\n" + "=BDkonn=C3=A9 moci mohly b=C3=BDt v ka=C5=BEd=C3=A9 chv=C3=ADli porovn=C3=\r\n" + "=A1v=C3=A1ny s =C3=BA=C4=8Delem ka=C5=BEd=C3=A9 politick=C3=A9 instituce=20=\r\n" + "a byly v d=C5=AFsledku toho chov=C3=A1ny je=C5=A1t=C4=9B v=C3=ADce v =C3=\r\n" + "=BAct=C4=9B; aby po=C5=BEadavky ob=C4=8Dan=C5=AF, kdy=C5=BE se budou nap=\r\n" + "=C5=99=C3=AD=C5=A1t=C4=9B zakl=C3=A1dat na jednoduch=C3=0Bdch a nepop=C3=\r\n" + "=ADrateln=C3=BDch z=C3=A1sad=C3=A1ch, sm=C4=9B=C5=99ovaly v=C5=0Bedy k za=\r\n" + "chov=C3=A1n=C3=AD =C3=BAstavy a ku blahu v=C5=A1ech.", + 0 + }, +}; + +static void +encode_test(const char *qp_input, const char *output, int stream_errno, + unsigned int buffer_size) +{ + size_t qp_input_len = strlen(qp_input); + struct istream *input_data, *input; + const unsigned char *data; + size_t i, size; + string_t *str = t_str_new(32); + int ret = 0; + + input_data = test_istream_create_data(qp_input, qp_input_len); + test_istream_set_max_buffer_size(input_data, buffer_size); + test_istream_set_allow_eof(input_data, FALSE); + input = i_stream_create_qp_encoder(input_data, 0); + + for (i = 1; i <= qp_input_len; i++) { + test_istream_set_size(input_data, i); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + str_append_n(str, data, size); + i_stream_skip(input, size); + } + if (ret == -1 && stream_errno != 0) + break; + test_assert(ret == 0); + } + if (ret == 0) { + test_istream_set_allow_eof(input_data, TRUE); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + str_append_n(str, data, size); + i_stream_skip(input, size); + } + } + + test_assert(ret == -1); + test_assert(input->stream_errno == stream_errno); + test_assert(strcmp(str_c(str), output) == 0); + + i_stream_unref(&input); + i_stream_unref(&input_data); +} + +static void test_istream_qp_encoder(void) +{ + unsigned int i, j; + + for (i = 0; i < N_ELEMENTS(tests); i++) { + test_begin(t_strdup_printf("istream qp encoder %u", i+1)); + for (j = 1; j < 10; j++) T_BEGIN { + encode_test(tests[i].input, tests[i].output, + tests[i].stream_errno, j); + } T_END; + test_end(); + } +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_istream_qp_encoder, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-mail/test-qp-encoder.c b/src/lib-mail/test-qp-encoder.c new file mode 100644 index 0000000000..0982218f33 --- /dev/null +++ b/src/lib-mail/test-qp-encoder.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "qp-encoder.h" +#include "test-common.h" + +struct test_quoted_printable_encode_data { + const void *input; + size_t input_len; + const char *output; +}; + +static void test_qp_encoder(void) +{ + static struct test_quoted_printable_encode_data tests[] = { + { "", 0, "" }, + { "a", 1, "a" }, + { "a b \r c d", 9, "a b =0D c d" }, + { "a b \n c d", 9, "a b \r\n c d" }, + { + "test wrap at max 20 characters tab\ttoo", 38, + "test wrap at max=20=\r\n20 characters tab=09=\r\ntoo" + }, + { "Invalid UTF-8 sequence in \x99", 27, "Invalid UTF-8 sequ=\r\nence in =99" }, + { "keep CRLF\r\non two lines", 23, "keep CRLF\r\non two lines" }, + }; + string_t *str; + unsigned int i, j; + + test_begin("qp-encoder"); + str = t_str_new(128); + for (i = 0; i < N_ELEMENTS(tests); i++) { + const unsigned char *input = tests[i].input; + struct qp_encoder *qp = qp_encoder_init(str, 20, 0); + + /* try all at once */ + qp_encoder_more(qp, input, tests[i].input_len); + qp_encoder_finish(qp); + + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + /* try in small pieces */ + str_truncate(str, 0); + for (j = 0; j < tests[i].input_len; j++) { + unsigned char c = input[j]; + qp_encoder_more(qp, &c, 1); + } + qp_encoder_finish(qp); + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + qp_encoder_deinit(&qp); + str_truncate(str, 0); + } + test_end(); +} + +static void test_qp_encoder_binary(void) +{ + static struct test_quoted_printable_encode_data tests[] = { + { "\0nil\0delimited\0string\0", 22, "=00nil=00delimited=\r\n=00string=00" }, + { + "\xef\x4e\xc5\xe0\x31\x66\xd7\xef\xae\x12\x7d\x45\x1e\x05\xc7\x2a", + 16, + "=EFN=C5=E01f=D7=EF=\r\n=AE=12}E=1E=05=C7*" + }, + }; + + string_t *str; + unsigned int i, j; + + test_begin("qp-encoder (binary safe)"); + str = t_str_new(128); + for (i = 0; i < N_ELEMENTS(tests); i++) { + const unsigned char *input = tests[i].input; + struct qp_encoder *qp = qp_encoder_init(str, 20, QP_ENCODER_FLAG_BINARY_DATA); + + /* try all at once */ + qp_encoder_more(qp, input, tests[i].input_len); + qp_encoder_finish(qp); + + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + /* try in small pieces */ + str_truncate(str, 0); + for (j = 0; j < tests[i].input_len; j++) { + unsigned char c = input[j]; + qp_encoder_more(qp, &c, 1); + } + qp_encoder_finish(qp); + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + qp_encoder_deinit(&qp); + str_truncate(str, 0); + } + test_end(); +} + +static void test_qp_encoder_header(void) +{ + static struct test_quoted_printable_encode_data tests[] = { + { "simple", 6, "=?utf-8?Q?simple?=" }, + { "J'esuis de paris caf\xc3\xa9", 22, "=?utf-8?Q?J'esuis_de_paris_caf=C3=A9?=" }, + { "hello_world", 11, "=?utf-8?Q?hello=5Fworld?=" }, + { + "make sure this wraps and that the actual lines are not longer than maximum length including preamble", + 100, + "=?utf-8?Q?make_sure_this_wraps_and_that_the_actual_lines_are_not_longer_t?=\r\n" + " =?utf-8?Q?han_maximum_length_including_preamble?=" + }, + }; + + string_t *str; + unsigned int i, j; + + test_begin("qp-encoder (header format)"); + str = t_str_new(128); + for (i = 0; i < N_ELEMENTS(tests); i++) { + const unsigned char *input = tests[i].input; + struct qp_encoder *qp = qp_encoder_init(str, 75, QP_ENCODER_FLAG_HEADER_FORMAT); + + /* try all at once */ + qp_encoder_more(qp, input, tests[i].input_len); + qp_encoder_finish(qp); + + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + /* try in small pieces */ + str_truncate(str, 0); + for (j = 0; j < tests[i].input_len; j++) { + unsigned char c = input[j]; + qp_encoder_more(qp, &c, 1); + } + qp_encoder_finish(qp); + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + + qp_encoder_deinit(&qp); + str_truncate(str, 0); + } + test_end(); +} + + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_qp_encoder, + test_qp_encoder_binary, + test_qp_encoder_header, + NULL + }; + return test_run(test_functions); +}