summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/test-istream-qp-decoder.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-mail/test-istream-qp-decoder.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/lib-mail/test-istream-qp-decoder.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/lib-mail/test-istream-qp-decoder.c b/src/lib-mail/test-istream-qp-decoder.c
new file mode 100644
index 0000000..cdf5b22
--- /dev/null
+++ b/src/lib-mail/test-istream-qp-decoder.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2010-2018 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 char *input;
+ const char *output;
+ int stream_errno;
+ int eof;
+} tests[] = {
+ { "p=C3=A4=C3=A4t=C3=B6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 , 0 },
+ { "p=c3=a4=c3=a4t=c3=b6s= \n", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 0 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 1 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 2 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 3 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 4 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 5 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 7 },
+ { "p=c3", "p\xC3", 0, 2 },
+ { "=0A=0D ", "\n\r", 0, 7 },
+ { "foo_bar", "foo_bar", 0, 0 },
+ { "\n\n", "\r\n\r\n", 0, 0 },
+ { "\r\n\n\n\r\n", "\r\n\r\n\r\n\r\n", 0, 0 },
+ /* Unnecessarily encoded */
+ { "=66=6f=6f=42=61=72", "fooBar", 0, 4 },
+ /* Expected to be encoded but not */
+ { "\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 9 },
+ /* Decode control characters */
+ { "=0C=07", "\x0C\x07", 0, 0 },
+ /* Data */
+ { "=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF", 0, 0 },
+ /* Non hex data */
+ { "=FJ=X1", "", EINVAL, 0 },
+ /* No content allowed after Soft Line Break */
+ { "=C3=9C = ","\xc3\x9c ", EPIPE, 0 },
+ /* Boundary delimiter */
+ { "=C3=9C=\r\n-------","\xc3\x9c-------", 0, 0 },
+ { "=----------- =C3=9C","", EINVAL, 0 },
+ { "=___________ =C3=9C","", EINVAL, 0 },
+ { "___________ =C3=9C","___________ \xc3\x9c", 0, 0 },
+ { "=2D=2D=2D=2D=2D=2D =C3=9C","------ \xc3\x9c", 0, 0 },
+ { "=FC=83=BF=BF=BF=BF", "\xFC\x83\xBF\xBF\xBF\xBF", 0, 0 },
+ { "=FE=FE=FF=FF", "\xFE\xFE\xFF\xFF", 0, 0 },
+ { "\xFF=C3=9C\xFE\xFF""foobar", "\xFF\xc3\x9c\xFE\xFF""foobar", 0, 0 },
+
+ { "p=c3=a4\rasdf", "p\xC3\xA4", EINVAL, 0 },
+ { "=___________ \xc3\x9c","", EINVAL, 0 },
+ { "p=c", "p", EPIPE, 0 },
+ { "p=A", "p", EPIPE, 0 },
+ { "p=Ax", "p", EINVAL, 0 },
+ { "___________ \xc3\x9c=C3=9","___________ \xc3\x9c\xC3", EPIPE, 0},
+ { "p=c3=a4=c3=a4t=c3=b6s= ", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", EPIPE, 0 },
+ /* Soft Line Break example from the RFC */
+ {
+ "Now's the time =\r\nfor all folk to come=\r\n to the aid of "
+ "their country.", "Now's the time for all folk to come to the"
+ " aid of their country.", 0, 41
+ },
+};
+
+static bool is_hex(char c) {
+ return ((c >= 48 && c <= 57) || (c >= 65 && c <= 70)
+ || (c >= 97 && c <= 102));
+
+}
+
+static unsigned int
+get_encoding_size_diff(const char *qp_input, unsigned int limit)
+{
+ unsigned int encoded_chars = 0;
+ unsigned int soft_line_breaks = 0;
+ for (unsigned int i = 0; i < limit; i++) {
+ char c = qp_input[i];
+ if (c == '=' && i+2 < limit) {
+ if (qp_input[i+1] == '\r' && qp_input[i+2] == '\n') {
+ soft_line_breaks++;
+ i += 2;
+ limit += 3;
+ } else if (is_hex(qp_input[i+1]) && is_hex(qp_input[i+2])) {
+ encoded_chars++;
+ i += 2;
+ limit += 2;
+ }
+ }
+ }
+ return encoded_chars*2 + soft_line_breaks*3;
+}
+
+static void
+decode_test(const char *qp_input, const char *output, int stream_errno,
+ unsigned int buffer_size, unsigned int eof)
+{
+ size_t qp_input_len = strlen(qp_input);
+ struct istream *input_data, *input_data_limited, *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_decoder(input_data);
+
+ 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_data(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_data(str, data, size);
+ i_stream_skip(input, size);
+ }
+ }
+ test_assert(ret == -1);
+ test_assert(input->stream_errno == stream_errno);
+
+ if (stream_errno == 0) {
+ /* Test seeking on streams where the testcases do not
+ * expect a specific errno already */
+ uoff_t v_off = input->v_offset;
+ /* Seeking backwards */
+ i_stream_seek(input, 0);
+ test_assert(input->v_offset == 0);
+
+ /* Seeking forward */
+ i_stream_seek(input, v_off+1);
+ test_assert(input->stream_errno == ESPIPE);
+ }
+ /* Compare outputs */
+ test_assert_strcmp(str_c(str), output);
+
+ if (eof > 0) {
+ /* Insert early EOF into input_data */
+ i_stream_seek(input_data, 0);
+ str_truncate(str, 0);
+ input_data_limited = i_stream_create_limit(input_data, eof);
+ test_istream_set_allow_eof(input_data_limited, TRUE);
+ i_stream_unref(&input);
+ input = i_stream_create_qp_decoder(input_data_limited);
+ while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
+ str_append_data(str, data, size);
+ i_stream_skip(input, size);
+ }
+ test_assert(ret == -1);
+ /* If there is no error still assume that the result is valid
+ * till artifical eof. */
+ if (input->stream_errno == 0) {
+ unsigned int encoding_margin =
+ get_encoding_size_diff(qp_input, eof);
+
+ /* Cut the expected output at eof of input*/
+ const char *expected_output =
+ t_strdup_printf("%.*s", eof-encoding_margin,
+ output);
+ test_assert_strcmp(str_c(str), expected_output);
+ }
+ test_assert(input->eof);
+ i_stream_unref(&input_data_limited);
+ }
+
+ i_stream_unref(&input);
+ i_stream_unref(&input_data);
+}
+
+static void test_istream_qp_decoder(void)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ test_begin(t_strdup_printf("istream qp decoder %u", i+1));
+ for (j = 1; j < 10; j++) T_BEGIN {
+ decode_test(tests[i].input, tests[i].output,
+ tests[i].stream_errno, j, tests[i].eof);
+ } T_END;
+ test_end();
+ }
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_istream_qp_decoder,
+ NULL
+ };
+ return test_run(test_functions);
+}