summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/test-istream-qp-decoder.c
blob: cdf5b223058e2470260c475e9e9b5931abf67b81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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);
}